mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 01:49:46 +00:00
drm: Add RP1 VEC driver
Add support for the RP1 VEC hardware. Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com> drm: rp1: rp1-vec: Allow non-standard modes with various crops Tweak sync timings in the advertised modelines. Accept other, custom modes, provided they fit within the active area of one of the existing hardware-supported TV modes. Instead of always padding symmetrically, try to respect the user's [hv]sync_start values, allowing the image to be shifted around the screen (to fine-tune overscan correction). Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com> drm/rp1: depends on, instead of select, MFD_RP1 According to kconfig-language.txt [1], select should be used only for "non-visible symbols ... and for symbols with no dependencies". Since MFD_RP1 both is visible and has a dependency, "select" should not be used and "depends on" should be used instead. In particular, this fixes the build of this kernel tree on NixOS, where its kernel config system will try to answer 'M' to as many config as possible. [1] https://www.kernel.org/doc/html/latest/kbuild/kconfig-language.html Signed-off-by: Ratchanan Srirattanamet <peathot@hotmail.com> drm: rp1: Use tv_mode from the command line and fix for Linux 6.6 Use the standard enum drm_connector_tv_mode instead of a private enum and switch from the legacy to the standard tv_mode property. Remove the module parameter "tv_norm". Instead, get tv_mode from the command line and make this the connector's default TV mode. Don't restrict the choice of modes based on tv_mode, but interpret nonstandard combinations as NTSC or PAL, depending on resolution. Thus the default tv_mode=NTSC effectively means "Auto". Tweak the advertised horizontal timings for 625/50 to match Rec.601 Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com> drm: rp1: VEC and DPI drivers: Fix bug #5901 Rework probe() to use devm_drm_dev_alloc(), embedding the DRM device in the DPI or VEC device as now seems to be recommended. Change order of resource allocation and driver initialization. This prevents it trying to write to an unmapped register during clean-up, which previously could crash. Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com> drm: rp1: vec: Support more video modes in the RP1 VEC driver Support a wider range of pixel clock rates. The driver will round pixclock up to 108MHz/n but tries to honour the desired image width and position (of the centre of the display relative to HSYNC_STARTs). This adds complexity but removes the need for separate 13.5MHz and 15.428MHz modes. Support "fake" double-rate progressive modes (in which only every 2nd scanline is displayed). To work around aspect ratio issues. Add Monochrome TV mode support. Add "vintage" modes (544x380i for System A; 848x738i for System E) when configured for Monochrome. Add a way to create a "custom" display mode from a module parameter. Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com> drm: rp1: rp1-vec: Add DRM_FORMAT_ARGB8888 and DRM_FORMAT_ABGR8888 Android requires this. As the underlying hardware doesn't support alpha blending, we ignore the alpha value. Signed-off-by: Jan Kehren <jan.kehren@emteria.com> drivers: drm: rp1-vec: Increase width limit, for PAL 16:9 @ 18MHz There was no technical reason for the DRM mode's width limit of 848; increase it to 960 (720*18MHz/13.5MHz) to support ~square pixels on 16:9 screens. Tweak the PAL active window to start slightly earlier. (The maximum number of visible columns at 18MHz is about 942.) Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com> drm/vc4: Make VEC progressive modes readily accessible Add predefined modelines for the 240p (NTSC) and 288p (PAL) progressive modes, and report them through vc4_vec_connector_get_modes(). Signed-off-by: Mateusz Kwiatkowski <kfyatek+publicgit@gmail.com> drm/rp1-vec: Run DRM default client setup Call drm_client_setup() to run the kernel's default client setup for DRM. Set fbdev_probe in struct drm_driver, so that the client setup can start the common fbdev client. Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com> drm: rp1: Enable VEC->GPIO output; cosmetic change to registers In the VEC driver, enable mapping VEC (not DPI) to DPI GPIOs. This is to support VEC output over GPIO on Raspberry Pi CM5. It is harmless as DPI and VEC could not be used concurrently, and the output is anyway conditional on pinctrl. Also, tweak the style of VIDEO_OUT_CFG register definitions (in both DPI and VEC drivers) to be more Linux-friendly. Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com> drm: rp1: rp1-vec: Support 60fps in interlaced modes; other tweaks To work around the 30fps buffer-flip rate limit when using VEC's "native" interlaced modes, switch to sending individual fields to the VEC BE, using an ISR to flip between fields. When the TV mode is NTSC, change advertised progressive modes to have 263 total lines; this ameliorates colour artifacts, although it reduces the frame rate slightly from 60.05Hz to 59.83Hz. Progressive modes with 262 lines remain supported. Fix an error in equalising pulse configuration for PAL-M/PAL60. Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com> Fixup rp1-vec Kconfig
This commit is contained in:
committed by
Dom Cobley
parent
138f50bc53
commit
2d56bebc24
9
drivers/gpu/drm/rp1/rp1-vec/Kconfig
Normal file
9
drivers/gpu/drm/rp1/rp1-vec/Kconfig
Normal file
@@ -0,0 +1,9 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config DRM_RP1_VEC
|
||||
tristate "DRM Support for RP1 VEC"
|
||||
depends on DRM && MFD_RP1
|
||||
select DRM_CLIENT_SELECTION
|
||||
select DRM_GEM_DMA_HELPER
|
||||
select DRM_KMS_HELPER
|
||||
help
|
||||
Choose this option to enable Video Out on RP1
|
||||
5
drivers/gpu/drm/rp1/rp1-vec/Makefile
Normal file
5
drivers/gpu/drm/rp1/rp1-vec/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
drm-rp1-vec-y := rp1_vec.o rp1_vec_hw.o rp1_vec_cfg.o
|
||||
|
||||
obj-$(CONFIG_DRM_RP1_VEC) += drm-rp1-vec.o
|
||||
606
drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c
Normal file
606
drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c
Normal file
@@ -0,0 +1,606 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* DRM Driver for VEC output on Raspberry Pi RP1
|
||||
*
|
||||
* Copyright (c) 2023 Raspberry Pi Limited.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/cred.h>
|
||||
#include <drm/clients/drm_client_setup.h>
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_mm.h>
|
||||
#include <drm/drm_fourcc.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_managed.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_encoder.h>
|
||||
#include <drm/drm_fb_helper.h>
|
||||
#include <drm/drm_fbdev_dma.h>
|
||||
#include <drm/drm_framebuffer.h>
|
||||
#include <drm/drm_gem.h>
|
||||
#include <drm/drm_gem_atomic_helper.h>
|
||||
#include <drm/drm_gem_dma_helper.h>
|
||||
#include <drm/drm_gem_framebuffer_helper.h>
|
||||
#include <drm/drm_simple_kms_helper.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drm_modeset_helper_vtables.h>
|
||||
#include <drm/drm_vblank.h>
|
||||
#include <drm/drm_of.h>
|
||||
|
||||
#include "rp1_vec.h"
|
||||
|
||||
/*
|
||||
* Linux doesn't make it easy to create custom video modes for the console
|
||||
* with non-CVT timings; so add a module parameter for it. The format is:
|
||||
* "<pclk>,<hact>,<hfp>,<hsync>,<hbp>,<vact>,<vfp>,<vsync>,<vbp>[,i]"
|
||||
* (where each comma may be replaced by any sequence of punctuation).
|
||||
* pclk should be 108000/n for 5 <= n <= 16 (twice this for "fake" modes).
|
||||
*/
|
||||
|
||||
static char *rp1vec_cmode_str;
|
||||
module_param_named(cmode, rp1vec_cmode_str, charp, 0600);
|
||||
MODULE_PARM_DESC(cmode, "Custom video mode:\n"
|
||||
"\t\t<pclk>,<hact>,<hfp>,<hsync>,<hbp>,<vact>,<vfp>,<vsync>,<vbp>[,i]\n");
|
||||
|
||||
static struct drm_display_mode *rp1vec_parse_custom_mode(struct drm_device *dev)
|
||||
{
|
||||
char const *p = rp1vec_cmode_str;
|
||||
struct drm_display_mode *mode;
|
||||
unsigned int n, vals[9];
|
||||
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
for (n = 0; n < 9; n++) {
|
||||
unsigned int v = 0;
|
||||
|
||||
if (!isdigit(*p))
|
||||
return NULL;
|
||||
do {
|
||||
v = 10u * v + (*p - '0');
|
||||
} while (isdigit(*++p));
|
||||
|
||||
vals[n] = v;
|
||||
while (ispunct(*p))
|
||||
p++;
|
||||
}
|
||||
|
||||
mode = drm_mode_create(dev);
|
||||
if (!mode)
|
||||
return NULL;
|
||||
|
||||
mode->clock = vals[0];
|
||||
mode->hdisplay = vals[1];
|
||||
mode->hsync_start = mode->hdisplay + vals[2];
|
||||
mode->hsync_end = mode->hsync_start + vals[3];
|
||||
mode->htotal = mode->hsync_end + vals[4];
|
||||
mode->vdisplay = vals[5];
|
||||
mode->vsync_start = mode->vdisplay + vals[6];
|
||||
mode->vsync_end = mode->vsync_start + vals[7];
|
||||
mode->vtotal = mode->vsync_end + vals[8];
|
||||
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
|
||||
mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
|
||||
if (strchr(p, 'i'))
|
||||
mode->flags |= DRM_MODE_FLAG_INTERLACE;
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static void rp1vec_pipe_update(struct drm_simple_display_pipe *pipe,
|
||||
struct drm_plane_state *old_state)
|
||||
{
|
||||
struct drm_pending_vblank_event *event;
|
||||
unsigned long flags;
|
||||
struct drm_framebuffer *fb = pipe->plane.state->fb;
|
||||
struct rp1_vec *vec = pipe->crtc.dev->dev_private;
|
||||
struct drm_gem_object *gem = fb ? drm_gem_fb_get_obj(fb, 0) : NULL;
|
||||
struct drm_gem_dma_object *dma_obj = gem ? to_drm_gem_dma_obj(gem) : NULL;
|
||||
bool can_update = fb && dma_obj && vec && vec->pipe_enabled;
|
||||
|
||||
/* (Re-)start VEC where required; and update FB address */
|
||||
if (can_update) {
|
||||
if (!vec->vec_running || fb->format->format != vec->cur_fmt) {
|
||||
if (vec->vec_running && fb->format->format != vec->cur_fmt) {
|
||||
rp1vec_hw_stop(vec);
|
||||
vec->vec_running = false;
|
||||
}
|
||||
if (!vec->vec_running) {
|
||||
rp1vec_hw_setup(vec,
|
||||
fb->format->format,
|
||||
&pipe->crtc.state->mode,
|
||||
vec->connector.state->tv.mode);
|
||||
vec->vec_running = true;
|
||||
}
|
||||
vec->cur_fmt = fb->format->format;
|
||||
drm_crtc_vblank_on(&pipe->crtc);
|
||||
}
|
||||
rp1vec_hw_update(vec, dma_obj->dma_addr, fb->offsets[0], fb->pitches[0]);
|
||||
}
|
||||
|
||||
/* Check if VBLANK callback needs to be armed (or sent immediately in some error cases).
|
||||
* Note there is a tiny probability of a race between rp1vec_dma_update() and IRQ;
|
||||
* ordering it this way around is safe, but theoretically might delay an extra frame.
|
||||
*/
|
||||
spin_lock_irqsave(&pipe->crtc.dev->event_lock, flags);
|
||||
event = pipe->crtc.state->event;
|
||||
if (event) {
|
||||
pipe->crtc.state->event = NULL;
|
||||
if (can_update && drm_crtc_vblank_get(&pipe->crtc) == 0)
|
||||
drm_crtc_arm_vblank_event(&pipe->crtc, event);
|
||||
else
|
||||
drm_crtc_send_vblank_event(&pipe->crtc, event);
|
||||
}
|
||||
spin_unlock_irqrestore(&pipe->crtc.dev->event_lock, flags);
|
||||
}
|
||||
|
||||
static void rp1vec_pipe_enable(struct drm_simple_display_pipe *pipe,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_plane_state *plane_state)
|
||||
{
|
||||
struct rp1_vec *vec = pipe->crtc.dev->dev_private;
|
||||
|
||||
dev_info(&vec->pdev->dev, __func__);
|
||||
vec->pipe_enabled = true;
|
||||
vec->cur_fmt = 0xdeadbeef;
|
||||
rp1vec_vidout_setup(vec);
|
||||
rp1vec_pipe_update(pipe, 0);
|
||||
}
|
||||
|
||||
static void rp1vec_pipe_disable(struct drm_simple_display_pipe *pipe)
|
||||
{
|
||||
struct rp1_vec *vec = pipe->crtc.dev->dev_private;
|
||||
|
||||
dev_info(&vec->pdev->dev, __func__);
|
||||
drm_crtc_vblank_off(&pipe->crtc);
|
||||
if (vec) {
|
||||
if (vec->vec_running) {
|
||||
rp1vec_hw_stop(vec);
|
||||
vec->vec_running = false;
|
||||
}
|
||||
vec->pipe_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
static int rp1vec_pipe_enable_vblank(struct drm_simple_display_pipe *pipe)
|
||||
{
|
||||
if (pipe && pipe->crtc.dev) {
|
||||
struct rp1_vec *vec = pipe->crtc.dev->dev_private;
|
||||
|
||||
if (vec)
|
||||
rp1vec_hw_vblank_ctrl(vec, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rp1vec_pipe_disable_vblank(struct drm_simple_display_pipe *pipe)
|
||||
{
|
||||
if (pipe && pipe->crtc.dev) {
|
||||
struct rp1_vec *vec = pipe->crtc.dev->dev_private;
|
||||
|
||||
if (vec)
|
||||
rp1vec_hw_vblank_ctrl(vec, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct drm_simple_display_pipe_funcs rp1vec_pipe_funcs = {
|
||||
.enable = rp1vec_pipe_enable,
|
||||
.update = rp1vec_pipe_update,
|
||||
.disable = rp1vec_pipe_disable,
|
||||
.enable_vblank = rp1vec_pipe_enable_vblank,
|
||||
.disable_vblank = rp1vec_pipe_disable_vblank,
|
||||
};
|
||||
|
||||
static void rp1vec_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the mode roughly matches something we can generate.
|
||||
* The choice of hardware TV mode depends on total lines and frame rate.
|
||||
* Within each hardware mode, allow pixel clock, image size and offsets
|
||||
* to vary, up to a maximum horizontal active period and line count.
|
||||
* Don't check sync timings here: the HW driver will sanitize them.
|
||||
*/
|
||||
|
||||
static enum drm_mode_status rp1vec_mode_valid(struct drm_device *dev,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
int prog = !(mode->flags & DRM_MODE_FLAG_INTERLACE);
|
||||
int fake_31khz = prog && mode->vtotal >= 500;
|
||||
int vtotal_2fld = mode->vtotal << (prog && !fake_31khz);
|
||||
int vdisplay_2fld = mode->vdisplay << (prog && !fake_31khz);
|
||||
int real_clock = mode->clock >> fake_31khz;
|
||||
|
||||
/* Check pixel clock is in the permitted range */
|
||||
if (real_clock < 6750)
|
||||
return MODE_CLOCK_LOW;
|
||||
else if (real_clock > 21600)
|
||||
return MODE_CLOCK_HIGH;
|
||||
|
||||
/* Try to match against the 525-line 60Hz mode (System M) */
|
||||
if (vtotal_2fld >= 524 && vtotal_2fld <= 526 && vdisplay_2fld <= 486 &&
|
||||
mode->htotal * vtotal_2fld > 32 * real_clock &&
|
||||
mode->htotal * vtotal_2fld < 34 * real_clock &&
|
||||
37 * mode->hdisplay <= 2 * real_clock) /* 54us */
|
||||
return MODE_OK;
|
||||
|
||||
/* All other supported TV Systems (625-, 405-, 819-line) are 50Hz */
|
||||
if (mode->htotal * vtotal_2fld > 39 * real_clock &&
|
||||
mode->htotal * vtotal_2fld < 41 * real_clock) {
|
||||
if (vtotal_2fld >= 624 && vtotal_2fld <= 626 && vdisplay_2fld <= 576 &&
|
||||
37 * mode->hdisplay <= 2 * real_clock) /* 54us */
|
||||
return MODE_OK;
|
||||
|
||||
if (vtotal_2fld == 405 && vdisplay_2fld <= 380 &&
|
||||
49 * mode->hdisplay <= 4 * real_clock) /* 81.6us */
|
||||
return MODE_OK;
|
||||
|
||||
if (vtotal_2fld == 819 && vdisplay_2fld <= 738 &&
|
||||
25 * mode->hdisplay <= real_clock) /* 40us */
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
return MODE_BAD;
|
||||
}
|
||||
|
||||
static const struct drm_display_mode rp1vec_modes[6] = {
|
||||
{ /* Full size 525/60i with Rec.601 pixel rate */
|
||||
DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500,
|
||||
720, 720 + 16, 720 + 16 + 64, 858, 0,
|
||||
480, 480 + 6, 480 + 6 + 6, 525, 0,
|
||||
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
|
||||
DRM_MODE_FLAG_INTERLACE)
|
||||
},
|
||||
{ /* Cropped and horizontally squashed to be TV-safe */
|
||||
DRM_MODE("704x432i", DRM_MODE_TYPE_DRIVER, 15429,
|
||||
704, 704 + 76, 704 + 76 + 72, 980, 0,
|
||||
432, 432 + 30, 432 + 30 + 6, 525, 0,
|
||||
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
|
||||
DRM_MODE_FLAG_INTERLACE)
|
||||
},
|
||||
{ /* Full size 625/50i with Rec.601 pixel rate */
|
||||
DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500,
|
||||
720, 720 + 12, 720 + 12 + 64, 864, 0,
|
||||
576, 576 + 5, 576 + 5 + 5, 625, 0,
|
||||
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
|
||||
DRM_MODE_FLAG_INTERLACE)
|
||||
},
|
||||
{ /* Cropped and squashed, for square(ish) pixels */
|
||||
DRM_MODE("704x512i", DRM_MODE_TYPE_DRIVER, 15429,
|
||||
704, 704 + 72, 704 + 72 + 72, 987, 0,
|
||||
512, 512 + 37, 512 + 37 + 5, 625, 0,
|
||||
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
|
||||
DRM_MODE_FLAG_INTERLACE)
|
||||
},
|
||||
{ /* System A (405 lines) */
|
||||
DRM_MODE("544x380i", DRM_MODE_TYPE_DRIVER, 6750,
|
||||
544, 544 + 12, 544 + 12 + 60, 667, 0,
|
||||
380, 380 + 0, 380 + 0 + 8, 405, 0,
|
||||
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
|
||||
DRM_MODE_FLAG_INTERLACE)
|
||||
},
|
||||
{ /* System E (819 lines) */
|
||||
DRM_MODE("848x738i", DRM_MODE_TYPE_DRIVER, 21600,
|
||||
848, 848 + 12, 848 + 12 + 54, 1055, 0,
|
||||
738, 738 + 6, 738 + 6 + 1, 819, 0,
|
||||
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
|
||||
DRM_MODE_FLAG_INTERLACE)
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Advertise a custom mode, if specified; then those from the table above.
|
||||
* From each interlaced mode above, derive a half-height progressive one.
|
||||
*
|
||||
* This driver always supports all 525-line and 625-line standard modes
|
||||
* regardless of connector's tv_mode; non-standard combinations generally
|
||||
* default to PAL[-BDGHIK] or NTSC[-M] (with a special case for "PAL60").
|
||||
*
|
||||
* The "vintage" standards (System A, System E) are advertised only when
|
||||
* the default tv_mode was DRM_MODE_TV_MODE_MONOCHROME, and only interlaced.
|
||||
*/
|
||||
|
||||
static int rp1vec_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
u64 tvstd;
|
||||
int i, prog, limit, n = 0, preferred_lines = 525;
|
||||
struct drm_display_mode *mode;
|
||||
|
||||
if (!drm_object_property_get_default_value(&connector->base,
|
||||
connector->dev->mode_config.tv_mode_property,
|
||||
&tvstd))
|
||||
preferred_lines = (tvstd == DRM_MODE_TV_MODE_PAL ||
|
||||
tvstd == DRM_MODE_TV_MODE_PAL_N ||
|
||||
tvstd >= DRM_MODE_TV_MODE_SECAM) ? 625 : 525;
|
||||
|
||||
mode = rp1vec_parse_custom_mode(connector->dev);
|
||||
if (mode) {
|
||||
if (rp1vec_mode_valid(connector->dev, mode) == 0) {
|
||||
drm_mode_set_name(mode);
|
||||
drm_mode_probed_add(connector, mode);
|
||||
n++;
|
||||
preferred_lines = 0;
|
||||
} else {
|
||||
drm_mode_destroy(connector->dev, mode);
|
||||
}
|
||||
}
|
||||
|
||||
limit = (tvstd < DRM_MODE_TV_MODE_MONOCHROME) ? 4 : ARRAY_SIZE(rp1vec_modes);
|
||||
for (i = 0; i < limit; i++) {
|
||||
for (prog = 0; prog < 2; prog++) {
|
||||
mode = drm_mode_duplicate(connector->dev, &rp1vec_modes[i]);
|
||||
if (!mode)
|
||||
return n;
|
||||
|
||||
if (prog) {
|
||||
mode->flags &= ~DRM_MODE_FLAG_INTERLACE;
|
||||
mode->vdisplay >>= 1;
|
||||
mode->vsync_start >>= 1;
|
||||
mode->vsync_end >>= 1;
|
||||
mode->vtotal >>= 1;
|
||||
if (mode->vtotal == 262 && tvstd < DRM_MODE_TV_MODE_PAL)
|
||||
mode->vtotal++;
|
||||
} else if (mode->hdisplay == 704 && mode->vtotal == preferred_lines) {
|
||||
mode->type |= DRM_MODE_TYPE_PREFERRED;
|
||||
}
|
||||
drm_mode_set_name(mode);
|
||||
drm_mode_probed_add(connector, mode);
|
||||
n++;
|
||||
|
||||
if (mode->vtotal == 405 || mode->vtotal == 819)
|
||||
break; /* Don't offer progressive for Systems A, E */
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static void rp1vec_connector_reset(struct drm_connector *connector)
|
||||
{
|
||||
drm_atomic_helper_connector_reset(connector);
|
||||
drm_atomic_helper_connector_tv_reset(connector);
|
||||
}
|
||||
|
||||
static int rp1vec_connector_atomic_check(struct drm_connector *conn,
|
||||
struct drm_atomic_state *state)
|
||||
{ struct drm_connector_state *old_state =
|
||||
drm_atomic_get_old_connector_state(state, conn);
|
||||
struct drm_connector_state *new_state =
|
||||
drm_atomic_get_new_connector_state(state, conn);
|
||||
|
||||
if (new_state->crtc && old_state->tv.mode != new_state->tv.mode) {
|
||||
struct drm_crtc_state *crtc_state =
|
||||
drm_atomic_get_new_crtc_state(state, new_state->crtc);
|
||||
|
||||
crtc_state->mode_changed = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs rp1vec_connector_helper_funcs = {
|
||||
.get_modes = rp1vec_connector_get_modes,
|
||||
.atomic_check = rp1vec_connector_atomic_check,
|
||||
};
|
||||
|
||||
static const struct drm_connector_funcs rp1vec_connector_funcs = {
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = rp1vec_connector_destroy,
|
||||
.reset = rp1vec_connector_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static const struct drm_mode_config_funcs rp1vec_mode_funcs = {
|
||||
.fb_create = drm_gem_fb_create,
|
||||
.atomic_check = drm_atomic_helper_check,
|
||||
.atomic_commit = drm_atomic_helper_commit,
|
||||
.mode_valid = rp1vec_mode_valid,
|
||||
};
|
||||
|
||||
static const u32 rp1vec_formats[] = {
|
||||
DRM_FORMAT_XRGB8888,
|
||||
DRM_FORMAT_XBGR8888,
|
||||
DRM_FORMAT_ARGB8888,
|
||||
DRM_FORMAT_ABGR8888,
|
||||
DRM_FORMAT_RGB888,
|
||||
DRM_FORMAT_BGR888,
|
||||
DRM_FORMAT_RGB565
|
||||
};
|
||||
|
||||
static void rp1vec_stopall(struct drm_device *drm)
|
||||
{
|
||||
if (drm->dev_private) {
|
||||
struct rp1_vec *vec = drm->dev_private;
|
||||
|
||||
if (vec->vec_running || rp1vec_hw_busy(vec)) {
|
||||
rp1vec_hw_stop(vec);
|
||||
vec->vec_running = false;
|
||||
}
|
||||
rp1vec_vidout_poweroff(vec);
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_DRM_GEM_DMA_FOPS(rp1vec_fops);
|
||||
|
||||
static struct drm_driver rp1vec_driver = {
|
||||
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
|
||||
.fops = &rp1vec_fops,
|
||||
.name = "drm-rp1-vec",
|
||||
.desc = "drm-rp1-vec",
|
||||
.major = 1,
|
||||
.minor = 0,
|
||||
DRM_GEM_DMA_DRIVER_OPS,
|
||||
DRM_FBDEV_DMA_DRIVER_OPS,
|
||||
.release = rp1vec_stopall,
|
||||
};
|
||||
|
||||
static int rp1vec_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rp1_vec *vec;
|
||||
int i, ret;
|
||||
|
||||
dev_info(dev, __func__);
|
||||
vec = devm_drm_dev_alloc(dev, &rp1vec_driver, struct rp1_vec, drm);
|
||||
if (IS_ERR(vec)) {
|
||||
ret = PTR_ERR(vec);
|
||||
dev_err(dev, "%s devm_drm_dev_alloc %d", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
vec->pdev = pdev;
|
||||
spin_lock_init(&vec->hw_lock);
|
||||
|
||||
for (i = 0; i < RP1VEC_NUM_HW_BLOCKS; i++) {
|
||||
vec->hw_base[i] =
|
||||
devm_ioremap_resource(dev,
|
||||
platform_get_resource(vec->pdev, IORESOURCE_MEM, i));
|
||||
if (IS_ERR(vec->hw_base[i])) {
|
||||
ret = PTR_ERR(vec->hw_base[i]);
|
||||
dev_err(dev, "Error memory mapping regs[%d]\n", i);
|
||||
goto done_err;
|
||||
}
|
||||
}
|
||||
ret = platform_get_irq(vec->pdev, 0);
|
||||
if (ret > 0)
|
||||
ret = devm_request_irq(dev, ret, rp1vec_hw_isr,
|
||||
IRQF_SHARED, "rp1-vec", vec);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to request interrupt\n");
|
||||
ret = -EINVAL;
|
||||
goto done_err;
|
||||
}
|
||||
|
||||
vec->vec_clock = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(vec->vec_clock)) {
|
||||
ret = PTR_ERR(vec->vec_clock);
|
||||
goto done_err;
|
||||
}
|
||||
ret = clk_prepare_enable(vec->vec_clock);
|
||||
|
||||
ret = drmm_mode_config_init(&vec->drm);
|
||||
if (ret)
|
||||
goto done_err;
|
||||
|
||||
/* Now we have all our resources, finish driver initialization */
|
||||
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
|
||||
init_completion(&vec->finished);
|
||||
vec->drm.dev_private = vec;
|
||||
platform_set_drvdata(pdev, &vec->drm);
|
||||
|
||||
vec->drm.mode_config.min_width = 256;
|
||||
vec->drm.mode_config.min_height = 128;
|
||||
vec->drm.mode_config.max_width = 960; /* for "widescreen" @ 18MHz */
|
||||
vec->drm.mode_config.max_height = 738; /* for System E only */
|
||||
vec->drm.mode_config.preferred_depth = 32;
|
||||
vec->drm.mode_config.prefer_shadow = 0;
|
||||
vec->drm.mode_config.quirk_addfb_prefer_host_byte_order = true;
|
||||
vec->drm.mode_config.funcs = &rp1vec_mode_funcs;
|
||||
drm_vblank_init(&vec->drm, 1);
|
||||
|
||||
ret = drm_mode_create_tv_properties(&vec->drm, RP1VEC_SUPPORTED_TV_MODES);
|
||||
if (ret)
|
||||
goto done_err;
|
||||
|
||||
drm_connector_init(&vec->drm, &vec->connector, &rp1vec_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_Composite);
|
||||
if (ret)
|
||||
goto done_err;
|
||||
|
||||
vec->connector.interlace_allowed = true;
|
||||
drm_connector_helper_add(&vec->connector, &rp1vec_connector_helper_funcs);
|
||||
|
||||
drm_object_attach_property(&vec->connector.base,
|
||||
vec->drm.mode_config.tv_mode_property,
|
||||
(vec->connector.cmdline_mode.tv_mode_specified) ?
|
||||
vec->connector.cmdline_mode.tv_mode :
|
||||
DRM_MODE_TV_MODE_NTSC);
|
||||
|
||||
ret = drm_simple_display_pipe_init(&vec->drm,
|
||||
&vec->pipe,
|
||||
&rp1vec_pipe_funcs,
|
||||
rp1vec_formats,
|
||||
ARRAY_SIZE(rp1vec_formats),
|
||||
NULL,
|
||||
&vec->connector);
|
||||
if (ret)
|
||||
goto done_err;
|
||||
|
||||
drm_mode_config_reset(&vec->drm);
|
||||
|
||||
ret = drm_dev_register(&vec->drm, 0);
|
||||
if (ret)
|
||||
goto done_err;
|
||||
|
||||
drm_client_setup(&vec->drm, NULL);
|
||||
return ret;
|
||||
|
||||
done_err:
|
||||
dev_err(dev, "%s fail %d", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rp1vec_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct drm_device *drm = platform_get_drvdata(pdev);
|
||||
|
||||
rp1vec_stopall(drm);
|
||||
drm_dev_unregister(drm);
|
||||
drm_atomic_helper_shutdown(drm);
|
||||
drm_dev_put(drm);
|
||||
}
|
||||
|
||||
static void rp1vec_platform_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct drm_device *drm = platform_get_drvdata(pdev);
|
||||
|
||||
rp1vec_stopall(drm);
|
||||
}
|
||||
|
||||
static const struct of_device_id rp1vec_of_match[] = {
|
||||
{
|
||||
.compatible = "raspberrypi,rp1vec",
|
||||
},
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, rp1vec_of_match);
|
||||
|
||||
static struct platform_driver rp1vec_platform_driver = {
|
||||
.probe = rp1vec_platform_probe,
|
||||
.remove = rp1vec_platform_remove,
|
||||
.shutdown = rp1vec_platform_shutdown,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = rp1vec_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(rp1vec_platform_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("DRM driver for Composite Video on Raspberry Pi RP1");
|
||||
MODULE_AUTHOR("Nick Hollinghurst");
|
||||
78
drivers/gpu/drm/rp1/rp1-vec/rp1_vec.h
Normal file
78
drivers/gpu/drm/rp1/rp1-vec/rp1_vec.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* DRM Driver for DSI output on Raspberry Pi RP1
|
||||
*
|
||||
* Copyright (c) 2023 Raspberry Pi Limited.
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/clk.h>
|
||||
#include <drm/drm_device.h>
|
||||
#include <drm/drm_simple_kms_helper.h>
|
||||
|
||||
#define MODULE_NAME "drm-rp1-vec"
|
||||
#define DRIVER_NAME "drm-rp1-vec"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
#define RP1VEC_HW_BLOCK_VEC 0
|
||||
#define RP1VEC_HW_BLOCK_CFG 1
|
||||
#define RP1VEC_NUM_HW_BLOCKS 2
|
||||
|
||||
#define RP1VEC_SUPPORTED_TV_MODES \
|
||||
(BIT(DRM_MODE_TV_MODE_NTSC) | \
|
||||
BIT(DRM_MODE_TV_MODE_NTSC_443) | \
|
||||
BIT(DRM_MODE_TV_MODE_NTSC_J) | \
|
||||
BIT(DRM_MODE_TV_MODE_PAL) | \
|
||||
BIT(DRM_MODE_TV_MODE_PAL_M) | \
|
||||
BIT(DRM_MODE_TV_MODE_PAL_N) | \
|
||||
BIT(DRM_MODE_TV_MODE_MONOCHROME))
|
||||
|
||||
#define RP1VEC_VDAC_KHZ 108000
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
struct rp1_vec {
|
||||
/* DRM base and platform device pointer */
|
||||
struct drm_device drm;
|
||||
struct platform_device *pdev;
|
||||
|
||||
/* Framework and helper objects */
|
||||
struct drm_simple_display_pipe pipe;
|
||||
struct drm_connector connector;
|
||||
|
||||
/* Clock. We assume this is always at 108 MHz. */
|
||||
struct clk *vec_clock;
|
||||
|
||||
/* Block (VCC, CFG) base addresses, and current state */
|
||||
void __iomem *hw_base[RP1VEC_NUM_HW_BLOCKS];
|
||||
u32 cur_fmt;
|
||||
bool fake_31khz, vec_running, pipe_enabled;
|
||||
struct completion finished;
|
||||
|
||||
spinlock_t hw_lock; /* the following are used in line-match ISR */
|
||||
dma_addr_t last_dma_addr;
|
||||
u32 last_stride;
|
||||
bool field_flip;
|
||||
bool lower_field_flag;
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Functions to control the VEC/DMA block */
|
||||
|
||||
void rp1vec_hw_setup(struct rp1_vec *vec,
|
||||
u32 in_format,
|
||||
struct drm_display_mode const *mode,
|
||||
int tvstd);
|
||||
void rp1vec_hw_update(struct rp1_vec *vec, dma_addr_t addr, u32 offset, u32 stride);
|
||||
void rp1vec_hw_stop(struct rp1_vec *vec);
|
||||
int rp1vec_hw_busy(struct rp1_vec *vec);
|
||||
irqreturn_t rp1vec_hw_isr(int irq, void *dev);
|
||||
void rp1vec_hw_vblank_ctrl(struct rp1_vec *vec, int enable);
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Functions to control the VIDEO OUT CFG block and check RP1 platform */
|
||||
|
||||
void rp1vec_vidout_setup(struct rp1_vec *vec);
|
||||
void rp1vec_vidout_poweroff(struct rp1_vec *vec);
|
||||
191
drivers/gpu/drm/rp1/rp1-vec/rp1_vec_cfg.c
Normal file
191
drivers/gpu/drm/rp1/rp1-vec/rp1_vec_cfg.c
Normal file
@@ -0,0 +1,191 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* DRM Driver for Video output on Raspberry Pi RP1
|
||||
* Functions to set up VIDEO_OUT_CFG registers
|
||||
* Copyright (c) 2023-2025 Raspberry Pi Limited.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/rp1_platform.h>
|
||||
|
||||
#include "rp1_vec.h"
|
||||
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_SEL
|
||||
// Description : Selects source: 0 => DPI, 1 =>VEC; optionally invert clock
|
||||
#define VIDEO_OUT_CFG_SEL 0x0000
|
||||
#define VIDEO_OUT_CFG_SEL_BITS 0x00000013
|
||||
#define VIDEO_OUT_CFG_SEL_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_SEL_PCLK_INV BIT(4)
|
||||
#define VIDEO_OUT_CFG_SEL_PAD_MUX BIT(1)
|
||||
#define VIDEO_OUT_CFG_SEL_VDAC_MUX BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_VDAC_CFG
|
||||
// Description : Configure SNPS VDAC
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG 0x0004
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_BITS 0x1fffffff
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_RESET 0x0003ffff
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR GENMASK(28, 26)
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_ENSC GENMASK(25, 23)
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC GENMASK(22, 20)
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG BIT(19)
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF BIT(18)
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC GENMASK(17, 12)
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC GENMASK(11, 6)
|
||||
#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC GENMASK(5, 0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_VDAC_STATUS
|
||||
// Description : Read VDAC status
|
||||
#define VIDEO_OUT_CFG_VDAC_STATUS 0x0008
|
||||
#define VIDEO_OUT_CFG_VDAC_STATUS_BITS 0x00000017
|
||||
#define VIDEO_OUT_CFG_VDAC_STATUS_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3 BIT(4)
|
||||
#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT GENMASK(2, 0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_MEM_PD
|
||||
// Description : Control memory power down
|
||||
#define VIDEO_OUT_CFG_MEM_PD 0x000c
|
||||
#define VIDEO_OUT_CFG_MEM_PD_BITS 0x00000003
|
||||
#define VIDEO_OUT_CFG_MEM_PD_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_MEM_PD_VEC BIT(1)
|
||||
#define VIDEO_OUT_CFG_MEM_PD_DPI BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_TEST_OVERRIDE
|
||||
// Description : Allow forcing of output values
|
||||
#define VIDEO_OUT_CFG_TEST_OVERRIDE 0x0010
|
||||
#define VIDEO_OUT_CFG_TEST_OVERRIDE_BITS 0xffffffff
|
||||
#define VIDEO_OUT_CFG_TEST_OVERRIDE_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD BIT(31)
|
||||
#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC BIT(30)
|
||||
#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL GENMASK(29, 0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_INTR
|
||||
// Description : Raw Interrupts
|
||||
#define VIDEO_OUT_CFG_INTR 0x0014
|
||||
#define VIDEO_OUT_CFG_INTR_BITS 0x00000003
|
||||
#define VIDEO_OUT_CFG_INTR_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_INTR_DPI BIT(1)
|
||||
#define VIDEO_OUT_CFG_INTR_VEC BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_INTE
|
||||
// Description : Interrupt Enable
|
||||
#define VIDEO_OUT_CFG_INTE 0x0018
|
||||
#define VIDEO_OUT_CFG_INTE_BITS 0x00000003
|
||||
#define VIDEO_OUT_CFG_INTE_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_INTE_DPI BIT(1)
|
||||
#define VIDEO_OUT_CFG_INTE_VEC BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_INTF
|
||||
// Description : Interrupt Force
|
||||
#define VIDEO_OUT_CFG_INTF 0x001c
|
||||
#define VIDEO_OUT_CFG_INTF_BITS 0x00000003
|
||||
#define VIDEO_OUT_CFG_INTF_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_INTF_DPI BIT(1)
|
||||
#define VIDEO_OUT_CFG_INTF_VEC BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_INTS
|
||||
// Description : Interrupt status after masking & forcing
|
||||
#define VIDEO_OUT_CFG_INTS 0x0020
|
||||
#define VIDEO_OUT_CFG_INTS_BITS 0x00000003
|
||||
#define VIDEO_OUT_CFG_INTS_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_INTS_DPI BIT(1)
|
||||
#define VIDEO_OUT_CFG_INTS_VEC BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_BLOCK_ID
|
||||
// Description : Block Identifier
|
||||
// Hexadecimal representation of "VOCF"
|
||||
#define VIDEO_OUT_CFG_BLOCK_ID 0x0024
|
||||
#define VIDEO_OUT_CFG_BLOCK_ID_BITS 0xffffffff
|
||||
#define VIDEO_OUT_CFG_BLOCK_ID_RESET 0x564f4346
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_INSTANCE_ID
|
||||
// Description : Block Instance Identifier
|
||||
#define VIDEO_OUT_CFG_INSTANCE_ID 0x0028
|
||||
#define VIDEO_OUT_CFG_INSTANCE_ID_BITS 0x0000000f
|
||||
#define VIDEO_OUT_CFG_INSTANCE_ID_RESET 0x00000000
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_RSTSEQ_AUTO
|
||||
// Description : None
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_AUTO 0x002c
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BITS 0x00000007
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_AUTO_RESET 0x00000007
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC BIT(2)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI BIT(1)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_RSTSEQ_PARALLEL
|
||||
// Description : None
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL 0x0030
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BITS 0x00000007
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_RESET 0x00000006
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC BIT(2)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI BIT(1)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_RSTSEQ_CTRL
|
||||
// Description : None
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_CTRL 0x0034
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BITS 0x00000007
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_CTRL_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC BIT(2)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI BIT(1)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_RSTSEQ_TRIG
|
||||
// Description : None
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_TRIG 0x0038
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BITS 0x00000007
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_TRIG_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC BIT(2)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI BIT(1)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER BIT(0)
|
||||
// =============================================================================
|
||||
// Register : VIDEO_OUT_CFG_RSTSEQ_DONE
|
||||
// Description : None
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_DONE 0x003c
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_DONE_BITS 0x00000007
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_DONE_RESET 0x00000000
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC BIT(2)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI BIT(1)
|
||||
#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER BIT(0)
|
||||
// =============================================================================
|
||||
|
||||
#define CFG_WRITE(reg, val) writel((val), vec->hw_base[RP1VEC_HW_BLOCK_CFG] + (reg))
|
||||
#define CFG_READ(reg) readl(vec->hw_base[RP1VEC_HW_BLOCK_CFG] + (reg))
|
||||
|
||||
void rp1vec_vidout_setup(struct rp1_vec *vec)
|
||||
{
|
||||
/*
|
||||
* We assume DPI and VEC can't be used at the same time (due to
|
||||
* clashing requirements for PLL_VIDEO, and potentially for VDAC).
|
||||
* We therefore leave DPI memories powered down.
|
||||
*/
|
||||
CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_DPI);
|
||||
CFG_WRITE(VIDEO_OUT_CFG_TEST_OVERRIDE, 0);
|
||||
|
||||
/* VEC->Pads (GPIOs 11:4 with clock on GPIO0); VEC->VDAC */
|
||||
CFG_WRITE(VIDEO_OUT_CFG_SEL,
|
||||
VIDEO_OUT_CFG_SEL_VDAC_MUX | VIDEO_OUT_CFG_SEL_PAD_MUX);
|
||||
|
||||
/* configure VDAC for 1 channel, bandgap on, 1.28V swing */
|
||||
CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0x0019ffff);
|
||||
|
||||
/* enable VEC interrupt */
|
||||
CFG_WRITE(VIDEO_OUT_CFG_INTE, VIDEO_OUT_CFG_INTE_VEC);
|
||||
}
|
||||
|
||||
void rp1vec_vidout_poweroff(struct rp1_vec *vec)
|
||||
{
|
||||
/* disable VEC interrupt */
|
||||
CFG_WRITE(VIDEO_OUT_CFG_INTE, 0);
|
||||
|
||||
/* Ensure VDAC is turned off; power down DPI,VEC memories */
|
||||
CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0);
|
||||
CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_BITS);
|
||||
}
|
||||
634
drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c
Normal file
634
drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c
Normal file
@@ -0,0 +1,634 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* DRM Driver for VEC output on Raspberry Pi RP1
|
||||
*
|
||||
* Copyright (c) 2023 Raspberry Pi Limited.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <drm/drm_fourcc.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_vblank.h>
|
||||
|
||||
#include "rp1_vec.h"
|
||||
#include "vec_regs.h"
|
||||
|
||||
#define BITS(field, val) (((val) << (field ## _LSB)) & (field ## _BITS))
|
||||
#define VEC_WRITE(reg, val) writel((val), vec->hw_base[RP1VEC_HW_BLOCK_VEC] + (reg ## _OFFSET))
|
||||
#define VEC_READ(reg) readl(vec->hw_base[RP1VEC_HW_BLOCK_VEC] + (reg ## _OFFSET))
|
||||
|
||||
static void rp1vec_write_regs(struct rp1_vec *vec, u32 offset, u32 const *vals, u32 num)
|
||||
{
|
||||
while (num--) {
|
||||
writel(*vals++, vec->hw_base[RP1VEC_HW_BLOCK_VEC] + offset);
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
int rp1vec_hw_busy(struct rp1_vec *vec)
|
||||
{
|
||||
/* Read the undocumented "pline_busy" flag */
|
||||
return VEC_READ(VEC_STATUS) & 1;
|
||||
}
|
||||
|
||||
/* Table of supported input (in-memory/DMA) pixel formats. */
|
||||
struct rp1vec_ipixfmt {
|
||||
u32 format; /* DRM format code */
|
||||
u32 mask; /* RGB masks (10 bits each, left justified) */
|
||||
u32 shift; /* RGB MSB positions in the memory word */
|
||||
u32 rgbsz; /* Shifts used for scaling; also (BPP/8-1) */
|
||||
};
|
||||
|
||||
#define MASK_RGB(r, g, b) \
|
||||
(BITS(VEC_IMASK_MASK_R, r) | BITS(VEC_IMASK_MASK_G, g) | BITS(VEC_IMASK_MASK_B, b))
|
||||
#define SHIFT_RGB(r, g, b) \
|
||||
(BITS(VEC_SHIFT_SHIFT_R, r) | BITS(VEC_SHIFT_SHIFT_G, g) | BITS(VEC_SHIFT_SHIFT_B, b))
|
||||
|
||||
static const struct rp1vec_ipixfmt my_formats[] = {
|
||||
{
|
||||
.format = DRM_FORMAT_XRGB8888,
|
||||
.mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
||||
.shift = SHIFT_RGB(23, 15, 7),
|
||||
.rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3),
|
||||
},
|
||||
{
|
||||
.format = DRM_FORMAT_XBGR8888,
|
||||
.mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
||||
.shift = SHIFT_RGB(7, 15, 23),
|
||||
.rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3),
|
||||
},
|
||||
{
|
||||
.format = DRM_FORMAT_ARGB8888,
|
||||
.mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
||||
.shift = SHIFT_RGB(23, 15, 7),
|
||||
.rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3),
|
||||
},
|
||||
{
|
||||
.format = DRM_FORMAT_ABGR8888,
|
||||
.mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
||||
.shift = SHIFT_RGB(7, 15, 23),
|
||||
.rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3),
|
||||
},
|
||||
{
|
||||
.format = DRM_FORMAT_RGB888,
|
||||
.mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
||||
.shift = SHIFT_RGB(23, 15, 7),
|
||||
.rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 2),
|
||||
},
|
||||
{
|
||||
.format = DRM_FORMAT_BGR888,
|
||||
.mask = MASK_RGB(0x3fc, 0x3fc, 0x3fc),
|
||||
.shift = SHIFT_RGB(7, 15, 23),
|
||||
.rgbsz = BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 2),
|
||||
},
|
||||
{
|
||||
.format = DRM_FORMAT_RGB565,
|
||||
.mask = MASK_RGB(0x3e0, 0x3f0, 0x3e0),
|
||||
.shift = SHIFT_RGB(15, 10, 4),
|
||||
.rgbsz = BITS(VEC_RGBSZ_SCALE_R, 5) |
|
||||
BITS(VEC_RGBSZ_SCALE_G, 6) |
|
||||
BITS(VEC_RGBSZ_SCALE_B, 5) |
|
||||
BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 1),
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Hardware mode descriptions (@ 108 MHz VDAC clock)
|
||||
* See "vec_regs.h" for further descriptions of these registers and fields.
|
||||
* Driver should adjust some values for other TV standards and for pixel rate,
|
||||
* and must ensure that ((de_end - de_bgn) % rate) == 0.
|
||||
*
|
||||
* To support 60fps update in interlaced modes, we now do ISR-based field-flip.
|
||||
* The FIELDS_PER_FRAME_MINUS1 flag in "misc" is no longer set. Some vertical
|
||||
* timings have been rotated wrt conventional line-numbering (to ensure a gap
|
||||
* between the last active line and nominal end-of-field).
|
||||
*/
|
||||
|
||||
struct rp1vec_hwmode {
|
||||
u16 max_rows_per_field; /* active lines per field (including partial ones) */
|
||||
u16 ref_vfp; /* nominal (vsync_start - vdisplay) when max height */
|
||||
bool interlaced; /* set for interlaced */
|
||||
bool first_field_odd; /* true if odd-indexed scanlines go to first field */
|
||||
s16 scale_v; /* V scale in 2.8 format (for power-of-2 CIC rates) */
|
||||
s16 scale_u; /* U scale in 2.8 format (for power-of-2 CIC rates) */
|
||||
u16 scale_y; /* Y scale in 2.8 format (for power-of-2 CIC rates) */
|
||||
u16 de_end; /* end of horizontal Data Active period at 108MHz */
|
||||
u16 de_bgn; /* start of horizontal Data Active period */
|
||||
u16 half_lines_per_field; /* number of half lines per field */
|
||||
s16 pedestal; /* pedestal (1024 = 100IRE) including FIR overshoot */
|
||||
u16 scale_luma; /* back end luma scaling in 1.15 format wrt DAC FSD */
|
||||
u16 scale_sync; /* back end sync scaling / blanking level as above */
|
||||
u32 scale_burst_chroma; /* back end { burst, chroma } scaling */
|
||||
u32 misc; /* Contents of the "EC" register except rate,shift */
|
||||
u64 nco_freq; /* colour carrier frequency * (2**64) / 108MHz */
|
||||
u32 timing_regs[14]; /* other back end registers 0x84 .. 0xB8 */
|
||||
};
|
||||
|
||||
/* { NTSC, PAL, PAL-M } x { progressive, interlaced } */
|
||||
static const struct rp1vec_hwmode rp1vec_hwmodes[3][2] = {
|
||||
{
|
||||
/* NTSC */
|
||||
{
|
||||
.max_rows_per_field = 240,
|
||||
.ref_vfp = 2,
|
||||
.interlaced = false,
|
||||
.first_field_odd = false,
|
||||
.scale_v = 0x0cf,
|
||||
.scale_u = 0x074,
|
||||
.scale_y = 0x107,
|
||||
.de_end = 0x1a4f,
|
||||
.de_bgn = 0x038f,
|
||||
.half_lines_per_field = 524, /* also works with 526/2 lines */
|
||||
.pedestal = 0x04c,
|
||||
.scale_luma = 0x8c9a,
|
||||
.scale_sync = 0x3851,
|
||||
.scale_burst_chroma = 0x11195561,
|
||||
.misc = 0x00090c00, /* 5-tap FIR, SEQ_EN, 4 fld sync */
|
||||
.nco_freq = 0x087c1f07c1f07c1f,
|
||||
.timing_regs = {
|
||||
0x03e10cc6, 0x0d6801fb, 0x023d034c, 0x00f80b6d,
|
||||
0x00000005, 0x0006000b, 0x000c0011, 0x000a0106,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00170106, 0x00000000
|
||||
},
|
||||
}, {
|
||||
.max_rows_per_field = 243,
|
||||
.ref_vfp = 3,
|
||||
.interlaced = true,
|
||||
.first_field_odd = true,
|
||||
.scale_v = 0x0cf,
|
||||
.scale_u = 0x074,
|
||||
.scale_y = 0x107,
|
||||
.de_end = 0x1a4f,
|
||||
.de_bgn = 0x038f,
|
||||
.half_lines_per_field = 525,
|
||||
.pedestal = 0x04c,
|
||||
.scale_luma = 0x8c9a,
|
||||
.scale_sync = 0x3851,
|
||||
.scale_burst_chroma = 0x11195561,
|
||||
.misc = 0x00094c00, /* 5-tap FIR, SEQ_EN, 2 flds, 4 fld sync */
|
||||
.nco_freq = 0x087c1f07c1f07c1f,
|
||||
.timing_regs = {
|
||||
0x03e10cc6, 0x0d6801fb, 0x023d034c, 0x00f80b6d,
|
||||
0x0207020c, 0x00000005, 0x0006000b, 0x00070104,
|
||||
0x010e020a, 0x00000000, 0x00000000, 0x0119020a,
|
||||
0x00120103, 0x01040118,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
/* PAL */
|
||||
{
|
||||
.max_rows_per_field = 288,
|
||||
.ref_vfp = 2,
|
||||
.interlaced = false,
|
||||
.first_field_odd = false,
|
||||
.scale_v = 0x0e0,
|
||||
.scale_u = 0x07e,
|
||||
.scale_y = 0x11c,
|
||||
.de_end = 0x1ab6,
|
||||
.de_bgn = 0x03f6,
|
||||
.half_lines_per_field = 624,
|
||||
.pedestal = 0x00a, /* nonzero for max FIR overshoot after CIC */
|
||||
.scale_luma = 0x89d8,
|
||||
.scale_sync = 0x3c00,
|
||||
.scale_burst_chroma = 0x0caf53b5,
|
||||
.misc = 0x00091c01, /* 5-tap FIR, SEQ_EN, 8 fld sync, PAL */
|
||||
.nco_freq = 0x0a8262b2cc48c1d1,
|
||||
.timing_regs = {
|
||||
0x04660cee, 0x0d8001fb, 0x025c034f, 0x00fd0b84,
|
||||
0x026c0270, 0x00000004, 0x00050009, 0x00070135,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00170136, 0x00000000,
|
||||
},
|
||||
}, {
|
||||
.max_rows_per_field = 288,
|
||||
.ref_vfp = 5,
|
||||
.interlaced = true,
|
||||
.first_field_odd = false,
|
||||
.scale_v = 0x0e0,
|
||||
.scale_u = 0x07e,
|
||||
.scale_y = 0x11c,
|
||||
.de_end = 0x1ab6,
|
||||
.de_bgn = 0x03f6,
|
||||
.half_lines_per_field = 625,
|
||||
.pedestal = 0x00a,
|
||||
.scale_luma = 0x89d8,
|
||||
.scale_sync = 0x3c00,
|
||||
.scale_burst_chroma = 0x0caf53b5,
|
||||
.misc = 0x0009dc01, /* 5-tap FIR, SEQ_EN, 4 flds, 8 fld sync, PAL */
|
||||
.nco_freq = 0x0a8262b2cc48c1d1,
|
||||
.timing_regs = {
|
||||
0x04660cee, 0x0d8001fb, 0x025c034f, 0x00fd0b84,
|
||||
0x026c0270, 0x00000004, 0x00050009, 0x00070135,
|
||||
0x013f026d, 0x00060136, 0x0140026e, 0x0150026e,
|
||||
0x00180136, 0x026f0017,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
/* PAL-M */
|
||||
{
|
||||
.max_rows_per_field = 240,
|
||||
.ref_vfp = 2,
|
||||
.interlaced = false,
|
||||
.first_field_odd = false,
|
||||
.scale_v = 0x0e0,
|
||||
.scale_u = 0x07e,
|
||||
.scale_y = 0x11c,
|
||||
.de_end = 0x1a4f,
|
||||
.de_bgn = 0x038f,
|
||||
.half_lines_per_field = 524,
|
||||
.pedestal = 0x00a,
|
||||
.scale_luma = 0x89d8,
|
||||
.scale_sync = 0x3851,
|
||||
.scale_burst_chroma = 0x0d5c53b5,
|
||||
.misc = 0x00091c01, /* 5-tap FIR, SEQ_EN, 8 fld sync, PAL */
|
||||
.nco_freq = 0x0879bbf8d6d33ea8,
|
||||
.timing_regs = {
|
||||
0x03e10cc6, 0x0d6801fb, 0x023c034c, 0x00f80b6e,
|
||||
0x00000005, 0x0006000b, 0x000c0011, 0x000a0106,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00170106, 0x00000000,
|
||||
},
|
||||
}, {
|
||||
.max_rows_per_field = 243,
|
||||
.ref_vfp = 3,
|
||||
.interlaced = true,
|
||||
.first_field_odd = true,
|
||||
.scale_v = 0x0e0,
|
||||
.scale_u = 0x07e,
|
||||
.scale_y = 0x11c,
|
||||
.de_end = 0x1a4f,
|
||||
.de_bgn = 0x038f,
|
||||
.half_lines_per_field = 525,
|
||||
.pedestal = 0x00a,
|
||||
.scale_luma = 0x89d8,
|
||||
.scale_sync = 0x3851,
|
||||
.scale_burst_chroma = 0x0d5c53b5,
|
||||
.misc = 0x0009dc01, /* 5-tap FIR, SEQ_EN, 4 flds, 8 fld sync, PAL */
|
||||
.nco_freq = 0x0879bbf8d6d33ea8,
|
||||
.timing_regs = {
|
||||
0x03e10cc6, 0x0d6801fb, 0x023c034c, 0x00f80b6e,
|
||||
0x0207020c, 0x00000005, 0x0006000b, 0x00090103,
|
||||
0x010f0209, 0x00080102, 0x010e020a, 0x0119020a,
|
||||
0x00120103, 0x01040118,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/* System A, System E */
|
||||
static const struct rp1vec_hwmode rp1vec_vintage_modes[2] = {
|
||||
{
|
||||
.max_rows_per_field = 190,
|
||||
.ref_vfp = 0,
|
||||
.interlaced = true,
|
||||
.first_field_odd = true,
|
||||
.scale_v = 0,
|
||||
.scale_u = 0,
|
||||
.scale_y = 0x11c,
|
||||
.de_end = 0x2920,
|
||||
.de_bgn = 0x06a0,
|
||||
.half_lines_per_field = 405,
|
||||
.pedestal = 0x00a,
|
||||
.scale_luma = 0x89d8,
|
||||
.scale_sync = 0x3c00,
|
||||
.scale_burst_chroma = 0,
|
||||
.misc = 0x00084000, /* 5-tap FIR, 2 fields */
|
||||
.nco_freq = 0,
|
||||
.timing_regs = {
|
||||
0x06f01430, 0x14d503cc, 0x00000000, 0x000010de,
|
||||
0x03000300, 0x018d0194, 0x03000300, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00d50191,
|
||||
0x000a00c6, 0x00c700d4,
|
||||
},
|
||||
}, {
|
||||
.max_rows_per_field = 369,
|
||||
.ref_vfp = 6,
|
||||
.interlaced = true,
|
||||
.first_field_odd = true,
|
||||
.scale_v = 0,
|
||||
.scale_u = 0,
|
||||
.scale_y = 0x11c,
|
||||
.de_end = 0x145f,
|
||||
.de_bgn = 0x03a7,
|
||||
.half_lines_per_field = 819,
|
||||
.pedestal = 0x0010,
|
||||
.scale_luma = 0x89d8,
|
||||
.scale_sync = 0x3b13,
|
||||
.scale_burst_chroma = 0,
|
||||
.misc = 0x00084000, /* 5-tap FIR, 2 fields */
|
||||
.nco_freq = 0,
|
||||
.timing_regs = {
|
||||
0x03c10a08, 0x0a4d0114, 0x00000000, 0x000008a6,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x01c10330,
|
||||
0x00270196, 0x019701c0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const u32 rp1vec_fir_regs[4] = {
|
||||
0x00000000, 0x0be20200, 0x20f0f800, 0x265c7f00,
|
||||
};
|
||||
|
||||
/*
|
||||
* Correction for the 4th order CIC filter's gain of (rate ** 4)
|
||||
* expressed as a right-shift and a reciprocal scale factor (Q12).
|
||||
* These arrays are indexed by [rate - 4] where 4 <= rate <= 16.
|
||||
*/
|
||||
|
||||
static const int rp1vec_scale_table[13] = {
|
||||
4096, 6711, 6473, 6988,
|
||||
4096, 5114, 6711, 4584,
|
||||
6473, 4699, 6988, 5302,
|
||||
4096
|
||||
};
|
||||
|
||||
static const u32 rp1vec_rate_shift_table[13] = {
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 3) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 7),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 4) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 9),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 5) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 10),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 6) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 11),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 7) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 11),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 8) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 12),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 9) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 13),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 10) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 13),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 11) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 14),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 12) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 14),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 13) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 15),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 14) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 15),
|
||||
BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 15) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 15),
|
||||
};
|
||||
|
||||
void rp1vec_hw_setup(struct rp1_vec *vec,
|
||||
u32 in_format,
|
||||
struct drm_display_mode const *mode,
|
||||
int tvstd)
|
||||
{
|
||||
int i, mode_family, w, h;
|
||||
const struct rp1vec_hwmode *hwm;
|
||||
int wmax, hpad_r, vpad_b, rate, ref_2mid, usr_2mid;
|
||||
u32 misc;
|
||||
|
||||
/* Input pixel format conversion */
|
||||
for (i = 0; i < ARRAY_SIZE(my_formats); ++i) {
|
||||
if (my_formats[i].format == in_format)
|
||||
break;
|
||||
}
|
||||
if (i >= ARRAY_SIZE(my_formats)) {
|
||||
dev_err(&vec->pdev->dev, "%s: bad input format\n", __func__);
|
||||
i = 0;
|
||||
}
|
||||
VEC_WRITE(VEC_IMASK, my_formats[i].mask);
|
||||
VEC_WRITE(VEC_SHIFT, my_formats[i].shift);
|
||||
VEC_WRITE(VEC_RGBSZ, my_formats[i].rgbsz);
|
||||
|
||||
/* Pick an appropriate "base" mode, which we may modify.
|
||||
* Note that this driver supports a limited selection of video modes.
|
||||
* (A complete TV mode cannot be directly inferred from a DRM display mode:
|
||||
* features such as chroma burst sequence, half-lines and equalizing pulses
|
||||
* would be under-specified, and timings prone to rounding errors.)
|
||||
*/
|
||||
if (mode->vtotal == 405 || mode->vtotal == 819) {
|
||||
/* Systems A and E (interlaced only) */
|
||||
vec->fake_31khz = false;
|
||||
mode_family = 1;
|
||||
hwm = &rp1vec_vintage_modes[(mode->vtotal == 819) ? 1 : 0];
|
||||
} else {
|
||||
/* 525- and 625-line modes, with half-height and "fake" progressive variants */
|
||||
vec->fake_31khz = mode->vtotal >= 500 && !(mode->flags & DRM_MODE_FLAG_INTERLACE);
|
||||
h = (mode->vtotal >= 500) ? (mode->vtotal >> 1) : mode->vtotal;
|
||||
if (h >= 272)
|
||||
mode_family = 1; /* PAL-625 */
|
||||
else if (tvstd == DRM_MODE_TV_MODE_PAL_M || tvstd == DRM_MODE_TV_MODE_PAL)
|
||||
mode_family = 2; /* PAL-525 */
|
||||
else
|
||||
mode_family = 0; /* NTSC-525 */
|
||||
hwm = &rp1vec_hwmodes[mode_family][(mode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0];
|
||||
}
|
||||
|
||||
/*
|
||||
* Choose the upsampling rate (to 108MHz) in the range 4..16.
|
||||
* Clip dimensions to the limits of the chosen hardware mode, then add
|
||||
* padding as required, making some attempt to respect the DRM mode's
|
||||
* display position (relative to H and V sync start). Note that "wmax"
|
||||
* should be wider than the horizontal active region, to avoid boundary
|
||||
* artifacts (e.g. wmax = 728, w = 720, active ~= 704 in Rec.601 modes).
|
||||
*/
|
||||
i = (vec->fake_31khz) ? (mode->clock >> 1) : mode->clock;
|
||||
rate = (i < (RP1VEC_VDAC_KHZ / 16)) ? 16 : max(4, (RP1VEC_VDAC_KHZ + 256) / i);
|
||||
wmax = min((hwm->de_end - hwm->de_bgn) / rate, 1020);
|
||||
w = min(mode->hdisplay, wmax);
|
||||
ref_2mid = (hwm->de_bgn + hwm->de_end) / rate + 4; /* + 4 for FIR delay */
|
||||
usr_2mid = (2 * (mode->htotal - mode->hsync_start) + w) * 2 * (hwm->timing_regs[1] >> 16) /
|
||||
(rate * mode->htotal);
|
||||
hpad_r = (wmax - w + ref_2mid - usr_2mid) >> 1;
|
||||
hpad_r = min(max(0, hpad_r), wmax - w);
|
||||
h = mode->vdisplay >> (hwm->interlaced || vec->fake_31khz);
|
||||
h = min(h, 0 + hwm->max_rows_per_field);
|
||||
vpad_b = ((mode->vsync_start - hwm->ref_vfp) >> (hwm->interlaced || vec->fake_31khz)) - h;
|
||||
vpad_b = min(max(0, vpad_b), hwm->max_rows_per_field - h);
|
||||
|
||||
/*
|
||||
* Configure the hardware "front end" (in the sysclock domain).
|
||||
* Note: To support 60fps update (per-field buffer flips), we no longer
|
||||
* enable VEC's native interlaced mode (which can't flip in mid-frame).
|
||||
* Instead, send individual fields, using software to flip between them.
|
||||
*/
|
||||
VEC_WRITE(VEC_APB_TIMEOUT, 0x38);
|
||||
VEC_WRITE(VEC_QOS,
|
||||
BITS(VEC_QOS_DQOS, 0x0) |
|
||||
BITS(VEC_QOS_ULEV, 0x8) |
|
||||
BITS(VEC_QOS_UQOS, 0x2) |
|
||||
BITS(VEC_QOS_LLEV, 0x4) |
|
||||
BITS(VEC_QOS_LQOS, 0x7));
|
||||
VEC_WRITE(VEC_DMA_AREA,
|
||||
BITS(VEC_DMA_AREA_COLS_MINUS1, w - 1) |
|
||||
BITS(VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1, h - 1));
|
||||
VEC_WRITE(VEC_YUV_SCALING,
|
||||
BITS(VEC_YUV_SCALING_U10_SCALE_Y,
|
||||
(hwm->scale_y * rp1vec_scale_table[rate - 4] + 2048) >> 12) |
|
||||
BITS(VEC_YUV_SCALING_S10_SCALE_U,
|
||||
(hwm->scale_u * rp1vec_scale_table[rate - 4] + 2048) >> 12) |
|
||||
BITS(VEC_YUV_SCALING_S10_SCALE_V,
|
||||
(hwm->scale_v * rp1vec_scale_table[rate - 4] + 2048) >> 12));
|
||||
VEC_WRITE(VEC_BACK_PORCH,
|
||||
BITS(VEC_BACK_PORCH_HBP_MINUS1, wmax - w - hpad_r - 1) |
|
||||
BITS(VEC_BACK_PORCH_VBP_MINUS1, hwm->max_rows_per_field - h - vpad_b - 1));
|
||||
VEC_WRITE(VEC_FRONT_PORCH,
|
||||
BITS(VEC_FRONT_PORCH_HFP_MINUS1, hpad_r - 1) |
|
||||
BITS(VEC_FRONT_PORCH_VFP_MINUS1, vpad_b - 1));
|
||||
VEC_WRITE(VEC_MODE,
|
||||
BITS(VEC_MODE_HIGH_WATER, 0xE0) |
|
||||
BITS(VEC_MODE_ALIGN16, !((w | mode->hdisplay) & 15)) |
|
||||
BITS(VEC_MODE_VFP_EN, (vpad_b > 0)) |
|
||||
BITS(VEC_MODE_VBP_EN, (hwm->max_rows_per_field > h + vpad_b)) |
|
||||
BITS(VEC_MODE_HFP_EN, (hpad_r > 0)) |
|
||||
BITS(VEC_MODE_HBP_EN, (wmax > w + hpad_r)));
|
||||
|
||||
/* Configure the hardware "back end" (in the VDAC clock domain) */
|
||||
VEC_WRITE(VEC_DAC_80,
|
||||
BITS(VEC_DAC_80_U14_DE_BGN, hwm->de_bgn) |
|
||||
BITS(VEC_DAC_80_U14_DE_END, hwm->de_bgn + wmax * rate));
|
||||
rp1vec_write_regs(vec, 0x84, hwm->timing_regs, ARRAY_SIZE(hwm->timing_regs));
|
||||
VEC_WRITE(VEC_DAC_C0, 0x0); /* DAC control/status -- not wired up in RP1 */
|
||||
VEC_WRITE(VEC_DAC_C4, 0x007bffff); /* DAC control -- not wired up in RP1 */
|
||||
misc = hwm->half_lines_per_field;
|
||||
if (misc == 524 && (mode->vtotal >> vec->fake_31khz) == 263)
|
||||
misc += 2;
|
||||
if (tvstd == DRM_MODE_TV_MODE_NTSC_J && mode_family == 0) {
|
||||
/* NTSC-J modification: reduce pedestal and increase gain */
|
||||
VEC_WRITE(VEC_DAC_BC,
|
||||
BITS(VEC_DAC_BC_U11_HALF_LINES_PER_FIELD, misc) |
|
||||
BITS(VEC_DAC_BC_S11_PEDESTAL, 0x00a));
|
||||
VEC_WRITE(VEC_DAC_C8,
|
||||
BITS(VEC_DAC_C8_U16_SCALE_LUMA, 0x9400) |
|
||||
BITS(VEC_DAC_C8_U16_SCALE_SYNC, hwm->scale_sync));
|
||||
} else {
|
||||
VEC_WRITE(VEC_DAC_BC,
|
||||
BITS(VEC_DAC_BC_U11_HALF_LINES_PER_FIELD, misc) |
|
||||
BITS(VEC_DAC_BC_S11_PEDESTAL, hwm->pedestal));
|
||||
VEC_WRITE(VEC_DAC_C8,
|
||||
BITS(VEC_DAC_C8_U16_SCALE_LUMA, hwm->scale_luma) |
|
||||
BITS(VEC_DAC_C8_U16_SCALE_SYNC, hwm->scale_sync));
|
||||
}
|
||||
VEC_WRITE(VEC_DAC_CC, (tvstd >= DRM_MODE_TV_MODE_SECAM) ? 0 : hwm->scale_burst_chroma);
|
||||
VEC_WRITE(VEC_DAC_D0, 0x02000000); /* ADC offsets -- not needed in RP1? */
|
||||
misc = hwm->misc;
|
||||
if ((tvstd == DRM_MODE_TV_MODE_NTSC_443 || tvstd == DRM_MODE_TV_MODE_PAL) &&
|
||||
mode_family != 1) {
|
||||
/* Change colour carrier frequency to 4433618.75 Hz; disable hard sync */
|
||||
VEC_WRITE(VEC_DAC_D4, 0xcc48c1d1);
|
||||
VEC_WRITE(VEC_DAC_D8, 0x0a8262b2);
|
||||
misc &= ~VEC_DAC_EC_SEQ_EN_BITS;
|
||||
} else if (tvstd == DRM_MODE_TV_MODE_PAL_N && mode_family == 1) {
|
||||
/* Change colour carrier frequency to 3582056.25 Hz */
|
||||
VEC_WRITE(VEC_DAC_D4, 0x9ce075f7);
|
||||
VEC_WRITE(VEC_DAC_D8, 0x087da511);
|
||||
} else {
|
||||
VEC_WRITE(VEC_DAC_D4, (u32)(hwm->nco_freq));
|
||||
VEC_WRITE(VEC_DAC_D8, (u32)(hwm->nco_freq >> 32));
|
||||
}
|
||||
VEC_WRITE(VEC_DAC_EC, misc | rp1vec_rate_shift_table[rate - 4]);
|
||||
rp1vec_write_regs(vec, 0xDC, rp1vec_fir_regs, ARRAY_SIZE(rp1vec_fir_regs));
|
||||
|
||||
/* State for software-based field flipping */
|
||||
vec->field_flip = hwm->interlaced;
|
||||
vec->lower_field_flag = hwm->first_field_odd;
|
||||
vec->last_dma_addr = 0;
|
||||
|
||||
/* Set up interrupts and initialise VEC. It will start on the next rp1vec_hw_update() */
|
||||
VEC_WRITE(VEC_IRQ_FLAGS, 0xFFFFFFFFu);
|
||||
rp1vec_hw_vblank_ctrl(vec, 1);
|
||||
i = rp1vec_hw_busy(vec);
|
||||
if (i)
|
||||
dev_warn(&vec->pdev->dev,
|
||||
"%s: VEC unexpectedly busy at start (0x%08x)",
|
||||
__func__, VEC_READ(VEC_STATUS));
|
||||
|
||||
VEC_WRITE(VEC_CONTROL,
|
||||
BITS(VEC_CONTROL_START_ARM, (!i)) |
|
||||
BITS(VEC_CONTROL_AUTO_REPEAT, 1));
|
||||
}
|
||||
|
||||
void rp1vec_hw_update(struct rp1_vec *vec, dma_addr_t addr, u32 offset, u32 stride)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
addr += offset;
|
||||
|
||||
/*
|
||||
* Update STRIDE, DMAH and DMAL only. When called after rp1vec_hw_setup(),
|
||||
* DMA starts immediately; if already running, the buffer will flip at
|
||||
* the next vertical sync event. For field-rate update in interlaced modes,
|
||||
* we need to adjust the address and stride to display the current field,
|
||||
* saving the original address (so it can be flipped for subsequent fields).
|
||||
*/
|
||||
spin_lock_irqsave(&vec->hw_lock, flags);
|
||||
|
||||
vec->last_dma_addr = addr;
|
||||
vec->last_stride = stride;
|
||||
if (vec->field_flip || vec->fake_31khz) {
|
||||
if (vec->fake_31khz || vec->lower_field_flag)
|
||||
addr += stride;
|
||||
stride *= 2;
|
||||
}
|
||||
VEC_WRITE(VEC_DMA_STRIDE, stride);
|
||||
VEC_WRITE(VEC_DMA_ADDR_H, addr >> 32);
|
||||
VEC_WRITE(VEC_DMA_ADDR_L, addr & 0xFFFFFFFFu);
|
||||
|
||||
spin_unlock_irqrestore(&vec->hw_lock, flags);
|
||||
}
|
||||
|
||||
void rp1vec_hw_stop(struct rp1_vec *vec)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
* Stop DMA by turning off the Auto-Repeat flag, and wait up to 100ms for
|
||||
* the current and any queued frame to end. "Force drain" flags are not used,
|
||||
* as they seem to prevent DMA from re-starting properly; it's safer to wait.
|
||||
*/
|
||||
|
||||
spin_lock_irqsave(&vec->hw_lock, flags);
|
||||
vec->last_dma_addr = 0;
|
||||
reinit_completion(&vec->finished);
|
||||
VEC_WRITE(VEC_CONTROL, 0);
|
||||
spin_unlock_irqrestore(&vec->hw_lock, flags);
|
||||
|
||||
if (!wait_for_completion_timeout(&vec->finished, HZ / 10))
|
||||
drm_err(&vec->drm, "%s: timed out waiting for idle\n", __func__);
|
||||
VEC_WRITE(VEC_IRQ_ENABLES, 0);
|
||||
}
|
||||
|
||||
void rp1vec_hw_vblank_ctrl(struct rp1_vec *vec, int enable)
|
||||
{
|
||||
VEC_WRITE(VEC_IRQ_ENABLES,
|
||||
BITS(VEC_IRQ_ENABLES_DONE, 1) |
|
||||
BITS(VEC_IRQ_ENABLES_DMA, (enable ? 1 : 0)) |
|
||||
BITS(VEC_IRQ_ENABLES_MATCH, vec->field_flip) |
|
||||
BITS(VEC_IRQ_ENABLES_MATCH_ROW, 32));
|
||||
}
|
||||
|
||||
irqreturn_t rp1vec_hw_isr(int irq, void *dev)
|
||||
{
|
||||
struct rp1_vec *vec = dev;
|
||||
u32 u = VEC_READ(VEC_IRQ_FLAGS);
|
||||
|
||||
if (u) {
|
||||
VEC_WRITE(VEC_IRQ_FLAGS, u);
|
||||
if (u & VEC_IRQ_FLAGS_DMA_BITS)
|
||||
drm_crtc_handle_vblank(&vec->pipe.crtc);
|
||||
if (u & VEC_IRQ_FLAGS_DONE_BITS)
|
||||
complete(&vec->finished);
|
||||
|
||||
/*
|
||||
* VEC has native support for interlaced modes, but that only
|
||||
* supports buffer-flips per frame (30fps), not field (60fps).
|
||||
* Instead, we always run the VEC front end in a "progressive"
|
||||
* mode and use the "field-flip" trick (see RP1 DPI driver).
|
||||
*/
|
||||
if ((u & VEC_IRQ_FLAGS_MATCH_BITS) && vec->field_flip) {
|
||||
unsigned long flags;
|
||||
dma_addr_t a;
|
||||
|
||||
spin_lock_irqsave(&vec->hw_lock, flags);
|
||||
vec->lower_field_flag = !vec->lower_field_flag;
|
||||
a = vec->last_dma_addr;
|
||||
if (a) {
|
||||
if (vec->lower_field_flag)
|
||||
a += vec->last_stride;
|
||||
VEC_WRITE(VEC_DMA_ADDR_H, a >> 32);
|
||||
VEC_WRITE(VEC_DMA_ADDR_L, a & 0xFFFFFFFFu);
|
||||
}
|
||||
spin_unlock_irqrestore(&vec->hw_lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
return u ? IRQ_HANDLED : IRQ_NONE;
|
||||
}
|
||||
1420
drivers/gpu/drm/rp1/rp1-vec/vec_regs.h
Normal file
1420
drivers/gpu/drm/rp1/rp1-vec/vec_regs.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -272,6 +272,18 @@ static const struct debugfs_reg32 vec_regs[] = {
|
||||
VC4_REG32(VEC_DAC_MISC),
|
||||
};
|
||||
|
||||
static const struct drm_display_mode drm_mode_240p = {
|
||||
DRM_MODE("720x240", DRM_MODE_TYPE_DRIVER, 13500,
|
||||
720, 720 + 14, 720 + 14 + 64, 720 + 14 + 64 + 60, 0,
|
||||
240, 240 + 3, 240 + 3 + 3, 262, 0, 0)
|
||||
};
|
||||
|
||||
static const struct drm_display_mode drm_mode_288p = {
|
||||
DRM_MODE("720x288", DRM_MODE_TYPE_DRIVER, 13500,
|
||||
720, 720 + 20, 720 + 20 + 64, 720 + 20 + 64 + 60, 0,
|
||||
288, 288 + 2, 288 + 2 + 3, 312, 0, 0)
|
||||
};
|
||||
|
||||
static const struct vc4_vec_tv_mode vc4_vec_tv_modes[] = {
|
||||
{
|
||||
.mode = DRM_MODE_TV_MODE_NTSC,
|
||||
@@ -496,9 +508,31 @@ static const struct drm_connector_funcs vc4_vec_connector_funcs = {
|
||||
.atomic_set_property = vc4_vec_connector_set_property,
|
||||
};
|
||||
|
||||
static int vc4_vec_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_display_mode *mode;
|
||||
int count = drm_connector_helper_tv_get_modes(connector);
|
||||
|
||||
mode = drm_mode_duplicate(connector->dev, &drm_mode_240p);
|
||||
if (!mode)
|
||||
return -ENOMEM;
|
||||
|
||||
drm_mode_probed_add(connector, mode);
|
||||
count++;
|
||||
|
||||
mode = drm_mode_duplicate(connector->dev, &drm_mode_288p);
|
||||
if (!mode)
|
||||
return -ENOMEM;
|
||||
|
||||
drm_mode_probed_add(connector, mode);
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs vc4_vec_connector_helper_funcs = {
|
||||
.atomic_check = drm_atomic_helper_connector_tv_check,
|
||||
.get_modes = drm_connector_helper_tv_get_modes,
|
||||
.get_modes = vc4_vec_connector_get_modes,
|
||||
};
|
||||
|
||||
static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec)
|
||||
|
||||
Reference in New Issue
Block a user