1 /* 2 * Copyright (C) 2015 Broadcom 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 9 /** 10 * DOC: VC4 HVS module. 11 * 12 * The Hardware Video Scaler (HVS) is the piece of hardware that does 13 * translation, scaling, colorspace conversion, and compositing of 14 * pixels stored in framebuffers into a FIFO of pixels going out to 15 * the Pixel Valve (CRTC). It operates at the system clock rate (the 16 * system audio clock gate, specifically), which is much higher than 17 * the pixel clock rate. 18 * 19 * There is a single global HVS, with multiple output FIFOs that can 20 * be consumed by the PVs. This file just manages the resources for 21 * the HVS, while the vc4_crtc.c code actually drives HVS setup for 22 * each CRTC. 23 */ 24 25 #include <linux/component.h> 26 #include "vc4_drv.h" 27 #include "vc4_regs.h" 28 29 #define HVS_REG(reg) { reg, #reg } 30 static const struct { 31 u32 reg; 32 const char *name; 33 } hvs_regs[] = { 34 HVS_REG(SCALER_DISPCTRL), 35 HVS_REG(SCALER_DISPSTAT), 36 HVS_REG(SCALER_DISPID), 37 HVS_REG(SCALER_DISPECTRL), 38 HVS_REG(SCALER_DISPPROF), 39 HVS_REG(SCALER_DISPDITHER), 40 HVS_REG(SCALER_DISPEOLN), 41 HVS_REG(SCALER_DISPLIST0), 42 HVS_REG(SCALER_DISPLIST1), 43 HVS_REG(SCALER_DISPLIST2), 44 HVS_REG(SCALER_DISPLSTAT), 45 HVS_REG(SCALER_DISPLACT0), 46 HVS_REG(SCALER_DISPLACT1), 47 HVS_REG(SCALER_DISPLACT2), 48 HVS_REG(SCALER_DISPCTRL0), 49 HVS_REG(SCALER_DISPBKGND0), 50 HVS_REG(SCALER_DISPSTAT0), 51 HVS_REG(SCALER_DISPBASE0), 52 HVS_REG(SCALER_DISPCTRL1), 53 HVS_REG(SCALER_DISPBKGND1), 54 HVS_REG(SCALER_DISPSTAT1), 55 HVS_REG(SCALER_DISPBASE1), 56 HVS_REG(SCALER_DISPCTRL2), 57 HVS_REG(SCALER_DISPBKGND2), 58 HVS_REG(SCALER_DISPSTAT2), 59 HVS_REG(SCALER_DISPBASE2), 60 HVS_REG(SCALER_DISPALPHA2), 61 }; 62 63 void vc4_hvs_dump_state(struct drm_device *dev) 64 { 65 struct vc4_dev *vc4 = to_vc4_dev(dev); 66 int i; 67 68 for (i = 0; i < ARRAY_SIZE(hvs_regs); i++) { 69 DRM_INFO("0x%04x (%s): 0x%08x\n", 70 hvs_regs[i].reg, hvs_regs[i].name, 71 HVS_READ(hvs_regs[i].reg)); 72 } 73 74 DRM_INFO("HVS ctx:\n"); 75 for (i = 0; i < 64; i += 4) { 76 DRM_INFO("0x%08x (%s): 0x%08x 0x%08x 0x%08x 0x%08x\n", 77 i * 4, i < HVS_BOOTLOADER_DLIST_END ? "B" : "D", 78 readl((u32 __iomem *)vc4->hvs->dlist + i + 0), 79 readl((u32 __iomem *)vc4->hvs->dlist + i + 1), 80 readl((u32 __iomem *)vc4->hvs->dlist + i + 2), 81 readl((u32 __iomem *)vc4->hvs->dlist + i + 3)); 82 } 83 } 84 85 #ifdef CONFIG_DEBUG_FS 86 int vc4_hvs_debugfs_regs(struct seq_file *m, void *unused) 87 { 88 struct drm_info_node *node = (struct drm_info_node *)m->private; 89 struct drm_device *dev = node->minor->dev; 90 struct vc4_dev *vc4 = to_vc4_dev(dev); 91 int i; 92 93 for (i = 0; i < ARRAY_SIZE(hvs_regs); i++) { 94 seq_printf(m, "%s (0x%04x): 0x%08x\n", 95 hvs_regs[i].name, hvs_regs[i].reg, 96 HVS_READ(hvs_regs[i].reg)); 97 } 98 99 return 0; 100 } 101 #endif 102 103 /* The filter kernel is composed of dwords each containing 3 9-bit 104 * signed integers packed next to each other. 105 */ 106 #define VC4_INT_TO_COEFF(coeff) (coeff & 0x1ff) 107 #define VC4_PPF_FILTER_WORD(c0, c1, c2) \ 108 ((((c0) & 0x1ff) << 0) | \ 109 (((c1) & 0x1ff) << 9) | \ 110 (((c2) & 0x1ff) << 18)) 111 112 /* The whole filter kernel is arranged as the coefficients 0-16 going 113 * up, then a pad, then 17-31 going down and reversed within the 114 * dwords. This means that a linear phase kernel (where it's 115 * symmetrical at the boundary between 15 and 16) has the last 5 116 * dwords matching the first 5, but reversed. 117 */ 118 #define VC4_LINEAR_PHASE_KERNEL(c0, c1, c2, c3, c4, c5, c6, c7, c8, \ 119 c9, c10, c11, c12, c13, c14, c15) \ 120 {VC4_PPF_FILTER_WORD(c0, c1, c2), \ 121 VC4_PPF_FILTER_WORD(c3, c4, c5), \ 122 VC4_PPF_FILTER_WORD(c6, c7, c8), \ 123 VC4_PPF_FILTER_WORD(c9, c10, c11), \ 124 VC4_PPF_FILTER_WORD(c12, c13, c14), \ 125 VC4_PPF_FILTER_WORD(c15, c15, 0)} 126 127 #define VC4_LINEAR_PHASE_KERNEL_DWORDS 6 128 #define VC4_KERNEL_DWORDS (VC4_LINEAR_PHASE_KERNEL_DWORDS * 2 - 1) 129 130 /* Recommended B=1/3, C=1/3 filter choice from Mitchell/Netravali. 131 * http://www.cs.utexas.edu/~fussell/courses/cs384g/lectures/mitchell/Mitchell.pdf 132 */ 133 static const u32 mitchell_netravali_1_3_1_3_kernel[] = 134 VC4_LINEAR_PHASE_KERNEL(0, -2, -6, -8, -10, -8, -3, 2, 18, 135 50, 82, 119, 155, 187, 213, 227); 136 137 static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, 138 struct drm_mm_node *space, 139 const u32 *kernel) 140 { 141 int ret, i; 142 u32 __iomem *dst_kernel; 143 144 ret = drm_mm_insert_node(&hvs->dlist_mm, space, VC4_KERNEL_DWORDS); 145 if (ret) { 146 DRM_ERROR("Failed to allocate space for filter kernel: %d\n", 147 ret); 148 return ret; 149 } 150 151 dst_kernel = hvs->dlist + space->start; 152 153 for (i = 0; i < VC4_KERNEL_DWORDS; i++) { 154 if (i < VC4_LINEAR_PHASE_KERNEL_DWORDS) 155 writel(kernel[i], &dst_kernel[i]); 156 else { 157 writel(kernel[VC4_KERNEL_DWORDS - i - 1], 158 &dst_kernel[i]); 159 } 160 } 161 162 return 0; 163 } 164 165 static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) 166 { 167 struct platform_device *pdev = to_platform_device(dev); 168 struct drm_device *drm = dev_get_drvdata(master); 169 struct vc4_dev *vc4 = drm->dev_private; 170 struct vc4_hvs *hvs = NULL; 171 int ret; 172 u32 dispctrl; 173 174 hvs = devm_kzalloc(&pdev->dev, sizeof(*hvs), GFP_KERNEL); 175 if (!hvs) 176 return -ENOMEM; 177 178 hvs->pdev = pdev; 179 180 hvs->regs = vc4_ioremap_regs(pdev, 0); 181 if (IS_ERR(hvs->regs)) 182 return PTR_ERR(hvs->regs); 183 184 hvs->dlist = hvs->regs + SCALER_DLIST_START; 185 186 spin_lock_init(&hvs->mm_lock); 187 188 /* Set up the HVS display list memory manager. We never 189 * overwrite the setup from the bootloader (just 128b out of 190 * our 16K), since we don't want to scramble the screen when 191 * transitioning from the firmware's boot setup to runtime. 192 */ 193 drm_mm_init(&hvs->dlist_mm, 194 HVS_BOOTLOADER_DLIST_END, 195 (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END); 196 197 /* Set up the HVS LBM memory manager. We could have some more 198 * complicated data structure that allowed reuse of LBM areas 199 * between planes when they don't overlap on the screen, but 200 * for now we just allocate globally. 201 */ 202 drm_mm_init(&hvs->lbm_mm, 0, 96 * 1024); 203 204 /* Upload filter kernels. We only have the one for now, so we 205 * keep it around for the lifetime of the driver. 206 */ 207 ret = vc4_hvs_upload_linear_kernel(hvs, 208 &hvs->mitchell_netravali_filter, 209 mitchell_netravali_1_3_1_3_kernel); 210 if (ret) 211 return ret; 212 213 vc4->hvs = hvs; 214 215 dispctrl = HVS_READ(SCALER_DISPCTRL); 216 217 dispctrl |= SCALER_DISPCTRL_ENABLE; 218 219 /* Set DSP3 (PV1) to use HVS channel 2, which would otherwise 220 * be unused. 221 */ 222 dispctrl &= ~SCALER_DISPCTRL_DSP3_MUX_MASK; 223 dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_DSP3_MUX); 224 225 HVS_WRITE(SCALER_DISPCTRL, dispctrl); 226 227 return 0; 228 } 229 230 static void vc4_hvs_unbind(struct device *dev, struct device *master, 231 void *data) 232 { 233 struct drm_device *drm = dev_get_drvdata(master); 234 struct vc4_dev *vc4 = drm->dev_private; 235 236 if (vc4->hvs->mitchell_netravali_filter.allocated) 237 drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter); 238 239 drm_mm_takedown(&vc4->hvs->dlist_mm); 240 drm_mm_takedown(&vc4->hvs->lbm_mm); 241 242 vc4->hvs = NULL; 243 } 244 245 static const struct component_ops vc4_hvs_ops = { 246 .bind = vc4_hvs_bind, 247 .unbind = vc4_hvs_unbind, 248 }; 249 250 static int vc4_hvs_dev_probe(struct platform_device *pdev) 251 { 252 return component_add(&pdev->dev, &vc4_hvs_ops); 253 } 254 255 static int vc4_hvs_dev_remove(struct platform_device *pdev) 256 { 257 component_del(&pdev->dev, &vc4_hvs_ops); 258 return 0; 259 } 260 261 static const struct of_device_id vc4_hvs_dt_match[] = { 262 { .compatible = "brcm,bcm2835-hvs" }, 263 {} 264 }; 265 266 struct platform_driver vc4_hvs_driver = { 267 .probe = vc4_hvs_dev_probe, 268 .remove = vc4_hvs_dev_remove, 269 .driver = { 270 .name = "vc4_hvs", 271 .of_match_table = vc4_hvs_dt_match, 272 }, 273 }; 274