This patch adds support for the pair of LCD controllers on the Marvell Armada 510 SoCs. This driver supports: - multiple contiguous scanout buffers for video and graphics - shm backed cacheable buffer objects for X pixmaps for Vivante GPU acceleration - dual lcd0 and lcd1 crt operation - video overlay on each LCD crt via DRM planes - page flipping of the main scanout buffers - DRM prime for buffer export/import This driver is trivial to extend to other Armada SoCs. Included in this commit is the core driver with no output support; output support is platform and encoder driver dependent.
Unified · Split
| @@ -225,6 +225,8 @@ source "drivers/gpu/drm/mgag200/Kconfig" · 224 unmodified lines | ||
| 225 | 225 | |
| 226 | 226 | source "drivers/gpu/drm/cirrus/Kconfig" |
| 227 | 227 | |
| 228 | +source "drivers/gpu/drm/armada/Kconfig" | |
| 229 | + | |
| 228 | 230 | source "drivers/gpu/drm/rcar-du/Kconfig" |
| 229 | 231 | |
| 230 | 232 | source "drivers/gpu/drm/shmobile/Kconfig" |
| @@ -49,6 +49,7 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/ · 48 unmodified lines | ||
| 49 | 49 | obj-$(CONFIG_DRM_GMA500) += gma500/ |
| 50 | 50 | obj-$(CONFIG_DRM_UDL) += udl/ |
| 51 | 51 | obj-$(CONFIG_DRM_AST) += ast/ |
| 52 | +obj-$(CONFIG_DRM_ARMADA) += armada/ | |
| 52 | 53 | obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ |
| 53 | 54 | obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ |
| 54 | 55 | obj-$(CONFIG_DRM_OMAP) += omapdrm/ |
| @@ -0,0 +1,15 @@ | ||
| 1 | +config DRM_ARMADA | |
| 2 | + tristate "DRM support for Marvell Armada SoCs" | |
| 3 | + depends on DRM && HAVE_CLK | |
| 4 | + select FB_CFB_FILLRECT | |
| 5 | + select FB_CFB_COPYAREA | |
| 6 | + select FB_CFB_IMAGEBLIT | |
| 7 | + select DRM_KMS_HELPER | |
| 8 | + help | |
| 9 | + Support the "LCD" controllers found on the Marvell Armada 510 | |
| 10 | + devices. There are two controllers on the device, each controller | |
| 11 | + supports graphics and video overlays. | |
| 12 | + | |
| 13 | + This driver provides no built-in acceleration; acceleration is | |
| 14 | + performed by other IP found on the SoC. This driver provides | |
| 15 | + kernel mode setting and buffer management to userspace. | |
| @@ -0,0 +1,7 @@ | ||
| 1 | +armada-y := armada_crtc.o armada_drv.o armada_fb.o armada_fbdev.o \ | |
| 2 | + armada_gem.o armada_output.o armada_overlay.o \ | |
| 3 | + armada_slave.o | |
| 4 | +armada-y += armada_510.o | |
| 5 | +armada-$(CONFIG_DEBUG_FS) += armada_debugfs.o | |
| 6 | + | |
| 7 | +obj-$(CONFIG_DRM_ARMADA) := armada.o | |
| @@ -0,0 +1,86 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + * | |
| 8 | + * Armada 510 (aka Dove) variant support | |
| 9 | + */ | |
| 10 | +#include <linux/clk.h> | |
| 11 | +#include <linux/io.h> | |
| 12 | +#include <drm/drmP.h> | |
| 13 | +#include <drm/drm_crtc_helper.h> | |
| 14 | +#include "armada_crtc.h" | |
| 15 | +#include "armada_drm.h" | |
| 16 | +#include "armada_hw.h" | |
| 17 | + | |
| 18 | +static int armada510_init(struct armada_private *priv, struct device *dev) | |
| 19 | +{ | |
| 20 | + priv->extclk[0] = devm_clk_get(dev, "ext_ref_clk_1"); | |
| 21 | + | |
| 22 | + if (IS_ERR(priv->extclk[0]) && PTR_ERR(priv->extclk[0]) == -ENOENT) | |
| 23 | + priv->extclk[0] = ERR_PTR(-EPROBE_DEFER); | |
| 24 | + | |
| 25 | + return PTR_RET(priv->extclk[0]); | |
| 26 | +} | |
| 27 | + | |
| 28 | +static int armada510_crtc_init(struct armada_crtc *dcrtc) | |
| 29 | +{ | |
| 30 | + /* Lower the watermark so to eliminate jitter at higher bandwidths */ | |
| 31 | + armada_updatel(0x20, (1 << 11) | 0xff, dcrtc->base + LCD_CFG_RDREG4F); | |
| 32 | + return 0; | |
| 33 | +} | |
| 34 | + | |
| 35 | +/* | |
| 36 | + * Armada510 specific SCLK register selection. | |
| 37 | + * This gets called with sclk = NULL to test whether the mode is | |
| 38 | + * supportable, and again with sclk != NULL to set the clocks up for | |
| 39 | + * that. The former can return an error, but the latter is expected | |
| 40 | + * not to. | |
| 41 | + * | |
| 42 | + * We currently are pretty rudimentary here, always selecting | |
| 43 | + * EXT_REF_CLK_1 for LCD0 and erroring LCD1. This needs improvement! | |
| 44 | + */ | |
| 45 | +static int armada510_crtc_compute_clock(struct armada_crtc *dcrtc, | |
| 46 | + const struct drm_display_mode *mode, uint32_t *sclk) | |
| 47 | +{ | |
| 48 | + struct armada_private *priv = dcrtc->crtc.dev->dev_private; | |
| 49 | + struct clk *clk = priv->extclk[0]; | |
| 50 | + int ret; | |
| 51 | + | |
| 52 | + if (dcrtc->num == 1) | |
| 53 | + return -EINVAL; | |
| 54 | + | |
| 55 | + if (IS_ERR(clk)) | |
| 56 | + return PTR_ERR(clk); | |
| 57 | + | |
| 58 | + if (dcrtc->clk != clk) { | |
| 59 | + ret = clk_prepare_enable(clk); | |
| 60 | + if (ret) | |
| 61 | + return ret; | |
| 62 | + dcrtc->clk = clk; | |
| 63 | + } | |
| 64 | + | |
| 65 | + if (sclk) { | |
| 66 | + uint32_t rate, ref, div; | |
| 67 | + | |
| 68 | + rate = mode->clock * 1000; | |
| 69 | + ref = clk_round_rate(clk, rate); | |
| 70 | + div = DIV_ROUND_UP(ref, rate); | |
| 71 | + if (div < 1) | |
| 72 | + div = 1; | |
| 73 | + | |
| 74 | + clk_set_rate(clk, ref); | |
| 75 | + *sclk = div | SCLK_510_EXTCLK1; | |
| 76 | + } | |
| 77 | + | |
| 78 | + return 0; | |
| 79 | +} | |
| 80 | + | |
| 81 | +const struct armada_variant armada510_ops = { | |
| 82 | + .has_spu_adv_reg = true, | |
| 83 | + .init = armada510_init, | |
| 84 | + .crtc_init = armada510_crtc_init, | |
| 85 | + .crtc_compute_clock = armada510_crtc_compute_clock, | |
| 86 | +}; | |
| @@ -0,0 +1,861 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * Rewritten from the dovefb driver, and Armada510 manuals. | |
| 4 | + * | |
| 5 | + * This program is free software; you can redistribute it and/or modify | |
| 6 | + * it under the terms of the GNU General Public License version 2 as | |
| 7 | + * published by the Free Software Foundation. | |
| 8 | + */ | |
| 9 | +#include <linux/clk.h> | |
| 10 | +#include <drm/drmP.h> | |
| 11 | +#include <drm/drm_crtc_helper.h> | |
| 12 | +#include "armada_crtc.h" | |
| 13 | +#include "armada_drm.h" | |
| 14 | +#include "armada_fb.h" | |
| 15 | +#include "armada_gem.h" | |
| 16 | +#include "armada_hw.h" | |
| 17 | + | |
| 18 | +struct armada_frame_work { | |
| 19 | + struct drm_pending_vblank_event *event; | |
| 20 | + struct armada_regs regs[4]; | |
| 21 | + struct drm_framebuffer *old_fb; | |
| 22 | +}; | |
| 23 | + | |
| 24 | +enum csc_mode { | |
| 25 | + CSC_AUTO = 0, | |
| 26 | + CSC_YUV_CCIR601 = 1, | |
| 27 | + CSC_YUV_CCIR709 = 2, | |
| 28 | + CSC_RGB_COMPUTER = 1, | |
| 29 | + CSC_RGB_STUDIO = 2, | |
| 30 | +}; | |
| 31 | + | |
| 32 | +/* | |
| 33 | + * A note about interlacing. Let's consider HDMI 1920x1080i. | |
| 34 | + * The timing parameters we have from X are: | |
| 35 | + * Hact HsyA HsyI Htot Vact VsyA VsyI Vtot | |
| 36 | + * 1920 2448 2492 2640 1080 1084 1094 1125 | |
| 37 | + * Which get translated to: | |
| 38 | + * Hact HsyA HsyI Htot Vact VsyA VsyI Vtot | |
| 39 | + * 1920 2448 2492 2640 540 542 547 562 | |
| 40 | + * | |
| 41 | + * This is how it is defined by CEA-861-D - line and pixel numbers are | |
| 42 | + * referenced to the rising edge of VSYNC and HSYNC. Total clocks per | |
| 43 | + * line: 2640. The odd frame, the first active line is at line 21, and | |
| 44 | + * the even frame, the first active line is 584. | |
| 45 | + * | |
| 46 | + * LN: 560 561 562 563 567 568 569 | |
| 47 | + * DE: ~~~|____________________________//__________________________ | |
| 48 | + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____ | |
| 49 | + * VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________ | |
| 50 | + * 22 blanking lines. VSYNC at 1320 (referenced to the HSYNC rising edge). | |
| 51 | + * | |
| 52 | + * LN: 1123 1124 1125 1 5 6 7 | |
| 53 | + * DE: ~~~|____________________________//__________________________ | |
| 54 | + * HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____ | |
| 55 | + * VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________ | |
| 56 | + * 23 blanking lines | |
| 57 | + * | |
| 58 | + * The Armada LCD Controller line and pixel numbers are, like X timings, | |
| 59 | + * referenced to the top left of the active frame. | |
| 60 | + * | |
| 61 | + * So, translating these to our LCD controller: | |
| 62 | + * Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128. | |
| 63 | + * Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448. | |
| 64 | + * Note: Vsync front porch remains constant! | |
| 65 | + * | |
| 66 | + * if (odd_frame) { | |
| 67 | + * vtotal = mode->crtc_vtotal + 1; | |
| 68 | + * vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1; | |
| 69 | + * vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2 | |
| 70 | + * } else { | |
| 71 | + * vtotal = mode->crtc_vtotal; | |
| 72 | + * vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay; | |
| 73 | + * vhorizpos = mode->crtc_hsync_start; | |
| 74 | + * } | |
| 75 | + * vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end; | |
| 76 | + * | |
| 77 | + * So, we need to reprogram these registers on each vsync event: | |
| 78 | + * LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL | |
| 79 | + * | |
| 80 | + * Note: we do not use the frame done interrupts because these appear | |
| 81 | + * to happen too early, and lead to jitter on the display (presumably | |
| 82 | + * they occur at the end of the last active line, before the vsync back | |
| 83 | + * porch, which we're reprogramming.) | |
| 84 | + */ | |
| 85 | + | |
| 86 | +void | |
| 87 | +armada_drm_crtc_update_regs(struct armada_crtc *dcrtc, struct armada_regs *regs) | |
| 88 | +{ | |
| 89 | + while (regs->offset != ~0) { | |
| 90 | + void __iomem *reg = dcrtc->base + regs->offset; | |
| 91 | + uint32_t val; | |
| 92 | + | |
| 93 | + val = regs->mask; | |
| 94 | + if (val != 0) | |
| 95 | + val &= readl_relaxed(reg); | |
| 96 | + writel_relaxed(val | regs->val, reg); | |
| 97 | + ++regs; | |
| 98 | + } | |
| 99 | +} | |
| 100 | + | |
| 101 | +#define dpms_blanked(dpms) ((dpms) != DRM_MODE_DPMS_ON) | |
| 102 | + | |
| 103 | +static void armada_drm_crtc_update(struct armada_crtc *dcrtc) | |
| 104 | +{ | |
| 105 | + uint32_t dumb_ctrl; | |
| 106 | + | |
| 107 | + dumb_ctrl = dcrtc->cfg_dumb_ctrl; | |
| 108 | + | |
| 109 | + if (!dpms_blanked(dcrtc->dpms)) | |
| 110 | + dumb_ctrl |= CFG_DUMB_ENA; | |
| 111 | + | |
| 112 | + /* | |
| 113 | + * When the dumb interface isn't in DUMB24_RGB888_0 mode, it might | |
| 114 | + * be using SPI or GPIO. If we set this to DUMB_BLANK, we will | |
| 115 | + * force LCD_D[23:0] to output blank color, overriding the GPIO or | |
| 116 | + * SPI usage. So leave it as-is unless in DUMB24_RGB888_0 mode. | |
| 117 | + */ | |
| 118 | + if (dpms_blanked(dcrtc->dpms) && | |
| 119 | + (dumb_ctrl & DUMB_MASK) == DUMB24_RGB888_0) { | |
| 120 | + dumb_ctrl &= ~DUMB_MASK; | |
| 121 | + dumb_ctrl |= DUMB_BLANK; | |
| 122 | + } | |
| 123 | + | |
| 124 | + /* | |
| 125 | + * The documentation doesn't indicate what the normal state of | |
| 126 | + * the sync signals are. Sebastian Hesselbart kindly probed | |
| 127 | + * these signals on his board to determine their state. | |
| 128 | + * | |
| 129 | + * The non-inverted state of the sync signals is active high. | |
| 130 | + * Setting these bits makes the appropriate signal active low. | |
| 131 | + */ | |
| 132 | + if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NCSYNC) | |
| 133 | + dumb_ctrl |= CFG_INV_CSYNC; | |
| 134 | + if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NHSYNC) | |
| 135 | + dumb_ctrl |= CFG_INV_HSYNC; | |
| 136 | + if (dcrtc->crtc.mode.flags & DRM_MODE_FLAG_NVSYNC) | |
| 137 | + dumb_ctrl |= CFG_INV_VSYNC; | |
| 138 | + | |
| 139 | + if (dcrtc->dumb_ctrl != dumb_ctrl) { | |
| 140 | + dcrtc->dumb_ctrl = dumb_ctrl; | |
| 141 | + writel_relaxed(dumb_ctrl, dcrtc->base + LCD_SPU_DUMB_CTRL); | |
| 142 | + } | |
| 143 | +} | |
| 144 | + | |
| 145 | +static unsigned armada_drm_crtc_calc_fb(struct drm_framebuffer *fb, | |
| 146 | + int x, int y, struct armada_regs *regs, bool interlaced) | |
| 147 | +{ | |
| 148 | + struct armada_gem_object *obj = drm_fb_obj(fb); | |
| 149 | + unsigned pitch = fb->pitches[0]; | |
| 150 | + unsigned offset = y * pitch + x * fb->bits_per_pixel / 8; | |
| 151 | + uint32_t addr_odd, addr_even; | |
| 152 | + unsigned i = 0; | |
| 153 | + | |
| 154 | + DRM_DEBUG_DRIVER("pitch %u x %d y %d bpp %d\n", | |
| 155 | + pitch, x, y, fb->bits_per_pixel); | |
| 156 | + | |
| 157 | + addr_odd = addr_even = obj->dev_addr + offset; | |
| 158 | + | |
| 159 | + if (interlaced) { | |
| 160 | + addr_even += pitch; | |
| 161 | + pitch *= 2; | |
| 162 | + } | |
| 163 | + | |
| 164 | + /* write offset, base, and pitch */ | |
| 165 | + armada_reg_queue_set(regs, i, addr_odd, LCD_CFG_GRA_START_ADDR0); | |
| 166 | + armada_reg_queue_set(regs, i, addr_even, LCD_CFG_GRA_START_ADDR1); | |
| 167 | + armada_reg_queue_mod(regs, i, pitch, 0xffff, LCD_CFG_GRA_PITCH); | |
| 168 | + | |
| 169 | + return i; | |
| 170 | +} | |
| 171 | + | |
| 172 | +static int armada_drm_crtc_queue_frame_work(struct armada_crtc *dcrtc, | |
| 173 | + struct armada_frame_work *work) | |
| 174 | +{ | |
| 175 | + struct drm_device *dev = dcrtc->crtc.dev; | |
| 176 | + unsigned long flags; | |
| 177 | + int ret; | |
| 178 | + | |
| 179 | + ret = drm_vblank_get(dev, dcrtc->num); | |
| 180 | + if (ret) { | |
| 181 | + DRM_ERROR("failed to acquire vblank counter\n"); | |
| 182 | + return ret; | |
| 183 | + } | |
| 184 | + | |
| 185 | + spin_lock_irqsave(&dev->event_lock, flags); | |
| 186 | + if (!dcrtc->frame_work) | |
| 187 | + dcrtc->frame_work = work; | |
| 188 | + else | |
| 189 | + ret = -EBUSY; | |
| 190 | + spin_unlock_irqrestore(&dev->event_lock, flags); | |
| 191 | + | |
| 192 | + if (ret) | |
| 193 | + drm_vblank_put(dev, dcrtc->num); | |
| 194 | + | |
| 195 | + return ret; | |
| 196 | +} | |
| 197 | + | |
| 198 | +static void armada_drm_crtc_complete_frame_work(struct armada_crtc *dcrtc) | |
| 199 | +{ | |
| 200 | + struct drm_device *dev = dcrtc->crtc.dev; | |
| 201 | + struct armada_frame_work *work = dcrtc->frame_work; | |
| 202 | + | |
| 203 | + dcrtc->frame_work = NULL; | |
| 204 | + | |
| 205 | + armada_drm_crtc_update_regs(dcrtc, work->regs); | |
| 206 | + | |
| 207 | + if (work->event) | |
| 208 | + drm_send_vblank_event(dev, dcrtc->num, work->event); | |
| 209 | + | |
| 210 | + drm_vblank_put(dev, dcrtc->num); | |
| 211 | + | |
| 212 | + /* Finally, queue the process-half of the cleanup. */ | |
| 213 | + __armada_drm_queue_unref_work(dcrtc->crtc.dev, work->old_fb); | |
| 214 | + kfree(work); | |
| 215 | +} | |
| 216 | + | |
| 217 | +static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc, | |
| 218 | + struct drm_framebuffer *fb, bool force) | |
| 219 | +{ | |
| 220 | + struct armada_frame_work *work; | |
| 221 | + | |
| 222 | + if (!fb) | |
| 223 | + return; | |
| 224 | + | |
| 225 | + if (force) { | |
| 226 | + /* Display is disabled, so just drop the old fb */ | |
| 227 | + drm_framebuffer_unreference(fb); | |
| 228 | + return; | |
| 229 | + } | |
| 230 | + | |
| 231 | + work = kmalloc(sizeof(*work), GFP_KERNEL); | |
| 232 | + if (work) { | |
| 233 | + int i = 0; | |
| 234 | + work->event = NULL; | |
| 235 | + work->old_fb = fb; | |
| 236 | + armada_reg_queue_end(work->regs, i); | |
| 237 | + | |
| 238 | + if (armada_drm_crtc_queue_frame_work(dcrtc, work) == 0) | |
| 239 | + return; | |
| 240 | + | |
| 241 | + kfree(work); | |
| 242 | + } | |
| 243 | + | |
| 244 | + /* | |
| 245 | + * Oops - just drop the reference immediately and hope for | |
| 246 | + * the best. The worst that will happen is the buffer gets | |
| 247 | + * reused before it has finished being displayed. | |
| 248 | + */ | |
| 249 | + drm_framebuffer_unreference(fb); | |
| 250 | +} | |
| 251 | + | |
| 252 | +static void armada_drm_vblank_off(struct armada_crtc *dcrtc) | |
| 253 | +{ | |
| 254 | + struct drm_device *dev = dcrtc->crtc.dev; | |
| 255 | + | |
| 256 | + /* | |
| 257 | + * Tell the DRM core that vblank IRQs aren't going to happen for | |
| 258 | + * a while. This cleans up any pending vblank events for us. | |
| 259 | + */ | |
| 260 | + drm_vblank_off(dev, dcrtc->num); | |
| 261 | + | |
| 262 | + /* Handle any pending flip event. */ | |
| 263 | + spin_lock_irq(&dev->event_lock); | |
| 264 | + if (dcrtc->frame_work) | |
| 265 | + armada_drm_crtc_complete_frame_work(dcrtc); | |
| 266 | + spin_unlock_irq(&dev->event_lock); | |
| 267 | +} | |
| 268 | + | |
| 269 | +void armada_drm_crtc_gamma_set(struct drm_crtc *crtc, u16 r, u16 g, u16 b, | |
| 270 | + int idx) | |
| 271 | +{ | |
| 272 | +} | |
| 273 | + | |
| 274 | +void armada_drm_crtc_gamma_get(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b, | |
| 275 | + int idx) | |
| 276 | +{ | |
| 277 | +} | |
| 278 | + | |
| 279 | +/* The mode_config.mutex will be held for this call */ | |
| 280 | +static void armada_drm_crtc_dpms(struct drm_crtc *crtc, int dpms) | |
| 281 | +{ | |
| 282 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 283 | + | |
| 284 | + if (dcrtc->dpms != dpms) { | |
| 285 | + dcrtc->dpms = dpms; | |
| 286 | + armada_drm_crtc_update(dcrtc); | |
| 287 | + if (dpms_blanked(dpms)) | |
| 288 | + armada_drm_vblank_off(dcrtc); | |
| 289 | + } | |
| 290 | +} | |
| 291 | + | |
| 292 | +/* | |
| 293 | + * Prepare for a mode set. Turn off overlay to ensure that we don't end | |
| 294 | + * up with the overlay size being bigger than the active screen size. | |
| 295 | + * We rely upon X refreshing this state after the mode set has completed. | |
| 296 | + * | |
| 297 | + * The mode_config.mutex will be held for this call | |
| 298 | + */ | |
| 299 | +static void armada_drm_crtc_prepare(struct drm_crtc *crtc) | |
| 300 | +{ | |
| 301 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 302 | + struct drm_plane *plane; | |
| 303 | + | |
| 304 | + /* | |
| 305 | + * If we have an overlay plane associated with this CRTC, disable | |
| 306 | + * it before the modeset to avoid its coordinates being outside | |
| 307 | + * the new mode parameters. DRM doesn't provide help with this. | |
| 308 | + */ | |
| 309 | + plane = dcrtc->plane; | |
| 310 | + if (plane) { | |
| 311 | + struct drm_framebuffer *fb = plane->fb; | |
| 312 | + | |
| 313 | + plane->funcs->disable_plane(plane); | |
| 314 | + plane->fb = NULL; | |
| 315 | + plane->crtc = NULL; | |
| 316 | + drm_framebuffer_unreference(fb); | |
| 317 | + } | |
| 318 | +} | |
| 319 | + | |
| 320 | +/* The mode_config.mutex will be held for this call */ | |
| 321 | +static void armada_drm_crtc_commit(struct drm_crtc *crtc) | |
| 322 | +{ | |
| 323 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 324 | + | |
| 325 | + if (dcrtc->dpms != DRM_MODE_DPMS_ON) { | |
| 326 | + dcrtc->dpms = DRM_MODE_DPMS_ON; | |
| 327 | + armada_drm_crtc_update(dcrtc); | |
| 328 | + } | |
| 329 | +} | |
| 330 | + | |
| 331 | +/* The mode_config.mutex will be held for this call */ | |
| 332 | +static bool armada_drm_crtc_mode_fixup(struct drm_crtc *crtc, | |
| 333 | + const struct drm_display_mode *mode, struct drm_display_mode *adj) | |
| 334 | +{ | |
| 335 | + struct armada_private *priv = crtc->dev->dev_private; | |
| 336 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 337 | + int ret; | |
| 338 | + | |
| 339 | + /* We can't do interlaced modes if we don't have the SPU_ADV_REG */ | |
| 340 | + if (!priv->variant->has_spu_adv_reg && | |
| 341 | + adj->flags & DRM_MODE_FLAG_INTERLACE) | |
| 342 | + return false; | |
| 343 | + | |
| 344 | + /* Check whether the display mode is possible */ | |
| 345 | + ret = priv->variant->crtc_compute_clock(dcrtc, adj, NULL); | |
| 346 | + if (ret) | |
| 347 | + return false; | |
| 348 | + | |
| 349 | + return true; | |
| 350 | +} | |
| 351 | + | |
| 352 | +void armada_drm_crtc_irq(struct armada_crtc *dcrtc, u32 stat) | |
| 353 | +{ | |
| 354 | + struct armada_vbl_event *e, *n; | |
| 355 | + void __iomem *base = dcrtc->base; | |
| 356 | + | |
| 357 | + if (stat & DMA_FF_UNDERFLOW) | |
| 358 | + DRM_ERROR("video underflow on crtc %u\n", dcrtc->num); | |
| 359 | + if (stat & GRA_FF_UNDERFLOW) | |
| 360 | + DRM_ERROR("graphics underflow on crtc %u\n", dcrtc->num); | |
| 361 | + | |
| 362 | + if (stat & VSYNC_IRQ) | |
| 363 | + drm_handle_vblank(dcrtc->crtc.dev, dcrtc->num); | |
| 364 | + | |
| 365 | + spin_lock(&dcrtc->irq_lock); | |
| 366 | + | |
| 367 | + list_for_each_entry_safe(e, n, &dcrtc->vbl_list, node) { | |
| 368 | + list_del_init(&e->node); | |
| 369 | + drm_vblank_put(dcrtc->crtc.dev, dcrtc->num); | |
| 370 | + e->fn(dcrtc, e->data); | |
| 371 | + } | |
| 372 | + | |
| 373 | + if (stat & GRA_FRAME_IRQ && dcrtc->interlaced) { | |
| 374 | + int i = stat & GRA_FRAME_IRQ0 ? 0 : 1; | |
| 375 | + uint32_t val; | |
| 376 | + | |
| 377 | + writel_relaxed(dcrtc->v[i].spu_v_porch, base + LCD_SPU_V_PORCH); | |
| 378 | + writel_relaxed(dcrtc->v[i].spu_v_h_total, | |
| 379 | + base + LCD_SPUT_V_H_TOTAL); | |
| 380 | + | |
| 381 | + val = readl_relaxed(base + LCD_SPU_ADV_REG); | |
| 382 | + val &= ~(ADV_VSYNC_L_OFF | ADV_VSYNC_H_OFF | ADV_VSYNCOFFEN); | |
| 383 | + val |= dcrtc->v[i].spu_adv_reg; | |
| 384 | + writel_relaxed(val, dcrtc->base + LCD_SPU_ADV_REG); | |
| 385 | + } | |
| 386 | + spin_unlock(&dcrtc->irq_lock); | |
| 387 | + | |
| 388 | + if (stat & GRA_FRAME_IRQ) { | |
| 389 | + struct drm_device *dev = dcrtc->crtc.dev; | |
| 390 | + | |
| 391 | + spin_lock(&dev->event_lock); | |
| 392 | + if (dcrtc->frame_work) | |
| 393 | + armada_drm_crtc_complete_frame_work(dcrtc); | |
| 394 | + spin_unlock(&dev->event_lock); | |
| 395 | + | |
| 396 | + wake_up(&dcrtc->frame_wait); | |
| 397 | + } | |
| 398 | +} | |
| 399 | + | |
| 400 | +/* These are locked by dev->vbl_lock */ | |
| 401 | +void armada_drm_crtc_disable_irq(struct armada_crtc *dcrtc, u32 mask) | |
| 402 | +{ | |
| 403 | + if (dcrtc->irq_ena & mask) { | |
| 404 | + dcrtc->irq_ena &= ~mask; | |
| 405 | + writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA); | |
| 406 | + } | |
| 407 | +} | |
| 408 | + | |
| 409 | +void armada_drm_crtc_enable_irq(struct armada_crtc *dcrtc, u32 mask) | |
| 410 | +{ | |
| 411 | + if ((dcrtc->irq_ena & mask) != mask) { | |
| 412 | + dcrtc->irq_ena |= mask; | |
| 413 | + writel(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA); | |
| 414 | + if (readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR) & mask) | |
| 415 | + writel(0, dcrtc->base + LCD_SPU_IRQ_ISR); | |
| 416 | + } | |
| 417 | +} | |
| 418 | + | |
| 419 | +static uint32_t armada_drm_crtc_calculate_csc(struct armada_crtc *dcrtc) | |
| 420 | +{ | |
| 421 | + struct drm_display_mode *adj = &dcrtc->crtc.mode; | |
| 422 | + uint32_t val = 0; | |
| 423 | + | |
| 424 | + if (dcrtc->csc_yuv_mode == CSC_YUV_CCIR709) | |
| 425 | + val |= CFG_CSC_YUV_CCIR709; | |
| 426 | + if (dcrtc->csc_rgb_mode == CSC_RGB_STUDIO) | |
| 427 | + val |= CFG_CSC_RGB_STUDIO; | |
| 428 | + | |
| 429 | + /* | |
| 430 | + * In auto mode, set the colorimetry, based upon the HDMI spec. | |
| 431 | + * 1280x720p, 1920x1080p and 1920x1080i use ITU709, others use | |
| 432 | + * ITU601. It may be more appropriate to set this depending on | |
| 433 | + * the source - but what if the graphic frame is YUV and the | |
| 434 | + * video frame is RGB? | |
| 435 | + */ | |
| 436 | + if ((adj->hdisplay == 1280 && adj->vdisplay == 720 && | |
| 437 | + !(adj->flags & DRM_MODE_FLAG_INTERLACE)) || | |
| 438 | + (adj->hdisplay == 1920 && adj->vdisplay == 1080)) { | |
| 439 | + if (dcrtc->csc_yuv_mode == CSC_AUTO) | |
| 440 | + val |= CFG_CSC_YUV_CCIR709; | |
| 441 | + } | |
| 442 | + | |
| 443 | + /* | |
| 444 | + * We assume we're connected to a TV-like device, so the YUV->RGB | |
| 445 | + * conversion should produce a limited range. We should set this | |
| 446 | + * depending on the connectors attached to this CRTC, and what | |
| 447 | + * kind of device they report being connected. | |
| 448 | + */ | |
| 449 | + if (dcrtc->csc_rgb_mode == CSC_AUTO) | |
| 450 | + val |= CFG_CSC_RGB_STUDIO; | |
| 451 | + | |
| 452 | + return val; | |
| 453 | +} | |
| 454 | + | |
| 455 | +/* The mode_config.mutex will be held for this call */ | |
| 456 | +static int armada_drm_crtc_mode_set(struct drm_crtc *crtc, | |
| 457 | + struct drm_display_mode *mode, struct drm_display_mode *adj, | |
| 458 | + int x, int y, struct drm_framebuffer *old_fb) | |
| 459 | +{ | |
| 460 | + struct armada_private *priv = crtc->dev->dev_private; | |
| 461 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 462 | + struct armada_regs regs[17]; | |
| 463 | + uint32_t lm, rm, tm, bm, val, sclk; | |
| 464 | + unsigned long flags; | |
| 465 | + unsigned i; | |
| 466 | + bool interlaced; | |
| 467 | + | |
| 468 | + drm_framebuffer_reference(crtc->fb); | |
| 469 | + | |
| 470 | + interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE); | |
| 471 | + | |
| 472 | + i = armada_drm_crtc_calc_fb(dcrtc->crtc.fb, x, y, regs, interlaced); | |
| 473 | + | |
| 474 | + rm = adj->crtc_hsync_start - adj->crtc_hdisplay; | |
| 475 | + lm = adj->crtc_htotal - adj->crtc_hsync_end; | |
| 476 | + bm = adj->crtc_vsync_start - adj->crtc_vdisplay; | |
| 477 | + tm = adj->crtc_vtotal - adj->crtc_vsync_end; | |
| 478 | + | |
| 479 | + DRM_DEBUG_DRIVER("H: %d %d %d %d lm %d rm %d\n", | |
| 480 | + adj->crtc_hdisplay, | |
| 481 | + adj->crtc_hsync_start, | |
| 482 | + adj->crtc_hsync_end, | |
| 483 | + adj->crtc_htotal, lm, rm); | |
| 484 | + DRM_DEBUG_DRIVER("V: %d %d %d %d tm %d bm %d\n", | |
| 485 | + adj->crtc_vdisplay, | |
| 486 | + adj->crtc_vsync_start, | |
| 487 | + adj->crtc_vsync_end, | |
| 488 | + adj->crtc_vtotal, tm, bm); | |
| 489 | + | |
| 490 | + /* Wait for pending flips to complete */ | |
| 491 | + wait_event(dcrtc->frame_wait, !dcrtc->frame_work); | |
| 492 | + | |
| 493 | + drm_vblank_pre_modeset(crtc->dev, dcrtc->num); | |
| 494 | + | |
| 495 | + crtc->mode = *adj; | |
| 496 | + | |
| 497 | + val = dcrtc->dumb_ctrl & ~CFG_DUMB_ENA; | |
| 498 | + if (val != dcrtc->dumb_ctrl) { | |
| 499 | + dcrtc->dumb_ctrl = val; | |
| 500 | + writel_relaxed(val, dcrtc->base + LCD_SPU_DUMB_CTRL); | |
| 501 | + } | |
| 502 | + | |
| 503 | + /* Now compute the divider for real */ | |
| 504 | + priv->variant->crtc_compute_clock(dcrtc, adj, &sclk); | |
| 505 | + | |
| 506 | + /* Ensure graphic fifo is enabled */ | |
| 507 | + armada_reg_queue_mod(regs, i, 0, CFG_PDWN64x66, LCD_SPU_SRAM_PARA1); | |
| 508 | + armada_reg_queue_set(regs, i, sclk, LCD_CFG_SCLK_DIV); | |
| 509 | + | |
| 510 | + if (interlaced ^ dcrtc->interlaced) { | |
| 511 | + if (adj->flags & DRM_MODE_FLAG_INTERLACE) | |
| 512 | + drm_vblank_get(dcrtc->crtc.dev, dcrtc->num); | |
| 513 | + else | |
| 514 | + drm_vblank_put(dcrtc->crtc.dev, dcrtc->num); | |
| 515 | + dcrtc->interlaced = interlaced; | |
| 516 | + } | |
| 517 | + | |
| 518 | + spin_lock_irqsave(&dcrtc->irq_lock, flags); | |
| 519 | + | |
| 520 | + /* Even interlaced/progressive frame */ | |
| 521 | + dcrtc->v[1].spu_v_h_total = adj->crtc_vtotal << 16 | | |
| 522 | + adj->crtc_htotal; | |
| 523 | + dcrtc->v[1].spu_v_porch = tm << 16 | bm; | |
| 524 | + val = adj->crtc_hsync_start; | |
| 525 | + dcrtc->v[1].spu_adv_reg = val << 20 | val | ADV_VSYNCOFFEN; | |
| 526 | + | |
| 527 | + if (interlaced) { | |
| 528 | + /* Odd interlaced frame */ | |
| 529 | + dcrtc->v[0].spu_v_h_total = dcrtc->v[1].spu_v_h_total + | |
| 530 | + (1 << 16); | |
| 531 | + dcrtc->v[0].spu_v_porch = dcrtc->v[1].spu_v_porch + 1; | |
| 532 | + val = adj->crtc_hsync_start - adj->crtc_htotal / 2; | |
| 533 | + dcrtc->v[0].spu_adv_reg = val << 20 | val | ADV_VSYNCOFFEN; | |
| 534 | + } else { | |
| 535 | + dcrtc->v[0] = dcrtc->v[1]; | |
| 536 | + } | |
| 537 | + | |
| 538 | + val = adj->crtc_vdisplay << 16 | adj->crtc_hdisplay; | |
| 539 | + | |
| 540 | + armada_reg_queue_set(regs, i, val, LCD_SPU_V_H_ACTIVE); | |
| 541 | + armada_reg_queue_set(regs, i, val, LCD_SPU_GRA_HPXL_VLN); | |
| 542 | + armada_reg_queue_set(regs, i, val, LCD_SPU_GZM_HPXL_VLN); | |
| 543 | + armada_reg_queue_set(regs, i, (lm << 16) | rm, LCD_SPU_H_PORCH); | |
| 544 | + armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_porch, LCD_SPU_V_PORCH); | |
| 545 | + armada_reg_queue_set(regs, i, dcrtc->v[0].spu_v_h_total, | |
| 546 | + LCD_SPUT_V_H_TOTAL); | |
| 547 | + | |
| 548 | + if (priv->variant->has_spu_adv_reg) | |
| 549 | + armada_reg_queue_mod(regs, i, dcrtc->v[0].spu_adv_reg, | |
| 550 | + ADV_VSYNC_L_OFF | ADV_VSYNC_H_OFF | | |
| 551 | + ADV_VSYNCOFFEN, LCD_SPU_ADV_REG); | |
| 552 | + | |
| 553 | + val = CFG_GRA_ENA | CFG_GRA_HSMOOTH; | |
| 554 | + val |= CFG_GRA_FMT(drm_fb_to_armada_fb(dcrtc->crtc.fb)->fmt); | |
| 555 | + val |= CFG_GRA_MOD(drm_fb_to_armada_fb(dcrtc->crtc.fb)->mod); | |
| 556 | + | |
| 557 | + if (drm_fb_to_armada_fb(dcrtc->crtc.fb)->fmt > CFG_420) | |
| 558 | + val |= CFG_PALETTE_ENA; | |
| 559 | + | |
| 560 | + if (interlaced) | |
| 561 | + val |= CFG_GRA_FTOGGLE; | |
| 562 | + | |
| 563 | + armada_reg_queue_mod(regs, i, val, CFG_GRAFORMAT | | |
| 564 | + CFG_GRA_MOD(CFG_SWAPRB | CFG_SWAPUV | | |
| 565 | + CFG_SWAPYU | CFG_YUV2RGB) | | |
| 566 | + CFG_PALETTE_ENA | CFG_GRA_FTOGGLE, | |
| 567 | + LCD_SPU_DMA_CTRL0); | |
| 568 | + | |
| 569 | + val = adj->flags & DRM_MODE_FLAG_NVSYNC ? CFG_VSYNC_INV : 0; | |
| 570 | + armada_reg_queue_mod(regs, i, val, CFG_VSYNC_INV, LCD_SPU_DMA_CTRL1); | |
| 571 | + | |
| 572 | + val = dcrtc->spu_iopad_ctrl | armada_drm_crtc_calculate_csc(dcrtc); | |
| 573 | + armada_reg_queue_set(regs, i, val, LCD_SPU_IOPAD_CONTROL); | |
| 574 | + armada_reg_queue_end(regs, i); | |
| 575 | + | |
| 576 | + armada_drm_crtc_update_regs(dcrtc, regs); | |
| 577 | + spin_unlock_irqrestore(&dcrtc->irq_lock, flags); | |
| 578 | + | |
| 579 | + armada_drm_crtc_update(dcrtc); | |
| 580 | + | |
| 581 | + drm_vblank_post_modeset(crtc->dev, dcrtc->num); | |
| 582 | + armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms)); | |
| 583 | + | |
| 584 | + return 0; | |
| 585 | +} | |
| 586 | + | |
| 587 | +/* The mode_config.mutex will be held for this call */ | |
| 588 | +static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, | |
| 589 | + struct drm_framebuffer *old_fb) | |
| 590 | +{ | |
| 591 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 592 | + struct armada_regs regs[4]; | |
| 593 | + unsigned i; | |
| 594 | + | |
| 595 | + i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs, | |
| 596 | + dcrtc->interlaced); | |
| 597 | + armada_reg_queue_end(regs, i); | |
| 598 | + | |
| 599 | + /* Wait for pending flips to complete */ | |
| 600 | + wait_event(dcrtc->frame_wait, !dcrtc->frame_work); | |
| 601 | + | |
| 602 | + /* Take a reference to the new fb as we're using it */ | |
| 603 | + drm_framebuffer_reference(crtc->fb); | |
| 604 | + | |
| 605 | + /* Update the base in the CRTC */ | |
| 606 | + armada_drm_crtc_update_regs(dcrtc, regs); | |
| 607 | + | |
| 608 | + /* Drop our previously held reference */ | |
| 609 | + armada_drm_crtc_finish_fb(dcrtc, old_fb, dpms_blanked(dcrtc->dpms)); | |
| 610 | + | |
| 611 | + return 0; | |
| 612 | +} | |
| 613 | + | |
| 614 | +static void armada_drm_crtc_load_lut(struct drm_crtc *crtc) | |
| 615 | +{ | |
| 616 | +} | |
| 617 | + | |
| 618 | +/* The mode_config.mutex will be held for this call */ | |
| 619 | +static void armada_drm_crtc_disable(struct drm_crtc *crtc) | |
| 620 | +{ | |
| 621 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 622 | + | |
| 623 | + armada_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); | |
| 624 | + armada_drm_crtc_finish_fb(dcrtc, crtc->fb, true); | |
| 625 | + | |
| 626 | + /* Power down most RAMs and FIFOs */ | |
| 627 | + writel_relaxed(CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 | | |
| 628 | + CFG_PDWN32x32 | CFG_PDWN16x66 | CFG_PDWN32x66 | | |
| 629 | + CFG_PDWN64x66, dcrtc->base + LCD_SPU_SRAM_PARA1); | |
| 630 | +} | |
| 631 | + | |
| 632 | +static const struct drm_crtc_helper_funcs armada_crtc_helper_funcs = { | |
| 633 | + .dpms = armada_drm_crtc_dpms, | |
| 634 | + .prepare = armada_drm_crtc_prepare, | |
| 635 | + .commit = armada_drm_crtc_commit, | |
| 636 | + .mode_fixup = armada_drm_crtc_mode_fixup, | |
| 637 | + .mode_set = armada_drm_crtc_mode_set, | |
| 638 | + .mode_set_base = armada_drm_crtc_mode_set_base, | |
| 639 | + .load_lut = armada_drm_crtc_load_lut, | |
| 640 | + .disable = armada_drm_crtc_disable, | |
| 641 | +}; | |
| 642 | + | |
| 643 | +static void armada_drm_crtc_destroy(struct drm_crtc *crtc) | |
| 644 | +{ | |
| 645 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 646 | + struct armada_private *priv = crtc->dev->dev_private; | |
| 647 | + | |
| 648 | + priv->dcrtc[dcrtc->num] = NULL; | |
| 649 | + drm_crtc_cleanup(&dcrtc->crtc); | |
| 650 | + | |
| 651 | + if (!IS_ERR(dcrtc->clk)) | |
| 652 | + clk_disable_unprepare(dcrtc->clk); | |
| 653 | + | |
| 654 | + kfree(dcrtc); | |
| 655 | +} | |
| 656 | + | |
| 657 | +/* | |
| 658 | + * The mode_config lock is held here, to prevent races between this | |
| 659 | + * and a mode_set. | |
| 660 | + */ | |
| 661 | +static int armada_drm_crtc_page_flip(struct drm_crtc *crtc, | |
| 662 | + struct drm_framebuffer *fb, struct drm_pending_vblank_event *event) | |
| 663 | +{ | |
| 664 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 665 | + struct armada_frame_work *work; | |
| 666 | + struct drm_device *dev = crtc->dev; | |
| 667 | + unsigned long flags; | |
| 668 | + unsigned i; | |
| 669 | + int ret; | |
| 670 | + | |
| 671 | + /* We don't support changing the pixel format */ | |
| 672 | + if (fb->pixel_format != crtc->fb->pixel_format) | |
| 673 | + return -EINVAL; | |
| 674 | + | |
| 675 | + work = kmalloc(sizeof(*work), GFP_KERNEL); | |
| 676 | + if (!work) | |
| 677 | + return -ENOMEM; | |
| 678 | + | |
| 679 | + work->event = event; | |
| 680 | + work->old_fb = dcrtc->crtc.fb; | |
| 681 | + | |
| 682 | + i = armada_drm_crtc_calc_fb(fb, crtc->x, crtc->y, work->regs, | |
| 683 | + dcrtc->interlaced); | |
| 684 | + armada_reg_queue_end(work->regs, i); | |
| 685 | + | |
| 686 | + /* | |
| 687 | + * Hold the old framebuffer for the work - DRM appears to drop our | |
| 688 | + * reference to the old framebuffer in drm_mode_page_flip_ioctl(). | |
| 689 | + */ | |
| 690 | + drm_framebuffer_reference(work->old_fb); | |
| 691 | + | |
| 692 | + ret = armada_drm_crtc_queue_frame_work(dcrtc, work); | |
| 693 | + if (ret) { | |
| 694 | + /* | |
| 695 | + * Undo our reference above; DRM does not drop the reference | |
| 696 | + * to this object on error, so that's okay. | |
| 697 | + */ | |
| 698 | + drm_framebuffer_unreference(work->old_fb); | |
| 699 | + kfree(work); | |
| 700 | + return ret; | |
| 701 | + } | |
| 702 | + | |
| 703 | + /* | |
| 704 | + * Don't take a reference on the new framebuffer; | |
| 705 | + * drm_mode_page_flip_ioctl() has already grabbed a reference and | |
| 706 | + * will _not_ drop that reference on successful return from this | |
| 707 | + * function. Simply mark this new framebuffer as the current one. | |
| 708 | + */ | |
| 709 | + dcrtc->crtc.fb = fb; | |
| 710 | + | |
| 711 | + /* | |
| 712 | + * Finally, if the display is blanked, we won't receive an | |
| 713 | + * interrupt, so complete it now. | |
| 714 | + */ | |
| 715 | + if (dpms_blanked(dcrtc->dpms)) { | |
| 716 | + spin_lock_irqsave(&dev->event_lock, flags); | |
| 717 | + if (dcrtc->frame_work) | |
| 718 | + armada_drm_crtc_complete_frame_work(dcrtc); | |
| 719 | + spin_unlock_irqrestore(&dev->event_lock, flags); | |
| 720 | + } | |
| 721 | + | |
| 722 | + return 0; | |
| 723 | +} | |
| 724 | + | |
| 725 | +static int | |
| 726 | +armada_drm_crtc_set_property(struct drm_crtc *crtc, | |
| 727 | + struct drm_property *property, uint64_t val) | |
| 728 | +{ | |
| 729 | + struct armada_private *priv = crtc->dev->dev_private; | |
| 730 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 731 | + bool update_csc = false; | |
| 732 | + | |
| 733 | + if (property == priv->csc_yuv_prop) { | |
| 734 | + dcrtc->csc_yuv_mode = val; | |
| 735 | + update_csc = true; | |
| 736 | + } else if (property == priv->csc_rgb_prop) { | |
| 737 | + dcrtc->csc_rgb_mode = val; | |
| 738 | + update_csc = true; | |
| 739 | + } | |
| 740 | + | |
| 741 | + if (update_csc) { | |
| 742 | + uint32_t val; | |
| 743 | + | |
| 744 | + val = dcrtc->spu_iopad_ctrl | | |
| 745 | + armada_drm_crtc_calculate_csc(dcrtc); | |
| 746 | + writel_relaxed(val, dcrtc->base + LCD_SPU_IOPAD_CONTROL); | |
| 747 | + } | |
| 748 | + | |
| 749 | + return 0; | |
| 750 | +} | |
| 751 | + | |
| 752 | +static struct drm_crtc_funcs armada_crtc_funcs = { | |
| 753 | + .destroy = armada_drm_crtc_destroy, | |
| 754 | + .set_config = drm_crtc_helper_set_config, | |
| 755 | + .page_flip = armada_drm_crtc_page_flip, | |
| 756 | + .set_property = armada_drm_crtc_set_property, | |
| 757 | +}; | |
| 758 | + | |
| 759 | +static struct drm_prop_enum_list armada_drm_csc_yuv_enum_list[] = { | |
| 760 | + { CSC_AUTO, "Auto" }, | |
| 761 | + { CSC_YUV_CCIR601, "CCIR601" }, | |
| 762 | + { CSC_YUV_CCIR709, "CCIR709" }, | |
| 763 | +}; | |
| 764 | + | |
| 765 | +static struct drm_prop_enum_list armada_drm_csc_rgb_enum_list[] = { | |
| 766 | + { CSC_AUTO, "Auto" }, | |
| 767 | + { CSC_RGB_COMPUTER, "Computer system" }, | |
| 768 | + { CSC_RGB_STUDIO, "Studio" }, | |
| 769 | +}; | |
| 770 | + | |
| 771 | +static int armada_drm_crtc_create_properties(struct drm_device *dev) | |
| 772 | +{ | |
| 773 | + struct armada_private *priv = dev->dev_private; | |
| 774 | + | |
| 775 | + if (priv->csc_yuv_prop) | |
| 776 | + return 0; | |
| 777 | + | |
| 778 | + priv->csc_yuv_prop = drm_property_create_enum(dev, 0, | |
| 779 | + "CSC_YUV", armada_drm_csc_yuv_enum_list, | |
| 780 | + ARRAY_SIZE(armada_drm_csc_yuv_enum_list)); | |
| 781 | + priv->csc_rgb_prop = drm_property_create_enum(dev, 0, | |
| 782 | + "CSC_RGB", armada_drm_csc_rgb_enum_list, | |
| 783 | + ARRAY_SIZE(armada_drm_csc_rgb_enum_list)); | |
| 784 | + | |
| 785 | + if (!priv->csc_yuv_prop || !priv->csc_rgb_prop) | |
| 786 | + return -ENOMEM; | |
| 787 | + | |
| 788 | + return 0; | |
| 789 | +} | |
| 790 | + | |
| 791 | +int armada_drm_crtc_create(struct drm_device *dev, unsigned num, | |
| 792 | + struct resource *res) | |
| 793 | +{ | |
| 794 | + struct armada_private *priv = dev->dev_private; | |
| 795 | + struct armada_crtc *dcrtc; | |
| 796 | + void __iomem *base; | |
| 797 | + int ret; | |
| 798 | + | |
| 799 | + ret = armada_drm_crtc_create_properties(dev); | |
| 800 | + if (ret) | |
| 801 | + return ret; | |
| 802 | + | |
| 803 | + base = devm_request_and_ioremap(dev->dev, res); | |
| 804 | + if (!base) { | |
| 805 | + DRM_ERROR("failed to ioremap register\n"); | |
| 806 | + return -ENOMEM; | |
| 807 | + } | |
| 808 | + | |
| 809 | + dcrtc = kzalloc(sizeof(*dcrtc), GFP_KERNEL); | |
| 810 | + if (!dcrtc) { | |
| 811 | + DRM_ERROR("failed to allocate Armada crtc\n"); | |
| 812 | + return -ENOMEM; | |
| 813 | + } | |
| 814 | + | |
| 815 | + dcrtc->base = base; | |
| 816 | + dcrtc->num = num; | |
| 817 | + dcrtc->clk = ERR_PTR(-EINVAL); | |
| 818 | + dcrtc->csc_yuv_mode = CSC_AUTO; | |
| 819 | + dcrtc->csc_rgb_mode = CSC_AUTO; | |
| 820 | + dcrtc->cfg_dumb_ctrl = DUMB24_RGB888_0; | |
| 821 | + dcrtc->spu_iopad_ctrl = CFG_VSCALE_LN_EN | CFG_IOPAD_DUMB24; | |
| 822 | + spin_lock_init(&dcrtc->irq_lock); | |
| 823 | + dcrtc->irq_ena = CLEAN_SPU_IRQ_ISR; | |
| 824 | + INIT_LIST_HEAD(&dcrtc->vbl_list); | |
| 825 | + init_waitqueue_head(&dcrtc->frame_wait); | |
| 826 | + | |
| 827 | + /* Initialize some registers which we don't otherwise set */ | |
| 828 | + writel_relaxed(0x00000001, dcrtc->base + LCD_CFG_SCLK_DIV); | |
| 829 | + writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_BLANKCOLOR); | |
| 830 | + writel_relaxed(dcrtc->spu_iopad_ctrl, | |
| 831 | + dcrtc->base + LCD_SPU_IOPAD_CONTROL); | |
| 832 | + writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_SRAM_PARA0); | |
| 833 | + writel_relaxed(CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 | | |
| 834 | + CFG_PDWN32x32 | CFG_PDWN16x66 | CFG_PDWN32x66 | | |
| 835 | + CFG_PDWN64x66, dcrtc->base + LCD_SPU_SRAM_PARA1); | |
| 836 | + writel_relaxed(0x2032ff81, dcrtc->base + LCD_SPU_DMA_CTRL1); | |
| 837 | + writel_relaxed(0x00000000, dcrtc->base + LCD_SPU_GRA_OVSA_HPXL_VLN); | |
| 838 | + | |
| 839 | + if (priv->variant->crtc_init) { | |
| 840 | + ret = priv->variant->crtc_init(dcrtc); | |
| 841 | + if (ret) { | |
| 842 | + kfree(dcrtc); | |
| 843 | + return ret; | |
| 844 | + } | |
| 845 | + } | |
| 846 | + | |
| 847 | + /* Ensure AXI pipeline is enabled */ | |
| 848 | + armada_updatel(CFG_ARBFAST_ENA, 0, dcrtc->base + LCD_SPU_DMA_CTRL0); | |
| 849 | + | |
| 850 | + priv->dcrtc[dcrtc->num] = dcrtc; | |
| 851 | + | |
| 852 | + drm_crtc_init(dev, &dcrtc->crtc, &armada_crtc_funcs); | |
| 853 | + drm_crtc_helper_add(&dcrtc->crtc, &armada_crtc_helper_funcs); | |
| 854 | + | |
| 855 | + drm_object_attach_property(&dcrtc->crtc.base, priv->csc_yuv_prop, | |
| 856 | + dcrtc->csc_yuv_mode); | |
| 857 | + drm_object_attach_property(&dcrtc->crtc.base, priv->csc_rgb_prop, | |
| 858 | + dcrtc->csc_rgb_mode); | |
| 859 | + | |
| 860 | + return armada_overlay_plane_create(dev, 1 << dcrtc->num); | |
| 861 | +} | |
| @@ -0,0 +1,74 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#ifndef ARMADA_CRTC_H | |
| 9 | +#define ARMADA_CRTC_H | |
| 10 | + | |
| 11 | +struct armada_gem_object; | |
| 12 | + | |
| 13 | +struct armada_regs { | |
| 14 | + uint32_t offset; | |
| 15 | + uint32_t mask; | |
| 16 | + uint32_t val; | |
| 17 | +}; | |
| 18 | + | |
| 19 | +#define armada_reg_queue_mod(_r, _i, _v, _m, _o) \ | |
| 20 | + do { \ | |
| 21 | + struct armada_regs *__reg = _r; \ | |
| 22 | + __reg[_i].offset = _o; \ | |
| 23 | + __reg[_i].mask = ~(_m); \ | |
| 24 | + __reg[_i].val = _v; \ | |
| 25 | + _i++; \ | |
| 26 | + } while (0) | |
| 27 | + | |
| 28 | +#define armada_reg_queue_set(_r, _i, _v, _o) \ | |
| 29 | + armada_reg_queue_mod(_r, _i, _v, ~0, _o) | |
| 30 | + | |
| 31 | +#define armada_reg_queue_end(_r, _i) \ | |
| 32 | + armada_reg_queue_mod(_r, _i, 0, 0, ~0) | |
| 33 | + | |
| 34 | +struct armada_frame_work; | |
| 35 | + | |
| 36 | +struct armada_crtc { | |
| 37 | + struct drm_crtc crtc; | |
| 38 | + unsigned num; | |
| 39 | + void __iomem *base; | |
| 40 | + struct clk *clk; | |
| 41 | + struct { | |
| 42 | + uint32_t spu_v_h_total; | |
| 43 | + uint32_t spu_v_porch; | |
| 44 | + uint32_t spu_adv_reg; | |
| 45 | + } v[2]; | |
| 46 | + bool interlaced; | |
| 47 | + uint8_t csc_yuv_mode; | |
| 48 | + uint8_t csc_rgb_mode; | |
| 49 | + | |
| 50 | + struct drm_plane *plane; | |
| 51 | + | |
| 52 | + int dpms; | |
| 53 | + uint32_t cfg_dumb_ctrl; | |
| 54 | + uint32_t dumb_ctrl; | |
| 55 | + uint32_t spu_iopad_ctrl; | |
| 56 | + | |
| 57 | + wait_queue_head_t frame_wait; | |
| 58 | + struct armada_frame_work *frame_work; | |
| 59 | + | |
| 60 | + spinlock_t irq_lock; | |
| 61 | + uint32_t irq_ena; | |
| 62 | + struct list_head vbl_list; | |
| 63 | +}; | |
| 64 | +#define drm_to_armada_crtc(c) container_of(c, struct armada_crtc, crtc) | |
| 65 | + | |
| 66 | +int armada_drm_crtc_create(struct drm_device *, unsigned, struct resource *); | |
| 67 | +void armada_drm_crtc_gamma_set(struct drm_crtc *, u16, u16, u16, int); | |
| 68 | +void armada_drm_crtc_gamma_get(struct drm_crtc *, u16 *, u16 *, u16 *, int); | |
| 69 | +void armada_drm_crtc_irq(struct armada_crtc *, u32); | |
| 70 | +void armada_drm_crtc_disable_irq(struct armada_crtc *, u32); | |
| 71 | +void armada_drm_crtc_enable_irq(struct armada_crtc *, u32); | |
| 72 | +void armada_drm_crtc_update_regs(struct armada_crtc *, struct armada_regs *); | |
| 73 | + | |
| 74 | +#endif | |
| @@ -0,0 +1,183 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * Rewritten from the dovefb driver, and Armada510 manuals. | |
| 4 | + * | |
| 5 | + * This program is free software; you can redistribute it and/or modify | |
| 6 | + * it under the terms of the GNU General Public License version 2 as | |
| 7 | + * published by the Free Software Foundation. | |
| 8 | + */ | |
| 9 | +#include <linux/ctype.h> | |
| 10 | +#include <linux/debugfs.h> | |
| 11 | +#include <linux/module.h> | |
| 12 | +#include <linux/seq_file.h> | |
| 13 | +#include <drm/drmP.h> | |
| 14 | +#include "armada_crtc.h" | |
| 15 | +#include "armada_drm.h" | |
| 16 | + | |
| 17 | +static int armada_debugfs_gem_linear_show(struct seq_file *m, void *data) | |
| 18 | +{ | |
| 19 | + struct drm_info_node *node = m->private; | |
| 20 | + struct drm_device *dev = node->minor->dev; | |
| 21 | + struct armada_private *priv = dev->dev_private; | |
| 22 | + int ret; | |
| 23 | + | |
| 24 | + mutex_lock(&dev->struct_mutex); | |
| 25 | + ret = drm_mm_dump_table(m, &priv->linear); | |
| 26 | + mutex_unlock(&dev->struct_mutex); | |
| 27 | + | |
| 28 | + return ret; | |
| 29 | +} | |
| 30 | + | |
| 31 | +static int armada_debugfs_reg_show(struct seq_file *m, void *data) | |
| 32 | +{ | |
| 33 | + struct drm_device *dev = m->private; | |
| 34 | + struct armada_private *priv = dev->dev_private; | |
| 35 | + int n, i; | |
| 36 | + | |
| 37 | + if (priv) { | |
| 38 | + for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) { | |
| 39 | + struct armada_crtc *dcrtc = priv->dcrtc[n]; | |
| 40 | + if (!dcrtc) | |
| 41 | + continue; | |
| 42 | + | |
| 43 | + for (i = 0x84; i <= 0x1c4; i += 4) { | |
| 44 | + uint32_t v = readl_relaxed(dcrtc->base + i); | |
| 45 | + seq_printf(m, "%u: 0x%04x: 0x%08x\n", n, i, v); | |
| 46 | + } | |
| 47 | + } | |
| 48 | + } | |
| 49 | + | |
| 50 | + return 0; | |
| 51 | +} | |
| 52 | + | |
| 53 | +static int armada_debugfs_reg_r_open(struct inode *inode, struct file *file) | |
| 54 | +{ | |
| 55 | + return single_open(file, armada_debugfs_reg_show, inode->i_private); | |
| 56 | +} | |
| 57 | + | |
| 58 | +static const struct file_operations fops_reg_r = { | |
| 59 | + .owner = THIS_MODULE, | |
| 60 | + .open = armada_debugfs_reg_r_open, | |
| 61 | + .read = seq_read, | |
| 62 | + .llseek = seq_lseek, | |
| 63 | + .release = single_release, | |
| 64 | +}; | |
| 65 | + | |
| 66 | +static int armada_debugfs_write(struct file *file, const char __user *ptr, | |
| 67 | + size_t len, loff_t *off) | |
| 68 | +{ | |
| 69 | + struct drm_device *dev = file->private_data; | |
| 70 | + struct armada_private *priv = dev->dev_private; | |
| 71 | + struct armada_crtc *dcrtc = priv->dcrtc[0]; | |
| 72 | + char buf[32], *p; | |
| 73 | + uint32_t reg, val; | |
| 74 | + int ret; | |
| 75 | + | |
| 76 | + if (*off != 0) | |
| 77 | + return 0; | |
| 78 | + | |
| 79 | + if (len > sizeof(buf) - 1) | |
| 80 | + len = sizeof(buf) - 1; | |
| 81 | + | |
| 82 | + ret = strncpy_from_user(buf, ptr, len); | |
| 83 | + if (ret < 0) | |
| 84 | + return ret; | |
| 85 | + buf[len] = '\0'; | |
| 86 | + | |
| 87 | + reg = simple_strtoul(buf, &p, 16); | |
| 88 | + if (!isspace(*p)) | |
| 89 | + return -EINVAL; | |
| 90 | + val = simple_strtoul(p + 1, NULL, 16); | |
| 91 | + | |
| 92 | + if (reg >= 0x84 && reg <= 0x1c4) | |
| 93 | + writel(val, dcrtc->base + reg); | |
| 94 | + | |
| 95 | + return len; | |
| 96 | +} | |
| 97 | + | |
| 98 | +static int armada_debugfs_reg_w_open(struct inode *inode, struct file *file) | |
| 99 | +{ | |
| 100 | + file->private_data = inode->i_private; | |
| 101 | + return 0; | |
| 102 | +} | |
| 103 | + | |
| 104 | +static const struct file_operations fops_reg_w = { | |
| 105 | + .owner = THIS_MODULE, | |
| 106 | + .open = armada_debugfs_reg_w_open, | |
| 107 | + .write = armada_debugfs_write, | |
| 108 | + .llseek = noop_llseek, | |
| 109 | +}; | |
| 110 | + | |
| 111 | +static struct drm_info_list armada_debugfs_list[] = { | |
| 112 | + { "gem_linear", armada_debugfs_gem_linear_show, 0 }, | |
| 113 | +}; | |
| 114 | +#define ARMADA_DEBUGFS_ENTRIES ARRAY_SIZE(armada_debugfs_list) | |
| 115 | + | |
| 116 | +static int drm_add_fake_info_node(struct drm_minor *minor, struct dentry *ent, | |
| 117 | + const void *key) | |
| 118 | +{ | |
| 119 | + struct drm_info_node *node; | |
| 120 | + | |
| 121 | + node = kmalloc(sizeof(struct drm_info_node), GFP_KERNEL); | |
| 122 | + if (node == NULL) { | |
| 123 | + debugfs_remove(ent); | |
| 124 | + return -ENOMEM; | |
| 125 | + } | |
| 126 | + | |
| 127 | + node->minor = minor; | |
| 128 | + node->dent = ent; | |
| 129 | + node->info_ent = (void *) key; | |
| 130 | + | |
| 131 | + mutex_lock(&minor->debugfs_lock); | |
| 132 | + list_add(&node->list, &minor->debugfs_list); | |
| 133 | + mutex_unlock(&minor->debugfs_lock); | |
| 134 | + | |
| 135 | + return 0; | |
| 136 | +} | |
| 137 | + | |
| 138 | +static int armada_debugfs_create(struct dentry *root, struct drm_minor *minor, | |
| 139 | + const char *name, umode_t mode, const struct file_operations *fops) | |
| 140 | +{ | |
| 141 | + struct dentry *de; | |
| 142 | + | |
| 143 | + de = debugfs_create_file(name, mode, root, minor->dev, fops); | |
| 144 | + | |
| 145 | + return drm_add_fake_info_node(minor, de, fops); | |
| 146 | +} | |
| 147 | + | |
| 148 | +int armada_drm_debugfs_init(struct drm_minor *minor) | |
| 149 | +{ | |
| 150 | + int ret; | |
| 151 | + | |
| 152 | + ret = drm_debugfs_create_files(armada_debugfs_list, | |
| 153 | + ARMADA_DEBUGFS_ENTRIES, | |
| 154 | + minor->debugfs_root, minor); | |
| 155 | + if (ret) | |
| 156 | + return ret; | |
| 157 | + | |
| 158 | + ret = armada_debugfs_create(minor->debugfs_root, minor, | |
| 159 | + "reg", S_IFREG | S_IRUSR, &fops_reg_r); | |
| 160 | + if (ret) | |
| 161 | + goto err_1; | |
| 162 | + | |
| 163 | + ret = armada_debugfs_create(minor->debugfs_root, minor, | |
| 164 | + "reg_wr", S_IFREG | S_IWUSR, &fops_reg_w); | |
| 165 | + if (ret) | |
| 166 | + goto err_2; | |
| 167 | + return ret; | |
| 168 | + | |
| 169 | + err_2: | |
| 170 | + drm_debugfs_remove_files((struct drm_info_list *)&fops_reg_r, 1, minor); | |
| 171 | + err_1: | |
| 172 | + drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES, | |
| 173 | + minor); | |
| 174 | + return ret; | |
| 175 | +} | |
| 176 | + | |
| 177 | +void armada_drm_debugfs_cleanup(struct drm_minor *minor) | |
| 178 | +{ | |
| 179 | + drm_debugfs_remove_files((struct drm_info_list *)&fops_reg_w, 1, minor); | |
| 180 | + drm_debugfs_remove_files((struct drm_info_list *)&fops_reg_r, 1, minor); | |
| 181 | + drm_debugfs_remove_files(armada_debugfs_list, ARMADA_DEBUGFS_ENTRIES, | |
| 182 | + minor); | |
| 183 | +} | |
| @@ -0,0 +1,112 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#ifndef ARMADA_DRM_H | |
| 9 | +#define ARMADA_DRM_H | |
| 10 | + | |
| 11 | +#include <linux/kfifo.h> | |
| 12 | +#include <linux/io.h> | |
| 13 | +#include <linux/workqueue.h> | |
| 14 | +#include <drm/drmP.h> | |
| 15 | + | |
| 16 | +struct armada_crtc; | |
| 17 | +struct armada_gem_object; | |
| 18 | +struct clk; | |
| 19 | +struct drm_fb_helper; | |
| 20 | + | |
| 21 | +static inline void | |
| 22 | +armada_updatel(uint32_t val, uint32_t mask, void __iomem *ptr) | |
| 23 | +{ | |
| 24 | + uint32_t ov, v; | |
| 25 | + | |
| 26 | + ov = v = readl_relaxed(ptr); | |
| 27 | + v = (v & ~mask) | val; | |
| 28 | + if (ov != v) | |
| 29 | + writel_relaxed(v, ptr); | |
| 30 | +} | |
| 31 | + | |
| 32 | +static inline uint32_t armada_pitch(uint32_t width, uint32_t bpp) | |
| 33 | +{ | |
| 34 | + uint32_t pitch = bpp != 4 ? width * ((bpp + 7) / 8) : width / 2; | |
| 35 | + | |
| 36 | + /* 88AP510 spec recommends pitch be a multiple of 128 */ | |
| 37 | + return ALIGN(pitch, 128); | |
| 38 | +} | |
| 39 | + | |
| 40 | +struct armada_vbl_event { | |
| 41 | + struct list_head node; | |
| 42 | + void *data; | |
| 43 | + void (*fn)(struct armada_crtc *, void *); | |
| 44 | +}; | |
| 45 | +void armada_drm_vbl_event_add(struct armada_crtc *, | |
| 46 | + struct armada_vbl_event *); | |
| 47 | +void armada_drm_vbl_event_remove(struct armada_crtc *, | |
| 48 | + struct armada_vbl_event *); | |
| 49 | +void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *, | |
| 50 | + struct armada_vbl_event *); | |
| 51 | +#define armada_drm_vbl_event_init(_e, _f, _d) do { \ | |
| 52 | + struct armada_vbl_event *__e = _e; \ | |
| 53 | + INIT_LIST_HEAD(&__e->node); \ | |
| 54 | + __e->data = _d; \ | |
| 55 | + __e->fn = _f; \ | |
| 56 | +} while (0) | |
| 57 | + | |
| 58 | + | |
| 59 | +struct armada_private; | |
| 60 | + | |
| 61 | +struct armada_variant { | |
| 62 | + bool has_spu_adv_reg; | |
| 63 | + int (*init)(struct armada_private *, struct device *); | |
| 64 | + int (*crtc_init)(struct armada_crtc *); | |
| 65 | + int (*crtc_compute_clock)(struct armada_crtc *, | |
| 66 | + const struct drm_display_mode *, | |
| 67 | + uint32_t *); | |
| 68 | +}; | |
| 69 | + | |
| 70 | +/* Variant ops */ | |
| 71 | +extern const struct armada_variant armada510_ops; | |
| 72 | + | |
| 73 | +struct armada_private { | |
| 74 | + const struct armada_variant *variant; | |
| 75 | + struct work_struct fb_unref_work; | |
| 76 | + DECLARE_KFIFO(fb_unref, struct drm_framebuffer *, 8); | |
| 77 | + struct drm_fb_helper *fbdev; | |
| 78 | + struct armada_crtc *dcrtc[2]; | |
| 79 | + struct drm_mm linear; | |
| 80 | + struct clk *extclk[2]; | |
| 81 | + struct drm_property *csc_yuv_prop; | |
| 82 | + struct drm_property *csc_rgb_prop; | |
| 83 | + struct drm_property *colorkey_prop; | |
| 84 | + struct drm_property *colorkey_min_prop; | |
| 85 | + struct drm_property *colorkey_max_prop; | |
| 86 | + struct drm_property *colorkey_val_prop; | |
| 87 | + struct drm_property *colorkey_alpha_prop; | |
| 88 | + struct drm_property *colorkey_mode_prop; | |
| 89 | + struct drm_property *brightness_prop; | |
| 90 | + struct drm_property *contrast_prop; | |
| 91 | + struct drm_property *saturation_prop; | |
| 92 | +#ifdef CONFIG_DEBUG_FS | |
| 93 | + struct dentry *de; | |
| 94 | +#endif | |
| 95 | +}; | |
| 96 | + | |
| 97 | +void __armada_drm_queue_unref_work(struct drm_device *, | |
| 98 | + struct drm_framebuffer *); | |
| 99 | +void armada_drm_queue_unref_work(struct drm_device *, | |
| 100 | + struct drm_framebuffer *); | |
| 101 | + | |
| 102 | +extern const struct drm_mode_config_funcs armada_drm_mode_config_funcs; | |
| 103 | + | |
| 104 | +int armada_fbdev_init(struct drm_device *); | |
| 105 | +void armada_fbdev_fini(struct drm_device *); | |
| 106 | + | |
| 107 | +int armada_overlay_plane_create(struct drm_device *, unsigned long); | |
| 108 | + | |
| 109 | +int armada_drm_debugfs_init(struct drm_minor *); | |
| 110 | +void armada_drm_debugfs_cleanup(struct drm_minor *); | |
| 111 | + | |
| 112 | +#endif | |
| @@ -0,0 +1,380 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#include <linux/clk.h> | |
| 9 | +#include <linux/module.h> | |
| 10 | +#include <drm/drmP.h> | |
| 11 | +#include <drm/drm_crtc_helper.h> | |
| 12 | +#include "armada_crtc.h" | |
| 13 | +#include "armada_drm.h" | |
| 14 | +#include "armada_gem.h" | |
| 15 | +#include "armada_hw.h" | |
| 16 | +#include <drm/armada_drm.h> | |
| 17 | +#include "armada_ioctlP.h" | |
| 18 | + | |
| 19 | +static void armada_drm_unref_work(struct work_struct *work) | |
| 20 | +{ | |
| 21 | + struct armada_private *priv = | |
| 22 | + container_of(work, struct armada_private, fb_unref_work); | |
| 23 | + struct drm_framebuffer *fb; | |
| 24 | + | |
| 25 | + while (kfifo_get(&priv->fb_unref, &fb)) | |
| 26 | + drm_framebuffer_unreference(fb); | |
| 27 | +} | |
| 28 | + | |
| 29 | +/* Must be called with dev->event_lock held */ | |
| 30 | +void __armada_drm_queue_unref_work(struct drm_device *dev, | |
| 31 | + struct drm_framebuffer *fb) | |
| 32 | +{ | |
| 33 | + struct armada_private *priv = dev->dev_private; | |
| 34 | + | |
| 35 | + /* | |
| 36 | + * Yes, we really must jump through these hoops just to store a | |
| 37 | + * _pointer_ to something into the kfifo. This is utterly insane | |
| 38 | + * and idiotic, because it kfifo requires the _data_ pointed to by | |
| 39 | + * the pointer const, not the pointer itself. Not only that, but | |
| 40 | + * you have to pass a pointer _to_ the pointer you want stored. | |
| 41 | + */ | |
| 42 | + const struct drm_framebuffer *silly_api_alert = fb; | |
| 43 | + WARN_ON(!kfifo_put(&priv->fb_unref, &silly_api_alert)); | |
| 44 | + schedule_work(&priv->fb_unref_work); | |
| 45 | +} | |
| 46 | + | |
| 47 | +void armada_drm_queue_unref_work(struct drm_device *dev, | |
| 48 | + struct drm_framebuffer *fb) | |
| 49 | +{ | |
| 50 | + unsigned long flags; | |
| 51 | + | |
| 52 | + spin_lock_irqsave(&dev->event_lock, flags); | |
| 53 | + __armada_drm_queue_unref_work(dev, fb); | |
| 54 | + spin_unlock_irqrestore(&dev->event_lock, flags); | |
| 55 | +} | |
| 56 | + | |
| 57 | +static int armada_drm_load(struct drm_device *dev, unsigned long flags) | |
| 58 | +{ | |
| 59 | + const struct platform_device_id *id; | |
| 60 | + struct armada_private *priv; | |
| 61 | + struct resource *res[ARRAY_SIZE(priv->dcrtc)]; | |
| 62 | + struct resource *mem = NULL; | |
| 63 | + int ret, n, i; | |
| 64 | + | |
| 65 | + memset(res, 0, sizeof(res)); | |
| 66 | + | |
| 67 | + for (n = i = 0; ; n++) { | |
| 68 | + struct resource *r = platform_get_resource(dev->platformdev, | |
| 69 | + IORESOURCE_MEM, n); | |
| 70 | + if (!r) | |
| 71 | + break; | |
| 72 | + | |
| 73 | + /* Resources above 64K are graphics memory */ | |
| 74 | + if (resource_size(r) > SZ_64K) | |
| 75 | + mem = r; | |
| 76 | + else if (i < ARRAY_SIZE(priv->dcrtc)) | |
| 77 | + res[i++] = r; | |
| 78 | + else | |
| 79 | + return -EINVAL; | |
| 80 | + } | |
| 81 | + | |
| 82 | + if (!res[0] || !mem) | |
| 83 | + return -ENXIO; | |
| 84 | + | |
| 85 | + if (!devm_request_mem_region(dev->dev, mem->start, | |
| 86 | + resource_size(mem), "armada-drm")) | |
| 87 | + return -EBUSY; | |
| 88 | + | |
| 89 | + priv = devm_kzalloc(dev->dev, sizeof(*priv), GFP_KERNEL); | |
| 90 | + if (!priv) { | |
| 91 | + DRM_ERROR("failed to allocate private\n"); | |
| 92 | + return -ENOMEM; | |
| 93 | + } | |
| 94 | + | |
| 95 | + dev->dev_private = priv; | |
| 96 | + | |
| 97 | + /* Get the implementation specific driver data. */ | |
| 98 | + id = platform_get_device_id(dev->platformdev); | |
| 99 | + if (!id) | |
| 100 | + return -ENXIO; | |
| 101 | + | |
| 102 | + priv->variant = (struct armada_variant *)id->driver_data; | |
| 103 | + | |
| 104 | + ret = priv->variant->init(priv, dev->dev); | |
| 105 | + if (ret) | |
| 106 | + return ret; | |
| 107 | + | |
| 108 | + INIT_WORK(&priv->fb_unref_work, armada_drm_unref_work); | |
| 109 | + INIT_KFIFO(priv->fb_unref); | |
| 110 | + | |
| 111 | + /* Mode setting support */ | |
| 112 | + drm_mode_config_init(dev); | |
| 113 | + dev->mode_config.min_width = 320; | |
| 114 | + dev->mode_config.min_height = 200; | |
| 115 | + | |
| 116 | + /* | |
| 117 | + * With vscale enabled, the maximum width is 1920 due to the | |
| 118 | + * 1920 by 3 lines RAM | |
| 119 | + */ | |
| 120 | + dev->mode_config.max_width = 1920; | |
| 121 | + dev->mode_config.max_height = 2048; | |
| 122 | + | |
| 123 | + dev->mode_config.preferred_depth = 24; | |
| 124 | + dev->mode_config.funcs = &armada_drm_mode_config_funcs; | |
| 125 | + drm_mm_init(&priv->linear, mem->start, resource_size(mem)); | |
| 126 | + | |
| 127 | + /* Create all LCD controllers */ | |
| 128 | + for (n = 0; n < ARRAY_SIZE(priv->dcrtc); n++) { | |
| 129 | + if (!res[n]) | |
| 130 | + break; | |
| 131 | + | |
| 132 | + ret = armada_drm_crtc_create(dev, n, res[n]); | |
| 133 | + if (ret) | |
| 134 | + goto err_kms; | |
| 135 | + } | |
| 136 | + | |
| 137 | + ret = drm_vblank_init(dev, n); | |
| 138 | + if (ret) | |
| 139 | + goto err_kms; | |
| 140 | + | |
| 141 | + ret = drm_irq_install(dev); | |
| 142 | + if (ret) | |
| 143 | + goto err_kms; | |
| 144 | + | |
| 145 | + dev->vblank_disable_allowed = 1; | |
| 146 | + | |
| 147 | + ret = armada_fbdev_init(dev); | |
| 148 | + if (ret) | |
| 149 | + goto err_irq; | |
| 150 | + | |
| 151 | + drm_kms_helper_poll_init(dev); | |
| 152 | + | |
| 153 | + return 0; | |
| 154 | + | |
| 155 | + err_irq: | |
| 156 | + drm_irq_uninstall(dev); | |
| 157 | + err_kms: | |
| 158 | + drm_mode_config_cleanup(dev); | |
| 159 | + drm_mm_takedown(&priv->linear); | |
| 160 | + flush_work(&priv->fb_unref_work); | |
| 161 | + | |
| 162 | + return ret; | |
| 163 | +} | |
| 164 | + | |
| 165 | +static int armada_drm_unload(struct drm_device *dev) | |
| 166 | +{ | |
| 167 | + struct armada_private *priv = dev->dev_private; | |
| 168 | + | |
| 169 | + drm_kms_helper_poll_fini(dev); | |
| 170 | + armada_fbdev_fini(dev); | |
| 171 | + drm_irq_uninstall(dev); | |
| 172 | + drm_mode_config_cleanup(dev); | |
| 173 | + drm_mm_takedown(&priv->linear); | |
| 174 | + flush_work(&priv->fb_unref_work); | |
| 175 | + dev->dev_private = NULL; | |
| 176 | + | |
| 177 | + return 0; | |
| 178 | +} | |
| 179 | + | |
| 180 | +void armada_drm_vbl_event_add(struct armada_crtc *dcrtc, | |
| 181 | + struct armada_vbl_event *evt) | |
| 182 | +{ | |
| 183 | + unsigned long flags; | |
| 184 | + | |
| 185 | + spin_lock_irqsave(&dcrtc->irq_lock, flags); | |
| 186 | + if (list_empty(&evt->node)) { | |
| 187 | + list_add_tail(&evt->node, &dcrtc->vbl_list); | |
| 188 | + | |
| 189 | + drm_vblank_get(dcrtc->crtc.dev, dcrtc->num); | |
| 190 | + } | |
| 191 | + spin_unlock_irqrestore(&dcrtc->irq_lock, flags); | |
| 192 | +} | |
| 193 | + | |
| 194 | +void armada_drm_vbl_event_remove(struct armada_crtc *dcrtc, | |
| 195 | + struct armada_vbl_event *evt) | |
| 196 | +{ | |
| 197 | + if (!list_empty(&evt->node)) { | |
| 198 | + list_del_init(&evt->node); | |
| 199 | + drm_vblank_put(dcrtc->crtc.dev, dcrtc->num); | |
| 200 | + } | |
| 201 | +} | |
| 202 | + | |
| 203 | +void armada_drm_vbl_event_remove_unlocked(struct armada_crtc *dcrtc, | |
| 204 | + struct armada_vbl_event *evt) | |
| 205 | +{ | |
| 206 | + unsigned long flags; | |
| 207 | + | |
| 208 | + spin_lock_irqsave(&dcrtc->irq_lock, flags); | |
| 209 | + armada_drm_vbl_event_remove(dcrtc, evt); | |
| 210 | + spin_unlock_irqrestore(&dcrtc->irq_lock, flags); | |
| 211 | +} | |
| 212 | + | |
| 213 | +/* These are called under the vbl_lock. */ | |
| 214 | +static int armada_drm_enable_vblank(struct drm_device *dev, int crtc) | |
| 215 | +{ | |
| 216 | + struct armada_private *priv = dev->dev_private; | |
| 217 | + armada_drm_crtc_enable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA); | |
| 218 | + return 0; | |
| 219 | +} | |
| 220 | + | |
| 221 | +static void armada_drm_disable_vblank(struct drm_device *dev, int crtc) | |
| 222 | +{ | |
| 223 | + struct armada_private *priv = dev->dev_private; | |
| 224 | + armada_drm_crtc_disable_irq(priv->dcrtc[crtc], VSYNC_IRQ_ENA); | |
| 225 | +} | |
| 226 | + | |
| 227 | +static irqreturn_t armada_drm_irq_handler(int irq, void *arg) | |
| 228 | +{ | |
| 229 | + struct drm_device *dev = arg; | |
| 230 | + struct armada_private *priv = dev->dev_private; | |
| 231 | + struct armada_crtc *dcrtc = priv->dcrtc[0]; | |
| 232 | + uint32_t v, stat = readl_relaxed(dcrtc->base + LCD_SPU_IRQ_ISR); | |
| 233 | + irqreturn_t handled = IRQ_NONE; | |
| 234 | + | |
| 235 | + /* | |
| 236 | + * This is rediculous - rather than writing bits to clear, we | |
| 237 | + * have to set the actual status register value. This is racy. | |
| 238 | + */ | |
| 239 | + writel_relaxed(0, dcrtc->base + LCD_SPU_IRQ_ISR); | |
| 240 | + | |
| 241 | + /* Mask out those interrupts we haven't enabled */ | |
| 242 | + v = stat & dcrtc->irq_ena; | |
| 243 | + | |
| 244 | + if (v & (VSYNC_IRQ|GRA_FRAME_IRQ|DUMB_FRAMEDONE)) { | |
| 245 | + armada_drm_crtc_irq(dcrtc, stat); | |
| 246 | + handled = IRQ_HANDLED; | |
| 247 | + } | |
| 248 | + | |
| 249 | + return handled; | |
| 250 | +} | |
| 251 | + | |
| 252 | +static int armada_drm_irq_postinstall(struct drm_device *dev) | |
| 253 | +{ | |
| 254 | + struct armada_private *priv = dev->dev_private; | |
| 255 | + struct armada_crtc *dcrtc = priv->dcrtc[0]; | |
| 256 | + | |
| 257 | + spin_lock_irq(&dev->vbl_lock); | |
| 258 | + writel_relaxed(dcrtc->irq_ena, dcrtc->base + LCD_SPU_IRQ_ENA); | |
| 259 | + writel(0, dcrtc->base + LCD_SPU_IRQ_ISR); | |
| 260 | + spin_unlock_irq(&dev->vbl_lock); | |
| 261 | + | |
| 262 | + return 0; | |
| 263 | +} | |
| 264 | + | |
| 265 | +static void armada_drm_irq_uninstall(struct drm_device *dev) | |
| 266 | +{ | |
| 267 | + struct armada_private *priv = dev->dev_private; | |
| 268 | + struct armada_crtc *dcrtc = priv->dcrtc[0]; | |
| 269 | + | |
| 270 | + writel(0, dcrtc->base + LCD_SPU_IRQ_ENA); | |
| 271 | +} | |
| 272 | + | |
| 273 | +static struct drm_ioctl_desc armada_ioctls[] = { | |
| 274 | + DRM_IOCTL_DEF_DRV(ARMADA_GEM_CREATE, armada_gem_create_ioctl, | |
| 275 | + DRM_UNLOCKED), | |
| 276 | + DRM_IOCTL_DEF_DRV(ARMADA_GEM_MMAP, armada_gem_mmap_ioctl, | |
| 277 | + DRM_UNLOCKED), | |
| 278 | + DRM_IOCTL_DEF_DRV(ARMADA_GEM_PWRITE, armada_gem_pwrite_ioctl, | |
| 279 | + DRM_UNLOCKED), | |
| 280 | +}; | |
| 281 | + | |
| 282 | +static const struct file_operations armada_drm_fops = { | |
| 283 | + .owner = THIS_MODULE, | |
| 284 | + .llseek = no_llseek, | |
| 285 | + .read = drm_read, | |
| 286 | + .poll = drm_poll, | |
| 287 | + .unlocked_ioctl = drm_ioctl, | |
| 288 | + .mmap = drm_gem_mmap, | |
| 289 | + .open = drm_open, | |
| 290 | + .release = drm_release, | |
| 291 | +}; | |
| 292 | + | |
| 293 | +static struct drm_driver armada_drm_driver = { | |
| 294 | + .load = armada_drm_load, | |
| 295 | + .open = NULL, | |
| 296 | + .preclose = NULL, | |
| 297 | + .postclose = NULL, | |
| 298 | + .lastclose = NULL, | |
| 299 | + .unload = armada_drm_unload, | |
| 300 | + .get_vblank_counter = drm_vblank_count, | |
| 301 | + .enable_vblank = armada_drm_enable_vblank, | |
| 302 | + .disable_vblank = armada_drm_disable_vblank, | |
| 303 | + .irq_handler = armada_drm_irq_handler, | |
| 304 | + .irq_postinstall = armada_drm_irq_postinstall, | |
| 305 | + .irq_uninstall = armada_drm_irq_uninstall, | |
| 306 | +#ifdef CONFIG_DEBUG_FS | |
| 307 | + .debugfs_init = armada_drm_debugfs_init, | |
| 308 | + .debugfs_cleanup = armada_drm_debugfs_cleanup, | |
| 309 | +#endif | |
| 310 | + .gem_init_object = NULL, | |
| 311 | + .gem_free_object = armada_gem_free_object, | |
| 312 | + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, | |
| 313 | + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
| 314 | + .gem_prime_export = armada_gem_prime_export, | |
| 315 | + .gem_prime_import = armada_gem_prime_import, | |
| 316 | + .dumb_create = armada_gem_dumb_create, | |
| 317 | + .dumb_map_offset = armada_gem_dumb_map_offset, | |
| 318 | + .dumb_destroy = armada_gem_dumb_destroy, | |
| 319 | + .gem_vm_ops = &armada_gem_vm_ops, | |
| 320 | + .major = 1, | |
| 321 | + .minor = 0, | |
| 322 | + .name = "armada-drm", | |
| 323 | + .desc = "Armada SoC DRM", | |
| 324 | + .date = "20120730", | |
| 325 | + .driver_features = DRIVER_GEM | DRIVER_MODESET | | |
| 326 | + DRIVER_HAVE_IRQ | DRIVER_PRIME, | |
| 327 | + .ioctls = armada_ioctls, | |
| 328 | + .fops = &armada_drm_fops, | |
| 329 | +}; | |
| 330 | + | |
| 331 | +static int armada_drm_probe(struct platform_device *pdev) | |
| 332 | +{ | |
| 333 | + return drm_platform_init(&armada_drm_driver, pdev); | |
| 334 | +} | |
| 335 | + | |
| 336 | +static int armada_drm_remove(struct platform_device *pdev) | |
| 337 | +{ | |
| 338 | + drm_platform_exit(&armada_drm_driver, pdev); | |
| 339 | + return 0; | |
| 340 | +} | |
| 341 | + | |
| 342 | +static const struct platform_device_id armada_drm_platform_ids[] = { | |
| 343 | + { | |
| 344 | + .name = "armada-drm", | |
| 345 | + .driver_data = (unsigned long)&armada510_ops, | |
| 346 | + }, { | |
| 347 | + .name = "armada-510-drm", | |
| 348 | + .driver_data = (unsigned long)&armada510_ops, | |
| 349 | + }, | |
| 350 | + { }, | |
| 351 | +}; | |
| 352 | +MODULE_DEVICE_TABLE(platform, armada_drm_platform_ids); | |
| 353 | + | |
| 354 | +static struct platform_driver armada_drm_platform_driver = { | |
| 355 | + .probe = armada_drm_probe, | |
| 356 | + .remove = armada_drm_remove, | |
| 357 | + .driver = { | |
| 358 | + .name = "armada-drm", | |
| 359 | + .owner = THIS_MODULE, | |
| 360 | + }, | |
| 361 | + .id_table = armada_drm_platform_ids, | |
| 362 | +}; | |
| 363 | + | |
| 364 | +static int __init armada_drm_init(void) | |
| 365 | +{ | |
| 366 | + armada_drm_driver.num_ioctls = DRM_ARRAY_SIZE(armada_ioctls); | |
| 367 | + return platform_driver_register(&armada_drm_platform_driver); | |
| 368 | +} | |
| 369 | +module_init(armada_drm_init); | |
| 370 | + | |
| 371 | +static void __exit armada_drm_exit(void) | |
| 372 | +{ | |
| 373 | + platform_driver_unregister(&armada_drm_platform_driver); | |
| 374 | +} | |
| 375 | +module_exit(armada_drm_exit); | |
| 376 | + | |
| 377 | +MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>"); | |
| 378 | +MODULE_DESCRIPTION("Armada DRM Driver"); | |
| 379 | +MODULE_LICENSE("GPL"); | |
| 380 | +MODULE_ALIAS("platform:armada-drm"); | |
| @@ -0,0 +1,170 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#include <drm/drmP.h> | |
| 9 | +#include <drm/drm_crtc_helper.h> | |
| 10 | +#include <drm/drm_fb_helper.h> | |
| 11 | +#include "armada_drm.h" | |
| 12 | +#include "armada_fb.h" | |
| 13 | +#include "armada_gem.h" | |
| 14 | +#include "armada_hw.h" | |
| 15 | + | |
| 16 | +static void armada_fb_destroy(struct drm_framebuffer *fb) | |
| 17 | +{ | |
| 18 | + struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb); | |
| 19 | + | |
| 20 | + drm_framebuffer_cleanup(&dfb->fb); | |
| 21 | + drm_gem_object_unreference_unlocked(&dfb->obj->obj); | |
| 22 | + kfree(dfb); | |
| 23 | +} | |
| 24 | + | |
| 25 | +static int armada_fb_create_handle(struct drm_framebuffer *fb, | |
| 26 | + struct drm_file *dfile, unsigned int *handle) | |
| 27 | +{ | |
| 28 | + struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb); | |
| 29 | + return drm_gem_handle_create(dfile, &dfb->obj->obj, handle); | |
| 30 | +} | |
| 31 | + | |
| 32 | +static const struct drm_framebuffer_funcs armada_fb_funcs = { | |
| 33 | + .destroy = armada_fb_destroy, | |
| 34 | + .create_handle = armada_fb_create_handle, | |
| 35 | +}; | |
| 36 | + | |
| 37 | +struct armada_framebuffer *armada_framebuffer_create(struct drm_device *dev, | |
| 38 | + struct drm_mode_fb_cmd2 *mode, struct armada_gem_object *obj) | |
| 39 | +{ | |
| 40 | + struct armada_framebuffer *dfb; | |
| 41 | + uint8_t format, config; | |
| 42 | + int ret; | |
| 43 | + | |
| 44 | + switch (mode->pixel_format) { | |
| 45 | +#define FMT(drm, fmt, mod) \ | |
| 46 | + case DRM_FORMAT_##drm: \ | |
| 47 | + format = CFG_##fmt; \ | |
| 48 | + config = mod; \ | |
| 49 | + break | |
| 50 | + FMT(RGB565, 565, CFG_SWAPRB); | |
| 51 | + FMT(BGR565, 565, 0); | |
| 52 | + FMT(ARGB1555, 1555, CFG_SWAPRB); | |
| 53 | + FMT(ABGR1555, 1555, 0); | |
| 54 | + FMT(RGB888, 888PACK, CFG_SWAPRB); | |
| 55 | + FMT(BGR888, 888PACK, 0); | |
| 56 | + FMT(XRGB8888, X888, CFG_SWAPRB); | |
| 57 | + FMT(XBGR8888, X888, 0); | |
| 58 | + FMT(ARGB8888, 8888, CFG_SWAPRB); | |
| 59 | + FMT(ABGR8888, 8888, 0); | |
| 60 | + FMT(YUYV, 422PACK, CFG_YUV2RGB | CFG_SWAPYU | CFG_SWAPUV); | |
| 61 | + FMT(UYVY, 422PACK, CFG_YUV2RGB); | |
| 62 | + FMT(VYUY, 422PACK, CFG_YUV2RGB | CFG_SWAPUV); | |
| 63 | + FMT(YVYU, 422PACK, CFG_YUV2RGB | CFG_SWAPYU); | |
| 64 | + FMT(YUV422, 422, CFG_YUV2RGB); | |
| 65 | + FMT(YVU422, 422, CFG_YUV2RGB | CFG_SWAPUV); | |
| 66 | + FMT(YUV420, 420, CFG_YUV2RGB); | |
| 67 | + FMT(YVU420, 420, CFG_YUV2RGB | CFG_SWAPUV); | |
| 68 | + FMT(C8, PSEUDO8, 0); | |
| 69 | +#undef FMT | |
| 70 | + default: | |
| 71 | + return ERR_PTR(-EINVAL); | |
| 72 | + } | |
| 73 | + | |
| 74 | + dfb = kzalloc(sizeof(*dfb), GFP_KERNEL); | |
| 75 | + if (!dfb) { | |
| 76 | + DRM_ERROR("failed to allocate Armada fb object\n"); | |
| 77 | + return ERR_PTR(-ENOMEM); | |
| 78 | + } | |
| 79 | + | |
| 80 | + dfb->fmt = format; | |
| 81 | + dfb->mod = config; | |
| 82 | + dfb->obj = obj; | |
| 83 | + | |
| 84 | + drm_helper_mode_fill_fb_struct(&dfb->fb, mode); | |
| 85 | + | |
| 86 | + ret = drm_framebuffer_init(dev, &dfb->fb, &armada_fb_funcs); | |
| 87 | + if (ret) { | |
| 88 | + kfree(dfb); | |
| 89 | + return ERR_PTR(ret); | |
| 90 | + } | |
| 91 | + | |
| 92 | + /* | |
| 93 | + * Take a reference on our object as we're successful - the | |
| 94 | + * caller already holds a reference, which keeps us safe for | |
| 95 | + * the above call, but the caller will drop their reference | |
| 96 | + * to it. Hence we need to take our own reference. | |
| 97 | + */ | |
| 98 | + drm_gem_object_reference(&obj->obj); | |
| 99 | + | |
| 100 | + return dfb; | |
| 101 | +} | |
| 102 | + | |
| 103 | +static struct drm_framebuffer *armada_fb_create(struct drm_device *dev, | |
| 104 | + struct drm_file *dfile, struct drm_mode_fb_cmd2 *mode) | |
| 105 | +{ | |
| 106 | + struct armada_gem_object *obj; | |
| 107 | + struct armada_framebuffer *dfb; | |
| 108 | + int ret; | |
| 109 | + | |
| 110 | + DRM_DEBUG_DRIVER("w%u h%u pf%08x f%u p%u,%u,%u\n", | |
| 111 | + mode->width, mode->height, mode->pixel_format, | |
| 112 | + mode->flags, mode->pitches[0], mode->pitches[1], | |
| 113 | + mode->pitches[2]); | |
| 114 | + | |
| 115 | + /* We can only handle a single plane at the moment */ | |
| 116 | + if (drm_format_num_planes(mode->pixel_format) > 1 && | |
| 117 | + (mode->handles[0] != mode->handles[1] || | |
| 118 | + mode->handles[0] != mode->handles[2])) { | |
| 119 | + ret = -EINVAL; | |
| 120 | + goto err; | |
| 121 | + } | |
| 122 | + | |
| 123 | + obj = armada_gem_object_lookup(dev, dfile, mode->handles[0]); | |
| 124 | + if (!obj) { | |
| 125 | + ret = -ENOENT; | |
| 126 | + goto err; | |
| 127 | + } | |
| 128 | + | |
| 129 | + if (obj->obj.import_attach && !obj->sgt) { | |
| 130 | + ret = armada_gem_map_import(obj); | |
| 131 | + if (ret) | |
| 132 | + goto err_unref; | |
| 133 | + } | |
| 134 | + | |
| 135 | + /* Framebuffer objects must have a valid device address for scanout */ | |
| 136 | + if (obj->dev_addr == DMA_ERROR_CODE) { | |
| 137 | + ret = -EINVAL; | |
| 138 | + goto err_unref; | |
| 139 | + } | |
| 140 | + | |
| 141 | + dfb = armada_framebuffer_create(dev, mode, obj); | |
| 142 | + if (IS_ERR(dfb)) { | |
| 143 | + ret = PTR_ERR(dfb); | |
| 144 | + goto err; | |
| 145 | + } | |
| 146 | + | |
| 147 | + drm_gem_object_unreference_unlocked(&obj->obj); | |
| 148 | + | |
| 149 | + return &dfb->fb; | |
| 150 | + | |
| 151 | + err_unref: | |
| 152 | + drm_gem_object_unreference_unlocked(&obj->obj); | |
| 153 | + err: | |
| 154 | + DRM_ERROR("failed to initialize framebuffer: %d\n", ret); | |
| 155 | + return ERR_PTR(ret); | |
| 156 | +} | |
| 157 | + | |
| 158 | +static void armada_output_poll_changed(struct drm_device *dev) | |
| 159 | +{ | |
| 160 | + struct armada_private *priv = dev->dev_private; | |
| 161 | + struct drm_fb_helper *fbh = priv->fbdev; | |
| 162 | + | |
| 163 | + if (fbh) | |
| 164 | + drm_fb_helper_hotplug_event(fbh); | |
| 165 | +} | |
| 166 | + | |
| 167 | +const struct drm_mode_config_funcs armada_drm_mode_config_funcs = { | |
| 168 | + .fb_create = armada_fb_create, | |
| 169 | + .output_poll_changed = armada_output_poll_changed, | |
| 170 | +}; | |
| @@ -0,0 +1,24 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#ifndef ARMADA_FB_H | |
| 9 | +#define ARMADA_FB_H | |
| 10 | + | |
| 11 | +struct armada_framebuffer { | |
| 12 | + struct drm_framebuffer fb; | |
| 13 | + struct armada_gem_object *obj; | |
| 14 | + uint8_t fmt; | |
| 15 | + uint8_t mod; | |
| 16 | +}; | |
| 17 | +#define drm_fb_to_armada_fb(dfb) \ | |
| 18 | + container_of(dfb, struct armada_framebuffer, fb) | |
| 19 | +#define drm_fb_obj(fb) drm_fb_to_armada_fb(fb)->obj | |
| 20 | + | |
| 21 | +struct armada_framebuffer *armada_framebuffer_create(struct drm_device *, | |
| 22 | + struct drm_mode_fb_cmd2 *, struct armada_gem_object *); | |
| 23 | + | |
| 24 | +#endif | |
| @@ -0,0 +1,202 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * Written from the i915 driver. | |
| 4 | + * | |
| 5 | + * This program is free software; you can redistribute it and/or modify | |
| 6 | + * it under the terms of the GNU General Public License version 2 as | |
| 7 | + * published by the Free Software Foundation. | |
| 8 | + */ | |
| 9 | +#include <linux/errno.h> | |
| 10 | +#include <linux/fb.h> | |
| 11 | +#include <linux/kernel.h> | |
| 12 | +#include <linux/module.h> | |
| 13 | + | |
| 14 | +#include <drm/drmP.h> | |
| 15 | +#include <drm/drm_fb_helper.h> | |
| 16 | +#include "armada_crtc.h" | |
| 17 | +#include "armada_drm.h" | |
| 18 | +#include "armada_fb.h" | |
| 19 | +#include "armada_gem.h" | |
| 20 | + | |
| 21 | +static /*const*/ struct fb_ops armada_fb_ops = { | |
| 22 | + .owner = THIS_MODULE, | |
| 23 | + .fb_check_var = drm_fb_helper_check_var, | |
| 24 | + .fb_set_par = drm_fb_helper_set_par, | |
| 25 | + .fb_fillrect = cfb_fillrect, | |
| 26 | + .fb_copyarea = cfb_copyarea, | |
| 27 | + .fb_imageblit = cfb_imageblit, | |
| 28 | + .fb_pan_display = drm_fb_helper_pan_display, | |
| 29 | + .fb_blank = drm_fb_helper_blank, | |
| 30 | + .fb_setcmap = drm_fb_helper_setcmap, | |
| 31 | + .fb_debug_enter = drm_fb_helper_debug_enter, | |
| 32 | + .fb_debug_leave = drm_fb_helper_debug_leave, | |
| 33 | +}; | |
| 34 | + | |
| 35 | +static int armada_fb_create(struct drm_fb_helper *fbh, | |
| 36 | + struct drm_fb_helper_surface_size *sizes) | |
| 37 | +{ | |
| 38 | + struct drm_device *dev = fbh->dev; | |
| 39 | + struct drm_mode_fb_cmd2 mode; | |
| 40 | + struct armada_framebuffer *dfb; | |
| 41 | + struct armada_gem_object *obj; | |
| 42 | + struct fb_info *info; | |
| 43 | + int size, ret; | |
| 44 | + void *ptr; | |
| 45 | + | |
| 46 | + memset(&mode, 0, sizeof(mode)); | |
| 47 | + mode.width = sizes->surface_width; | |
| 48 | + mode.height = sizes->surface_height; | |
| 49 | + mode.pitches[0] = armada_pitch(mode.width, sizes->surface_bpp); | |
| 50 | + mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, | |
| 51 | + sizes->surface_depth); | |
| 52 | + | |
| 53 | + size = mode.pitches[0] * mode.height; | |
| 54 | + obj = armada_gem_alloc_private_object(dev, size); | |
| 55 | + if (!obj) { | |
| 56 | + DRM_ERROR("failed to allocate fb memory\n"); | |
| 57 | + return -ENOMEM; | |
| 58 | + } | |
| 59 | + | |
| 60 | + ret = armada_gem_linear_back(dev, obj); | |
| 61 | + if (ret) { | |
| 62 | + drm_gem_object_unreference_unlocked(&obj->obj); | |
| 63 | + return ret; | |
| 64 | + } | |
| 65 | + | |
| 66 | + ptr = armada_gem_map_object(dev, obj); | |
| 67 | + if (!ptr) { | |
| 68 | + drm_gem_object_unreference_unlocked(&obj->obj); | |
| 69 | + return -ENOMEM; | |
| 70 | + } | |
| 71 | + | |
| 72 | + dfb = armada_framebuffer_create(dev, &mode, obj); | |
| 73 | + | |
| 74 | + /* | |
| 75 | + * A reference is now held by the framebuffer object if | |
| 76 | + * successful, otherwise this drops the ref for the error path. | |
| 77 | + */ | |
| 78 | + drm_gem_object_unreference_unlocked(&obj->obj); | |
| 79 | + | |
| 80 | + if (IS_ERR(dfb)) | |
| 81 | + return PTR_ERR(dfb); | |
| 82 | + | |
| 83 | + info = framebuffer_alloc(0, dev->dev); | |
| 84 | + if (!info) { | |
| 85 | + ret = -ENOMEM; | |
| 86 | + goto err_fballoc; | |
| 87 | + } | |
| 88 | + | |
| 89 | + ret = fb_alloc_cmap(&info->cmap, 256, 0); | |
| 90 | + if (ret) { | |
| 91 | + ret = -ENOMEM; | |
| 92 | + goto err_fbcmap; | |
| 93 | + } | |
| 94 | + | |
| 95 | + strlcpy(info->fix.id, "armada-drmfb", sizeof(info->fix.id)); | |
| 96 | + info->par = fbh; | |
| 97 | + info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT; | |
| 98 | + info->fbops = &armada_fb_ops; | |
| 99 | + info->fix.smem_start = obj->phys_addr; | |
| 100 | + info->fix.smem_len = obj->obj.size; | |
| 101 | + info->screen_size = obj->obj.size; | |
| 102 | + info->screen_base = ptr; | |
| 103 | + fbh->fb = &dfb->fb; | |
| 104 | + fbh->fbdev = info; | |
| 105 | + drm_fb_helper_fill_fix(info, dfb->fb.pitches[0], dfb->fb.depth); | |
| 106 | + drm_fb_helper_fill_var(info, fbh, sizes->fb_width, sizes->fb_height); | |
| 107 | + | |
| 108 | + DRM_DEBUG_KMS("allocated %dx%d %dbpp fb: 0x%08x\n", | |
| 109 | + dfb->fb.width, dfb->fb.height, | |
| 110 | + dfb->fb.bits_per_pixel, obj->phys_addr); | |
| 111 | + | |
| 112 | + return 0; | |
| 113 | + | |
| 114 | + err_fbcmap: | |
| 115 | + framebuffer_release(info); | |
| 116 | + err_fballoc: | |
| 117 | + dfb->fb.funcs->destroy(&dfb->fb); | |
| 118 | + return ret; | |
| 119 | +} | |
| 120 | + | |
| 121 | +static int armada_fb_probe(struct drm_fb_helper *fbh, | |
| 122 | + struct drm_fb_helper_surface_size *sizes) | |
| 123 | +{ | |
| 124 | + int ret = 0; | |
| 125 | + | |
| 126 | + if (!fbh->fb) { | |
| 127 | + ret = armada_fb_create(fbh, sizes); | |
| 128 | + if (ret == 0) | |
| 129 | + ret = 1; | |
| 130 | + } | |
| 131 | + return ret; | |
| 132 | +} | |
| 133 | + | |
| 134 | +static struct drm_fb_helper_funcs armada_fb_helper_funcs = { | |
| 135 | + .gamma_set = armada_drm_crtc_gamma_set, | |
| 136 | + .gamma_get = armada_drm_crtc_gamma_get, | |
| 137 | + .fb_probe = armada_fb_probe, | |
| 138 | +}; | |
| 139 | + | |
| 140 | +int armada_fbdev_init(struct drm_device *dev) | |
| 141 | +{ | |
| 142 | + struct armada_private *priv = dev->dev_private; | |
| 143 | + struct drm_fb_helper *fbh; | |
| 144 | + int ret; | |
| 145 | + | |
| 146 | + fbh = devm_kzalloc(dev->dev, sizeof(*fbh), GFP_KERNEL); | |
| 147 | + if (!fbh) | |
| 148 | + return -ENOMEM; | |
| 149 | + | |
| 150 | + priv->fbdev = fbh; | |
| 151 | + | |
| 152 | + fbh->funcs = &armada_fb_helper_funcs; | |
| 153 | + | |
| 154 | + ret = drm_fb_helper_init(dev, fbh, 1, 1); | |
| 155 | + if (ret) { | |
| 156 | + DRM_ERROR("failed to initialize drm fb helper\n"); | |
| 157 | + goto err_fb_helper; | |
| 158 | + } | |
| 159 | + | |
| 160 | + ret = drm_fb_helper_single_add_all_connectors(fbh); | |
| 161 | + if (ret) { | |
| 162 | + DRM_ERROR("failed to add fb connectors\n"); | |
| 163 | + goto err_fb_setup; | |
| 164 | + } | |
| 165 | + | |
| 166 | + ret = drm_fb_helper_initial_config(fbh, 32); | |
| 167 | + if (ret) { | |
| 168 | + DRM_ERROR("failed to set initial config\n"); | |
| 169 | + goto err_fb_setup; | |
| 170 | + } | |
| 171 | + | |
| 172 | + return 0; | |
| 173 | + err_fb_setup: | |
| 174 | + drm_fb_helper_fini(fbh); | |
| 175 | + err_fb_helper: | |
| 176 | + priv->fbdev = NULL; | |
| 177 | + return ret; | |
| 178 | +} | |
| 179 | + | |
| 180 | +void armada_fbdev_fini(struct drm_device *dev) | |
| 181 | +{ | |
| 182 | + struct armada_private *priv = dev->dev_private; | |
| 183 | + struct drm_fb_helper *fbh = priv->fbdev; | |
| 184 | + | |
| 185 | + if (fbh) { | |
| 186 | + struct fb_info *info = fbh->fbdev; | |
| 187 | + | |
| 188 | + if (info) { | |
| 189 | + unregister_framebuffer(info); | |
| 190 | + if (info->cmap.len) | |
| 191 | + fb_dealloc_cmap(&info->cmap); | |
| 192 | + framebuffer_release(info); | |
| 193 | + } | |
| 194 | + | |
| 195 | + if (fbh->fb) | |
| 196 | + fbh->fb->funcs->destroy(fbh->fb); | |
| 197 | + | |
| 198 | + drm_fb_helper_fini(fbh); | |
| 199 | + | |
| 200 | + priv->fbdev = NULL; | |
| 201 | + } | |
| 202 | +} | |
| @@ -0,0 +1,611 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#include <linux/dma-buf.h> | |
| 9 | +#include <linux/dma-mapping.h> | |
| 10 | +#include <linux/shmem_fs.h> | |
| 11 | +#include <drm/drmP.h> | |
| 12 | +#include "armada_drm.h" | |
| 13 | +#include "armada_gem.h" | |
| 14 | +#include <drm/armada_drm.h> | |
| 15 | +#include "armada_ioctlP.h" | |
| 16 | + | |
| 17 | +static int armada_gem_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) | |
| 18 | +{ | |
| 19 | + struct armada_gem_object *obj = drm_to_armada_gem(vma->vm_private_data); | |
| 20 | + unsigned long addr = (unsigned long)vmf->virtual_address; | |
| 21 | + unsigned long pfn = obj->phys_addr >> PAGE_SHIFT; | |
| 22 | + int ret; | |
| 23 | + | |
| 24 | + pfn += (addr - vma->vm_start) >> PAGE_SHIFT; | |
| 25 | + ret = vm_insert_pfn(vma, addr, pfn); | |
| 26 | + | |
| 27 | + switch (ret) { | |
| 28 | + case 0: | |
| 29 | + case -EBUSY: | |
| 30 | + return VM_FAULT_NOPAGE; | |
| 31 | + case -ENOMEM: | |
| 32 | + return VM_FAULT_OOM; | |
| 33 | + default: | |
| 34 | + return VM_FAULT_SIGBUS; | |
| 35 | + } | |
| 36 | +} | |
| 37 | + | |
| 38 | +const struct vm_operations_struct armada_gem_vm_ops = { | |
| 39 | + .fault = armada_gem_vm_fault, | |
| 40 | + .open = drm_gem_vm_open, | |
| 41 | + .close = drm_gem_vm_close, | |
| 42 | +}; | |
| 43 | + | |
| 44 | +static size_t roundup_gem_size(size_t size) | |
| 45 | +{ | |
| 46 | + return roundup(size, PAGE_SIZE); | |
| 47 | +} | |
| 48 | + | |
| 49 | +/* dev->struct_mutex is held here */ | |
| 50 | +void armada_gem_free_object(struct drm_gem_object *obj) | |
| 51 | +{ | |
| 52 | + struct armada_gem_object *dobj = drm_to_armada_gem(obj); | |
| 53 | + | |
| 54 | + DRM_DEBUG_DRIVER("release obj %p\n", dobj); | |
| 55 | + | |
| 56 | + drm_gem_free_mmap_offset(&dobj->obj); | |
| 57 | + | |
| 58 | + if (dobj->page) { | |
| 59 | + /* page backed memory */ | |
| 60 | + unsigned int order = get_order(dobj->obj.size); | |
| 61 | + __free_pages(dobj->page, order); | |
| 62 | + } else if (dobj->linear) { | |
| 63 | + /* linear backed memory */ | |
| 64 | + drm_mm_remove_node(dobj->linear); | |
| 65 | + kfree(dobj->linear); | |
| 66 | + if (dobj->addr) | |
| 67 | + iounmap(dobj->addr); | |
| 68 | + } | |
| 69 | + | |
| 70 | + if (dobj->obj.import_attach) { | |
| 71 | + /* We only ever display imported data */ | |
| 72 | + dma_buf_unmap_attachment(dobj->obj.import_attach, dobj->sgt, | |
| 73 | + DMA_TO_DEVICE); | |
| 74 | + drm_prime_gem_destroy(&dobj->obj, NULL); | |
| 75 | + } | |
| 76 | + | |
| 77 | + drm_gem_object_release(&dobj->obj); | |
| 78 | + | |
| 79 | + kfree(dobj); | |
| 80 | +} | |
| 81 | + | |
| 82 | +int | |
| 83 | +armada_gem_linear_back(struct drm_device *dev, struct armada_gem_object *obj) | |
| 84 | +{ | |
| 85 | + struct armada_private *priv = dev->dev_private; | |
| 86 | + size_t size = obj->obj.size; | |
| 87 | + | |
| 88 | + if (obj->page || obj->linear) | |
| 89 | + return 0; | |
| 90 | + | |
| 91 | + /* | |
| 92 | + * If it is a small allocation (typically cursor, which will | |
| 93 | + * be 32x64 or 64x32 ARGB pixels) try to get it from the system. | |
| 94 | + * Framebuffers will never be this small (our minimum size for | |
| 95 | + * framebuffers is larger than this anyway.) Such objects are | |
| 96 | + * only accessed by the CPU so we don't need any special handing | |
| 97 | + * here. | |
| 98 | + */ | |
| 99 | + if (size <= 8192) { | |
| 100 | + unsigned int order = get_order(size); | |
| 101 | + struct page *p = alloc_pages(GFP_KERNEL, order); | |
| 102 | + | |
| 103 | + if (p) { | |
| 104 | + obj->addr = page_address(p); | |
| 105 | + obj->phys_addr = page_to_phys(p); | |
| 106 | + obj->page = p; | |
| 107 | + | |
| 108 | + memset(obj->addr, 0, PAGE_ALIGN(size)); | |
| 109 | + } | |
| 110 | + } | |
| 111 | + | |
| 112 | + /* | |
| 113 | + * We could grab something from CMA if it's enabled, but that | |
| 114 | + * involves building in a problem: | |
| 115 | + * | |
| 116 | + * CMA's interface uses dma_alloc_coherent(), which provides us | |
| 117 | + * with an CPU virtual address and a device address. | |
| 118 | + * | |
| 119 | + * The CPU virtual address may be either an address in the kernel | |
| 120 | + * direct mapped region (for example, as it would be on x86) or | |
| 121 | + * it may be remapped into another part of kernel memory space | |
| 122 | + * (eg, as it would be on ARM.) This means virt_to_phys() on the | |
| 123 | + * returned virtual address is invalid depending on the architecture | |
| 124 | + * implementation. | |
| 125 | + * | |
| 126 | + * The device address may also not be a physical address; it may | |
| 127 | + * be that there is some kind of remapping between the device and | |
| 128 | + * system RAM, which makes the use of the device address also | |
| 129 | + * unsafe to re-use as a physical address. | |
| 130 | + * | |
| 131 | + * This makes DRM usage of dma_alloc_coherent() in a generic way | |
| 132 | + * at best very questionable and unsafe. | |
| 133 | + */ | |
| 134 | + | |
| 135 | + /* Otherwise, grab it from our linear allocation */ | |
| 136 | + if (!obj->page) { | |
| 137 | + struct drm_mm_node *node; | |
| 138 | + unsigned align = min_t(unsigned, size, SZ_2M); | |
| 139 | + void __iomem *ptr; | |
| 140 | + int ret; | |
| 141 | + | |
| 142 | + node = kzalloc(sizeof(*node), GFP_KERNEL); | |
| 143 | + if (!node) | |
| 144 | + return -ENOSPC; | |
| 145 | + | |
| 146 | + mutex_lock(&dev->struct_mutex); | |
| 147 | + ret = drm_mm_insert_node(&priv->linear, node, size, align, | |
| 148 | + DRM_MM_SEARCH_DEFAULT); | |
| 149 | + mutex_unlock(&dev->struct_mutex); | |
| 150 | + if (ret) { | |
| 151 | + kfree(node); | |
| 152 | + return ret; | |
| 153 | + } | |
| 154 | + | |
| 155 | + obj->linear = node; | |
| 156 | + | |
| 157 | + /* Ensure that the memory we're returning is cleared. */ | |
| 158 | + ptr = ioremap_wc(obj->linear->start, size); | |
| 159 | + if (!ptr) { | |
| 160 | + mutex_lock(&dev->struct_mutex); | |
| 161 | + drm_mm_remove_node(obj->linear); | |
| 162 | + mutex_unlock(&dev->struct_mutex); | |
| 163 | + kfree(obj->linear); | |
| 164 | + obj->linear = NULL; | |
| 165 | + return -ENOMEM; | |
| 166 | + } | |
| 167 | + | |
| 168 | + memset_io(ptr, 0, size); | |
| 169 | + iounmap(ptr); | |
| 170 | + | |
| 171 | + obj->phys_addr = obj->linear->start; | |
| 172 | + obj->dev_addr = obj->linear->start; | |
| 173 | + } | |
| 174 | + | |
| 175 | + DRM_DEBUG_DRIVER("obj %p phys %#x dev %#x\n", | |
| 176 | + obj, obj->phys_addr, obj->dev_addr); | |
| 177 | + | |
| 178 | + return 0; | |
| 179 | +} | |
| 180 | + | |
| 181 | +void * | |
| 182 | +armada_gem_map_object(struct drm_device *dev, struct armada_gem_object *dobj) | |
| 183 | +{ | |
| 184 | + /* only linear objects need to be ioremap'd */ | |
| 185 | + if (!dobj->addr && dobj->linear) | |
| 186 | + dobj->addr = ioremap_wc(dobj->phys_addr, dobj->obj.size); | |
| 187 | + return dobj->addr; | |
| 188 | +} | |
| 189 | + | |
| 190 | +struct armada_gem_object * | |
| 191 | +armada_gem_alloc_private_object(struct drm_device *dev, size_t size) | |
| 192 | +{ | |
| 193 | + struct armada_gem_object *obj; | |
| 194 | + | |
| 195 | + size = roundup_gem_size(size); | |
| 196 | + | |
| 197 | + obj = kzalloc(sizeof(*obj), GFP_KERNEL); | |
| 198 | + if (!obj) | |
| 199 | + return NULL; | |
| 200 | + | |
| 201 | + drm_gem_private_object_init(dev, &obj->obj, size); | |
| 202 | + obj->dev_addr = DMA_ERROR_CODE; | |
| 203 | + | |
| 204 | + DRM_DEBUG_DRIVER("alloc private obj %p size %zu\n", obj, size); | |
| 205 | + | |
| 206 | + return obj; | |
| 207 | +} | |
| 208 | + | |
| 209 | +struct armada_gem_object *armada_gem_alloc_object(struct drm_device *dev, | |
| 210 | + size_t size) | |
| 211 | +{ | |
| 212 | + struct armada_gem_object *obj; | |
| 213 | + struct address_space *mapping; | |
| 214 | + | |
| 215 | + size = roundup_gem_size(size); | |
| 216 | + | |
| 217 | + obj = kzalloc(sizeof(*obj), GFP_KERNEL); | |
| 218 | + if (!obj) | |
| 219 | + return NULL; | |
| 220 | + | |
| 221 | + if (drm_gem_object_init(dev, &obj->obj, size)) { | |
| 222 | + kfree(obj); | |
| 223 | + return NULL; | |
| 224 | + } | |
| 225 | + | |
| 226 | + obj->dev_addr = DMA_ERROR_CODE; | |
| 227 | + | |
| 228 | + mapping = obj->obj.filp->f_path.dentry->d_inode->i_mapping; | |
| 229 | + mapping_set_gfp_mask(mapping, GFP_HIGHUSER | __GFP_RECLAIMABLE); | |
| 230 | + | |
| 231 | + DRM_DEBUG_DRIVER("alloc obj %p size %zu\n", obj, size); | |
| 232 | + | |
| 233 | + return obj; | |
| 234 | +} | |
| 235 | + | |
| 236 | +/* Dumb alloc support */ | |
| 237 | +int armada_gem_dumb_create(struct drm_file *file, struct drm_device *dev, | |
| 238 | + struct drm_mode_create_dumb *args) | |
| 239 | +{ | |
| 240 | + struct armada_gem_object *dobj; | |
| 241 | + u32 handle; | |
| 242 | + size_t size; | |
| 243 | + int ret; | |
| 244 | + | |
| 245 | + args->pitch = armada_pitch(args->width, args->bpp); | |
| 246 | + args->size = size = args->pitch * args->height; | |
| 247 | + | |
| 248 | + dobj = armada_gem_alloc_private_object(dev, size); | |
| 249 | + if (dobj == NULL) | |
| 250 | + return -ENOMEM; | |
| 251 | + | |
| 252 | + ret = armada_gem_linear_back(dev, dobj); | |
| 253 | + if (ret) | |
| 254 | + goto err; | |
| 255 | + | |
| 256 | + ret = drm_gem_handle_create(file, &dobj->obj, &handle); | |
| 257 | + if (ret) | |
| 258 | + goto err; | |
| 259 | + | |
| 260 | + args->handle = handle; | |
| 261 | + | |
| 262 | + /* drop reference from allocate - handle holds it now */ | |
| 263 | + DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle); | |
| 264 | + err: | |
| 265 | + drm_gem_object_unreference_unlocked(&dobj->obj); | |
| 266 | + return ret; | |
| 267 | +} | |
| 268 | + | |
| 269 | +int armada_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, | |
| 270 | + uint32_t handle, uint64_t *offset) | |
| 271 | +{ | |
| 272 | + struct armada_gem_object *obj; | |
| 273 | + int ret = 0; | |
| 274 | + | |
| 275 | + mutex_lock(&dev->struct_mutex); | |
| 276 | + obj = armada_gem_object_lookup(dev, file, handle); | |
| 277 | + if (!obj) { | |
| 278 | + DRM_ERROR("failed to lookup gem object\n"); | |
| 279 | + ret = -EINVAL; | |
| 280 | + goto err_unlock; | |
| 281 | + } | |
| 282 | + | |
| 283 | + /* Don't allow imported objects to be mapped */ | |
| 284 | + if (obj->obj.import_attach) { | |
| 285 | + ret = -EINVAL; | |
| 286 | + goto err_unlock; | |
| 287 | + } | |
| 288 | + | |
| 289 | + ret = drm_gem_create_mmap_offset(&obj->obj); | |
| 290 | + if (ret == 0) { | |
| 291 | + *offset = drm_vma_node_offset_addr(&obj->obj.vma_node); | |
| 292 | + DRM_DEBUG_DRIVER("handle %#x offset %llx\n", handle, *offset); | |
| 293 | + } | |
| 294 | + | |
| 295 | + drm_gem_object_unreference(&obj->obj); | |
| 296 | + err_unlock: | |
| 297 | + mutex_unlock(&dev->struct_mutex); | |
| 298 | + | |
| 299 | + return ret; | |
| 300 | +} | |
| 301 | + | |
| 302 | +int armada_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, | |
| 303 | + uint32_t handle) | |
| 304 | +{ | |
| 305 | + return drm_gem_handle_delete(file, handle); | |
| 306 | +} | |
| 307 | + | |
| 308 | +/* Private driver gem ioctls */ | |
| 309 | +int armada_gem_create_ioctl(struct drm_device *dev, void *data, | |
| 310 | + struct drm_file *file) | |
| 311 | +{ | |
| 312 | + struct drm_armada_gem_create *args = data; | |
| 313 | + struct armada_gem_object *dobj; | |
| 314 | + size_t size; | |
| 315 | + u32 handle; | |
| 316 | + int ret; | |
| 317 | + | |
| 318 | + if (args->size == 0) | |
| 319 | + return -ENOMEM; | |
| 320 | + | |
| 321 | + size = args->size; | |
| 322 | + | |
| 323 | + dobj = armada_gem_alloc_object(dev, size); | |
| 324 | + if (dobj == NULL) | |
| 325 | + return -ENOMEM; | |
| 326 | + | |
| 327 | + ret = drm_gem_handle_create(file, &dobj->obj, &handle); | |
| 328 | + if (ret) | |
| 329 | + goto err; | |
| 330 | + | |
| 331 | + args->handle = handle; | |
| 332 | + | |
| 333 | + /* drop reference from allocate - handle holds it now */ | |
| 334 | + DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle); | |
| 335 | + err: | |
| 336 | + drm_gem_object_unreference_unlocked(&dobj->obj); | |
| 337 | + return ret; | |
| 338 | +} | |
| 339 | + | |
| 340 | +/* Map a shmem-backed object into process memory space */ | |
| 341 | +int armada_gem_mmap_ioctl(struct drm_device *dev, void *data, | |
| 342 | + struct drm_file *file) | |
| 343 | +{ | |
| 344 | + struct drm_armada_gem_mmap *args = data; | |
| 345 | + struct armada_gem_object *dobj; | |
| 346 | + unsigned long addr; | |
| 347 | + | |
| 348 | + dobj = armada_gem_object_lookup(dev, file, args->handle); | |
| 349 | + if (dobj == NULL) | |
| 350 | + return -ENOENT; | |
| 351 | + | |
| 352 | + if (!dobj->obj.filp) { | |
| 353 | + drm_gem_object_unreference(&dobj->obj); | |
| 354 | + return -EINVAL; | |
| 355 | + } | |
| 356 | + | |
| 357 | + addr = vm_mmap(dobj->obj.filp, 0, args->size, PROT_READ | PROT_WRITE, | |
| 358 | + MAP_SHARED, args->offset); | |
| 359 | + drm_gem_object_unreference(&dobj->obj); | |
| 360 | + if (IS_ERR_VALUE(addr)) | |
| 361 | + return addr; | |
| 362 | + | |
| 363 | + args->addr = addr; | |
| 364 | + | |
| 365 | + return 0; | |
| 366 | +} | |
| 367 | + | |
| 368 | +int armada_gem_pwrite_ioctl(struct drm_device *dev, void *data, | |
| 369 | + struct drm_file *file) | |
| 370 | +{ | |
| 371 | + struct drm_armada_gem_pwrite *args = data; | |
| 372 | + struct armada_gem_object *dobj; | |
| 373 | + char __user *ptr; | |
| 374 | + int ret; | |
| 375 | + | |
| 376 | + DRM_DEBUG_DRIVER("handle %u off %u size %u ptr 0x%llx\n", | |
| 377 | + args->handle, args->offset, args->size, args->ptr); | |
| 378 | + | |
| 379 | + if (args->size == 0) | |
| 380 | + return 0; | |
| 381 | + | |
| 382 | + ptr = (char __user *)(uintptr_t)args->ptr; | |
| 383 | + | |
| 384 | + if (!access_ok(VERIFY_READ, ptr, args->size)) | |
| 385 | + return -EFAULT; | |
| 386 | + | |
| 387 | + ret = fault_in_multipages_readable(ptr, args->size); | |
| 388 | + if (ret) | |
| 389 | + return ret; | |
| 390 | + | |
| 391 | + dobj = armada_gem_object_lookup(dev, file, args->handle); | |
| 392 | + if (dobj == NULL) | |
| 393 | + return -ENOENT; | |
| 394 | + | |
| 395 | + /* Must be a kernel-mapped object */ | |
| 396 | + if (!dobj->addr) | |
| 397 | + return -EINVAL; | |
| 398 | + | |
| 399 | + if (args->offset > dobj->obj.size || | |
| 400 | + args->size > dobj->obj.size - args->offset) { | |
| 401 | + DRM_ERROR("invalid size: object size %u\n", dobj->obj.size); | |
| 402 | + ret = -EINVAL; | |
| 403 | + goto unref; | |
| 404 | + } | |
| 405 | + | |
| 406 | + if (copy_from_user(dobj->addr + args->offset, ptr, args->size)) { | |
| 407 | + ret = -EFAULT; | |
| 408 | + } else if (dobj->update) { | |
| 409 | + dobj->update(dobj->update_data); | |
| 410 | + ret = 0; | |
| 411 | + } | |
| 412 | + | |
| 413 | + unref: | |
| 414 | + drm_gem_object_unreference_unlocked(&dobj->obj); | |
| 415 | + return ret; | |
| 416 | +} | |
| 417 | + | |
| 418 | +/* Prime support */ | |
| 419 | +struct sg_table * | |
| 420 | +armada_gem_prime_map_dma_buf(struct dma_buf_attachment *attach, | |
| 421 | + enum dma_data_direction dir) | |
| 422 | +{ | |
| 423 | + struct drm_gem_object *obj = attach->dmabuf->priv; | |
| 424 | + struct armada_gem_object *dobj = drm_to_armada_gem(obj); | |
| 425 | + struct scatterlist *sg; | |
| 426 | + struct sg_table *sgt; | |
| 427 | + int i, num; | |
| 428 | + | |
| 429 | + sgt = kmalloc(sizeof(*sgt), GFP_KERNEL); | |
| 430 | + if (!sgt) | |
| 431 | + return NULL; | |
| 432 | + | |
| 433 | + if (dobj->obj.filp) { | |
| 434 | + struct address_space *mapping; | |
| 435 | + gfp_t gfp; | |
| 436 | + int count; | |
| 437 | + | |
| 438 | + count = dobj->obj.size / PAGE_SIZE; | |
| 439 | + if (sg_alloc_table(sgt, count, GFP_KERNEL)) | |
| 440 | + goto free_sgt; | |
| 441 | + | |
| 442 | + mapping = file_inode(dobj->obj.filp)->i_mapping; | |
| 443 | + gfp = mapping_gfp_mask(mapping); | |
| 444 | + | |
| 445 | + for_each_sg(sgt->sgl, sg, count, i) { | |
| 446 | + struct page *page; | |
| 447 | + | |
| 448 | + page = shmem_read_mapping_page_gfp(mapping, i, gfp); | |
| 449 | + if (IS_ERR(page)) { | |
| 450 | + num = i; | |
| 451 | + goto release; | |
| 452 | + } | |
| 453 | + | |
| 454 | + sg_set_page(sg, page, PAGE_SIZE, 0); | |
| 455 | + } | |
| 456 | + | |
| 457 | + if (dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir) == 0) { | |
| 458 | + num = sgt->nents; | |
| 459 | + goto release; | |
| 460 | + } | |
| 461 | + } else if (dobj->page) { | |
| 462 | + /* Single contiguous page */ | |
| 463 | + if (sg_alloc_table(sgt, 1, GFP_KERNEL)) | |
| 464 | + goto free_sgt; | |
| 465 | + | |
| 466 | + sg_set_page(sgt->sgl, dobj->page, dobj->obj.size, 0); | |
| 467 | + | |
| 468 | + if (dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir) == 0) | |
| 469 | + goto free_table; | |
| 470 | + } else if (dobj->linear) { | |
| 471 | + /* Single contiguous physical region - no struct page */ | |
| 472 | + if (sg_alloc_table(sgt, 1, GFP_KERNEL)) | |
| 473 | + goto free_sgt; | |
| 474 | + sg_dma_address(sgt->sgl) = dobj->dev_addr; | |
| 475 | + sg_dma_len(sgt->sgl) = dobj->obj.size; | |
| 476 | + } else { | |
| 477 | + goto free_sgt; | |
| 478 | + } | |
| 479 | + return sgt; | |
| 480 | + | |
| 481 | + release: | |
| 482 | + for_each_sg(sgt->sgl, sg, num, i) | |
| 483 | + page_cache_release(sg_page(sg)); | |
| 484 | + free_table: | |
| 485 | + sg_free_table(sgt); | |
| 486 | + free_sgt: | |
| 487 | + kfree(sgt); | |
| 488 | + return NULL; | |
| 489 | +} | |
| 490 | + | |
| 491 | +static void armada_gem_prime_unmap_dma_buf(struct dma_buf_attachment *attach, | |
| 492 | + struct sg_table *sgt, enum dma_data_direction dir) | |
| 493 | +{ | |
| 494 | + struct drm_gem_object *obj = attach->dmabuf->priv; | |
| 495 | + struct armada_gem_object *dobj = drm_to_armada_gem(obj); | |
| 496 | + int i; | |
| 497 | + | |
| 498 | + if (!dobj->linear) | |
| 499 | + dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents, dir); | |
| 500 | + | |
| 501 | + if (dobj->obj.filp) { | |
| 502 | + struct scatterlist *sg; | |
| 503 | + for_each_sg(sgt->sgl, sg, sgt->nents, i) | |
| 504 | + page_cache_release(sg_page(sg)); | |
| 505 | + } | |
| 506 | + | |
| 507 | + sg_free_table(sgt); | |
| 508 | + kfree(sgt); | |
| 509 | +} | |
| 510 | + | |
| 511 | +static void *armada_gem_dmabuf_no_kmap(struct dma_buf *buf, unsigned long n) | |
| 512 | +{ | |
| 513 | + return NULL; | |
| 514 | +} | |
| 515 | + | |
| 516 | +static void | |
| 517 | +armada_gem_dmabuf_no_kunmap(struct dma_buf *buf, unsigned long n, void *addr) | |
| 518 | +{ | |
| 519 | +} | |
| 520 | + | |
| 521 | +static int | |
| 522 | +armada_gem_dmabuf_mmap(struct dma_buf *buf, struct vm_area_struct *vma) | |
| 523 | +{ | |
| 524 | + return -EINVAL; | |
| 525 | +} | |
| 526 | + | |
| 527 | +static const struct dma_buf_ops armada_gem_prime_dmabuf_ops = { | |
| 528 | + .map_dma_buf = armada_gem_prime_map_dma_buf, | |
| 529 | + .unmap_dma_buf = armada_gem_prime_unmap_dma_buf, | |
| 530 | + .release = drm_gem_dmabuf_release, | |
| 531 | + .kmap_atomic = armada_gem_dmabuf_no_kmap, | |
| 532 | + .kunmap_atomic = armada_gem_dmabuf_no_kunmap, | |
| 533 | + .kmap = armada_gem_dmabuf_no_kmap, | |
| 534 | + .kunmap = armada_gem_dmabuf_no_kunmap, | |
| 535 | + .mmap = armada_gem_dmabuf_mmap, | |
| 536 | +}; | |
| 537 | + | |
| 538 | +struct dma_buf * | |
| 539 | +armada_gem_prime_export(struct drm_device *dev, struct drm_gem_object *obj, | |
| 540 | + int flags) | |
| 541 | +{ | |
| 542 | + return dma_buf_export(obj, &armada_gem_prime_dmabuf_ops, obj->size, | |
| 543 | + O_RDWR); | |
| 544 | +} | |
| 545 | + | |
| 546 | +struct drm_gem_object * | |
| 547 | +armada_gem_prime_import(struct drm_device *dev, struct dma_buf *buf) | |
| 548 | +{ | |
| 549 | + struct dma_buf_attachment *attach; | |
| 550 | + struct armada_gem_object *dobj; | |
| 551 | + | |
| 552 | + if (buf->ops == &armada_gem_prime_dmabuf_ops) { | |
| 553 | + struct drm_gem_object *obj = buf->priv; | |
| 554 | + if (obj->dev == dev) { | |
| 555 | + /* | |
| 556 | + * Importing our own dmabuf(s) increases the | |
| 557 | + * refcount on the gem object itself. | |
| 558 | + */ | |
| 559 | + drm_gem_object_reference(obj); | |
| 560 | + dma_buf_put(buf); | |
| 561 | + return obj; | |
| 562 | + } | |
| 563 | + } | |
| 564 | + | |
| 565 | + attach = dma_buf_attach(buf, dev->dev); | |
| 566 | + if (IS_ERR(attach)) | |
| 567 | + return ERR_CAST(attach); | |
| 568 | + | |
| 569 | + dobj = armada_gem_alloc_private_object(dev, buf->size); | |
| 570 | + if (!dobj) { | |
| 571 | + dma_buf_detach(buf, attach); | |
| 572 | + return ERR_PTR(-ENOMEM); | |
| 573 | + } | |
| 574 | + | |
| 575 | + dobj->obj.import_attach = attach; | |
| 576 | + | |
| 577 | + /* | |
| 578 | + * Don't call dma_buf_map_attachment() here - it maps the | |
| 579 | + * scatterlist immediately for DMA, and this is not always | |
| 580 | + * an appropriate thing to do. | |
| 581 | + */ | |
| 582 | + return &dobj->obj; | |
| 583 | +} | |
| 584 | + | |
| 585 | +int armada_gem_map_import(struct armada_gem_object *dobj) | |
| 586 | +{ | |
| 587 | + int ret; | |
| 588 | + | |
| 589 | + dobj->sgt = dma_buf_map_attachment(dobj->obj.import_attach, | |
| 590 | + DMA_TO_DEVICE); | |
| 591 | + if (!dobj->sgt) { | |
| 592 | + DRM_ERROR("dma_buf_map_attachment() returned NULL\n"); | |
| 593 | + return -EINVAL; | |
| 594 | + } | |
| 595 | + if (IS_ERR(dobj->sgt)) { | |
| 596 | + ret = PTR_ERR(dobj->sgt); | |
| 597 | + dobj->sgt = NULL; | |
| 598 | + DRM_ERROR("dma_buf_map_attachment() error: %d\n", ret); | |
| 599 | + return ret; | |
| 600 | + } | |
| 601 | + if (dobj->sgt->nents > 1) { | |
| 602 | + DRM_ERROR("dma_buf_map_attachment() returned an (unsupported) scattered list\n"); | |
| 603 | + return -EINVAL; | |
| 604 | + } | |
| 605 | + if (sg_dma_len(dobj->sgt->sgl) < dobj->obj.size) { | |
| 606 | + DRM_ERROR("dma_buf_map_attachment() returned a small buffer\n"); | |
| 607 | + return -EINVAL; | |
| 608 | + } | |
| 609 | + dobj->dev_addr = sg_dma_address(dobj->sgt->sgl); | |
| 610 | + return 0; | |
| 611 | +} | |
| @@ -0,0 +1,52 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#ifndef ARMADA_GEM_H | |
| 9 | +#define ARMADA_GEM_H | |
| 10 | + | |
| 11 | +/* GEM */ | |
| 12 | +struct armada_gem_object { | |
| 13 | + struct drm_gem_object obj; | |
| 14 | + void *addr; | |
| 15 | + phys_addr_t phys_addr; | |
| 16 | + resource_size_t dev_addr; | |
| 17 | + struct drm_mm_node *linear; /* for linear backed */ | |
| 18 | + struct page *page; /* for page backed */ | |
| 19 | + struct sg_table *sgt; /* for imported */ | |
| 20 | + void (*update)(void *); | |
| 21 | + void *update_data; | |
| 22 | +}; | |
| 23 | + | |
| 24 | +extern const struct vm_operations_struct armada_gem_vm_ops; | |
| 25 | + | |
| 26 | +#define drm_to_armada_gem(o) container_of(o, struct armada_gem_object, obj) | |
| 27 | + | |
| 28 | +void armada_gem_free_object(struct drm_gem_object *); | |
| 29 | +int armada_gem_linear_back(struct drm_device *, struct armada_gem_object *); | |
| 30 | +void *armada_gem_map_object(struct drm_device *, struct armada_gem_object *); | |
| 31 | +struct armada_gem_object *armada_gem_alloc_private_object(struct drm_device *, | |
| 32 | + size_t); | |
| 33 | +int armada_gem_dumb_create(struct drm_file *, struct drm_device *, | |
| 34 | + struct drm_mode_create_dumb *); | |
| 35 | +int armada_gem_dumb_map_offset(struct drm_file *, struct drm_device *, | |
| 36 | + uint32_t, uint64_t *); | |
| 37 | +int armada_gem_dumb_destroy(struct drm_file *, struct drm_device *, | |
| 38 | + uint32_t); | |
| 39 | +struct dma_buf *armada_gem_prime_export(struct drm_device *dev, | |
| 40 | + struct drm_gem_object *obj, int flags); | |
| 41 | +struct drm_gem_object *armada_gem_prime_import(struct drm_device *, | |
| 42 | + struct dma_buf *); | |
| 43 | +int armada_gem_map_import(struct armada_gem_object *); | |
| 44 | + | |
| 45 | +static inline struct armada_gem_object *armada_gem_object_lookup( | |
| 46 | + struct drm_device *dev, struct drm_file *dfile, unsigned handle) | |
| 47 | +{ | |
| 48 | + struct drm_gem_object *obj = drm_gem_object_lookup(dev, dfile, handle); | |
| 49 | + | |
| 50 | + return obj ? drm_to_armada_gem(obj) : NULL; | |
| 51 | +} | |
| 52 | +#endif | |
| @@ -0,0 +1,316 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * Rewritten from the dovefb driver, and Armada510 manuals. | |
| 4 | + * | |
| 5 | + * This program is free software; you can redistribute it and/or modify | |
| 6 | + * it under the terms of the GNU General Public License version 2 as | |
| 7 | + * published by the Free Software Foundation. | |
| 8 | + */ | |
| 9 | +#ifndef ARMADA_HW_H | |
| 10 | +#define ARMADA_HW_H | |
| 11 | + | |
| 12 | +/* | |
| 13 | + * Note: the following registers are written from IRQ context: | |
| 14 | + * LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL | |
| 15 | + * LCD_SPU_DMA_START_ADDR_[YUV][01], LCD_SPU_DMA_PITCH_YC, | |
| 16 | + * LCD_SPU_DMA_PITCH_UV, LCD_SPU_DMA_OVSA_HPXL_VLN, | |
| 17 | + * LCD_SPU_DMA_HPXL_VLN, LCD_SPU_DZM_HPXL_VLN, LCD_SPU_DMA_CTRL0 | |
| 18 | + */ | |
| 19 | +enum { | |
| 20 | + LCD_SPU_ADV_REG = 0x0084, /* Armada 510 */ | |
| 21 | + LCD_SPU_DMA_START_ADDR_Y0 = 0x00c0, | |
| 22 | + LCD_SPU_DMA_START_ADDR_U0 = 0x00c4, | |
| 23 | + LCD_SPU_DMA_START_ADDR_V0 = 0x00c8, | |
| 24 | + LCD_CFG_DMA_START_ADDR_0 = 0x00cc, | |
| 25 | + LCD_SPU_DMA_START_ADDR_Y1 = 0x00d0, | |
| 26 | + LCD_SPU_DMA_START_ADDR_U1 = 0x00d4, | |
| 27 | + LCD_SPU_DMA_START_ADDR_V1 = 0x00d8, | |
| 28 | + LCD_CFG_DMA_START_ADDR_1 = 0x00dc, | |
| 29 | + LCD_SPU_DMA_PITCH_YC = 0x00e0, | |
| 30 | + LCD_SPU_DMA_PITCH_UV = 0x00e4, | |
| 31 | + LCD_SPU_DMA_OVSA_HPXL_VLN = 0x00e8, | |
| 32 | + LCD_SPU_DMA_HPXL_VLN = 0x00ec, | |
| 33 | + LCD_SPU_DZM_HPXL_VLN = 0x00f0, | |
| 34 | + LCD_CFG_GRA_START_ADDR0 = 0x00f4, | |
| 35 | + LCD_CFG_GRA_START_ADDR1 = 0x00f8, | |
| 36 | + LCD_CFG_GRA_PITCH = 0x00fc, | |
| 37 | + LCD_SPU_GRA_OVSA_HPXL_VLN = 0x0100, | |
| 38 | + LCD_SPU_GRA_HPXL_VLN = 0x0104, | |
| 39 | + LCD_SPU_GZM_HPXL_VLN = 0x0108, | |
| 40 | + LCD_SPU_HWC_OVSA_HPXL_VLN = 0x010c, | |
| 41 | + LCD_SPU_HWC_HPXL_VLN = 0x0110, | |
| 42 | + LCD_SPUT_V_H_TOTAL = 0x0114, | |
| 43 | + LCD_SPU_V_H_ACTIVE = 0x0118, | |
| 44 | + LCD_SPU_H_PORCH = 0x011c, | |
| 45 | + LCD_SPU_V_PORCH = 0x0120, | |
| 46 | + LCD_SPU_BLANKCOLOR = 0x0124, | |
| 47 | + LCD_SPU_ALPHA_COLOR1 = 0x0128, | |
| 48 | + LCD_SPU_ALPHA_COLOR2 = 0x012c, | |
| 49 | + LCD_SPU_COLORKEY_Y = 0x0130, | |
| 50 | + LCD_SPU_COLORKEY_U = 0x0134, | |
| 51 | + LCD_SPU_COLORKEY_V = 0x0138, | |
| 52 | + LCD_CFG_RDREG4F = 0x013c, /* Armada 510 */ | |
| 53 | + LCD_SPU_SPI_RXDATA = 0x0140, | |
| 54 | + LCD_SPU_ISA_RXDATA = 0x0144, | |
| 55 | + LCD_SPU_HWC_RDDAT = 0x0158, | |
| 56 | + LCD_SPU_GAMMA_RDDAT = 0x015c, | |
| 57 | + LCD_SPU_PALETTE_RDDAT = 0x0160, | |
| 58 | + LCD_SPU_IOPAD_IN = 0x0178, | |
| 59 | + LCD_CFG_RDREG5F = 0x017c, | |
| 60 | + LCD_SPU_SPI_CTRL = 0x0180, | |
| 61 | + LCD_SPU_SPI_TXDATA = 0x0184, | |
| 62 | + LCD_SPU_SMPN_CTRL = 0x0188, | |
| 63 | + LCD_SPU_DMA_CTRL0 = 0x0190, | |
| 64 | + LCD_SPU_DMA_CTRL1 = 0x0194, | |
| 65 | + LCD_SPU_SRAM_CTRL = 0x0198, | |
| 66 | + LCD_SPU_SRAM_WRDAT = 0x019c, | |
| 67 | + LCD_SPU_SRAM_PARA0 = 0x01a0, /* Armada 510 */ | |
| 68 | + LCD_SPU_SRAM_PARA1 = 0x01a4, | |
| 69 | + LCD_CFG_SCLK_DIV = 0x01a8, | |
| 70 | + LCD_SPU_CONTRAST = 0x01ac, | |
| 71 | + LCD_SPU_SATURATION = 0x01b0, | |
| 72 | + LCD_SPU_CBSH_HUE = 0x01b4, | |
| 73 | + LCD_SPU_DUMB_CTRL = 0x01b8, | |
| 74 | + LCD_SPU_IOPAD_CONTROL = 0x01bc, | |
| 75 | + LCD_SPU_IRQ_ENA = 0x01c0, | |
| 76 | + LCD_SPU_IRQ_ISR = 0x01c4, | |
| 77 | +}; | |
| 78 | + | |
| 79 | +/* For LCD_SPU_ADV_REG */ | |
| 80 | +enum { | |
| 81 | + ADV_VSYNC_L_OFF = 0xfff << 20, | |
| 82 | + ADV_GRACOLORKEY = 1 << 19, | |
| 83 | + ADV_VIDCOLORKEY = 1 << 18, | |
| 84 | + ADV_HWC32BLEND = 1 << 15, | |
| 85 | + ADV_HWC32ARGB = 1 << 14, | |
| 86 | + ADV_HWC32ENABLE = 1 << 13, | |
| 87 | + ADV_VSYNCOFFEN = 1 << 12, | |
| 88 | + ADV_VSYNC_H_OFF = 0xfff << 0, | |
| 89 | +}; | |
| 90 | + | |
| 91 | +enum { | |
| 92 | + CFG_565 = 0, | |
| 93 | + CFG_1555 = 1, | |
| 94 | + CFG_888PACK = 2, | |
| 95 | + CFG_X888 = 3, | |
| 96 | + CFG_8888 = 4, | |
| 97 | + CFG_422PACK = 5, | |
| 98 | + CFG_422 = 6, | |
| 99 | + CFG_420 = 7, | |
| 100 | + CFG_PSEUDO4 = 9, | |
| 101 | + CFG_PSEUDO8 = 10, | |
| 102 | + CFG_SWAPRB = 1 << 4, | |
| 103 | + CFG_SWAPUV = 1 << 3, | |
| 104 | + CFG_SWAPYU = 1 << 2, | |
| 105 | + CFG_YUV2RGB = 1 << 1, | |
| 106 | +}; | |
| 107 | + | |
| 108 | +/* For LCD_SPU_DMA_CTRL0 */ | |
| 109 | +enum { | |
| 110 | + CFG_NOBLENDING = 1 << 31, | |
| 111 | + CFG_GAMMA_ENA = 1 << 30, | |
| 112 | + CFG_CBSH_ENA = 1 << 29, | |
| 113 | + CFG_PALETTE_ENA = 1 << 28, | |
| 114 | + CFG_ARBFAST_ENA = 1 << 27, | |
| 115 | + CFG_HWC_1BITMOD = 1 << 26, | |
| 116 | + CFG_HWC_1BITENA = 1 << 25, | |
| 117 | + CFG_HWC_ENA = 1 << 24, | |
| 118 | + CFG_DMAFORMAT = 0xf << 20, | |
| 119 | +#define CFG_DMA_FMT(x) ((x) << 20) | |
| 120 | + CFG_GRAFORMAT = 0xf << 16, | |
| 121 | +#define CFG_GRA_FMT(x) ((x) << 16) | |
| 122 | +#define CFG_GRA_MOD(x) ((x) << 8) | |
| 123 | + CFG_GRA_FTOGGLE = 1 << 15, | |
| 124 | + CFG_GRA_HSMOOTH = 1 << 14, | |
| 125 | + CFG_GRA_TSTMODE = 1 << 13, | |
| 126 | + CFG_GRA_ENA = 1 << 8, | |
| 127 | +#define CFG_DMA_MOD(x) ((x) << 0) | |
| 128 | + CFG_DMA_FTOGGLE = 1 << 7, | |
| 129 | + CFG_DMA_HSMOOTH = 1 << 6, | |
| 130 | + CFG_DMA_TSTMODE = 1 << 5, | |
| 131 | + CFG_DMA_ENA = 1 << 0, | |
| 132 | +}; | |
| 133 | + | |
| 134 | +enum { | |
| 135 | + CKMODE_DISABLE = 0, | |
| 136 | + CKMODE_Y = 1, | |
| 137 | + CKMODE_U = 2, | |
| 138 | + CKMODE_RGB = 3, | |
| 139 | + CKMODE_V = 4, | |
| 140 | + CKMODE_R = 5, | |
| 141 | + CKMODE_G = 6, | |
| 142 | + CKMODE_B = 7, | |
| 143 | +}; | |
| 144 | + | |
| 145 | +/* For LCD_SPU_DMA_CTRL1 */ | |
| 146 | +enum { | |
| 147 | + CFG_FRAME_TRIG = 1 << 31, | |
| 148 | + CFG_VSYNC_INV = 1 << 27, | |
| 149 | + CFG_CKMODE_MASK = 0x7 << 24, | |
| 150 | +#define CFG_CKMODE(x) ((x) << 24) | |
| 151 | + CFG_CARRY = 1 << 23, | |
| 152 | + CFG_GATED_CLK = 1 << 21, | |
| 153 | + CFG_PWRDN_ENA = 1 << 20, | |
| 154 | + CFG_DSCALE_MASK = 0x3 << 18, | |
| 155 | + CFG_DSCALE_NONE = 0x0 << 18, | |
| 156 | + CFG_DSCALE_HALF = 0x1 << 18, | |
| 157 | + CFG_DSCALE_QUAR = 0x2 << 18, | |
| 158 | + CFG_ALPHAM_MASK = 0x3 << 16, | |
| 159 | + CFG_ALPHAM_VIDEO = 0x0 << 16, | |
| 160 | + CFG_ALPHAM_GRA = 0x1 << 16, | |
| 161 | + CFG_ALPHAM_CFG = 0x2 << 16, | |
| 162 | + CFG_ALPHA_MASK = 0xff << 8, | |
| 163 | + CFG_PIXCMD_MASK = 0xff, | |
| 164 | +}; | |
| 165 | + | |
| 166 | +/* For LCD_SPU_SRAM_CTRL */ | |
| 167 | +enum { | |
| 168 | + SRAM_READ = 0 << 14, | |
| 169 | + SRAM_WRITE = 2 << 14, | |
| 170 | + SRAM_INIT = 3 << 14, | |
| 171 | + SRAM_HWC32_RAMR = 0xc << 8, | |
| 172 | + SRAM_HWC32_RAMG = 0xd << 8, | |
| 173 | + SRAM_HWC32_RAMB = 0xe << 8, | |
| 174 | + SRAM_HWC32_TRAN = 0xf << 8, | |
| 175 | + SRAM_HWC = 0xf << 8, | |
| 176 | +}; | |
| 177 | + | |
| 178 | +/* For LCD_SPU_SRAM_PARA1 */ | |
| 179 | +enum { | |
| 180 | + CFG_CSB_256x32 = 1 << 15, /* cursor */ | |
| 181 | + CFG_CSB_256x24 = 1 << 14, /* palette */ | |
| 182 | + CFG_CSB_256x8 = 1 << 13, /* gamma */ | |
| 183 | + CFG_PDWN1920x32 = 1 << 8, /* Armada 510: power down vscale ram */ | |
| 184 | + CFG_PDWN256x32 = 1 << 7, /* power down cursor */ | |
| 185 | + CFG_PDWN256x24 = 1 << 6, /* power down palette */ | |
| 186 | + CFG_PDWN256x8 = 1 << 5, /* power down gamma */ | |
| 187 | + CFG_PDWNHWC = 1 << 4, /* Armada 510: power down all hwc ram */ | |
| 188 | + CFG_PDWN32x32 = 1 << 3, /* power down slave->smart ram */ | |
| 189 | + CFG_PDWN16x66 = 1 << 2, /* power down UV fifo */ | |
| 190 | + CFG_PDWN32x66 = 1 << 1, /* power down Y fifo */ | |
| 191 | + CFG_PDWN64x66 = 1 << 0, /* power down graphic fifo */ | |
| 192 | +}; | |
| 193 | + | |
| 194 | +/* For LCD_CFG_SCLK_DIV */ | |
| 195 | +enum { | |
| 196 | + /* Armada 510 */ | |
| 197 | + SCLK_510_AXI = 0x0 << 30, | |
| 198 | + SCLK_510_EXTCLK0 = 0x1 << 30, | |
| 199 | + SCLK_510_PLL = 0x2 << 30, | |
| 200 | + SCLK_510_EXTCLK1 = 0x3 << 30, | |
| 201 | + SCLK_510_DIV_CHANGE = 1 << 29, | |
| 202 | + SCLK_510_FRAC_DIV_MASK = 0xfff << 16, | |
| 203 | + SCLK_510_INT_DIV_MASK = 0xffff << 0, | |
| 204 | + | |
| 205 | + /* Armada 16x */ | |
| 206 | + SCLK_16X_AHB = 0x0 << 28, | |
| 207 | + SCLK_16X_PCLK = 0x1 << 28, | |
| 208 | + SCLK_16X_AXI = 0x4 << 28, | |
| 209 | + SCLK_16X_PLL = 0x8 << 28, | |
| 210 | + SCLK_16X_FRAC_DIV_MASK = 0xfff << 16, | |
| 211 | + SCLK_16X_INT_DIV_MASK = 0xffff << 0, | |
| 212 | +}; | |
| 213 | + | |
| 214 | +/* For LCD_SPU_DUMB_CTRL */ | |
| 215 | +enum { | |
| 216 | + DUMB16_RGB565_0 = 0x0 << 28, | |
| 217 | + DUMB16_RGB565_1 = 0x1 << 28, | |
| 218 | + DUMB18_RGB666_0 = 0x2 << 28, | |
| 219 | + DUMB18_RGB666_1 = 0x3 << 28, | |
| 220 | + DUMB12_RGB444_0 = 0x4 << 28, | |
| 221 | + DUMB12_RGB444_1 = 0x5 << 28, | |
| 222 | + DUMB24_RGB888_0 = 0x6 << 28, | |
| 223 | + DUMB_BLANK = 0x7 << 28, | |
| 224 | + DUMB_MASK = 0xf << 28, | |
| 225 | + CFG_BIAS_OUT = 1 << 8, | |
| 226 | + CFG_REV_RGB = 1 << 7, | |
| 227 | + CFG_INV_CBLANK = 1 << 6, | |
| 228 | + CFG_INV_CSYNC = 1 << 5, /* Normally active high */ | |
| 229 | + CFG_INV_HENA = 1 << 4, | |
| 230 | + CFG_INV_VSYNC = 1 << 3, /* Normally active high */ | |
| 231 | + CFG_INV_HSYNC = 1 << 2, /* Normally active high */ | |
| 232 | + CFG_INV_PCLK = 1 << 1, | |
| 233 | + CFG_DUMB_ENA = 1 << 0, | |
| 234 | +}; | |
| 235 | + | |
| 236 | +/* For LCD_SPU_IOPAD_CONTROL */ | |
| 237 | +enum { | |
| 238 | + CFG_VSCALE_LN_EN = 3 << 18, | |
| 239 | + CFG_GRA_VM_ENA = 1 << 15, | |
| 240 | + CFG_DMA_VM_ENA = 1 << 13, | |
| 241 | + CFG_CMD_VM_ENA = 1 << 11, | |
| 242 | + CFG_CSC_MASK = 3 << 8, | |
| 243 | + CFG_CSC_YUV_CCIR709 = 1 << 9, | |
| 244 | + CFG_CSC_YUV_CCIR601 = 0 << 9, | |
| 245 | + CFG_CSC_RGB_STUDIO = 1 << 8, | |
| 246 | + CFG_CSC_RGB_COMPUTER = 0 << 8, | |
| 247 | + CFG_IOPAD_MASK = 0xf << 0, | |
| 248 | + CFG_IOPAD_DUMB24 = 0x0 << 0, | |
| 249 | + CFG_IOPAD_DUMB18SPI = 0x1 << 0, | |
| 250 | + CFG_IOPAD_DUMB18GPIO = 0x2 << 0, | |
| 251 | + CFG_IOPAD_DUMB16SPI = 0x3 << 0, | |
| 252 | + CFG_IOPAD_DUMB16GPIO = 0x4 << 0, | |
| 253 | + CFG_IOPAD_DUMB12GPIO = 0x5 << 0, | |
| 254 | + CFG_IOPAD_SMART18 = 0x6 << 0, | |
| 255 | + CFG_IOPAD_SMART16 = 0x7 << 0, | |
| 256 | + CFG_IOPAD_SMART8 = 0x8 << 0, | |
| 257 | +}; | |
| 258 | + | |
| 259 | +#define IOPAD_DUMB24 0x0 | |
| 260 | + | |
| 261 | +/* For LCD_SPU_IRQ_ENA */ | |
| 262 | +enum { | |
| 263 | + DMA_FRAME_IRQ0_ENA = 1 << 31, | |
| 264 | + DMA_FRAME_IRQ1_ENA = 1 << 30, | |
| 265 | + DMA_FRAME_IRQ_ENA = DMA_FRAME_IRQ0_ENA | DMA_FRAME_IRQ1_ENA, | |
| 266 | + DMA_FF_UNDERFLOW_ENA = 1 << 29, | |
| 267 | + GRA_FRAME_IRQ0_ENA = 1 << 27, | |
| 268 | + GRA_FRAME_IRQ1_ENA = 1 << 26, | |
| 269 | + GRA_FRAME_IRQ_ENA = GRA_FRAME_IRQ0_ENA | GRA_FRAME_IRQ1_ENA, | |
| 270 | + GRA_FF_UNDERFLOW_ENA = 1 << 25, | |
| 271 | + VSYNC_IRQ_ENA = 1 << 23, | |
| 272 | + DUMB_FRAMEDONE_ENA = 1 << 22, | |
| 273 | + TWC_FRAMEDONE_ENA = 1 << 21, | |
| 274 | + HWC_FRAMEDONE_ENA = 1 << 20, | |
| 275 | + SLV_IRQ_ENA = 1 << 19, | |
| 276 | + SPI_IRQ_ENA = 1 << 18, | |
| 277 | + PWRDN_IRQ_ENA = 1 << 17, | |
| 278 | + ERR_IRQ_ENA = 1 << 16, | |
| 279 | + CLEAN_SPU_IRQ_ISR = 0xffff, | |
| 280 | +}; | |
| 281 | + | |
| 282 | +/* For LCD_SPU_IRQ_ISR */ | |
| 283 | +enum { | |
| 284 | + DMA_FRAME_IRQ0 = 1 << 31, | |
| 285 | + DMA_FRAME_IRQ1 = 1 << 30, | |
| 286 | + DMA_FRAME_IRQ = DMA_FRAME_IRQ0 | DMA_FRAME_IRQ1, | |
| 287 | + DMA_FF_UNDERFLOW = 1 << 29, | |
| 288 | + GRA_FRAME_IRQ0 = 1 << 27, | |
| 289 | + GRA_FRAME_IRQ1 = 1 << 26, | |
| 290 | + GRA_FRAME_IRQ = GRA_FRAME_IRQ0 | GRA_FRAME_IRQ1, | |
| 291 | + GRA_FF_UNDERFLOW = 1 << 25, | |
| 292 | + VSYNC_IRQ = 1 << 23, | |
| 293 | + DUMB_FRAMEDONE = 1 << 22, | |
| 294 | + TWC_FRAMEDONE = 1 << 21, | |
| 295 | + HWC_FRAMEDONE = 1 << 20, | |
| 296 | + SLV_IRQ = 1 << 19, | |
| 297 | + SPI_IRQ = 1 << 18, | |
| 298 | + PWRDN_IRQ = 1 << 17, | |
| 299 | + ERR_IRQ = 1 << 16, | |
| 300 | + DMA_FRAME_IRQ0_LEVEL = 1 << 15, | |
| 301 | + DMA_FRAME_IRQ1_LEVEL = 1 << 14, | |
| 302 | + DMA_FRAME_CNT_ISR = 3 << 12, | |
| 303 | + GRA_FRAME_IRQ0_LEVEL = 1 << 11, | |
| 304 | + GRA_FRAME_IRQ1_LEVEL = 1 << 10, | |
| 305 | + GRA_FRAME_CNT_ISR = 3 << 8, | |
| 306 | + VSYNC_IRQ_LEVEL = 1 << 7, | |
| 307 | + DUMB_FRAMEDONE_LEVEL = 1 << 6, | |
| 308 | + TWC_FRAMEDONE_LEVEL = 1 << 5, | |
| 309 | + HWC_FRAMEDONE_LEVEL = 1 << 4, | |
| 310 | + SLV_FF_EMPTY = 1 << 3, | |
| 311 | + DMA_FF_ALLEMPTY = 1 << 2, | |
| 312 | + GRA_FF_ALLEMPTY = 1 << 1, | |
| 313 | + PWRDN_IRQ_LEVEL = 1 << 0, | |
| 314 | +}; | |
| 315 | + | |
| 316 | +#endif | |
| @@ -0,0 +1,18 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#ifndef ARMADA_IOCTLP_H | |
| 9 | +#define ARMADA_IOCTLP_H | |
| 10 | + | |
| 11 | +#define ARMADA_IOCTL_PROTO(name)\ | |
| 12 | +extern int armada_##name##_ioctl(struct drm_device *, void *, struct drm_file *) | |
| 13 | + | |
| 14 | +ARMADA_IOCTL_PROTO(gem_create); | |
| 15 | +ARMADA_IOCTL_PROTO(gem_mmap); | |
| 16 | +ARMADA_IOCTL_PROTO(gem_pwrite); | |
| 17 | + | |
| 18 | +#endif | |
| @@ -0,0 +1,158 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#include <drm/drmP.h> | |
| 9 | +#include <drm/drm_crtc_helper.h> | |
| 10 | +#include <drm/drm_edid.h> | |
| 11 | +#include <drm/drm_encoder_slave.h> | |
| 12 | +#include "armada_output.h" | |
| 13 | +#include "armada_drm.h" | |
| 14 | + | |
| 15 | +struct armada_connector { | |
| 16 | + struct drm_connector conn; | |
| 17 | + const struct armada_output_type *type; | |
| 18 | +}; | |
| 19 | + | |
| 20 | +#define drm_to_armada_conn(c) container_of(c, struct armada_connector, conn) | |
| 21 | + | |
| 22 | +struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn) | |
| 23 | +{ | |
| 24 | + struct drm_encoder *enc = conn->encoder; | |
| 25 | + | |
| 26 | + return enc ? enc : drm_encoder_find(conn->dev, conn->encoder_ids[0]); | |
| 27 | +} | |
| 28 | + | |
| 29 | +static enum drm_connector_status armada_drm_connector_detect( | |
| 30 | + struct drm_connector *conn, bool force) | |
| 31 | +{ | |
| 32 | + struct armada_connector *dconn = drm_to_armada_conn(conn); | |
| 33 | + enum drm_connector_status status = connector_status_disconnected; | |
| 34 | + | |
| 35 | + if (dconn->type->detect) { | |
| 36 | + status = dconn->type->detect(conn, force); | |
| 37 | + } else { | |
| 38 | + struct drm_encoder *enc = armada_drm_connector_encoder(conn); | |
| 39 | + | |
| 40 | + if (enc) | |
| 41 | + status = encoder_helper_funcs(enc)->detect(enc, conn); | |
| 42 | + } | |
| 43 | + | |
| 44 | + return status; | |
| 45 | +} | |
| 46 | + | |
| 47 | +static void armada_drm_connector_destroy(struct drm_connector *conn) | |
| 48 | +{ | |
| 49 | + struct armada_connector *dconn = drm_to_armada_conn(conn); | |
| 50 | + | |
| 51 | + drm_sysfs_connector_remove(conn); | |
| 52 | + drm_connector_cleanup(conn); | |
| 53 | + kfree(dconn); | |
| 54 | +} | |
| 55 | + | |
| 56 | +static int armada_drm_connector_set_property(struct drm_connector *conn, | |
| 57 | + struct drm_property *property, uint64_t value) | |
| 58 | +{ | |
| 59 | + struct armada_connector *dconn = drm_to_armada_conn(conn); | |
| 60 | + | |
| 61 | + if (!dconn->type->set_property) | |
| 62 | + return -EINVAL; | |
| 63 | + | |
| 64 | + return dconn->type->set_property(conn, property, value); | |
| 65 | +} | |
| 66 | + | |
| 67 | +static const struct drm_connector_funcs armada_drm_conn_funcs = { | |
| 68 | + .dpms = drm_helper_connector_dpms, | |
| 69 | + .fill_modes = drm_helper_probe_single_connector_modes, | |
| 70 | + .detect = armada_drm_connector_detect, | |
| 71 | + .destroy = armada_drm_connector_destroy, | |
| 72 | + .set_property = armada_drm_connector_set_property, | |
| 73 | +}; | |
| 74 | + | |
| 75 | +void armada_drm_encoder_prepare(struct drm_encoder *encoder) | |
| 76 | +{ | |
| 77 | + encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_OFF); | |
| 78 | +} | |
| 79 | + | |
| 80 | +void armada_drm_encoder_commit(struct drm_encoder *encoder) | |
| 81 | +{ | |
| 82 | + encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_ON); | |
| 83 | +} | |
| 84 | + | |
| 85 | +bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder, | |
| 86 | + const struct drm_display_mode *mode, struct drm_display_mode *adjusted) | |
| 87 | +{ | |
| 88 | + return true; | |
| 89 | +} | |
| 90 | + | |
| 91 | +/* Shouldn't this be a generic helper function? */ | |
| 92 | +int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn, | |
| 93 | + struct drm_display_mode *mode) | |
| 94 | +{ | |
| 95 | + struct drm_encoder *encoder = armada_drm_connector_encoder(conn); | |
| 96 | + int valid = MODE_BAD; | |
| 97 | + | |
| 98 | + if (encoder) { | |
| 99 | + struct drm_encoder_slave *slave = to_encoder_slave(encoder); | |
| 100 | + | |
| 101 | + valid = slave->slave_funcs->mode_valid(encoder, mode); | |
| 102 | + } | |
| 103 | + return valid; | |
| 104 | +} | |
| 105 | + | |
| 106 | +int armada_drm_slave_encoder_set_property(struct drm_connector *conn, | |
| 107 | + struct drm_property *property, uint64_t value) | |
| 108 | +{ | |
| 109 | + struct drm_encoder *encoder = armada_drm_connector_encoder(conn); | |
| 110 | + int rc = -EINVAL; | |
| 111 | + | |
| 112 | + if (encoder) { | |
| 113 | + struct drm_encoder_slave *slave = to_encoder_slave(encoder); | |
| 114 | + | |
| 115 | + rc = slave->slave_funcs->set_property(encoder, conn, property, | |
| 116 | + value); | |
| 117 | + } | |
| 118 | + return rc; | |
| 119 | +} | |
| 120 | + | |
| 121 | +int armada_output_create(struct drm_device *dev, | |
| 122 | + const struct armada_output_type *type, const void *data) | |
| 123 | +{ | |
| 124 | + struct armada_connector *dconn; | |
| 125 | + int ret; | |
| 126 | + | |
| 127 | + dconn = kzalloc(sizeof(*dconn), GFP_KERNEL); | |
| 128 | + if (!dconn) | |
| 129 | + return -ENOMEM; | |
| 130 | + | |
| 131 | + dconn->type = type; | |
| 132 | + | |
| 133 | + ret = drm_connector_init(dev, &dconn->conn, &armada_drm_conn_funcs, | |
| 134 | + type->connector_type); | |
| 135 | + if (ret) { | |
| 136 | + DRM_ERROR("unable to init connector\n"); | |
| 137 | + goto err_destroy_dconn; | |
| 138 | + } | |
| 139 | + | |
| 140 | + ret = type->create(&dconn->conn, data); | |
| 141 | + if (ret) | |
| 142 | + goto err_conn; | |
| 143 | + | |
| 144 | + ret = drm_sysfs_connector_add(&dconn->conn); | |
| 145 | + if (ret) | |
| 146 | + goto err_sysfs; | |
| 147 | + | |
| 148 | + return 0; | |
| 149 | + | |
| 150 | + err_sysfs: | |
| 151 | + if (dconn->conn.encoder) | |
| 152 | + dconn->conn.encoder->funcs->destroy(dconn->conn.encoder); | |
| 153 | + err_conn: | |
| 154 | + drm_connector_cleanup(&dconn->conn); | |
| 155 | + err_destroy_dconn: | |
| 156 | + kfree(dconn); | |
| 157 | + return ret; | |
| 158 | +} | |
| @@ -0,0 +1,39 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#ifndef ARMADA_CONNETOR_H | |
| 9 | +#define ARMADA_CONNETOR_H | |
| 10 | + | |
| 11 | +#define encoder_helper_funcs(encoder) \ | |
| 12 | + ((struct drm_encoder_helper_funcs *)encoder->helper_private) | |
| 13 | + | |
| 14 | +struct armada_output_type { | |
| 15 | + int connector_type; | |
| 16 | + enum drm_connector_status (*detect)(struct drm_connector *, bool); | |
| 17 | + int (*create)(struct drm_connector *, const void *); | |
| 18 | + int (*set_property)(struct drm_connector *, struct drm_property *, | |
| 19 | + uint64_t); | |
| 20 | +}; | |
| 21 | + | |
| 22 | +struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn); | |
| 23 | + | |
| 24 | +void armada_drm_encoder_prepare(struct drm_encoder *encoder); | |
| 25 | +void armada_drm_encoder_commit(struct drm_encoder *encoder); | |
| 26 | + | |
| 27 | +bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder, | |
| 28 | + const struct drm_display_mode *mode, struct drm_display_mode *adj); | |
| 29 | + | |
| 30 | +int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn, | |
| 31 | + struct drm_display_mode *mode); | |
| 32 | + | |
| 33 | +int armada_drm_slave_encoder_set_property(struct drm_connector *conn, | |
| 34 | + struct drm_property *property, uint64_t value); | |
| 35 | + | |
| 36 | +int armada_output_create(struct drm_device *dev, | |
| 37 | + const struct armada_output_type *type, const void *data); | |
| 38 | + | |
| 39 | +#endif | |
| @@ -0,0 +1,477 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * Rewritten from the dovefb driver, and Armada510 manuals. | |
| 4 | + * | |
| 5 | + * This program is free software; you can redistribute it and/or modify | |
| 6 | + * it under the terms of the GNU General Public License version 2 as | |
| 7 | + * published by the Free Software Foundation. | |
| 8 | + */ | |
| 9 | +#include <drm/drmP.h> | |
| 10 | +#include "armada_crtc.h" | |
| 11 | +#include "armada_drm.h" | |
| 12 | +#include "armada_fb.h" | |
| 13 | +#include "armada_gem.h" | |
| 14 | +#include "armada_hw.h" | |
| 15 | +#include <drm/armada_drm.h> | |
| 16 | +#include "armada_ioctlP.h" | |
| 17 | + | |
| 18 | +struct armada_plane_properties { | |
| 19 | + uint32_t colorkey_yr; | |
| 20 | + uint32_t colorkey_ug; | |
| 21 | + uint32_t colorkey_vb; | |
| 22 | +#define K2R(val) (((val) >> 0) & 0xff) | |
| 23 | +#define K2G(val) (((val) >> 8) & 0xff) | |
| 24 | +#define K2B(val) (((val) >> 16) & 0xff) | |
| 25 | + int16_t brightness; | |
| 26 | + uint16_t contrast; | |
| 27 | + uint16_t saturation; | |
| 28 | + uint32_t colorkey_mode; | |
| 29 | +}; | |
| 30 | + | |
| 31 | +struct armada_plane { | |
| 32 | + struct drm_plane base; | |
| 33 | + spinlock_t lock; | |
| 34 | + struct drm_framebuffer *old_fb; | |
| 35 | + uint32_t src_hw; | |
| 36 | + uint32_t dst_hw; | |
| 37 | + uint32_t dst_yx; | |
| 38 | + uint32_t ctrl0; | |
| 39 | + struct { | |
| 40 | + struct armada_vbl_event update; | |
| 41 | + struct armada_regs regs[13]; | |
| 42 | + wait_queue_head_t wait; | |
| 43 | + } vbl; | |
| 44 | + struct armada_plane_properties prop; | |
| 45 | +}; | |
| 46 | +#define drm_to_armada_plane(p) container_of(p, struct armada_plane, base) | |
| 47 | + | |
| 48 | + | |
| 49 | +static void | |
| 50 | +armada_ovl_update_attr(struct armada_plane_properties *prop, | |
| 51 | + struct armada_crtc *dcrtc) | |
| 52 | +{ | |
| 53 | + writel_relaxed(prop->colorkey_yr, dcrtc->base + LCD_SPU_COLORKEY_Y); | |
| 54 | + writel_relaxed(prop->colorkey_ug, dcrtc->base + LCD_SPU_COLORKEY_U); | |
| 55 | + writel_relaxed(prop->colorkey_vb, dcrtc->base + LCD_SPU_COLORKEY_V); | |
| 56 | + | |
| 57 | + writel_relaxed(prop->brightness << 16 | prop->contrast, | |
| 58 | + dcrtc->base + LCD_SPU_CONTRAST); | |
| 59 | + /* Docs say 15:0, but it seems to actually be 31:16 on Armada 510 */ | |
| 60 | + writel_relaxed(prop->saturation << 16, | |
| 61 | + dcrtc->base + LCD_SPU_SATURATION); | |
| 62 | + writel_relaxed(0x00002000, dcrtc->base + LCD_SPU_CBSH_HUE); | |
| 63 | + | |
| 64 | + spin_lock_irq(&dcrtc->irq_lock); | |
| 65 | + armada_updatel(prop->colorkey_mode | CFG_ALPHAM_GRA, | |
| 66 | + CFG_CKMODE_MASK | CFG_ALPHAM_MASK | CFG_ALPHA_MASK, | |
| 67 | + dcrtc->base + LCD_SPU_DMA_CTRL1); | |
| 68 | + | |
| 69 | + armada_updatel(ADV_GRACOLORKEY, 0, dcrtc->base + LCD_SPU_ADV_REG); | |
| 70 | + spin_unlock_irq(&dcrtc->irq_lock); | |
| 71 | +} | |
| 72 | + | |
| 73 | +/* === Plane support === */ | |
| 74 | +static void armada_plane_vbl(struct armada_crtc *dcrtc, void *data) | |
| 75 | +{ | |
| 76 | + struct armada_plane *dplane = data; | |
| 77 | + struct drm_framebuffer *fb; | |
| 78 | + | |
| 79 | + armada_drm_crtc_update_regs(dcrtc, dplane->vbl.regs); | |
| 80 | + | |
| 81 | + spin_lock(&dplane->lock); | |
| 82 | + fb = dplane->old_fb; | |
| 83 | + dplane->old_fb = NULL; | |
| 84 | + spin_unlock(&dplane->lock); | |
| 85 | + | |
| 86 | + if (fb) | |
| 87 | + armada_drm_queue_unref_work(dcrtc->crtc.dev, fb); | |
| 88 | +} | |
| 89 | + | |
| 90 | +static unsigned armada_limit(int start, unsigned size, unsigned max) | |
| 91 | +{ | |
| 92 | + int end = start + size; | |
| 93 | + if (end < 0) | |
| 94 | + return 0; | |
| 95 | + if (start < 0) | |
| 96 | + start = 0; | |
| 97 | + return (unsigned)end > max ? max - start : end - start; | |
| 98 | +} | |
| 99 | + | |
| 100 | +static int | |
| 101 | +armada_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, | |
| 102 | + struct drm_framebuffer *fb, | |
| 103 | + int crtc_x, int crtc_y, unsigned crtc_w, unsigned crtc_h, | |
| 104 | + uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h) | |
| 105 | +{ | |
| 106 | + struct armada_plane *dplane = drm_to_armada_plane(plane); | |
| 107 | + struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc); | |
| 108 | + uint32_t val, ctrl0; | |
| 109 | + unsigned idx = 0; | |
| 110 | + int ret; | |
| 111 | + | |
| 112 | + crtc_w = armada_limit(crtc_x, crtc_w, dcrtc->crtc.mode.hdisplay); | |
| 113 | + crtc_h = armada_limit(crtc_y, crtc_h, dcrtc->crtc.mode.vdisplay); | |
| 114 | + ctrl0 = CFG_DMA_FMT(drm_fb_to_armada_fb(fb)->fmt) | | |
| 115 | + CFG_DMA_MOD(drm_fb_to_armada_fb(fb)->mod) | | |
| 116 | + CFG_CBSH_ENA | CFG_DMA_HSMOOTH | CFG_DMA_ENA; | |
| 117 | + | |
| 118 | + /* Does the position/size result in nothing to display? */ | |
| 119 | + if (crtc_w == 0 || crtc_h == 0) { | |
| 120 | + ctrl0 &= ~CFG_DMA_ENA; | |
| 121 | + } | |
| 122 | + | |
| 123 | + /* | |
| 124 | + * FIXME: if the starting point is off screen, we need to | |
| 125 | + * adjust src_x, src_y, src_w, src_h appropriately, and | |
| 126 | + * according to the scale. | |
| 127 | + */ | |
| 128 | + | |
| 129 | + if (!dcrtc->plane) { | |
| 130 | + dcrtc->plane = plane; | |
| 131 | + armada_ovl_update_attr(&dplane->prop, dcrtc); | |
| 132 | + } | |
| 133 | + | |
| 134 | + /* FIXME: overlay on an interlaced display */ | |
| 135 | + /* Just updating the position/size? */ | |
| 136 | + if (plane->fb == fb && dplane->ctrl0 == ctrl0) { | |
| 137 | + val = (src_h & 0xffff0000) | src_w >> 16; | |
| 138 | + dplane->src_hw = val; | |
| 139 | + writel_relaxed(val, dcrtc->base + LCD_SPU_DMA_HPXL_VLN); | |
| 140 | + val = crtc_h << 16 | crtc_w; | |
| 141 | + dplane->dst_hw = val; | |
| 142 | + writel_relaxed(val, dcrtc->base + LCD_SPU_DZM_HPXL_VLN); | |
| 143 | + val = crtc_y << 16 | crtc_x; | |
| 144 | + dplane->dst_yx = val; | |
| 145 | + writel_relaxed(val, dcrtc->base + LCD_SPU_DMA_OVSA_HPXL_VLN); | |
| 146 | + return 0; | |
| 147 | + } else if (~dplane->ctrl0 & ctrl0 & CFG_DMA_ENA) { | |
| 148 | + /* Power up the Y/U/V FIFOs on ENA 0->1 transitions */ | |
| 149 | + armada_updatel(0, CFG_PDWN16x66 | CFG_PDWN32x66, | |
| 150 | + dcrtc->base + LCD_SPU_SRAM_PARA1); | |
| 151 | + } | |
| 152 | + | |
| 153 | + ret = wait_event_timeout(dplane->vbl.wait, | |
| 154 | + list_empty(&dplane->vbl.update.node), | |
| 155 | + HZ/25); | |
| 156 | + if (ret < 0) | |
| 157 | + return ret; | |
| 158 | + | |
| 159 | + if (plane->fb != fb) { | |
| 160 | + struct armada_gem_object *obj = drm_fb_obj(fb); | |
| 161 | + uint32_t sy, su, sv; | |
| 162 | + | |
| 163 | + /* | |
| 164 | + * Take a reference on the new framebuffer - we want to | |
| 165 | + * hold on to it while the hardware is displaying it. | |
| 166 | + */ | |
| 167 | + drm_framebuffer_reference(fb); | |
| 168 | + | |
| 169 | + if (plane->fb) { | |
| 170 | + struct drm_framebuffer *older_fb; | |
| 171 | + | |
| 172 | + spin_lock_irq(&dplane->lock); | |
| 173 | + older_fb = dplane->old_fb; | |
| 174 | + dplane->old_fb = plane->fb; | |
| 175 | + spin_unlock_irq(&dplane->lock); | |
| 176 | + if (older_fb) | |
| 177 | + armada_drm_queue_unref_work(dcrtc->crtc.dev, | |
| 178 | + older_fb); | |
| 179 | + } | |
| 180 | + | |
| 181 | + src_y >>= 16; | |
| 182 | + src_x >>= 16; | |
| 183 | + sy = obj->dev_addr + fb->offsets[0] + src_y * fb->pitches[0] + | |
| 184 | + src_x * fb->bits_per_pixel / 8; | |
| 185 | + su = obj->dev_addr + fb->offsets[1] + src_y * fb->pitches[1] + | |
| 186 | + src_x; | |
| 187 | + sv = obj->dev_addr + fb->offsets[2] + src_y * fb->pitches[2] + | |
| 188 | + src_x; | |
| 189 | + | |
| 190 | + armada_reg_queue_set(dplane->vbl.regs, idx, sy, | |
| 191 | + LCD_SPU_DMA_START_ADDR_Y0); | |
| 192 | + armada_reg_queue_set(dplane->vbl.regs, idx, su, | |
| 193 | + LCD_SPU_DMA_START_ADDR_U0); | |
| 194 | + armada_reg_queue_set(dplane->vbl.regs, idx, sv, | |
| 195 | + LCD_SPU_DMA_START_ADDR_V0); | |
| 196 | + armada_reg_queue_set(dplane->vbl.regs, idx, sy, | |
| 197 | + LCD_SPU_DMA_START_ADDR_Y1); | |
| 198 | + armada_reg_queue_set(dplane->vbl.regs, idx, su, | |
| 199 | + LCD_SPU_DMA_START_ADDR_U1); | |
| 200 | + armada_reg_queue_set(dplane->vbl.regs, idx, sv, | |
| 201 | + LCD_SPU_DMA_START_ADDR_V1); | |
| 202 | + | |
| 203 | + val = fb->pitches[0] << 16 | fb->pitches[0]; | |
| 204 | + armada_reg_queue_set(dplane->vbl.regs, idx, val, | |
| 205 | + LCD_SPU_DMA_PITCH_YC); | |
| 206 | + val = fb->pitches[1] << 16 | fb->pitches[2]; | |
| 207 | + armada_reg_queue_set(dplane->vbl.regs, idx, val, | |
| 208 | + LCD_SPU_DMA_PITCH_UV); | |
| 209 | + } | |
| 210 | + | |
| 211 | + val = (src_h & 0xffff0000) | src_w >> 16; | |
| 212 | + if (dplane->src_hw != val) { | |
| 213 | + dplane->src_hw = val; | |
| 214 | + armada_reg_queue_set(dplane->vbl.regs, idx, val, | |
| 215 | + LCD_SPU_DMA_HPXL_VLN); | |
| 216 | + } | |
| 217 | + val = crtc_h << 16 | crtc_w; | |
| 218 | + if (dplane->dst_hw != val) { | |
| 219 | + dplane->dst_hw = val; | |
| 220 | + armada_reg_queue_set(dplane->vbl.regs, idx, val, | |
| 221 | + LCD_SPU_DZM_HPXL_VLN); | |
| 222 | + } | |
| 223 | + val = crtc_y << 16 | crtc_x; | |
| 224 | + if (dplane->dst_yx != val) { | |
| 225 | + dplane->dst_yx = val; | |
| 226 | + armada_reg_queue_set(dplane->vbl.regs, idx, val, | |
| 227 | + LCD_SPU_DMA_OVSA_HPXL_VLN); | |
| 228 | + } | |
| 229 | + if (dplane->ctrl0 != ctrl0) { | |
| 230 | + dplane->ctrl0 = ctrl0; | |
| 231 | + armada_reg_queue_mod(dplane->vbl.regs, idx, ctrl0, | |
| 232 | + CFG_CBSH_ENA | CFG_DMAFORMAT | CFG_DMA_FTOGGLE | | |
| 233 | + CFG_DMA_HSMOOTH | CFG_DMA_TSTMODE | | |
| 234 | + CFG_DMA_MOD(CFG_SWAPRB | CFG_SWAPUV | CFG_SWAPYU | | |
| 235 | + CFG_YUV2RGB) | CFG_DMA_ENA, | |
| 236 | + LCD_SPU_DMA_CTRL0); | |
| 237 | + } | |
| 238 | + if (idx) { | |
| 239 | + armada_reg_queue_end(dplane->vbl.regs, idx); | |
| 240 | + armada_drm_vbl_event_add(dcrtc, &dplane->vbl.update); | |
| 241 | + } | |
| 242 | + return 0; | |
| 243 | +} | |
| 244 | + | |
| 245 | +static int armada_plane_disable(struct drm_plane *plane) | |
| 246 | +{ | |
| 247 | + struct armada_plane *dplane = drm_to_armada_plane(plane); | |
| 248 | + struct drm_framebuffer *fb; | |
| 249 | + struct armada_crtc *dcrtc; | |
| 250 | + | |
| 251 | + if (!dplane->base.crtc) | |
| 252 | + return 0; | |
| 253 | + | |
| 254 | + dcrtc = drm_to_armada_crtc(dplane->base.crtc); | |
| 255 | + dcrtc->plane = NULL; | |
| 256 | + | |
| 257 | + spin_lock_irq(&dcrtc->irq_lock); | |
| 258 | + armada_drm_vbl_event_remove(dcrtc, &dplane->vbl.update); | |
| 259 | + armada_updatel(0, CFG_DMA_ENA, dcrtc->base + LCD_SPU_DMA_CTRL0); | |
| 260 | + dplane->ctrl0 = 0; | |
| 261 | + spin_unlock_irq(&dcrtc->irq_lock); | |
| 262 | + | |
| 263 | + /* Power down the Y/U/V FIFOs */ | |
| 264 | + armada_updatel(CFG_PDWN16x66 | CFG_PDWN32x66, 0, | |
| 265 | + dcrtc->base + LCD_SPU_SRAM_PARA1); | |
| 266 | + | |
| 267 | + if (plane->fb) | |
| 268 | + drm_framebuffer_unreference(plane->fb); | |
| 269 | + | |
| 270 | + spin_lock_irq(&dplane->lock); | |
| 271 | + fb = dplane->old_fb; | |
| 272 | + dplane->old_fb = NULL; | |
| 273 | + spin_unlock_irq(&dplane->lock); | |
| 274 | + if (fb) | |
| 275 | + drm_framebuffer_unreference(fb); | |
| 276 | + | |
| 277 | + return 0; | |
| 278 | +} | |
| 279 | + | |
| 280 | +static void armada_plane_destroy(struct drm_plane *plane) | |
| 281 | +{ | |
| 282 | + kfree(plane); | |
| 283 | +} | |
| 284 | + | |
| 285 | +static int armada_plane_set_property(struct drm_plane *plane, | |
| 286 | + struct drm_property *property, uint64_t val) | |
| 287 | +{ | |
| 288 | + struct armada_private *priv = plane->dev->dev_private; | |
| 289 | + struct armada_plane *dplane = drm_to_armada_plane(plane); | |
| 290 | + bool update_attr = false; | |
| 291 | + | |
| 292 | + if (property == priv->colorkey_prop) { | |
| 293 | +#define CCC(v) ((v) << 24 | (v) << 16 | (v) << 8) | |
| 294 | + dplane->prop.colorkey_yr = CCC(K2R(val)); | |
| 295 | + dplane->prop.colorkey_ug = CCC(K2G(val)); | |
| 296 | + dplane->prop.colorkey_vb = CCC(K2B(val)); | |
| 297 | +#undef CCC | |
| 298 | + update_attr = true; | |
| 299 | + } else if (property == priv->colorkey_min_prop) { | |
| 300 | + dplane->prop.colorkey_yr &= ~0x00ff0000; | |
| 301 | + dplane->prop.colorkey_yr |= K2R(val) << 16; | |
| 302 | + dplane->prop.colorkey_ug &= ~0x00ff0000; | |
| 303 | + dplane->prop.colorkey_ug |= K2G(val) << 16; | |
| 304 | + dplane->prop.colorkey_vb &= ~0x00ff0000; | |
| 305 | + dplane->prop.colorkey_vb |= K2B(val) << 16; | |
| 306 | + update_attr = true; | |
| 307 | + } else if (property == priv->colorkey_max_prop) { | |
| 308 | + dplane->prop.colorkey_yr &= ~0xff000000; | |
| 309 | + dplane->prop.colorkey_yr |= K2R(val) << 24; | |
| 310 | + dplane->prop.colorkey_ug &= ~0xff000000; | |
| 311 | + dplane->prop.colorkey_ug |= K2G(val) << 24; | |
| 312 | + dplane->prop.colorkey_vb &= ~0xff000000; | |
| 313 | + dplane->prop.colorkey_vb |= K2B(val) << 24; | |
| 314 | + update_attr = true; | |
| 315 | + } else if (property == priv->colorkey_val_prop) { | |
| 316 | + dplane->prop.colorkey_yr &= ~0x0000ff00; | |
| 317 | + dplane->prop.colorkey_yr |= K2R(val) << 8; | |
| 318 | + dplane->prop.colorkey_ug &= ~0x0000ff00; | |
| 319 | + dplane->prop.colorkey_ug |= K2G(val) << 8; | |
| 320 | + dplane->prop.colorkey_vb &= ~0x0000ff00; | |
| 321 | + dplane->prop.colorkey_vb |= K2B(val) << 8; | |
| 322 | + update_attr = true; | |
| 323 | + } else if (property == priv->colorkey_alpha_prop) { | |
| 324 | + dplane->prop.colorkey_yr &= ~0x000000ff; | |
| 325 | + dplane->prop.colorkey_yr |= K2R(val); | |
| 326 | + dplane->prop.colorkey_ug &= ~0x000000ff; | |
| 327 | + dplane->prop.colorkey_ug |= K2G(val); | |
| 328 | + dplane->prop.colorkey_vb &= ~0x000000ff; | |
| 329 | + dplane->prop.colorkey_vb |= K2B(val); | |
| 330 | + update_attr = true; | |
| 331 | + } else if (property == priv->colorkey_mode_prop) { | |
| 332 | + dplane->prop.colorkey_mode &= ~CFG_CKMODE_MASK; | |
| 333 | + dplane->prop.colorkey_mode |= CFG_CKMODE(val); | |
| 334 | + update_attr = true; | |
| 335 | + } else if (property == priv->brightness_prop) { | |
| 336 | + dplane->prop.brightness = val - 256; | |
| 337 | + update_attr = true; | |
| 338 | + } else if (property == priv->contrast_prop) { | |
| 339 | + dplane->prop.contrast = val; | |
| 340 | + update_attr = true; | |
| 341 | + } else if (property == priv->saturation_prop) { | |
| 342 | + dplane->prop.saturation = val; | |
| 343 | + update_attr = true; | |
| 344 | + } | |
| 345 | + | |
| 346 | + if (update_attr && dplane->base.crtc) | |
| 347 | + armada_ovl_update_attr(&dplane->prop, | |
| 348 | + drm_to_armada_crtc(dplane->base.crtc)); | |
| 349 | + | |
| 350 | + return 0; | |
| 351 | +} | |
| 352 | + | |
| 353 | +static const struct drm_plane_funcs armada_plane_funcs = { | |
| 354 | + .update_plane = armada_plane_update, | |
| 355 | + .disable_plane = armada_plane_disable, | |
| 356 | + .destroy = armada_plane_destroy, | |
| 357 | + .set_property = armada_plane_set_property, | |
| 358 | +}; | |
| 359 | + | |
| 360 | +static const uint32_t armada_formats[] = { | |
| 361 | + DRM_FORMAT_UYVY, | |
| 362 | + DRM_FORMAT_YUYV, | |
| 363 | + DRM_FORMAT_YUV420, | |
| 364 | + DRM_FORMAT_YVU420, | |
| 365 | + DRM_FORMAT_YUV422, | |
| 366 | + DRM_FORMAT_YVU422, | |
| 367 | + DRM_FORMAT_VYUY, | |
| 368 | + DRM_FORMAT_YVYU, | |
| 369 | + DRM_FORMAT_ARGB8888, | |
| 370 | + DRM_FORMAT_ABGR8888, | |
| 371 | + DRM_FORMAT_XRGB8888, | |
| 372 | + DRM_FORMAT_XBGR8888, | |
| 373 | + DRM_FORMAT_RGB888, | |
| 374 | + DRM_FORMAT_BGR888, | |
| 375 | + DRM_FORMAT_ARGB1555, | |
| 376 | + DRM_FORMAT_ABGR1555, | |
| 377 | + DRM_FORMAT_RGB565, | |
| 378 | + DRM_FORMAT_BGR565, | |
| 379 | +}; | |
| 380 | + | |
| 381 | +static struct drm_prop_enum_list armada_drm_colorkey_enum_list[] = { | |
| 382 | + { CKMODE_DISABLE, "disabled" }, | |
| 383 | + { CKMODE_Y, "Y component" }, | |
| 384 | + { CKMODE_U, "U component" }, | |
| 385 | + { CKMODE_V, "V component" }, | |
| 386 | + { CKMODE_RGB, "RGB" }, | |
| 387 | + { CKMODE_R, "R component" }, | |
| 388 | + { CKMODE_G, "G component" }, | |
| 389 | + { CKMODE_B, "B component" }, | |
| 390 | +}; | |
| 391 | + | |
| 392 | +static int armada_overlay_create_properties(struct drm_device *dev) | |
| 393 | +{ | |
| 394 | + struct armada_private *priv = dev->dev_private; | |
| 395 | + | |
| 396 | + if (priv->colorkey_prop) | |
| 397 | + return 0; | |
| 398 | + | |
| 399 | + priv->colorkey_prop = drm_property_create_range(dev, 0, | |
| 400 | + "colorkey", 0, 0xffffff); | |
| 401 | + priv->colorkey_min_prop = drm_property_create_range(dev, 0, | |
| 402 | + "colorkey_min", 0, 0xffffff); | |
| 403 | + priv->colorkey_max_prop = drm_property_create_range(dev, 0, | |
| 404 | + "colorkey_max", 0, 0xffffff); | |
| 405 | + priv->colorkey_val_prop = drm_property_create_range(dev, 0, | |
| 406 | + "colorkey_val", 0, 0xffffff); | |
| 407 | + priv->colorkey_alpha_prop = drm_property_create_range(dev, 0, | |
| 408 | + "colorkey_alpha", 0, 0xffffff); | |
| 409 | + priv->colorkey_mode_prop = drm_property_create_enum(dev, 0, | |
| 410 | + "colorkey_mode", | |
| 411 | + armada_drm_colorkey_enum_list, | |
| 412 | + ARRAY_SIZE(armada_drm_colorkey_enum_list)); | |
| 413 | + priv->brightness_prop = drm_property_create_range(dev, 0, | |
| 414 | + "brightness", 0, 256 + 255); | |
| 415 | + priv->contrast_prop = drm_property_create_range(dev, 0, | |
| 416 | + "contrast", 0, 0x7fff); | |
| 417 | + priv->saturation_prop = drm_property_create_range(dev, 0, | |
| 418 | + "saturation", 0, 0x7fff); | |
| 419 | + | |
| 420 | + if (!priv->colorkey_prop) | |
| 421 | + return -ENOMEM; | |
| 422 | + | |
| 423 | + return 0; | |
| 424 | +} | |
| 425 | + | |
| 426 | +int armada_overlay_plane_create(struct drm_device *dev, unsigned long crtcs) | |
| 427 | +{ | |
| 428 | + struct armada_private *priv = dev->dev_private; | |
| 429 | + struct drm_mode_object *mobj; | |
| 430 | + struct armada_plane *dplane; | |
| 431 | + int ret; | |
| 432 | + | |
| 433 | + ret = armada_overlay_create_properties(dev); | |
| 434 | + if (ret) | |
| 435 | + return ret; | |
| 436 | + | |
| 437 | + dplane = kzalloc(sizeof(*dplane), GFP_KERNEL); | |
| 438 | + if (!dplane) | |
| 439 | + return -ENOMEM; | |
| 440 | + | |
| 441 | + spin_lock_init(&dplane->lock); | |
| 442 | + init_waitqueue_head(&dplane->vbl.wait); | |
| 443 | + armada_drm_vbl_event_init(&dplane->vbl.update, armada_plane_vbl, | |
| 444 | + dplane); | |
| 445 | + | |
| 446 | + drm_plane_init(dev, &dplane->base, crtcs, &armada_plane_funcs, | |
| 447 | + armada_formats, ARRAY_SIZE(armada_formats), false); | |
| 448 | + | |
| 449 | + dplane->prop.colorkey_yr = 0xfefefe00; | |
| 450 | + dplane->prop.colorkey_ug = 0x01010100; | |
| 451 | + dplane->prop.colorkey_vb = 0x01010100; | |
| 452 | + dplane->prop.colorkey_mode = CFG_CKMODE(CKMODE_RGB); | |
| 453 | + dplane->prop.brightness = 0; | |
| 454 | + dplane->prop.contrast = 0x4000; | |
| 455 | + dplane->prop.saturation = 0x4000; | |
| 456 | + | |
| 457 | + mobj = &dplane->base.base; | |
| 458 | + drm_object_attach_property(mobj, priv->colorkey_prop, | |
| 459 | + 0x0101fe); | |
| 460 | + drm_object_attach_property(mobj, priv->colorkey_min_prop, | |
| 461 | + 0x0101fe); | |
| 462 | + drm_object_attach_property(mobj, priv->colorkey_max_prop, | |
| 463 | + 0x0101fe); | |
| 464 | + drm_object_attach_property(mobj, priv->colorkey_val_prop, | |
| 465 | + 0x0101fe); | |
| 466 | + drm_object_attach_property(mobj, priv->colorkey_alpha_prop, | |
| 467 | + 0x000000); | |
| 468 | + drm_object_attach_property(mobj, priv->colorkey_mode_prop, | |
| 469 | + CKMODE_RGB); | |
| 470 | + drm_object_attach_property(mobj, priv->brightness_prop, 256); | |
| 471 | + drm_object_attach_property(mobj, priv->contrast_prop, | |
| 472 | + dplane->prop.contrast); | |
| 473 | + drm_object_attach_property(mobj, priv->saturation_prop, | |
| 474 | + dplane->prop.saturation); | |
| 475 | + | |
| 476 | + return 0; | |
| 477 | +} | |
| @@ -0,0 +1,139 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * Rewritten from the dovefb driver, and Armada510 manuals. | |
| 4 | + * | |
| 5 | + * This program is free software; you can redistribute it and/or modify | |
| 6 | + * it under the terms of the GNU General Public License version 2 as | |
| 7 | + * published by the Free Software Foundation. | |
| 8 | + */ | |
| 9 | +#include <drm/drmP.h> | |
| 10 | +#include <drm/drm_crtc_helper.h> | |
| 11 | +#include <drm/drm_edid.h> | |
| 12 | +#include <drm/drm_encoder_slave.h> | |
| 13 | +#include "armada_drm.h" | |
| 14 | +#include "armada_output.h" | |
| 15 | +#include "armada_slave.h" | |
| 16 | + | |
| 17 | +static int armada_drm_slave_get_modes(struct drm_connector *conn) | |
| 18 | +{ | |
| 19 | + struct drm_encoder *enc = armada_drm_connector_encoder(conn); | |
| 20 | + int count = 0; | |
| 21 | + | |
| 22 | + if (enc) { | |
| 23 | + struct drm_encoder_slave *slave = to_encoder_slave(enc); | |
| 24 | + | |
| 25 | + count = slave->slave_funcs->get_modes(enc, conn); | |
| 26 | + } | |
| 27 | + | |
| 28 | + return count; | |
| 29 | +} | |
| 30 | + | |
| 31 | +static void armada_drm_slave_destroy(struct drm_encoder *enc) | |
| 32 | +{ | |
| 33 | + struct drm_encoder_slave *slave = to_encoder_slave(enc); | |
| 34 | + struct i2c_client *client = drm_i2c_encoder_get_client(enc); | |
| 35 | + | |
| 36 | + if (slave->slave_funcs) | |
| 37 | + slave->slave_funcs->destroy(enc); | |
| 38 | + if (client) | |
| 39 | + i2c_put_adapter(client->adapter); | |
| 40 | + | |
| 41 | + drm_encoder_cleanup(&slave->base); | |
| 42 | + kfree(slave); | |
| 43 | +} | |
| 44 | + | |
| 45 | +static const struct drm_encoder_funcs armada_drm_slave_encoder_funcs = { | |
| 46 | + .destroy = armada_drm_slave_destroy, | |
| 47 | +}; | |
| 48 | + | |
| 49 | +static const struct drm_connector_helper_funcs armada_drm_slave_helper_funcs = { | |
| 50 | + .get_modes = armada_drm_slave_get_modes, | |
| 51 | + .mode_valid = armada_drm_slave_encoder_mode_valid, | |
| 52 | + .best_encoder = armada_drm_connector_encoder, | |
| 53 | +}; | |
| 54 | + | |
| 55 | +static const struct drm_encoder_helper_funcs drm_slave_encoder_helpers = { | |
| 56 | + .dpms = drm_i2c_encoder_dpms, | |
| 57 | + .save = drm_i2c_encoder_save, | |
| 58 | + .restore = drm_i2c_encoder_restore, | |
| 59 | + .mode_fixup = drm_i2c_encoder_mode_fixup, | |
| 60 | + .prepare = drm_i2c_encoder_prepare, | |
| 61 | + .commit = drm_i2c_encoder_commit, | |
| 62 | + .mode_set = drm_i2c_encoder_mode_set, | |
| 63 | + .detect = drm_i2c_encoder_detect, | |
| 64 | +}; | |
| 65 | + | |
| 66 | +static int | |
| 67 | +armada_drm_conn_slave_create(struct drm_connector *conn, const void *data) | |
| 68 | +{ | |
| 69 | + const struct armada_drm_slave_config *config = data; | |
| 70 | + struct drm_encoder_slave *slave; | |
| 71 | + struct i2c_adapter *adap; | |
| 72 | + int ret; | |
| 73 | + | |
| 74 | + conn->interlace_allowed = config->interlace_allowed; | |
| 75 | + conn->doublescan_allowed = config->doublescan_allowed; | |
| 76 | + conn->polled = config->polled; | |
| 77 | + | |
| 78 | + drm_connector_helper_add(conn, &armada_drm_slave_helper_funcs); | |
| 79 | + | |
| 80 | + slave = kzalloc(sizeof(*slave), GFP_KERNEL); | |
| 81 | + if (!slave) | |
| 82 | + return -ENOMEM; | |
| 83 | + | |
| 84 | + slave->base.possible_crtcs = config->crtcs; | |
| 85 | + | |
| 86 | + adap = i2c_get_adapter(config->i2c_adapter_id); | |
| 87 | + if (!adap) { | |
| 88 | + kfree(slave); | |
| 89 | + return -EPROBE_DEFER; | |
| 90 | + } | |
| 91 | + | |
| 92 | + ret = drm_encoder_init(conn->dev, &slave->base, | |
| 93 | + &armada_drm_slave_encoder_funcs, | |
| 94 | + DRM_MODE_ENCODER_TMDS); | |
| 95 | + if (ret) { | |
| 96 | + DRM_ERROR("unable to init encoder\n"); | |
| 97 | + i2c_put_adapter(adap); | |
| 98 | + kfree(slave); | |
| 99 | + return ret; | |
| 100 | + } | |
| 101 | + | |
| 102 | + ret = drm_i2c_encoder_init(conn->dev, slave, adap, &config->info); | |
| 103 | + i2c_put_adapter(adap); | |
| 104 | + if (ret) { | |
| 105 | + DRM_ERROR("unable to init encoder slave\n"); | |
| 106 | + armada_drm_slave_destroy(&slave->base); | |
| 107 | + return ret; | |
| 108 | + } | |
| 109 | + | |
| 110 | + drm_encoder_helper_add(&slave->base, &drm_slave_encoder_helpers); | |
| 111 | + | |
| 112 | + ret = slave->slave_funcs->create_resources(&slave->base, conn); | |
| 113 | + if (ret) { | |
| 114 | + armada_drm_slave_destroy(&slave->base); | |
| 115 | + return ret; | |
| 116 | + } | |
| 117 | + | |
| 118 | + ret = drm_mode_connector_attach_encoder(conn, &slave->base); | |
| 119 | + if (ret) { | |
| 120 | + armada_drm_slave_destroy(&slave->base); | |
| 121 | + return ret; | |
| 122 | + } | |
| 123 | + | |
| 124 | + conn->encoder = &slave->base; | |
| 125 | + | |
| 126 | + return ret; | |
| 127 | +} | |
| 128 | + | |
| 129 | +static const struct armada_output_type armada_drm_conn_slave = { | |
| 130 | + .connector_type = DRM_MODE_CONNECTOR_HDMIA, | |
| 131 | + .create = armada_drm_conn_slave_create, | |
| 132 | + .set_property = armada_drm_slave_encoder_set_property, | |
| 133 | +}; | |
| 134 | + | |
| 135 | +int armada_drm_connector_slave_create(struct drm_device *dev, | |
| 136 | + const struct armada_drm_slave_config *config) | |
| 137 | +{ | |
| 138 | + return armada_output_create(dev, &armada_drm_conn_slave, config); | |
| 139 | +} | |
| @@ -0,0 +1,26 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * | |
| 4 | + * This program is free software; you can redistribute it and/or modify | |
| 5 | + * it under the terms of the GNU General Public License version 2 as | |
| 6 | + * published by the Free Software Foundation. | |
| 7 | + */ | |
| 8 | +#ifndef ARMADA_SLAVE_H | |
| 9 | +#define ARMADA_SLAVE_H | |
| 10 | + | |
| 11 | +#include <linux/i2c.h> | |
| 12 | +#include <drm/drmP.h> | |
| 13 | + | |
| 14 | +struct armada_drm_slave_config { | |
| 15 | + int i2c_adapter_id; | |
| 16 | + uint32_t crtcs; | |
| 17 | + uint8_t polled; | |
| 18 | + bool interlace_allowed; | |
| 19 | + bool doublescan_allowed; | |
| 20 | + struct i2c_board_info info; | |
| 21 | +}; | |
| 22 | + | |
| 23 | +int armada_drm_connector_slave_create(struct drm_device *dev, | |
| 24 | + const struct armada_drm_slave_config *); | |
| 25 | + | |
| 26 | +#endif | |
| @@ -1135,4 +1135,21 @@ extern int drm_format_horz_chroma_subsampling(uint32_t format); · 1134 unmodified lines | ||
| 1135 | 1135 | extern int drm_format_vert_chroma_subsampling(uint32_t format); |
| 1136 | 1136 | extern const char *drm_get_format_name(uint32_t format); |
| 1137 | 1137 | |
| 1138 | +/* Helpers */ | |
| 1139 | +static inline struct drm_crtc *drm_crtc_find(struct drm_device *dev, | |
| 1140 | + uint32_t id) | |
| 1141 | +{ | |
| 1142 | + struct drm_mode_object *mo; | |
| 1143 | + mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_CRTC); | |
| 1144 | + return mo ? obj_to_crtc(mo) : NULL; | |
| 1145 | +} | |
| 1146 | + | |
| 1147 | +static inline struct drm_encoder *drm_encoder_find(struct drm_device *dev, | |
| 1148 | + uint32_t id) | |
| 1149 | +{ | |
| 1150 | + struct drm_mode_object *mo; | |
| 1151 | + mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER); | |
| 1152 | + return mo ? obj_to_encoder(mo) : NULL; | |
| 1153 | +} | |
| 1154 | + | |
| 1138 | 1155 | #endif /* __DRM_CRTC_H__ */ |
| @@ -0,0 +1,45 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (C) 2012 Russell King | |
| 3 | + * With inspiration from the i915 driver | |
| 4 | + * | |
| 5 | + * This program is free software; you can redistribute it and/or modify | |
| 6 | + * it under the terms of the GNU General Public License version 2 as | |
| 7 | + * published by the Free Software Foundation. | |
| 8 | + */ | |
| 9 | +#ifndef DRM_ARMADA_IOCTL_H | |
| 10 | +#define DRM_ARMADA_IOCTL_H | |
| 11 | + | |
| 12 | +#define DRM_ARMADA_GEM_CREATE 0x00 | |
| 13 | +#define DRM_ARMADA_GEM_MMAP 0x02 | |
| 14 | +#define DRM_ARMADA_GEM_PWRITE 0x03 | |
| 15 | + | |
| 16 | +#define ARMADA_IOCTL(dir, name, str) \ | |
| 17 | + DRM_##dir(DRM_COMMAND_BASE + DRM_ARMADA_##name, struct drm_armada_##str) | |
| 18 | + | |
| 19 | +struct drm_armada_gem_create { | |
| 20 | + uint32_t handle; | |
| 21 | + uint32_t size; | |
| 22 | +}; | |
| 23 | +#define DRM_IOCTL_ARMADA_GEM_CREATE \ | |
| 24 | + ARMADA_IOCTL(IOWR, GEM_CREATE, gem_create) | |
| 25 | + | |
| 26 | +struct drm_armada_gem_mmap { | |
| 27 | + uint32_t handle; | |
| 28 | + uint32_t pad; | |
| 29 | + uint64_t offset; | |
| 30 | + uint64_t size; | |
| 31 | + uint64_t addr; | |
| 32 | +}; | |
| 33 | +#define DRM_IOCTL_ARMADA_GEM_MMAP \ | |
| 34 | + ARMADA_IOCTL(IOWR, GEM_MMAP, gem_mmap) | |
| 35 | + | |
| 36 | +struct drm_armada_gem_pwrite { | |
| 37 | + uint64_t ptr; | |
| 38 | + uint32_t handle; | |
| 39 | + uint32_t offset; | |
| 40 | + uint32_t size; | |
| 41 | +}; | |
| 42 | +#define DRM_IOCTL_ARMADA_GEM_PWRITE \ | |
| 43 | + ARMADA_IOCTL(IOW, GEM_PWRITE, gem_pwrite) | |
| 44 | + | |
| 45 | +#endif | |