[PATCH v3 09/12] drm/ingenic: Add support for OSD mode

From: Paul Cercueil
Date: Thu Jul 16 2020 - 12:39:58 EST


All Ingenic SoCs starting from the JZ4725B support OSD mode.

In this mode, two separate planes can be used. They can have different
positions and sizes, and one can be overlayed on top of the other.

v2: Use fallthrough; instead of /* fall-through */

v3: - Add custom atomic_tail function to handle case where HW gives no
VBLANK
- Use regmap_set_bits() / regmap_clear_bits() when possible
- Use dma_hwdesc_f{0,1} fields in priv structure instead of array
- Use dmam_alloc_coherent() instead of dma_alloc_coherent()
- Use more meaningful 0xf0 / 0xf1 values as DMA descriptors IDs
- Add a bit more code comments

Signed-off-by: Paul Cercueil <paul@xxxxxxxxxxxxxxx>
---
drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 303 ++++++++++++++++++----
drivers/gpu/drm/ingenic/ingenic-drm.h | 35 +++
2 files changed, 288 insertions(+), 50 deletions(-)

diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
index 9cc785776594..e922b910ad39 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
+++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
@@ -43,12 +43,18 @@ struct ingenic_dma_hwdesc {

struct jz_soc_info {
bool needs_dev_clk;
+ bool has_osd;
unsigned int max_width, max_height;
};

struct ingenic_drm {
struct drm_device drm;
- struct drm_plane primary;
+ /*
+ * f1 (aka. foreground1) is our primary plane, on top of which
+ * f0 (aka. foreground0) can be overlayed. Z-order is fixed in
+ * hardware and cannot be changed.
+ */
+ struct drm_plane f0, f1;
struct drm_crtc crtc;
struct drm_encoder encoder;

@@ -57,10 +63,11 @@ struct ingenic_drm {
struct clk *lcd_clk, *pix_clk;
const struct jz_soc_info *soc_info;

- struct ingenic_dma_hwdesc *dma_hwdesc;
- dma_addr_t dma_hwdesc_phys;
+ struct ingenic_dma_hwdesc *dma_hwdesc_f0, *dma_hwdesc_f1;
+ dma_addr_t dma_hwdesc_phys_f0, dma_hwdesc_phys_f1;

bool panel_is_sharp;
+ bool no_vblank;
};

static const u32 ingenic_drm_primary_formats[] = {
@@ -90,7 +97,7 @@ static const struct regmap_config ingenic_drm_regmap_config = {
.val_bits = 32,
.reg_stride = 4,

- .max_register = JZ_REG_LCD_CMD1,
+ .max_register = JZ_REG_LCD_SIZE1,
.writeable_reg = ingenic_drm_writeable_reg,
};

@@ -110,11 +117,6 @@ drm_encoder_get_priv(struct drm_encoder *encoder)
return container_of(encoder, struct ingenic_drm, encoder);
}

-static inline struct ingenic_drm *drm_plane_get_priv(struct drm_plane *plane)
-{
- return container_of(plane, struct ingenic_drm, primary);
-}
-
static void ingenic_drm_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
@@ -185,34 +187,16 @@ static void ingenic_drm_crtc_update_timings(struct ingenic_drm *priv,
regmap_write(priv->map, JZ_REG_LCD_SPL, hpe << 16 | (hpe + 1));
regmap_write(priv->map, JZ_REG_LCD_REV, mode->htotal << 16);
}
-}
-
-static void ingenic_drm_crtc_update_ctrl(struct ingenic_drm *priv,
- const struct drm_format_info *finfo)
-{
- unsigned int ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16;
-
- switch (finfo->format) {
- case DRM_FORMAT_XRGB1555:
- ctrl |= JZ_LCD_CTRL_RGB555;
- /* fall-through */
- case DRM_FORMAT_RGB565:
- ctrl |= JZ_LCD_CTRL_BPP_15_16;
- break;
- case DRM_FORMAT_XRGB8888:
- ctrl |= JZ_LCD_CTRL_BPP_18_24;
- break;
- }

- regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
- JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16 |
- JZ_LCD_CTRL_BPP_MASK, ctrl);
+ regmap_set_bits(priv->map, JZ_REG_LCD_CTRL,
+ JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16);
}

static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
+ struct drm_plane_state *f1_state, *f0_state;
long rate;

if (!drm_atomic_crtc_needs_modeset(state))
@@ -227,6 +211,14 @@ static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc,
if (rate < 0)
return rate;

+ if (priv->soc_info->has_osd) {
+ f1_state = drm_atomic_get_plane_state(state->state, &priv->f1);
+ f0_state = drm_atomic_get_plane_state(state->state, &priv->f0);
+
+ /* If all the planes are disabled, we won't get a VBLANK IRQ */
+ priv->no_vblank = !f1_state->fb && !f0_state->fb;
+ }
+
return 0;
}

@@ -236,14 +228,9 @@ static void ingenic_drm_crtc_atomic_flush(struct drm_crtc *crtc,
struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
struct drm_crtc_state *state = crtc->state;
struct drm_pending_vblank_event *event = state->event;
- struct drm_framebuffer *drm_fb = crtc->primary->state->fb;
- const struct drm_format_info *finfo;

if (drm_atomic_crtc_needs_modeset(state)) {
- finfo = drm_format_info(drm_fb->format->format);
-
ingenic_drm_crtc_update_timings(priv, &state->mode);
- ingenic_drm_crtc_update_ctrl(priv, finfo);

clk_set_rate(priv->pix_clk, state->adjusted_mode.clock * 1000);
}
@@ -260,11 +247,152 @@ static void ingenic_drm_crtc_atomic_flush(struct drm_crtc *crtc,
}
}

+static int ingenic_drm_plane_atomic_check(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc *crtc = state->crtc ?: plane->state->crtc;
+ int ret;
+
+ if (!crtc)
+ return 0;
+
+ crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ ret = drm_atomic_helper_check_plane_state(state, crtc_state,
+ DRM_PLANE_HELPER_NO_SCALING,
+ DRM_PLANE_HELPER_NO_SCALING,
+ priv->soc_info->has_osd,
+ true);
+ if (ret)
+ return ret;
+
+ /*
+ * If OSD is not available, check that the width/height match.
+ * Note that state->src_* are in 16.16 fixed-point format.
+ */
+ if (!priv->soc_info->has_osd &&
+ (state->src_x != 0 ||
+ (state->src_w >> 16) != state->crtc_w ||
+ (state->src_h >> 16) != state->crtc_h))
+ return -EINVAL;
+
+ /*
+ * Require full modeset if enabling or disabling a plane, or changing
+ * its position, size or depth.
+ */
+ if (priv->soc_info->has_osd &&
+ (!plane->state->fb || !state->fb ||
+ plane->state->crtc_x != state->crtc_x ||
+ plane->state->crtc_y != state->crtc_y ||
+ plane->state->crtc_w != state->crtc_w ||
+ plane->state->crtc_h != state->crtc_h ||
+ plane->state->fb->format->format != state->fb->format->format))
+ crtc_state->mode_changed = true;
+
+ return 0;
+}
+
+static void ingenic_drm_plane_enable(struct ingenic_drm *priv,
+ struct drm_plane *plane)
+{
+ unsigned int en_bit;
+
+ if (priv->soc_info->has_osd) {
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+ en_bit = JZ_LCD_OSDC_F1EN;
+ else
+ en_bit = JZ_LCD_OSDC_F0EN;
+
+ regmap_set_bits(priv->map, JZ_REG_LCD_OSDC, en_bit);
+ }
+}
+
+static void ingenic_drm_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
+ unsigned int en_bit;
+
+ if (priv->soc_info->has_osd) {
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+ en_bit = JZ_LCD_OSDC_F1EN;
+ else
+ en_bit = JZ_LCD_OSDC_F0EN;
+
+ regmap_clear_bits(priv->map, JZ_REG_LCD_OSDC, en_bit);
+ }
+}
+
+static void ingenic_drm_plane_config(struct ingenic_drm *priv,
+ struct drm_plane *plane, u32 fourcc)
+{
+ struct drm_plane_state *state = plane->state;
+ unsigned int xy_reg, size_reg;
+ unsigned int ctrl = 0;
+
+ ingenic_drm_plane_enable(priv, plane);
+
+ if (priv->soc_info->has_osd &&
+ plane->type == DRM_PLANE_TYPE_PRIMARY) {
+ switch (fourcc) {
+ case DRM_FORMAT_XRGB1555:
+ ctrl |= JZ_LCD_OSDCTRL_RGB555;
+ fallthrough;
+ case DRM_FORMAT_RGB565:
+ ctrl |= JZ_LCD_OSDCTRL_BPP_15_16;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ ctrl |= JZ_LCD_OSDCTRL_BPP_18_24;
+ break;
+ }
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_OSDCTRL,
+ JZ_LCD_OSDCTRL_BPP_MASK, ctrl);
+ } else {
+ switch (fourcc) {
+ case DRM_FORMAT_XRGB1555:
+ ctrl |= JZ_LCD_CTRL_RGB555;
+ fallthrough;
+ case DRM_FORMAT_RGB565:
+ ctrl |= JZ_LCD_CTRL_BPP_15_16;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ ctrl |= JZ_LCD_CTRL_BPP_18_24;
+ break;
+ }
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
+ JZ_LCD_CTRL_BPP_MASK, ctrl);
+ }
+
+ if (priv->soc_info->has_osd) {
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY) {
+ xy_reg = JZ_REG_LCD_XYP1;
+ size_reg = JZ_REG_LCD_SIZE1;
+ } else {
+ xy_reg = JZ_REG_LCD_XYP0;
+ size_reg = JZ_REG_LCD_SIZE0;
+ }
+
+ regmap_write(priv->map, xy_reg,
+ state->crtc_x << JZ_LCD_XYP01_XPOS_LSB |
+ state->crtc_y << JZ_LCD_XYP01_YPOS_LSB);
+ regmap_write(priv->map, size_reg,
+ state->crtc_w << JZ_LCD_SIZE01_WIDTH_LSB |
+ state->crtc_h << JZ_LCD_SIZE01_HEIGHT_LSB);
+ }
+}
+
static void ingenic_drm_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *oldstate)
{
- struct ingenic_drm *priv = drm_plane_get_priv(plane);
+ struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
struct drm_plane_state *state = plane->state;
+ struct ingenic_dma_hwdesc *hwdesc;
unsigned int width, height, cpp;
dma_addr_t addr;

@@ -274,9 +402,17 @@ static void ingenic_drm_plane_atomic_update(struct drm_plane *plane,
height = state->src_h >> 16;
cpp = state->fb->format->cpp[0];

- priv->dma_hwdesc->addr = addr;
- priv->dma_hwdesc->cmd = width * height * cpp / 4;
- priv->dma_hwdesc->cmd |= JZ_LCD_CMD_EOF_IRQ;
+ if (priv->soc_info->has_osd && plane->type == DRM_PLANE_TYPE_OVERLAY)
+ hwdesc = priv->dma_hwdesc_f0;
+ else
+ hwdesc = priv->dma_hwdesc_f1;
+
+ hwdesc->addr = addr;
+ hwdesc->cmd = JZ_LCD_CMD_EOF_IRQ | (width * height * cpp / 4);
+
+ if (drm_atomic_crtc_needs_modeset(state->crtc->state))
+ ingenic_drm_plane_config(priv, plane,
+ state->fb->format->format);
}
}

@@ -360,6 +496,29 @@ static int ingenic_drm_encoder_atomic_check(struct drm_encoder *encoder,
}
}

+static void ingenic_drm_atomic_helper_commit_tail(struct drm_atomic_state *old_state)
+{
+ /*
+ * Just your regular drm_atomic_helper_commit_tail(), but only calls
+ * drm_atomic_helper_wait_for_vblanks() if priv->no_vblank.
+ */
+ struct drm_device *dev = old_state->dev;
+ struct ingenic_drm *priv = drm_device_get_priv(dev);
+
+ drm_atomic_helper_commit_modeset_disables(dev, old_state);
+
+ drm_atomic_helper_commit_planes(dev, old_state, 0);
+
+ drm_atomic_helper_commit_modeset_enables(dev, old_state);
+
+ drm_atomic_helper_commit_hw_done(old_state);
+
+ if (!priv->no_vblank)
+ drm_atomic_helper_wait_for_vblanks(dev, old_state);
+
+ drm_atomic_helper_cleanup_planes(dev, old_state);
+}
+
static irqreturn_t ingenic_drm_irq_handler(int irq, void *arg)
{
struct ingenic_drm *priv = drm_device_get_priv(arg);
@@ -437,6 +596,8 @@ static const struct drm_crtc_funcs ingenic_drm_crtc_funcs = {

static const struct drm_plane_helper_funcs ingenic_drm_plane_helper_funcs = {
.atomic_update = ingenic_drm_plane_atomic_update,
+ .atomic_check = ingenic_drm_plane_atomic_check,
+ .atomic_disable = ingenic_drm_plane_atomic_disable,
.prepare_fb = drm_gem_fb_prepare_fb,
};

@@ -459,6 +620,10 @@ static const struct drm_mode_config_funcs ingenic_drm_mode_config_funcs = {
.atomic_commit = drm_atomic_helper_commit,
};

+static struct drm_mode_config_helper_funcs ingenic_drm_mode_config_helpers = {
+ .atomic_commit_tail = ingenic_drm_atomic_helper_commit_tail,
+};
+
static int ingenic_drm_probe(struct platform_device *pdev)
{
const struct jz_soc_info *soc_info;
@@ -498,6 +663,7 @@ static int ingenic_drm_probe(struct platform_device *pdev)
drm->mode_config.max_width = soc_info->max_width;
drm->mode_config.max_height = 4095;
drm->mode_config.funcs = &ingenic_drm_mode_config_funcs;
+ drm->mode_config.helper_private = &ingenic_drm_mode_config_helpers;

base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base)) {
@@ -541,19 +707,31 @@ static int ingenic_drm_probe(struct platform_device *pdev)
bridge = devm_drm_panel_bridge_add_typed(dev, panel,
DRM_MODE_CONNECTOR_DPI);

- priv->dma_hwdesc = dmam_alloc_coherent(dev, sizeof(*priv->dma_hwdesc),
- &priv->dma_hwdesc_phys,
- GFP_KERNEL);
- if (!priv->dma_hwdesc)
+ priv->dma_hwdesc_f1 = dmam_alloc_coherent(dev, sizeof(*priv->dma_hwdesc_f1),
+ &priv->dma_hwdesc_phys_f1,
+ GFP_KERNEL);
+ if (!priv->dma_hwdesc_f1)
return -ENOMEM;

- priv->dma_hwdesc->next = priv->dma_hwdesc_phys;
- priv->dma_hwdesc->id = 0xdeafbead;
+ priv->dma_hwdesc_f1->next = priv->dma_hwdesc_phys_f1;
+ priv->dma_hwdesc_f1->id = 0xf1;

- drm_plane_helper_add(&priv->primary, &ingenic_drm_plane_helper_funcs);
+ if (priv->soc_info->has_osd) {
+ priv->dma_hwdesc_f0 = dmam_alloc_coherent(dev,
+ sizeof(*priv->dma_hwdesc_f0),
+ &priv->dma_hwdesc_phys_f0,
+ GFP_KERNEL);
+ if (!priv->dma_hwdesc_f0)
+ return -ENOMEM;

- ret = drm_universal_plane_init(drm, &priv->primary,
- 0, &ingenic_drm_primary_plane_funcs,
+ priv->dma_hwdesc_f0->next = priv->dma_hwdesc_phys_f0;
+ priv->dma_hwdesc_f0->id = 0xf0;
+ }
+
+ drm_plane_helper_add(&priv->f1, &ingenic_drm_plane_helper_funcs);
+
+ ret = drm_universal_plane_init(drm, &priv->f1, 1,
+ &ingenic_drm_primary_plane_funcs,
ingenic_drm_primary_formats,
ARRAY_SIZE(ingenic_drm_primary_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
@@ -564,13 +742,30 @@ static int ingenic_drm_probe(struct platform_device *pdev)

drm_crtc_helper_add(&priv->crtc, &ingenic_drm_crtc_helper_funcs);

- ret = drm_crtc_init_with_planes(drm, &priv->crtc, &priv->primary,
+ ret = drm_crtc_init_with_planes(drm, &priv->crtc, &priv->f1,
NULL, &ingenic_drm_crtc_funcs, NULL);
if (ret) {
dev_err(dev, "Failed to init CRTC: %i\n", ret);
return ret;
}

+ if (soc_info->has_osd) {
+ drm_plane_helper_add(&priv->f0,
+ &ingenic_drm_plane_helper_funcs);
+
+ ret = drm_universal_plane_init(drm, &priv->f0, 1,
+ &ingenic_drm_primary_plane_funcs,
+ ingenic_drm_primary_formats,
+ ARRAY_SIZE(ingenic_drm_primary_formats),
+ NULL, DRM_PLANE_TYPE_OVERLAY,
+ NULL);
+ if (ret) {
+ dev_err(dev, "Failed to register overlay plane: %i\n",
+ ret);
+ return ret;
+ }
+ }
+
priv->encoder.possible_crtcs = 1;

drm_encoder_helper_add(&priv->encoder,
@@ -632,7 +827,12 @@ static int ingenic_drm_probe(struct platform_device *pdev)
}

/* Set address of our DMA descriptor chain */
- regmap_write(priv->map, JZ_REG_LCD_DA0, priv->dma_hwdesc_phys);
+ regmap_write(priv->map, JZ_REG_LCD_DA0, priv->dma_hwdesc_phys_f0);
+ regmap_write(priv->map, JZ_REG_LCD_DA1, priv->dma_hwdesc_phys_f1);
+
+ /* Enable OSD if available */
+ if (soc_info->has_osd)
+ regmap_write(priv->map, JZ_REG_LCD_OSDC, JZ_LCD_OSDC_OSDEN);

ret = drm_dev_register(drm, 0);
if (ret) {
@@ -668,18 +868,21 @@ static int ingenic_drm_remove(struct platform_device *pdev)

static const struct jz_soc_info jz4740_soc_info = {
.needs_dev_clk = true,
+ .has_osd = false,
.max_width = 800,
.max_height = 600,
};

static const struct jz_soc_info jz4725b_soc_info = {
.needs_dev_clk = false,
+ .has_osd = true,
.max_width = 800,
.max_height = 600,
};

static const struct jz_soc_info jz4770_soc_info = {
.needs_dev_clk = false,
+ .has_osd = true,
.max_width = 1280,
.max_height = 720,
};
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.h b/drivers/gpu/drm/ingenic/ingenic-drm.h
index cb578cff7bb1..d0b827a9fe83 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm.h
+++ b/drivers/gpu/drm/ingenic/ingenic-drm.h
@@ -30,6 +30,18 @@
#define JZ_REG_LCD_SA1 0x54
#define JZ_REG_LCD_FID1 0x58
#define JZ_REG_LCD_CMD1 0x5C
+#define JZ_REG_LCD_OSDC 0x100
+#define JZ_REG_LCD_OSDCTRL 0x104
+#define JZ_REG_LCD_OSDS 0x108
+#define JZ_REG_LCD_BGC 0x10c
+#define JZ_REG_LCD_KEY0 0x110
+#define JZ_REG_LCD_KEY1 0x114
+#define JZ_REG_LCD_ALPHA 0x118
+#define JZ_REG_LCD_IPUR 0x11c
+#define JZ_REG_LCD_XYP0 0x120
+#define JZ_REG_LCD_XYP1 0x124
+#define JZ_REG_LCD_SIZE0 0x128
+#define JZ_REG_LCD_SIZE1 0x12c

#define JZ_LCD_CFG_SLCD BIT(31)
#define JZ_LCD_CFG_PS_DISABLE BIT(23)
@@ -123,4 +135,27 @@
#define JZ_LCD_STATE_SOF_IRQ BIT(4)
#define JZ_LCD_STATE_DISABLED BIT(0)

+#define JZ_LCD_OSDC_OSDEN BIT(0)
+#define JZ_LCD_OSDC_F0EN BIT(3)
+#define JZ_LCD_OSDC_F1EN BIT(4)
+
+#define JZ_LCD_OSDCTRL_IPU BIT(15)
+#define JZ_LCD_OSDCTRL_RGB555 BIT(4)
+#define JZ_LCD_OSDCTRL_CHANGE BIT(3)
+#define JZ_LCD_OSDCTRL_BPP_15_16 0x4
+#define JZ_LCD_OSDCTRL_BPP_18_24 0x5
+#define JZ_LCD_OSDCTRL_BPP_30 0x7
+#define JZ_LCD_OSDCTRL_BPP_MASK (JZ_LCD_OSDCTRL_RGB555 | 0x7)
+
+#define JZ_LCD_OSDS_READY BIT(0)
+
+#define JZ_LCD_IPUR_IPUREN BIT(31)
+#define JZ_LCD_IPUR_IPUR_LSB 0
+
+#define JZ_LCD_XYP01_XPOS_LSB 0
+#define JZ_LCD_XYP01_YPOS_LSB 16
+
+#define JZ_LCD_SIZE01_WIDTH_LSB 0
+#define JZ_LCD_SIZE01_HEIGHT_LSB 16
+
#endif /* DRIVERS_GPU_DRM_INGENIC_INGENIC_DRM_H */
--
2.27.0