[PATCH 5/7] drm/vc4: Add support for the TXP (transposer) block
Brian Starkey
brian.starkey at arm.com
Mon Jun 5 04:25:50 PDT 2017
Hi Boris,
I can't speak for the HW-specific details, but the writeback part
looks pretty good (and familiar ;-) to me. There certainly seems to be
some scope for code-sharing there, but I think it's fine not to do it
yet.
I've a further query below about the handling of CRTC events.
On Fri, Jun 02, 2017 at 10:32:10AM +0200, Boris Brezillon wrote:
>The TXP block is providing writeback support. Add a driver to expose this
>feature.
>
>Supporting this engine requires reworking the CRTC because it's not using
>the usual 'HVS -> PV -> VC4 Encoder' scheme. Instead, the TXP block is
>directly connected to HVS FIFO2, and the PV is completely bypassed.
>
>In order to make things work, we have to detect when the TXP is enabled,
>avoid enabling the PV when this is the case, and generate fake VBLANK
>events when the TXP is done writing the frame back to memory.
>
>Signed-off-by: Boris Brezillon <boris.brezillon at free-electrons.com>
>---
[snip]
>diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c
>new file mode 100644
>index 000000000000..1886e031bef3
>--- /dev/null
>+++ b/drivers/gpu/drm/vc4/vc4_txp.c
>@@ -0,0 +1,509 @@
>+/*
>+ * Copyright © 2017 Broadcom
>+ *
>+ * Author: Boris Brezillon <boris.brezillon at free-electrons.com>
>+ *
>+ * Permission is hereby granted, free of charge, to any person obtaining a
>+ * copy of this software and associated documentation files (the "Software"),
>+ * to deal in the Software without restriction, including without limitation
>+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
>+ * and/or sell copies of the Software, and to permit persons to whom the
>+ * Software is furnished to do so, subject to the following conditions:
>+ *
>+ * The above copyright notice and this permission notice (including the next
>+ * paragraph) shall be included in all copies or substantial portions of the
>+ * Software.
>+ *
>+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
>+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
>+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
>+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
>+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
>+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
>+ * IN THE SOFTWARE.
>+ */
>+
>+#include <drm/drm_atomic.h>
>+#include <drm/drm_atomic_helper.h>
>+#include <drm/drm_fb_cma_helper.h>
>+#include <drm/drm_crtc_helper.h>
>+#include <drm/drm_edid.h>
>+#include <drm/drm_panel.h>
>+#include <drm/drm_writeback.h>
>+#include <linux/clk.h>
>+#include <linux/component.h>
>+#include <linux/of_graph.h>
>+#include <linux/of_platform.h>
>+#include <linux/pm_runtime.h>
>+
>+#include "vc4_drv.h"
>+#include "vc4_regs.h"
>+
>+/* Base address of the output. Raster formats must be 4-byte aligned,
>+ * T and LT must be 16-byte aligned or maybe utile-aligned (docs are
>+ * inconsistent, but probably utile).
>+ */
>+#define TXP_DST_PTR 0x00
>+
>+/* Pitch in bytes for raster images, 16-byte aligned. For tiled, it's
>+ * the width in tiles.
>+ */
>+#define TXP_DST_PITCH 0x04
>+/* For T-tiled imgaes, DST_PITCH should be the number of tiles wide,
>+ * shifted up.
>+ */
>+# define TXP_T_TILE_WIDTH_SHIFT 7
>+/* For LT-tiled images, DST_PITCH should be the number of utiles wide,
>+ * shifted up.
>+ */
>+# define TXP_LT_TILE_WIDTH_SHIFT 4
>+
>+/* Pre-rotation width/height of the image. Must match HVS config.
>+ *
>+ * If TFORMAT and 32-bit, limit is 1920 for 32-bit and 3840 to 16-bit
>+ * and width/height must be tile or utile-aligned as appropriate. If
>+ * transposing (rotating), width is limited to 1920.
>+ *
>+ * Height is limited to various numbers between 4088 and 4095. I'd
>+ * just use 4088 to be safe.
>+ */
>+#define TXP_DIM 0x08
>+# define TXP_HEIGHT_SHIFT 16
>+# define TXP_HEIGHT_MASK GENMASK(31, 16)
>+# define TXP_WIDTH_SHIFT 0
>+# define TXP_WIDTH_MASK GENMASK(15, 0)
>+
>+#define TXP_DST_CTRL 0x0c
>+/* These bits are set to 0x54 */
>+#define TXP_PILOT_SHIFT 24
>+#define TXP_PILOT_MASK GENMASK(31, 24)
>+/* Bits 22-23 are set to 0x01 */
>+#define TXP_VERSION_SHIFT 22
>+#define TXP_VERSION_MASK GENMASK(23, 22)
>+
>+/* Powers down the internal memory. */
>+# define TXP_POWERDOWN BIT(21)
>+
>+/* Enables storing the alpha component in 8888/4444, instead of
>+ * filling with ~ALPHA_INVERT.
>+ */
>+# define TXP_ALPHA_ENABLE BIT(20)
>+
>+/* 4 bits, each enables stores for a channel in each set of 4 bytes.
>+ * Set to 0xf for normal operation.
>+ */
>+# define TXP_BYTE_ENABLE_SHIFT 16
>+# define TXP_BYTE_ENABLE_MASK GENMASK(19, 16)
>+
>+/* Debug: Generate VSTART again at EOF. */
>+# define TXP_VSTART_AT_EOF BIT(15)
>+
>+/* Debug: Terminate the current frame immediately. Stops AXI
>+ * writes.
>+ */
>+# define TXP_ABORT BIT(14)
>+
>+# define TXP_DITHER BIT(13)
>+
>+/* Inverts alpha if TXP_ALPHA_ENABLE, chooses fill value for
>+ * !TXP_ALPHA_ENABLE.
>+ */
>+# define TXP_ALPHA_INVERT BIT(12)
>+
>+/* Note: I've listed the channels here in high bit (in byte 3/2/1) to
>+ * low bit (in byte 0) order.
>+ */
>+# define TXP_FORMAT_SHIFT 8
>+# define TXP_FORMAT_MASK GENMASK(11, 8)
>+# define TXP_FORMAT_ABGR4444 0
>+# define TXP_FORMAT_ARGB4444 1
>+# define TXP_FORMAT_BGRA4444 2
>+# define TXP_FORMAT_RGBA4444 3
>+# define TXP_FORMAT_BGR565 6
>+# define TXP_FORMAT_RGB565 7
>+/* 888s are non-rotated, raster-only */
>+# define TXP_FORMAT_BGR888 8
>+# define TXP_FORMAT_RGB888 9
>+# define TXP_FORMAT_ABGR8888 12
>+# define TXP_FORMAT_ARGB8888 13
>+# define TXP_FORMAT_BGRA8888 14
>+# define TXP_FORMAT_RGBA8888 15
>+
>+/* If TFORMAT is set, generates LT instead of T format. */
>+# define TXP_LINEAR_UTILE BIT(7)
>+
>+/* Rotate output by 90 degrees. */
>+# define TXP_TRANSPOSE BIT(6)
>+
>+/* Generate a tiled format for V3D. */
>+# define TXP_TFORMAT BIT(5)
>+
>+/* Generates some undefined test mode output. */
>+# define TXP_TEST_MODE BIT(4)
>+
>+/* Request odd field from HVS. */
>+# define TXP_FIELD BIT(3)
>+
>+/* Raise interrupt when idle. */
>+# define TXP_EI BIT(2)
>+
>+/* Set when generating a frame, clears when idle. */
>+# define TXP_BUSY BIT(1)
>+
>+/* Starts a frame. Self-clearing. */
>+# define TXP_GO BIT(0)
>+
>+/* Number of lines received and committed to memory. */
>+#define TXP_PROGRESS 0x10
>+
>+#define TXP_READ(offset) readl(txp->regs + (offset))
>+#define TXP_WRITE(offset, val) writel(val, txp->regs + (offset))
>+
>+struct vc4_txp {
>+ struct platform_device *pdev;
>+
>+ struct drm_writeback_connector connector;
>+
>+ void __iomem *regs;
>+};
>+
>+static inline struct vc4_txp *connector_to_vc4_txp(struct drm_connector *conn)
>+{
>+ return container_of(conn, struct vc4_txp, connector.base);
>+}
>+
>+#define TXP_REG(reg) { reg, #reg }
>+static const struct {
>+ u32 reg;
>+ const char *name;
>+} txp_regs[] = {
>+ TXP_REG(TXP_DST_PTR),
>+ TXP_REG(TXP_DST_PITCH),
>+ TXP_REG(TXP_DIM),
>+ TXP_REG(TXP_DST_CTRL),
>+ TXP_REG(TXP_PROGRESS),
>+};
>+
>+#ifdef CONFIG_DEBUG_FS
>+int vc4_txp_debugfs_regs(struct seq_file *m, void *unused)
>+{
>+ struct drm_info_node *node = (struct drm_info_node *)m->private;
>+ struct drm_device *dev = node->minor->dev;
>+ struct vc4_dev *vc4 = to_vc4_dev(dev);
>+ struct vc4_txp *txp = vc4->txp;
>+ int i;
>+
>+ if (!txp)
>+ return 0;
>+
>+ for (i = 0; i < ARRAY_SIZE(txp_regs); i++) {
>+ seq_printf(m, "%s (0x%04x): 0x%08x\n",
>+ txp_regs[i].name, txp_regs[i].reg,
>+ TXP_READ(txp_regs[i].reg));
>+ }
>+
>+ return 0;
>+}
>+#endif
>+
>+static int vc4_txp_connector_get_modes(struct drm_connector *connector)
>+{
>+ struct drm_device *dev = connector->dev;
>+
>+ return drm_add_modes_noedid(connector, dev->mode_config.max_width,
>+ dev->mode_config.max_height);
>+}
>+
>+static enum drm_mode_status
>+vc4_txp_connector_mode_valid(struct drm_connector *connector,
>+ struct drm_display_mode *mode)
>+{
>+ struct drm_device *dev = connector->dev;
>+ struct drm_mode_config *mode_config = &dev->mode_config;
>+ int w = mode->hdisplay, h = mode->vdisplay;
>+
>+ if (w < mode_config->min_width || w > mode_config->max_width)
>+ return MODE_BAD_HVALUE;
>+
>+ if (h < mode_config->min_height || h > mode_config->max_height)
>+ return MODE_BAD_VVALUE;
>+
>+ return MODE_OK;
>+}
>+
>+static const u32 vc4_txp_formats[] = {
>+ DRM_FORMAT_RGB888,
>+ DRM_FORMAT_BGR888,
>+ DRM_FORMAT_XRGB8888,
>+ DRM_FORMAT_XBGR8888,
>+ DRM_FORMAT_ARGB8888,
>+ DRM_FORMAT_ABGR8888,
>+ DRM_FORMAT_RGBX8888,
>+ DRM_FORMAT_BGRX8888,
>+ DRM_FORMAT_RGBA8888,
>+ DRM_FORMAT_BGRA8888,
>+};
>+
>+static int
>+vc4_txp_connector_atomic_check(struct drm_connector *connector,
>+ struct drm_connector_state *conn_state)
>+{
>+ struct drm_crtc_state *crtc_state;
>+ struct drm_gem_cma_object *gem;
>+ struct drm_framebuffer *fb;
>+ int i;
>+
>+ if (!conn_state->crtc)
>+ return 0;
>+
>+ if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
>+ return 0;
>+
>+ crtc_state = drm_atomic_get_crtc_state(conn_state->state,
>+ conn_state->crtc);
>+ fb = conn_state->writeback_job->fb;
>+ if (fb->width != crtc_state->mode.hdisplay ||
>+ fb->height != crtc_state->mode.vdisplay) {
>+ DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n",
>+ fb->width, fb->height);
>+ return -EINVAL;
>+ }
>+
>+ for (i = 0; i < ARRAY_SIZE(vc4_txp_formats); i++) {
>+ if (fb->format->format == vc4_txp_formats[i])
>+ break;
>+ }
>+
>+ if (i == ARRAY_SIZE(vc4_txp_formats))
>+ return -EINVAL;
>+
>+ gem = drm_fb_cma_get_gem_obj(fb, 0);
>+
>+ /* Pitch must be aligned on 16 bytes. */
>+ if (fb->pitches[0] & GENMASK(3, 0))
>+ return -EINVAL;
>+
>+ return 0;
>+}
>+
>+void vc4_txp_atomic_commit(struct drm_device *dev,
>+ struct drm_atomic_state *old_state)
>+{
>+ struct vc4_dev *vc4 = to_vc4_dev(dev);
>+ struct vc4_txp *txp = vc4->txp;
>+ struct drm_connector_state *conn_state = txp->connector.base.state;
>+ struct drm_display_mode *mode;
>+ struct drm_gem_cma_object *gem;
>+ struct drm_framebuffer *fb;
>+ u32 ctrl = TXP_GO | TXP_EI;
>+
>+ /* Writeback connector is disabled, nothing to do. */
>+ if (!conn_state->crtc)
>+ return;
>+
>+ /* Writeback connector is enabled, but has no FB assigned to it. Fake a
>+ * vblank event to complete ->flip_done.
>+ */
>+ if (!conn_state->writeback_job || !conn_state->writeback_job->fb) {
>+ vc4_crtc_eof_event(conn_state->crtc);
I'm not sure about hiding away the one-shot thing like this. If the
CRTC remains "active" from the API point of view, I'd expect it to be
able to keep generating VBLANK events.
I don't know how to do if, but if there were some notion of
"auto-disabling" CRTCs then this quirk would go away, and you'd also
be able to enforce that the CRTC can't be enabled without a writeback
framebuffer.
I'm also not sure how (if?) this works today with a CRTC driving a DSI
command-mode panel. Does the CRTC keep generating VBLANKs even when
there are no updates?
>+ return;
>+ }
>+
>+ fb = conn_state->writeback_job->fb;
>+
>+ switch (fb->format->format) {
>+ case DRM_FORMAT_ARGB8888:
>+ ctrl |= TXP_ALPHA_ENABLE;
>+ case DRM_FORMAT_XRGB8888:
>+ ctrl |= VC4_SET_FIELD(TXP_FORMAT_ARGB8888, TXP_FORMAT) |
>+ VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE);
>+ break;
>+
>+ case DRM_FORMAT_ABGR8888:
>+ ctrl |= TXP_ALPHA_ENABLE;
>+ case DRM_FORMAT_XBGR8888:
>+ ctrl |= VC4_SET_FIELD(TXP_FORMAT_ABGR8888, TXP_FORMAT) |
>+ VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE);
>+ break;
>+
>+ case DRM_FORMAT_RGBA8888:
>+ ctrl |= TXP_ALPHA_ENABLE;
>+ case DRM_FORMAT_RGBX8888:
>+ ctrl |= VC4_SET_FIELD(TXP_FORMAT_RGBA8888, TXP_FORMAT) |
>+ VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE);
>+ break;
>+
>+ case DRM_FORMAT_BGRA8888:
>+ ctrl |= TXP_ALPHA_ENABLE;
>+ case DRM_FORMAT_BGRX8888:
>+ ctrl |= VC4_SET_FIELD(TXP_FORMAT_BGRA8888, TXP_FORMAT) |
>+ VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE);
>+ break;
>+
>+ case DRM_FORMAT_BGR888:
>+ ctrl |= VC4_SET_FIELD(TXP_FORMAT_BGR888, TXP_FORMAT) |
>+ VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE);
>+ break;
>+
>+ case DRM_FORMAT_RGB888:
>+ ctrl |= VC4_SET_FIELD(TXP_FORMAT_RGB888, TXP_FORMAT) |
>+ VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE);
>+ break;
>+ default:
>+ WARN_ON(1);
>+ return;
>+ }
>+
>+ if (!(ctrl & TXP_ALPHA_ENABLE))
>+ ctrl |= TXP_ALPHA_INVERT;
>+
>+ mode = &conn_state->crtc->state->adjusted_mode;
>+ gem = drm_fb_cma_get_gem_obj(fb, 0);
>+ TXP_WRITE(TXP_DST_PTR, gem->paddr + fb->offsets[0]);
>+ TXP_WRITE(TXP_DST_PITCH, fb->pitches[0]);
>+ TXP_WRITE(TXP_DIM,
>+ VC4_SET_FIELD(mode->hdisplay, TXP_WIDTH) |
>+ VC4_SET_FIELD(mode->vdisplay, TXP_HEIGHT));
>+
>+ TXP_WRITE(TXP_DST_CTRL, ctrl);
>+
>+ drm_writeback_queue_job(&txp->connector, conn_state->writeback_job);
>+ conn_state->writeback_job = NULL;
>+}
>+
>+bool vc4_is_txp_encoder(struct drm_device *dev, struct drm_encoder *encoder)
>+{
>+ struct vc4_dev *vc4 = to_vc4_dev(dev);
>+
>+ return encoder == &vc4->txp->connector.encoder;
>+}
>+
>+static const struct drm_connector_helper_funcs vc4_txp_connector_helper_funcs = {
>+ .get_modes = vc4_txp_connector_get_modes,
>+ .mode_valid = vc4_txp_connector_mode_valid,
>+ .atomic_check = vc4_txp_connector_atomic_check,
huh. This hook didn't exist when I did Mali-DP. I wonder if we should
switch Mali-DP to it too. Do you know if the semantics are any
different from the encoder atomic_check?
Cheers,
-Brian
>+};
>+
>+static enum drm_connector_status
>+vc4_txp_connector_detect(struct drm_connector *connector, bool force)
>+{
>+ return connector_status_disconnected;
>+}
>+
>+static void vc4_txp_connector_destroy(struct drm_connector *connector)
>+{
>+ drm_connector_unregister(connector);
>+ drm_connector_cleanup(connector);
>+}
>+
>+static const struct drm_connector_funcs vc4_txp_connector_funcs = {
>+ .dpms = drm_atomic_helper_connector_dpms,
>+ .detect = vc4_txp_connector_detect,
>+ .fill_modes = drm_helper_probe_single_connector_modes,
>+ .set_property = drm_atomic_helper_connector_set_property,
>+ .destroy = vc4_txp_connector_destroy,
>+ .reset = drm_atomic_helper_connector_reset,
>+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
>+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>+};
>+
>+static const struct drm_encoder_funcs vc4_txp_encoder_funcs = {
>+ .destroy = drm_encoder_cleanup,
>+};
>+
>+static irqreturn_t vc4_txp_interrupt(int irq, void *data)
>+{
>+ struct vc4_txp *txp = data;
>+ struct drm_crtc *crtc = txp->connector.base.state->crtc;
>+
>+ TXP_WRITE(TXP_DST_CTRL, 0);
>+ vc4_crtc_eof_event(crtc);
>+ drm_writeback_signal_completion(&txp->connector, 0);
>+
>+ return IRQ_HANDLED;
>+}
>+
>+static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
>+{
>+ struct platform_device *pdev = to_platform_device(dev);
>+ struct drm_device *drm = dev_get_drvdata(master);
>+ struct vc4_dev *vc4 = to_vc4_dev(drm);
>+ struct vc4_txp *txp;
>+ int ret, irq;
>+
>+ irq = platform_get_irq(pdev, 0);
>+ if (irq < 0)
>+ return irq;
>+
>+ txp = devm_kzalloc(dev, sizeof(*txp), GFP_KERNEL);
>+ if (!txp)
>+ return -ENOMEM;
>+
>+ txp->pdev = pdev;
>+
>+ txp->regs = vc4_ioremap_regs(pdev, 0);
>+ if (IS_ERR(txp->regs))
>+ return PTR_ERR(txp->regs);
>+
>+ drm_connector_helper_add(&txp->connector.base,
>+ &vc4_txp_connector_helper_funcs);
>+ ret = drm_writeback_connector_init(drm, &txp->connector,
>+ &vc4_txp_connector_funcs,
>+ NULL,
>+ vc4_txp_formats,
>+ ARRAY_SIZE(vc4_txp_formats));
>+ if (ret)
>+ return ret;
>+
>+ ret = devm_request_irq(dev, irq, vc4_txp_interrupt, 0,
>+ dev_name(dev), txp);
>+ if (ret)
>+ return ret;
>+
>+ dev_set_drvdata(dev, txp);
>+ vc4->txp = txp;
>+
>+ return 0;
>+}
>+
>+static void vc4_txp_unbind(struct device *dev, struct device *master,
>+ void *data)
>+{
>+ struct drm_device *drm = dev_get_drvdata(master);
>+ struct vc4_dev *vc4 = to_vc4_dev(drm);
>+ struct vc4_txp *txp = dev_get_drvdata(dev);
>+
>+ vc4_txp_connector_destroy(&txp->connector.base);
>+
>+ vc4->txp = NULL;
>+}
>+
>+static const struct component_ops vc4_txp_ops = {
>+ .bind = vc4_txp_bind,
>+ .unbind = vc4_txp_unbind,
>+};
>+
>+static int vc4_txp_dev_probe(struct platform_device *pdev)
>+{
>+ return component_add(&pdev->dev, &vc4_txp_ops);
>+}
>+
>+static int vc4_txp_dev_remove(struct platform_device *pdev)
>+{
>+ component_del(&pdev->dev, &vc4_txp_ops);
>+ return 0;
>+}
>+
>+static const struct of_device_id vc4_txp_dt_match[] = {
>+ { .compatible = "brcm,bcm2835-txp" },
>+ { /* sentinel */ },
>+};
>+
>+struct platform_driver vc4_txp_driver = {
>+ .probe = vc4_txp_dev_probe,
>+ .remove = vc4_txp_dev_remove,
>+ .driver = {
>+ .name = "vc4_txp",
>+ .of_match_table = vc4_txp_dt_match,
>+ },
>+};
>--
>2.7.4
>
More information about the linux-rpi-kernel
mailing list