summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/sun4i
diff options
context:
space:
mode:
authorDave Airlie2018-02-16 00:29:27 +0100
committerDave Airlie2018-02-16 00:29:27 +0100
commit76ea0f334e7fb13226e64ee7de928611f5303faf (patch)
tree4a6b8d8f1b19a68072f2ce8f33add59be9a67be7 /drivers/gpu/drm/sun4i
parentMerge tag 'drm-intel-next-2018-02-07' of git://anongit.freedesktop.org/drm/dr... (diff)
parentdrm/radeon: only enable swiotlb path when need v2 (diff)
downloadkernel-qcow2-linux-76ea0f334e7fb13226e64ee7de928611f5303faf.tar.gz
kernel-qcow2-linux-76ea0f334e7fb13226e64ee7de928611f5303faf.tar.xz
kernel-qcow2-linux-76ea0f334e7fb13226e64ee7de928611f5303faf.zip
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/Makefile3
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_backend.c248
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_backend.h18
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_crtc.c21
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_drv.c27
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_drv.h1
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_framebuffer.c24
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_frontend.c389
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_frontend.h99
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_layer.c117
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_layer.h11
-rw-r--r--drivers/gpu/drm/sun4i/sun4i_tcon.c4
-rw-r--r--drivers/gpu/drm/sun4i/sun8i_ui_layer.c9
-rw-r--r--drivers/gpu/drm/sun4i/sun8i_vi_layer.c9
-rw-r--r--drivers/gpu/drm/sun4i/sunxi_engine.h90
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, &copy->state);
+ copy->uses_frontend = orig->uses_frontend;
+
+ return &copy->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);
};
/**