diff options
author | Dave Airlie <airlied@redhat.com> | 2018-02-16 09:29:27 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2018-02-16 09:29:27 +1000 |
commit | 76ea0f334e7fb13226e64ee7de928611f5303faf (patch) | |
tree | 4a6b8d8f1b19a68072f2ce8f33add59be9a67be7 /drivers/gpu/drm/sun4i | |
parent | f0308d76906a5b65ec4fcc3b133394caa9f00638 (diff) | |
parent | 1bc3d3cce8c3b44c2b5ac6cee98c830bb40e6b0f (diff) | |
download | linux-76ea0f334e7fb13226e64ee7de928611f5303faf.tar.gz linux-76ea0f334e7fb13226e64ee7de928611f5303faf.tar.xz |
Merge tag 'drm-misc-next-2018-02-13' of git://anongit.freedesktop.org/drm/drm-misc into drm-next
drm-misc-next for 4.17:
UAPI Changes:
- drm/vc4: Expose performance counters to userspace (Boris)
Cross-subsystem Changes:
- MAINTAINERS: Linus to maintain panel-arm-versatile in -misc (Linus)
Core Changes:
- Only use swiotlb when necessary (Chunming)
Driver Changes:
- drm/panel: Add support for ARM Versatile panels (Linus)
- pl111: Improvements around versatile panel support (Linus)
----------------------------------------
Tagged on 2018-02-06:
drm-misc-next for 4.17:
UAPI Changes:
- Validate mode flags + type (Ville)
- Deprecate unused mode flags PIXMUX, BCAST (Ville)
- Deprecate unused mode types BUILTIN, CRTC_C, CLOCK_C, DEFAULT (Ville)
Cross-subsystem Changes:
- MAINTAINERS: s/Daniel/Maarten/ for drm-misc (Daniel)
Core Changes:
- gem: Export gem functions for drivers to use (Samuel)
- bridge: Introduce bridge timings in drm_bridge (Linus)
- dma-buf: Allow exclusive fence to be bundled in fence array when
calling reservation_object_get_fences_rcu (Christian)
- dp: Add training pattern 4 and HBR3 support to dp helpers (Manasi)
- fourcc: Add alpha bit to formats to avoid driver format LUTs (Maxime)
- mode: Various cleanups + add new device-wide .mode_valid hook (Ville)
- atomic: Fix state leak when non-blocking commits fail (Leo)
NOTE: IIRC, this was cross-picked to -fixes so it might fall out
- crc: Allow polling on the data fd (Maarten)
Driver Changes:
- bridge/vga-dac: Add THS8134* support (Linus)
- tinydrm: Various MIPI DBI improvements/cleanups (Noralf)
- bridge/dw-mipi-dsi: Cleanups + use create_packet helper (Brian)
- drm/sun4i: Add Display Engine frontend support (Maxime)
- drm/sun4i: Add zpos support + increase num planes from 2 to 4 (Maxime)
- various: Use drm_mode_get_hv_timing() to fill plane clip rectangle (Ville)
- stm: Add 8-bit clut support, add dsi phy v1.31 support, +fixes (Phillipe)
Cc: Boris Brezillon <boris.brezillon@free-electrons.com>
Cc: Chunming Zhou <david1.zhou@amd.com>
Cc: Samuel Li <Samuel.Li@amd.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Noralf Trønnes <noralf@tronnes.org>
Cc: Brian Norris <briannorris@chromium.org>
Cc: Maxime Ripard <maxime.ripard@free-electrons.com>
Cc: Ville Syrjala <ville.syrjala@linux.intel.com>
Cc: Christian König <christian.koenig@amd.com>
Cc: Manasi Navare <manasi.d.navare@intel.com>
Cc: Philippe Cornu <philippe.cornu@st.com>
Cc: Leo (Sunpeng) Li <sunpeng.li@amd.com>
Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
* tag 'drm-misc-next-2018-02-13' of git://anongit.freedesktop.org/drm/drm-misc: (115 commits)
drm/radeon: only enable swiotlb path when need v2
drm/amdgpu: only enable swiotlb alloc when need v2
drm: add func to get max iomem address v2
drm/vc4: Expose performance counters to userspace
drm: Print the pid when debug logging an ioctl error.
drm/stm: ltdc: remove non-alpha color formats on layer 2 for older hw
drm/stm: ltdc: add non-alpha color formats
drm/bridge/synopsys: dsi: Add 1.31 version support
drm/bridge/synopsys: dsi: Add read feature
drm/pl111: Support multiple endpoints on the CLCD
drm/pl111: Support variants with broken VBLANK
drm/pl111: Support variants with broken clock divider
drm/pl111: Handle the Versatile RGB/BGR565 mode
drm/pl111: Properly detect the ARM PL110 variants
drm/panel: Add support for ARM Versatile panels
drm/panel: Device tree bindings for ARM Versatile panels
drm/bridge: Rename argument from crtc to bridge
drm/crc: Add support for polling on the data fd.
drm/sun4i: Use drm_mode_get_hv_timing() to populate plane clip rectangle
drm/rcar-du: Use drm_mode_get_hv_timing() to populate plane clip rectangle
...
Diffstat (limited to 'drivers/gpu/drm/sun4i')
-rw-r--r-- | drivers/gpu/drm/sun4i/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_backend.c | 248 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_backend.h | 18 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_crtc.c | 21 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_drv.c | 27 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_drv.h | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_framebuffer.c | 24 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_frontend.c | 389 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_frontend.h | 99 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_layer.c | 117 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_layer.h | 11 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sunxi_engine.h | 90 |
15 files changed, 1013 insertions, 57 deletions
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile index 2b37a6abbb1d..582607c0c488 100644 --- a/drivers/gpu/drm/sun4i/Makefile +++ b/drivers/gpu/drm/sun4i/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 sun4i-backend-y += sun4i_backend.o sun4i_layer.o +sun4i-frontend-y += sun4i_frontend.o sun4i-drm-y += sun4i_drv.o sun4i-drm-y += sun4i_framebuffer.o @@ -24,6 +25,6 @@ obj-$(CONFIG_DRM_SUN4I) += sun4i-tcon.o obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o -obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o +obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o sun4i-frontend.o obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c index 847eecbe4d14..245b189fc4d8 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.c +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -11,6 +11,7 @@ */ #include <drm/drmP.h> +#include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> @@ -26,6 +27,7 @@ #include "sun4i_backend.h" #include "sun4i_drv.h" +#include "sun4i_frontend.h" #include "sun4i_layer.h" #include "sunxi_engine.h" @@ -93,7 +95,7 @@ void sun4i_backend_layer_enable(struct sun4i_backend *backend, static int sun4i_backend_drm_format_to_layer(struct drm_plane *plane, u32 format, u32 *mode) { - if ((plane->type == DRM_PLANE_TYPE_PRIMARY) && + if (plane && (plane->type == DRM_PLANE_TYPE_PRIMARY) && (format == DRM_FORMAT_ARGB8888)) format = DRM_FORMAT_XRGB8888; @@ -141,7 +143,6 @@ int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, int layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; - struct drm_framebuffer *fb = state->fb; DRM_DEBUG_DRIVER("Updating layer %d\n", layer); @@ -153,12 +154,6 @@ int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, state->crtc_h)); } - /* Set the line width */ - DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); - regmap_write(backend->engine.regs, - SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), - fb->pitches[0] * 8); - /* Set height and width */ DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", state->crtc_w, state->crtc_h); @@ -210,6 +205,30 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, return 0; } +int sun4i_backend_update_layer_frontend(struct sun4i_backend *backend, + int layer, uint32_t fmt) +{ + u32 val; + int ret; + + ret = sun4i_backend_drm_format_to_layer(NULL, fmt, &val); + if (ret) { + DRM_DEBUG_DRIVER("Invalid format\n"); + return ret; + } + + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_ATTCTL_REG0(layer), + SUN4I_BACKEND_ATTCTL_REG0_LAY_VDOEN, + SUN4I_BACKEND_ATTCTL_REG0_LAY_VDOEN); + + regmap_update_bits(backend->engine.regs, + SUN4I_BACKEND_ATTCTL_REG1(layer), + SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val); + + return 0; +} + int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, int layer, struct drm_plane *plane) { @@ -218,6 +237,12 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, u32 lo_paddr, hi_paddr; dma_addr_t paddr; + /* Set the line width */ + DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); + regmap_write(backend->engine.regs, + SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), + fb->pitches[0] * 8); + /* Get the start of the displayed memory */ paddr = drm_fb_cma_get_gem_addr(fb, state, 0); DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); @@ -246,6 +271,176 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, return 0; } +int sun4i_backend_update_layer_zpos(struct sun4i_backend *backend, int layer, + struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + unsigned int priority = state->normalized_zpos; + + DRM_DEBUG_DRIVER("Setting layer %d's priority to %d\n", layer, priority); + + regmap_update_bits(backend->engine.regs, SUN4I_BACKEND_ATTCTL_REG0(layer), + SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL_MASK, + SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(priority)); + + return 0; +} + +static bool sun4i_backend_plane_uses_scaler(struct drm_plane_state *state) +{ + u16 src_h = state->src_h >> 16; + u16 src_w = state->src_w >> 16; + + DRM_DEBUG_DRIVER("Input size %dx%d, output size %dx%d\n", + src_w, src_h, state->crtc_w, state->crtc_h); + + if ((state->crtc_h != src_h) || (state->crtc_w != src_w)) + return true; + + return false; +} + +static bool sun4i_backend_plane_uses_frontend(struct drm_plane_state *state) +{ + struct sun4i_layer *layer = plane_to_sun4i_layer(state->plane); + struct sun4i_backend *backend = layer->backend; + + if (IS_ERR(backend->frontend)) + return false; + + return sun4i_backend_plane_uses_scaler(state); +} + +static void sun4i_backend_atomic_begin(struct sunxi_engine *engine, + struct drm_crtc_state *old_state) +{ + u32 val; + + WARN_ON(regmap_read_poll_timeout(engine->regs, + SUN4I_BACKEND_REGBUFFCTL_REG, + val, !(val & SUN4I_BACKEND_REGBUFFCTL_LOADCTL), + 100, 50000)); +} + +static int sun4i_backend_atomic_check(struct sunxi_engine *engine, + struct drm_crtc_state *crtc_state) +{ + struct drm_atomic_state *state = crtc_state->state; + struct drm_device *drm = state->dev; + struct drm_plane *plane; + unsigned int num_planes = 0; + unsigned int num_alpha_planes = 0; + unsigned int num_frontend_planes = 0; + + DRM_DEBUG_DRIVER("Starting checking our planes\n"); + + if (!crtc_state->planes_changed) + return 0; + + drm_for_each_plane_mask(plane, drm, crtc_state->plane_mask) { + struct drm_plane_state *plane_state = + drm_atomic_get_plane_state(state, plane); + struct sun4i_layer_state *layer_state = + state_to_sun4i_layer_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_format_name_buf format_name; + + if (sun4i_backend_plane_uses_frontend(plane_state)) { + DRM_DEBUG_DRIVER("Using the frontend for plane %d\n", + plane->index); + + layer_state->uses_frontend = true; + num_frontend_planes++; + } else { + layer_state->uses_frontend = false; + } + + DRM_DEBUG_DRIVER("Plane FB format is %s\n", + drm_get_format_name(fb->format->format, + &format_name)); + if (fb->format->has_alpha) + num_alpha_planes++; + + num_planes++; + } + + /* + * The hardware is a bit unusual here. + * + * Even though it supports 4 layers, it does the composition + * in two separate steps. + * + * The first one is assigning a layer to one of its two + * pipes. If more that 1 layer is assigned to the same pipe, + * and if pixels overlaps, the pipe will take the pixel from + * the layer with the highest priority. + * + * The second step is the actual alpha blending, that takes + * the two pipes as input, and uses the eventual alpha + * component to do the transparency between the two. + * + * This two steps scenario makes us unable to guarantee a + * robust alpha blending between the 4 layers in all + * situations, since this means that we need to have one layer + * with alpha at the lowest position of our two pipes. + * + * However, we cannot even do that, since the hardware has a + * bug where the lowest plane of the lowest pipe (pipe 0, + * priority 0), if it has any alpha, will discard the pixel + * entirely and just display the pixels in the background + * color (black by default). + * + * This means that we effectively have only three valid + * configurations with alpha, all of them with the alpha being + * on pipe1 with the lowest position, which can be 1, 2 or 3 + * depending on the number of planes and their zpos. + */ + if (num_alpha_planes > SUN4I_BACKEND_NUM_ALPHA_LAYERS) { + DRM_DEBUG_DRIVER("Too many planes with alpha, rejecting...\n"); + return -EINVAL; + } + + if (num_frontend_planes > SUN4I_BACKEND_NUM_FRONTEND_LAYERS) { + DRM_DEBUG_DRIVER("Too many planes going through the frontend, rejecting\n"); + return -EINVAL; + } + + DRM_DEBUG_DRIVER("State valid with %u planes, %u alpha, %u video\n", + num_planes, num_alpha_planes, num_frontend_planes); + + return 0; +} + +static void sun4i_backend_vblank_quirk(struct sunxi_engine *engine) +{ + struct sun4i_backend *backend = engine_to_sun4i_backend(engine); + struct sun4i_frontend *frontend = backend->frontend; + + if (!frontend) + return; + + /* + * In a teardown scenario with the frontend involved, we have + * to keep the frontend enabled until the next vblank, and + * only then disable it. + * + * This is due to the fact that the backend will not take into + * account the new configuration (with the plane that used to + * be fed by the frontend now disabled) until we write to the + * commit bit and the hardware fetches the new configuration + * during the next vblank. + * + * So we keep the frontend around in order to prevent any + * visual artifacts. + */ + spin_lock(&backend->frontend_lock); + if (backend->frontend_teardown) { + sun4i_frontend_exit(frontend); + backend->frontend_teardown = false; + } + spin_unlock(&backend->frontend_lock); +}; + static int sun4i_backend_init_sat(struct device *dev) { struct sun4i_backend *backend = dev_get_drvdata(dev); int ret; @@ -330,11 +525,43 @@ static int sun4i_backend_of_get_id(struct device_node *node) return ret; } +/* TODO: This needs to take multiple pipelines into account */ +static struct sun4i_frontend *sun4i_backend_find_frontend(struct sun4i_drv *drv, + struct device_node *node) +{ + struct device_node *port, *ep, *remote; + struct sun4i_frontend *frontend; + + port = of_graph_get_port_by_id(node, 0); + if (!port) + return ERR_PTR(-EINVAL); + + for_each_available_child_of_node(port, ep) { + remote = of_graph_get_remote_port_parent(ep); + if (!remote) + continue; + + /* does this node match any registered engines? */ + list_for_each_entry(frontend, &drv->frontend_list, list) { + if (remote == frontend->node) { + of_node_put(remote); + of_node_put(port); + return frontend; + } + } + } + + return ERR_PTR(-EINVAL); +} + static const struct sunxi_engine_ops sun4i_backend_engine_ops = { + .atomic_begin = sun4i_backend_atomic_begin, + .atomic_check = sun4i_backend_atomic_check, .commit = sun4i_backend_commit, .layers_init = sun4i_layers_init, .apply_color_correction = sun4i_backend_apply_color_correction, .disable_color_correction = sun4i_backend_disable_color_correction, + .vblank_quirk = sun4i_backend_vblank_quirk, }; static struct regmap_config sun4i_backend_regmap_config = { @@ -360,6 +587,7 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, if (!backend) return -ENOMEM; dev_set_drvdata(dev, backend); + spin_lock_init(&backend->frontend_lock); backend->engine.node = dev->of_node; backend->engine.ops = &sun4i_backend_engine_ops; @@ -367,6 +595,10 @@ static int sun4i_backend_bind(struct device *dev, struct device *master, if (backend->engine.id < 0) return backend->engine.id; + backend->frontend = sun4i_backend_find_frontend(drv, dev->of_node); + if (IS_ERR(backend->frontend)) + dev_warn(dev, "Couldn't find matching frontend, frontend features disabled\n"); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(dev, res); if (IS_ERR(regs)) diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h index ac3cc029f5cd..52e77591186a 100644 --- a/drivers/gpu/drm/sun4i/sun4i_backend.h +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -72,6 +72,7 @@ #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(x) ((x) << 15) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL_MASK GENMASK(11, 10) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(x) ((x) << 10) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_VDOEN BIT(1) #define SUN4I_BACKEND_ATTCTL_REG1(l) (0x8a0 + (0x4 * (l))) #define SUN4I_BACKEND_ATTCTL_REG1_LAY_HSCAFCT GENMASK(15, 14) @@ -111,7 +112,9 @@ #define SUN4I_BACKEND_SPRALPHACTL_REG 0x90c #define SUN4I_BACKEND_IYUVCTL_REG 0x920 #define SUN4I_BACKEND_IYUVADD_REG(c) (0x930 + (0x4 * (c))) -#define SUN4I_BACKEND_IYUVLINEWITDTH_REG(c) (0x940 + (0x4 * (c))) + +#define SUN4I_BACKEND_IYUVLINEWIDTH_REG(c) (0x940 + (0x4 * (c))) + #define SUN4I_BACKEND_YGCOEF_REG(c) (0x950 + (0x4 * (c))) #define SUN4I_BACKEND_YGCONS_REG 0x95c #define SUN4I_BACKEND_URCOEF_REG(c) (0x960 + (0x4 * (c))) @@ -143,8 +146,13 @@ #define SUN4I_BACKEND_HWCCOLORTAB_OFF 0x4c00 #define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p))) +#define SUN4I_BACKEND_NUM_LAYERS 4 +#define SUN4I_BACKEND_NUM_ALPHA_LAYERS 1 +#define SUN4I_BACKEND_NUM_FRONTEND_LAYERS 1 + struct sun4i_backend { struct sunxi_engine engine; + struct sun4i_frontend *frontend; struct reset_control *reset; @@ -154,6 +162,10 @@ struct sun4i_backend { struct clk *sat_clk; struct reset_control *sat_reset; + + /* Protects against races in the frontend teardown */ + spinlock_t frontend_lock; + bool frontend_teardown; }; static inline struct sun4i_backend * @@ -170,5 +182,9 @@ int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, int layer, struct drm_plane *plane); int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, int layer, struct drm_plane *plane); +int sun4i_backend_update_layer_frontend(struct sun4i_backend *backend, + int layer, uint32_t in_fmt); +int sun4i_backend_update_layer_zpos(struct sun4i_backend *backend, + int layer, struct drm_plane *plane); #endif /* _SUN4I_BACKEND_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c index 5decae0069d0..3b2d11b675e8 100644 --- a/drivers/gpu/drm/sun4i/sun4i_crtc.c +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -25,6 +25,7 @@ #include <video/videomode.h> +#include "sun4i_backend.h" #include "sun4i_crtc.h" #include "sun4i_drv.h" #include "sunxi_engine.h" @@ -46,11 +47,25 @@ static struct drm_encoder *sun4i_crtc_get_encoder(struct drm_crtc *crtc) return NULL; } +static int sun4i_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct sunxi_engine *engine = scrtc->engine; + int ret = 0; + + if (engine && engine->ops && engine->ops->atomic_check) + ret = engine->ops->atomic_check(engine, state); + + return ret; +} + static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); struct drm_device *dev = crtc->dev; + struct sunxi_engine *engine = scrtc->engine; unsigned long flags; if (crtc->state->event) { @@ -60,7 +75,10 @@ static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, scrtc->event = crtc->state->event; spin_unlock_irqrestore(&dev->event_lock, flags); crtc->state->event = NULL; - } + } + + if (engine->ops->atomic_begin) + engine->ops->atomic_begin(engine, old_state); } static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc, @@ -125,6 +143,7 @@ static void sun4i_crtc_mode_set_nofb(struct drm_crtc *crtc) } static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = { + .atomic_check = sun4i_crtc_atomic_check, .atomic_begin = sun4i_crtc_atomic_begin, .atomic_flush = sun4i_crtc_atomic_flush, .atomic_enable = sun4i_crtc_atomic_enable, diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index 4570da0227b4..3957c2ff6870 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -23,6 +23,7 @@ #include <drm/drm_of.h> #include "sun4i_drv.h" +#include "sun4i_frontend.h" #include "sun4i_framebuffer.h" #include "sun4i_tcon.h" @@ -91,6 +92,7 @@ static int sun4i_drv_bind(struct device *dev) goto free_drm; } drm->dev_private = drv; + INIT_LIST_HEAD(&drv->frontend_list); INIT_LIST_HEAD(&drv->engine_list); INIT_LIST_HEAD(&drv->tcon_list); @@ -177,6 +179,14 @@ static bool sun4i_drv_node_is_frontend(struct device_node *node) of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend"); } +static bool sun4i_drv_node_is_supported_frontend(struct device_node *node) +{ + if (IS_ENABLED(CONFIG_DRM_SUN4I_BACKEND)) + return !!of_match_node(sun4i_frontend_of_table, node); + + return false; +} + static bool sun4i_drv_node_is_tcon(struct device_node *node) { return !!of_match_node(sun4i_tcon_of_table, node); @@ -225,9 +235,11 @@ static int sun4i_drv_add_endpoints(struct device *dev, int count = 0; /* - * We don't support the frontend for now, so we will never - * have a device bound. Just skip over it, but we still want - * the rest our pipeline to be added. + * The frontend has been disabled in some of our old device + * trees. If we find a node that is the frontend and is + * disabled, we should just follow through and parse its + * child, but without adding it to the component list. + * Otherwise, we obviously want to add it to the list. */ if (!sun4i_drv_node_is_frontend(node) && !of_device_is_available(node)) @@ -240,7 +252,14 @@ static int sun4i_drv_add_endpoints(struct device *dev, if (sun4i_drv_node_is_connector(node)) return 0; - if (!sun4i_drv_node_is_frontend(node)) { + /* + * If the device is either just a regular device, or an + * enabled frontend supported by the driver, we add it to our + * component list. + */ + if (!sun4i_drv_node_is_frontend(node) || + (sun4i_drv_node_is_supported_frontend(node) && + of_device_is_available(node))) { /* Add current component */ DRM_DEBUG_DRIVER("Adding component %pOF\n", node); drm_of_component_match_add(dev, match, compare_of, node); diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.h b/drivers/gpu/drm/sun4i/sun4i_drv.h index 2825f140da54..5750b8ce8b31 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.h +++ b/drivers/gpu/drm/sun4i/sun4i_drv.h @@ -19,6 +19,7 @@ struct sun4i_drv { struct list_head engine_list; + struct list_head frontend_list; struct list_head tcon_list; }; diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c index 38a36c0dfa2f..5f29850ef8ac 100644 --- a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -10,6 +10,7 @@ * the License, or (at your option) any later version. */ +#include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_fb_helper.h> #include <drm/drm_fb_cma_helper.h> @@ -19,13 +20,33 @@ #include "sun4i_drv.h" #include "sun4i_framebuffer.h" +static int sun4i_de_atomic_check(struct drm_device *dev, + struct drm_atomic_state *state) +{ + int ret; + + ret = drm_atomic_helper_check_modeset(dev, state); + if (ret) + return ret; + + ret = drm_atomic_normalize_zpos(dev, state); + if (ret) + return ret; + + return drm_atomic_helper_check_planes(dev, state); +} + static const struct drm_mode_config_funcs sun4i_de_mode_config_funcs = { .output_poll_changed = drm_fb_helper_output_poll_changed, - .atomic_check = drm_atomic_helper_check, + .atomic_check = sun4i_de_atomic_check, .atomic_commit = drm_atomic_helper_commit, .fb_create = drm_gem_fb_create, }; +static struct drm_mode_config_helper_funcs sun4i_de_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + int sun4i_framebuffer_init(struct drm_device *drm) { drm_mode_config_reset(drm); @@ -34,6 +55,7 @@ int sun4i_framebuffer_init(struct drm_device *drm) drm->mode_config.max_height = 8192; drm->mode_config.funcs = &sun4i_de_mode_config_funcs; + drm->mode_config.helper_private = &sun4i_de_mode_config_helpers; return drm_fb_cma_fbdev_init(drm, 32, 0); } diff --git a/drivers/gpu/drm/sun4i/sun4i_frontend.c b/drivers/gpu/drm/sun4i/sun4i_frontend.c new file mode 100644 index 000000000000..ddf6cfa6dd23 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_frontend.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 Free Electrons + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ +#include <drm/drmP.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include "sun4i_drv.h" +#include "sun4i_frontend.h" + +static const u32 sun4i_frontend_vert_coef[32] = { + 0x00004000, 0x000140ff, 0x00033ffe, 0x00043ffd, + 0x00063efc, 0xff083dfc, 0x000a3bfb, 0xff0d39fb, + 0xff0f37fb, 0xff1136fa, 0xfe1433fb, 0xfe1631fb, + 0xfd192ffb, 0xfd1c2cfb, 0xfd1f29fb, 0xfc2127fc, + 0xfc2424fc, 0xfc2721fc, 0xfb291ffd, 0xfb2c1cfd, + 0xfb2f19fd, 0xfb3116fe, 0xfb3314fe, 0xfa3611ff, + 0xfb370fff, 0xfb390dff, 0xfb3b0a00, 0xfc3d08ff, + 0xfc3e0600, 0xfd3f0400, 0xfe3f0300, 0xff400100, +}; + +static const u32 sun4i_frontend_horz_coef[64] = { + 0x40000000, 0x00000000, 0x40fe0000, 0x0000ff03, + 0x3ffd0000, 0x0000ff05, 0x3ffc0000, 0x0000ff06, + 0x3efb0000, 0x0000ff08, 0x3dfb0000, 0x0000ff09, + 0x3bfa0000, 0x0000fe0d, 0x39fa0000, 0x0000fe0f, + 0x38fa0000, 0x0000fe10, 0x36fa0000, 0x0000fe12, + 0x33fa0000, 0x0000fd16, 0x31fa0000, 0x0000fd18, + 0x2ffa0000, 0x0000fd1a, 0x2cfa0000, 0x0000fc1e, + 0x29fa0000, 0x0000fc21, 0x27fb0000, 0x0000fb23, + 0x24fb0000, 0x0000fb26, 0x21fb0000, 0x0000fb29, + 0x1ffc0000, 0x0000fa2b, 0x1cfc0000, 0x0000fa2e, + 0x19fd0000, 0x0000fa30, 0x16fd0000, 0x0000fa33, + 0x14fd0000, 0x0000fa35, 0x11fe0000, 0x0000fa37, + 0x0ffe0000, 0x0000fa39, 0x0dfe0000, 0x0000fa3b, + 0x0afe0000, 0x0000fa3e, 0x08ff0000, 0x0000fb3e, + 0x06ff0000, 0x0000fb40, 0x05ff0000, 0x0000fc40, + 0x03ff0000, 0x0000fd41, 0x01ff0000, 0x0000fe42, +}; + +static void sun4i_frontend_scaler_init(struct sun4i_frontend *frontend) +{ + int i; + + for (i = 0; i < 32; i++) { + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZCOEF0_REG(i), + sun4i_frontend_horz_coef[2 * i]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZCOEF0_REG(i), + sun4i_frontend_horz_coef[2 * i]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZCOEF1_REG(i), + sun4i_frontend_horz_coef[2 * i + 1]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZCOEF1_REG(i), + sun4i_frontend_horz_coef[2 * i + 1]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTCOEF_REG(i), + sun4i_frontend_vert_coef[i]); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTCOEF_REG(i), + sun4i_frontend_vert_coef[i]); + } + + regmap_update_bits(frontend->regs, SUN4I_FRONTEND_FRM_CTRL_REG, + SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL, + SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL); +} + +int sun4i_frontend_init(struct sun4i_frontend *frontend) +{ + return pm_runtime_get_sync(frontend->dev); +} +EXPORT_SYMBOL(sun4i_frontend_init); + +void sun4i_frontend_exit(struct sun4i_frontend *frontend) +{ + pm_runtime_put(frontend->dev); +} +EXPORT_SYMBOL(sun4i_frontend_exit); + +void sun4i_frontend_update_buffer(struct sun4i_frontend *frontend, + struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + dma_addr_t paddr; + + /* Set the line width */ + DRM_DEBUG_DRIVER("Frontend stride: %d bytes\n", fb->pitches[0]); + regmap_write(frontend->regs, SUN4I_FRONTEND_LINESTRD0_REG, + fb->pitches[0]); + + /* Set the physical address of the buffer in memory */ + paddr = drm_fb_cma_get_gem_addr(fb, state, 0); + paddr -= PHYS_OFFSET; + DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr); + regmap_write(frontend->regs, SUN4I_FRONTEND_BUF_ADDR0_REG, paddr); +} +EXPORT_SYMBOL(sun4i_frontend_update_buffer); + +static int sun4i_frontend_drm_format_to_input_fmt(uint32_t fmt, u32 *val) +{ + switch (fmt) { + case DRM_FORMAT_ARGB8888: + *val = 5; + return 0; + + default: + return -EINVAL; + } +} + +static int sun4i_frontend_drm_format_to_output_fmt(uint32_t fmt, u32 *val) +{ + switch (fmt) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + *val = 2; + return 0; + + default: + return -EINVAL; + } +} + +int sun4i_frontend_update_formats(struct sun4i_frontend *frontend, + struct drm_plane *plane, uint32_t out_fmt) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + u32 out_fmt_val; + u32 in_fmt_val; + int ret; + + ret = sun4i_frontend_drm_format_to_input_fmt(fb->format->format, + &in_fmt_val); + if (ret) { + DRM_DEBUG_DRIVER("Invalid input format\n"); + return ret; + } + + ret = sun4i_frontend_drm_format_to_output_fmt(out_fmt, &out_fmt_val); + if (ret) { + DRM_DEBUG_DRIVER("Invalid output format\n"); + return ret; + } + + /* + * I have no idea what this does exactly, but it seems to be + * related to the scaler FIR filter phase parameters. + */ + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZPHASE_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZPHASE_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE0_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE0_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTPHASE1_REG, 0x400); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTPHASE1_REG, 0x400); + + regmap_write(frontend->regs, SUN4I_FRONTEND_INPUT_FMT_REG, + SUN4I_FRONTEND_INPUT_FMT_DATA_MOD(1) | + SUN4I_FRONTEND_INPUT_FMT_DATA_FMT(in_fmt_val) | + SUN4I_FRONTEND_INPUT_FMT_PS(1)); + + /* + * TODO: It look like the A31 and A80 at least will need the + * bit 7 (ALPHA_EN) enabled when using a format with alpha (so + * ARGB8888). + */ + regmap_write(frontend->regs, SUN4I_FRONTEND_OUTPUT_FMT_REG, + SUN4I_FRONTEND_OUTPUT_FMT_DATA_FMT(out_fmt_val)); + + return 0; +} +EXPORT_SYMBOL(sun4i_frontend_update_formats); + +void sun4i_frontend_update_coord(struct sun4i_frontend *frontend, + struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + + /* Set height and width */ + DRM_DEBUG_DRIVER("Frontend size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_INSIZE_REG, + SUN4I_FRONTEND_INSIZE(state->src_h >> 16, + state->src_w >> 16)); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_INSIZE_REG, + SUN4I_FRONTEND_INSIZE(state->src_h >> 16, + state->src_w >> 16)); + + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_OUTSIZE_REG, + SUN4I_FRONTEND_OUTSIZE(state->crtc_h, state->crtc_w)); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_OUTSIZE_REG, + SUN4I_FRONTEND_OUTSIZE(state->crtc_h, state->crtc_w)); + + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_HORZFACT_REG, + state->src_w / state->crtc_w); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_HORZFACT_REG, + state->src_w / state->crtc_w); + + regmap_write(frontend->regs, SUN4I_FRONTEND_CH0_VERTFACT_REG, + state->src_h / state->crtc_h); + regmap_write(frontend->regs, SUN4I_FRONTEND_CH1_VERTFACT_REG, + state->src_h / state->crtc_h); + + regmap_write_bits(frontend->regs, SUN4I_FRONTEND_FRM_CTRL_REG, + SUN4I_FRONTEND_FRM_CTRL_REG_RDY, + SUN4I_FRONTEND_FRM_CTRL_REG_RDY); +} +EXPORT_SYMBOL(sun4i_frontend_update_coord); + +int sun4i_frontend_enable(struct sun4i_frontend *frontend) +{ + regmap_write_bits(frontend->regs, SUN4I_FRONTEND_FRM_CTRL_REG, + SUN4I_FRONTEND_FRM_CTRL_FRM_START, + SUN4I_FRONTEND_FRM_CTRL_FRM_START); + + return 0; +} +EXPORT_SYMBOL(sun4i_frontend_enable); + +static struct regmap_config sun4i_frontend_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x0a14, +}; + +static int sun4i_frontend_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sun4i_frontend *frontend; + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct resource *res; + void __iomem *regs; + + frontend = devm_kzalloc(dev, sizeof(*frontend), GFP_KERNEL); + if (!frontend) + return -ENOMEM; + + dev_set_drvdata(dev, frontend); + frontend->dev = dev; + frontend->node = dev->of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + frontend->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_frontend_regmap_config); + if (IS_ERR(frontend->regs)) { + dev_err(dev, "Couldn't create the frontend regmap\n"); + return PTR_ERR(frontend->regs); + } + + frontend->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(frontend->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(frontend->reset); + } + + frontend->bus_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(frontend->bus_clk)) { + dev_err(dev, "Couldn't get our bus clock\n"); + return PTR_ERR(frontend->bus_clk); + } + + frontend->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(frontend->mod_clk)) { + dev_err(dev, "Couldn't get our mod clock\n"); + return PTR_ERR(frontend->mod_clk); + } + + frontend->ram_clk = devm_clk_get(dev, "ram"); + if (IS_ERR(frontend->ram_clk)) { + dev_err(dev, "Couldn't get our ram clock\n"); + return PTR_ERR(frontend->ram_clk); + } + + list_add_tail(&frontend->list, &drv->frontend_list); + pm_runtime_enable(dev); + + return 0; +} + +static void sun4i_frontend_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_frontend *frontend = dev_get_drvdata(dev); + + list_del(&frontend->list); + pm_runtime_force_suspend(dev); +} + +static const struct component_ops sun4i_frontend_ops = { + .bind = sun4i_frontend_bind, + .unbind = sun4i_frontend_unbind, +}; + +static int sun4i_frontend_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_frontend_ops); +} + +static int sun4i_frontend_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_frontend_ops); + + return 0; +} + +static int sun4i_frontend_runtime_resume(struct device *dev) +{ + struct sun4i_frontend *frontend = dev_get_drvdata(dev); + int ret; + + clk_set_rate(frontend->mod_clk, 300000000); + + clk_prepare_enable(frontend->bus_clk); + clk_prepare_enable(frontend->mod_clk); + clk_prepare_enable(frontend->ram_clk); + + ret = reset_control_reset(frontend->reset); + if (ret) { + dev_err(dev, "Couldn't reset our device\n"); + return ret; + } + + regmap_update_bits(frontend->regs, SUN4I_FRONTEND_EN_REG, + SUN4I_FRONTEND_EN_EN, + SUN4I_FRONTEND_EN_EN); + + regmap_update_bits(frontend->regs, SUN4I_FRONTEND_BYPASS_REG, + SUN4I_FRONTEND_BYPASS_CSC_EN, + SUN4I_FRONTEND_BYPASS_CSC_EN); + + sun4i_frontend_scaler_init(frontend); + + return 0; +} + +static int sun4i_frontend_runtime_suspend(struct device *dev) +{ + struct sun4i_frontend *frontend = dev_get_drvdata(dev); + + clk_disable_unprepare(frontend->ram_clk); + clk_disable_unprepare(frontend->mod_clk); + clk_disable_unprepare(frontend->bus_clk); + + reset_control_assert(frontend->reset); + + return 0; +} + +static const struct dev_pm_ops sun4i_frontend_pm_ops = { + .runtime_resume = sun4i_frontend_runtime_resume, + .runtime_suspend = sun4i_frontend_runtime_suspend, +}; + +const struct of_device_id sun4i_frontend_of_table[] = { + { .compatible = "allwinner,sun8i-a33-display-frontend" }, + { } +}; +EXPORT_SYMBOL(sun4i_frontend_of_table); +MODULE_DEVICE_TABLE(of, sun4i_frontend_of_table); + +static struct platform_driver sun4i_frontend_driver = { + .probe = sun4i_frontend_probe, + .remove = sun4i_frontend_remove, + .driver = { + .name = "sun4i-frontend", + .of_match_table = sun4i_frontend_of_table, + .pm = &sun4i_frontend_pm_ops, + }, +}; +module_platform_driver(sun4i_frontend_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 Display Engine Frontend Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_frontend.h b/drivers/gpu/drm/sun4i/sun4i_frontend.h new file mode 100644 index 000000000000..02661ce81f3e --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_frontend.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 Free Electrons + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#ifndef _SUN4I_FRONTEND_H_ +#define _SUN4I_FRONTEND_H_ + +#include <linux/list.h> + +#define SUN4I_FRONTEND_EN_REG 0x000 +#define SUN4I_FRONTEND_EN_EN BIT(0) + +#define SUN4I_FRONTEND_FRM_CTRL_REG 0x004 +#define SUN4I_FRONTEND_FRM_CTRL_COEF_ACCESS_CTRL BIT(23) +#define SUN4I_FRONTEND_FRM_CTRL_FRM_START BIT(16) +#define SUN4I_FRONTEND_FRM_CTRL_COEF_RDY BIT(1) +#define SUN4I_FRONTEND_FRM_CTRL_REG_RDY BIT(0) + +#define SUN4I_FRONTEND_BYPASS_REG 0x008 +#define SUN4I_FRONTEND_BYPASS_CSC_EN BIT(1) + +#define SUN4I_FRONTEND_BUF_ADDR0_REG 0x020 + +#define SUN4I_FRONTEND_LINESTRD0_REG 0x040 + +#define SUN4I_FRONTEND_INPUT_FMT_REG 0x04c +#define SUN4I_FRONTEND_INPUT_FMT_DATA_MOD(mod) ((mod) << 8) +#define SUN4I_FRONTEND_INPUT_FMT_DATA_FMT(fmt) ((fmt) << 4) +#define SUN4I_FRONTEND_INPUT_FMT_PS(ps) (ps) + +#define SUN4I_FRONTEND_OUTPUT_FMT_REG 0x05c +#define SUN4I_FRONTEND_OUTPUT_FMT_DATA_FMT(fmt) (fmt) + +#define SUN4I_FRONTEND_CH0_INSIZE_REG 0x100 +#define SUN4I_FRONTEND_INSIZE(h, w) ((((h) - 1) << 16) | (((w) - 1))) + +#define SUN4I_FRONTEND_CH0_OUTSIZE_REG 0x104 +#define SUN4I_FRONTEND_OUTSIZE(h, w) ((((h) - 1) << 16) | (((w) - 1))) + +#define SUN4I_FRONTEND_CH0_HORZFACT_REG 0x108 +#define SUN4I_FRONTEND_HORZFACT(i, f) (((i) << 16) | (f)) + +#define SUN4I_FRONTEND_CH0_VERTFACT_REG 0x10c +#define SUN4I_FRONTEND_VERTFACT(i, f) (((i) << 16) | (f)) + +#define SUN4I_FRONTEND_CH0_HORZPHASE_REG 0x110 +#define SUN4I_FRONTEND_CH0_VERTPHASE0_REG 0x114 +#define SUN4I_FRONTEND_CH0_VERTPHASE1_REG 0x118 + +#define SUN4I_FRONTEND_CH1_INSIZE_REG 0x200 +#define SUN4I_FRONTEND_CH1_OUTSIZE_REG 0x204 +#define SUN4I_FRONTEND_CH1_HORZFACT_REG 0x208 +#define SUN4I_FRONTEND_CH1_VERTFACT_REG 0x20c + +#define SUN4I_FRONTEND_CH1_HORZPHASE_REG 0x210 +#define SUN4I_FRONTEND_CH1_VERTPHASE0_REG 0x214 +#define SUN4I_FRONTEND_CH1_VERTPHASE1_REG 0x218 + +#define SUN4I_FRONTEND_CH0_HORZCOEF0_REG(i) (0x400 + i * 4) +#define SUN4I_FRONTEND_CH0_HORZCOEF1_REG(i) (0x480 + i * 4) +#define SUN4I_FRONTEND_CH0_VERTCOEF_REG(i) (0x500 + i * 4) +#define SUN4I_FRONTEND_CH1_HORZCOEF0_REG(i) (0x600 + i * 4) +#define SUN4I_FRONTEND_CH1_HORZCOEF1_REG(i) (0x680 + i * 4) +#define SUN4I_FRONTEND_CH1_VERTCOEF_REG(i) (0x700 + i * 4) + +struct clk; +struct device_node; +struct drm_plane; +struct regmap; +struct reset_control; + +struct sun4i_frontend { + struct list_head list; + struct device *dev; + struct device_node *node; + + struct clk *bus_clk; + struct clk *mod_clk; + struct clk *ram_clk; + struct regmap *regs; + struct reset_control *reset; +}; + +extern const struct of_device_id sun4i_frontend_of_table[]; + +int sun4i_frontend_init(struct sun4i_frontend *frontend); +void sun4i_frontend_exit(struct sun4i_frontend *frontend); +int sun4i_frontend_enable(struct sun4i_frontend *frontend); + +void sun4i_frontend_update_buffer(struct sun4i_frontend *frontend, + struct drm_plane *plane); +void sun4i_frontend_update_coord(struct sun4i_frontend *frontend, + struct drm_plane *plane); +int sun4i_frontend_update_formats(struct sun4i_frontend *frontend, + struct drm_plane *plane, uint32_t out_fmt); + +#endif /* _SUN4I_FRONTEND_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c index 7bddf12548d3..19be798e4fac 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.c +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c @@ -15,34 +15,107 @@ #include <drm/drmP.h> #include "sun4i_backend.h" +#include "sun4i_frontend.h" #include "sun4i_layer.h" #include "sunxi_engine.h" struct sun4i_plane_desc { - enum drm_plane_type type; - u8 pipe; - const uint32_t *formats; - uint32_t nformats; + enum drm_plane_type type; + u8 pipe; + const uint32_t *formats; + uint32_t nformats; }; +static void sun4i_backend_layer_reset(struct drm_plane *plane) +{ + struct sun4i_layer *layer = plane_to_sun4i_layer(plane); + struct sun4i_layer_state *state; + + if (plane->state) { + state = state_to_sun4i_layer_state(plane->state); + + __drm_atomic_helper_plane_destroy_state(&state->state); + + kfree(state); + plane->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + plane->state = &state->state; + plane->state->plane = plane; + plane->state->zpos = layer->id; + } +} + +static struct drm_plane_state * +sun4i_backend_layer_duplicate_state(struct drm_plane *plane) +{ + struct sun4i_layer_state *orig = state_to_sun4i_layer_state(plane->state); + struct sun4i_layer_state *copy; + + copy = kzalloc(sizeof(*copy), GFP_KERNEL); + if (!copy) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, ©->state); + copy->uses_frontend = orig->uses_frontend; + + return ©->state; +} + +static void sun4i_backend_layer_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct sun4i_layer_state *s_state = state_to_sun4i_layer_state(state); + + __drm_atomic_helper_plane_destroy_state(state); + + kfree(s_state); +} + static void sun4i_backend_layer_atomic_disable(struct drm_plane *plane, struct drm_plane_state *old_state) { + struct sun4i_layer_state *layer_state = state_to_sun4i_layer_state(old_state); struct sun4i_layer *layer = plane_to_sun4i_layer(plane); struct sun4i_backend *backend = layer->backend; sun4i_backend_layer_enable(backend, layer->id, false); + + if (layer_state->uses_frontend) { + unsigned long flags; + + spin_lock_irqsave(&backend->frontend_lock, flags); + backend->frontend_teardown = true; + spin_unlock_irqrestore(&backend->frontend_lock, flags); + } } static void sun4i_backend_layer_atomic_update(struct drm_plane *plane, struct drm_plane_state *old_state) { + struct sun4i_layer_state *layer_state = state_to_sun4i_layer_state(plane->state); struct sun4i_layer *layer = plane_to_sun4i_layer(plane); struct sun4i_backend *backend = layer->backend; + struct sun4i_frontend *frontend = backend->frontend; + + if (layer_state->uses_frontend) { + sun4i_frontend_init(frontend); + sun4i_frontend_update_coord(frontend, plane); + sun4i_frontend_update_buffer(frontend, plane); + sun4i_frontend_update_formats(frontend, plane, + DRM_FORMAT_ARGB8888); + sun4i_backend_update_layer_frontend(backend, layer->id, + DRM_FORMAT_ARGB8888); + sun4i_frontend_enable(frontend); + } else { + sun4i_backend_update_layer_formats(backend, layer->id, plane); + sun4i_backend_update_layer_buffer(backend, layer->id, plane); + } sun4i_backend_update_layer_coord(backend, layer->id, plane); - sun4i_backend_update_layer_formats(backend, layer->id, plane); - sun4i_backend_update_layer_buffer(backend, layer->id, plane); + sun4i_backend_update_layer_zpos(backend, layer->id, plane); sun4i_backend_layer_enable(backend, layer->id, true); } @@ -52,11 +125,11 @@ static const struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = { }; static const struct drm_plane_funcs sun4i_backend_layer_funcs = { - .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, - .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = sun4i_backend_layer_destroy_state, + .atomic_duplicate_state = sun4i_backend_layer_duplicate_state, .destroy = drm_plane_cleanup, .disable_plane = drm_atomic_helper_disable_plane, - .reset = drm_atomic_helper_plane_reset, + .reset = sun4i_backend_layer_reset, .update_plane = drm_atomic_helper_update_plane, }; @@ -128,32 +201,12 @@ struct drm_plane **sun4i_layers_init(struct drm_device *drm, struct sun4i_backend *backend = engine_to_sun4i_backend(engine); int i; - planes = devm_kcalloc(drm->dev, ARRAY_SIZE(sun4i_backend_planes) + 1, + /* We need to have a sentinel at the need, hence the overallocation */ + planes = devm_kcalloc(drm->dev, SUN4I_BACKEND_NUM_LAYERS + 1, sizeof(*planes), GFP_KERNEL); if (!planes) return ERR_PTR(-ENOMEM); - /* - * The hardware is a bit unusual here. - * - * Even though it supports 4 layers, it does the composition - * in two separate steps. - * - * The first one is assigning a layer to one of its two - * pipes. If more that 1 layer is assigned to the same pipe, - * and if pixels overlaps, the pipe will take the pixel from - * the layer with the highest priority. - * - * The second step is the actual alpha blending, that takes - * the two pipes as input, and uses the eventual alpha - * component to do the transparency between the two. - * - * This two steps scenario makes us unable to guarantee a - * robust alpha blending between the 4 layers in all - * situations. So we just expose two layers, one per pipe. On - * SoCs that support it, sprites could fill the need for more - * layers. - */ for (i = 0; i < ARRAY_SIZE(sun4i_backend_planes); i++) { const struct sun4i_plane_desc *plane = &sun4i_backend_planes[i]; struct sun4i_layer *layer; @@ -165,6 +218,8 @@ struct drm_plane **sun4i_layers_init(struct drm_device *drm, return ERR_CAST(layer); }; + drm_plane_create_zpos_immutable_property(&layer->plane, i); + DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n", i ? "overlay" : "primary", plane->pipe); regmap_update_bits(engine->regs, SUN4I_BACKEND_ATTCTL_REG0(i), diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.h b/drivers/gpu/drm/sun4i/sun4i_layer.h index 4e84f438b346..75b4868ba87c 100644 --- a/drivers/gpu/drm/sun4i/sun4i_layer.h +++ b/drivers/gpu/drm/sun4i/sun4i_layer.h @@ -22,12 +22,23 @@ struct sun4i_layer { int id; }; +struct sun4i_layer_state { + struct drm_plane_state state; + bool uses_frontend; +}; + static inline struct sun4i_layer * plane_to_sun4i_layer(struct drm_plane *plane) { return container_of(plane, struct sun4i_layer, plane); } +static inline struct sun4i_layer_state * +state_to_sun4i_layer_state(struct drm_plane_state *state) +{ + return container_of(state, struct sun4i_layer_state, state); +} + struct drm_plane **sun4i_layers_init(struct drm_device *drm, struct sunxi_engine *engine); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 3c15cf24b503..4b7340ee6290 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -540,6 +540,7 @@ static irqreturn_t sun4i_tcon_handler(int irq, void *private) struct sun4i_tcon *tcon = private; struct drm_device *drm = tcon->drm; struct sun4i_crtc *scrtc = tcon->crtc; + struct sunxi_engine *engine = scrtc->engine; unsigned int status; regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status); @@ -557,6 +558,9 @@ static irqreturn_t sun4i_tcon_handler(int irq, void *private) SUN4I_TCON_GINT0_VBLANK_INT(1), 0); + if (engine->ops->vblank_quirk) + engine->ops->vblank_quirk(engine); + return IRQ_HANDLED; } diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 28d7c48d50fe..2f0ccd50b54d 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -211,7 +211,7 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, struct drm_crtc *crtc = state->crtc; struct drm_crtc_state *crtc_state; int min_scale, max_scale; - struct drm_rect clip; + struct drm_rect clip = {}; if (!crtc) return 0; @@ -220,10 +220,9 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, if (WARN_ON(!crtc_state)) return -EINVAL; - clip.x1 = 0; - clip.y1 = 0; - clip.x2 = crtc_state->adjusted_mode.hdisplay; - clip.y2 = crtc_state->adjusted_mode.vdisplay; + if (crtc_state->enable) + drm_mode_get_hv_timing(&crtc_state->mode, + &clip.x2, &clip.y2); min_scale = DRM_PLANE_HELPER_NO_SCALING; max_scale = DRM_PLANE_HELPER_NO_SCALING; diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 40c3b303068a..eb3bf2d7291a 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -239,7 +239,7 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, struct drm_crtc *crtc = state->crtc; struct drm_crtc_state *crtc_state; int min_scale, max_scale; - struct drm_rect clip; + struct drm_rect clip = {}; if (!crtc) return 0; @@ -248,10 +248,9 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, if (WARN_ON(!crtc_state)) return -EINVAL; - clip.x1 = 0; - clip.y1 = 0; - clip.x2 = crtc_state->adjusted_mode.hdisplay; - clip.y2 = crtc_state->adjusted_mode.vdisplay; + if (crtc_state->enable) + drm_mode_get_hv_timing(&crtc_state->mode, + &clip.x2, &clip.y2); min_scale = DRM_PLANE_HELPER_NO_SCALING; max_scale = DRM_PLANE_HELPER_NO_SCALING; diff --git a/drivers/gpu/drm/sun4i/sunxi_engine.h b/drivers/gpu/drm/sun4i/sunxi_engine.h index 4cb70ae65c79..d317ea04b8aa 100644 --- a/drivers/gpu/drm/sun4i/sunxi_engine.h +++ b/drivers/gpu/drm/sun4i/sunxi_engine.h @@ -12,16 +12,106 @@ struct drm_plane; struct drm_device; +struct drm_crtc_state; struct sunxi_engine; +/** + * struct sunxi_engine_ops - helper operations for sunXi engines + * + * These hooks are used by the common part of the DRM driver to + * implement the proper behaviour. + */ struct sunxi_engine_ops { + /** + * @atomic_begin: + * + * This callback allows to prepare our engine for an atomic + * update. This is mirroring the + * &drm_crtc_helper_funcs.atomic_begin callback, so any + * documentation there applies. + * + * This function is optional. + */ + void (*atomic_begin)(struct sunxi_engine *engine, + struct drm_crtc_state *old_state); + + /** + * @atomic_check: + * + * This callback allows to validate plane-update related CRTC + * constraints specific to engines. This is mirroring the + * &drm_crtc_helper_funcs.atomic_check callback, so any + * documentation there applies. + * + * This function is optional. + * + * RETURNS: + * + * 0 on success or a negative error code. + */ + int (*atomic_check)(struct sunxi_engine *engine, + struct drm_crtc_state *state); + + /** + * @commit: + * + * This callback will trigger the hardware switch to commit + * the new configuration that has been setup during the next + * vblank period. + * + * This function is optional. + */ void (*commit)(struct sunxi_engine *engine); + + /** + * @layers_init: + * + * This callback is used to allocate, initialize and register + * the layers supported by that engine. + * + * This function is mandatory. + * + * RETURNS: + * + * The array of struct drm_plane backing the layers, or an + * error pointer on failure. + */ struct drm_plane **(*layers_init)(struct drm_device *drm, struct sunxi_engine *engine); + /** + * @apply_color_correction: + * + * This callback will enable the color correction in the + * engine. This is useful only for the composite output. + * + * This function is optional. + */ void (*apply_color_correction)(struct sunxi_engine *engine); + + /** + * @disable_color_correction: + * + * This callback will stop the color correction in the + * engine. This is useful only for the composite output. + * + * This function is optional. + */ void (*disable_color_correction)(struct sunxi_engine *engine); + + /** + * @vblank_quirk: + * + * This callback is used to implement engine-specific + * behaviour part of the VBLANK event. It is run with all the + * constraints of an interrupt (can't sleep, all local + * interrupts disabled) and therefore should be as fast as + * possible. + * + * This function is optional. + */ + void (*vblank_quirk)(struct sunxi_engine *engine); }; /** |