summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/ingenic
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/ingenic')
-rw-r--r--drivers/gpu/drm/ingenic/Kconfig38
-rw-r--r--drivers/gpu/drm/ingenic/Makefile4
-rw-r--r--drivers/gpu/drm/ingenic/ingenic-drm-drv.c1649
-rw-r--r--drivers/gpu/drm/ingenic/ingenic-drm.h232
-rw-r--r--drivers/gpu/drm/ingenic/ingenic-dw-hdmi.c103
-rw-r--r--drivers/gpu/drm/ingenic/ingenic-ipu.c998
-rw-r--r--drivers/gpu/drm/ingenic/ingenic-ipu.h109
7 files changed, 3133 insertions, 0 deletions
diff --git a/drivers/gpu/drm/ingenic/Kconfig b/drivers/gpu/drm/ingenic/Kconfig
new file mode 100644
index 000000000..b440e0cdc
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/Kconfig
@@ -0,0 +1,38 @@
+config DRM_INGENIC
+ tristate "DRM Support for Ingenic SoCs"
+ depends on MIPS || COMPILE_TEST
+ depends on DRM
+ depends on CMA
+ depends on OF
+ depends on COMMON_CLK
+ select DRM_BRIDGE
+ select DRM_PANEL_BRIDGE
+ select DRM_KMS_HELPER
+ select DRM_GEM_DMA_HELPER
+ select REGMAP
+ select REGMAP_MMIO
+ select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE
+ help
+ Choose this option for DRM support for the Ingenic SoCs.
+
+ If M is selected the module will be called ingenic-drm.
+
+if DRM_INGENIC
+
+config DRM_INGENIC_IPU
+ bool "IPU support for Ingenic SoCs"
+ help
+ Choose this option to enable support for the IPU found in Ingenic SoCs.
+
+ The Image Processing Unit (IPU) will appear as a second primary plane.
+
+config DRM_INGENIC_DW_HDMI
+ tristate "Ingenic specific support for Synopsys DW HDMI"
+ depends on MACH_JZ4780
+ select DRM_DW_HDMI
+ help
+ Choose this option to enable Synopsys DesignWare HDMI based driver.
+ If you want to enable HDMI on Ingenic JZ4780 based SoC, you should
+ select this option.
+
+endif
diff --git a/drivers/gpu/drm/ingenic/Makefile b/drivers/gpu/drm/ingenic/Makefile
new file mode 100644
index 000000000..f10cc1c5a
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/Makefile
@@ -0,0 +1,4 @@
+obj-$(CONFIG_DRM_INGENIC) += ingenic-drm.o
+ingenic-drm-y = ingenic-drm-drv.o
+ingenic-drm-$(CONFIG_DRM_INGENIC_IPU) += ingenic-ipu.o
+obj-$(CONFIG_DRM_INGENIC_DW_HDMI) += ingenic-dw-hdmi.o
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm-drv.c b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
new file mode 100644
index 000000000..8dbd4847d
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c
@@ -0,0 +1,1649 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Ingenic JZ47xx KMS driver
+//
+// Copyright (C) 2019, Paul Cercueil <paul@crapouillou.net>
+
+#include "ingenic-drm.h"
+
+#include <linux/bitfield.h>
+#include <linux/component.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_color_mgmt.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fbdev_generic.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#define HWDESC_PALETTE 2
+
+struct ingenic_dma_hwdesc {
+ u32 next;
+ u32 addr;
+ u32 id;
+ u32 cmd;
+ /* extended hw descriptor for jz4780 */
+ u32 offsize;
+ u32 pagewidth;
+ u32 cpos;
+ u32 dessize;
+} __aligned(16);
+
+struct ingenic_dma_hwdescs {
+ struct ingenic_dma_hwdesc hwdesc[3];
+ u16 palette[256] __aligned(16);
+};
+
+struct jz_soc_info {
+ bool needs_dev_clk;
+ bool has_osd;
+ bool has_alpha;
+ bool map_noncoherent;
+ bool use_extended_hwdesc;
+ bool plane_f0_not_working;
+ u32 max_burst;
+ unsigned int max_width, max_height;
+ const u32 *formats_f0, *formats_f1;
+ unsigned int num_formats_f0, num_formats_f1;
+};
+
+struct ingenic_drm_private_state {
+ struct drm_private_state base;
+ bool use_palette;
+};
+
+struct ingenic_drm {
+ struct drm_device drm;
+ /*
+ * 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, *ipu_plane;
+ struct drm_crtc crtc;
+
+ struct device *dev;
+ struct regmap *map;
+ struct clk *lcd_clk, *pix_clk;
+ const struct jz_soc_info *soc_info;
+
+ struct ingenic_dma_hwdescs *dma_hwdescs;
+ dma_addr_t dma_hwdescs_phys;
+
+ bool panel_is_sharp;
+ bool no_vblank;
+
+ /*
+ * clk_mutex is used to synchronize the pixel clock rate update with
+ * the VBLANK. When the pixel clock's parent clock needs to be updated,
+ * clock_nb's notifier function will lock the mutex, then wait until the
+ * next VBLANK. At that point, the parent clock's rate can be updated,
+ * and the mutex is then unlocked. If an atomic commit happens in the
+ * meantime, it will lock on the mutex, effectively waiting until the
+ * clock update process finishes. Finally, the pixel clock's rate will
+ * be recomputed when the mutex has been released, in the pending atomic
+ * commit, or a future one.
+ */
+ struct mutex clk_mutex;
+ bool update_clk_rate;
+ struct notifier_block clock_nb;
+
+ struct drm_private_obj private_obj;
+};
+
+struct ingenic_drm_bridge {
+ struct drm_encoder encoder;
+ struct drm_bridge bridge, *next_bridge;
+
+ struct drm_bus_cfg bus_cfg;
+};
+
+static inline struct ingenic_drm_bridge *
+to_ingenic_drm_bridge(struct drm_encoder *encoder)
+{
+ return container_of(encoder, struct ingenic_drm_bridge, encoder);
+}
+
+static inline struct ingenic_drm_private_state *
+to_ingenic_drm_priv_state(struct drm_private_state *state)
+{
+ return container_of(state, struct ingenic_drm_private_state, base);
+}
+
+static struct ingenic_drm_private_state *
+ingenic_drm_get_priv_state(struct ingenic_drm *priv, struct drm_atomic_state *state)
+{
+ struct drm_private_state *priv_state;
+
+ priv_state = drm_atomic_get_private_obj_state(state, &priv->private_obj);
+ if (IS_ERR(priv_state))
+ return ERR_CAST(priv_state);
+
+ return to_ingenic_drm_priv_state(priv_state);
+}
+
+static struct ingenic_drm_private_state *
+ingenic_drm_get_new_priv_state(struct ingenic_drm *priv, struct drm_atomic_state *state)
+{
+ struct drm_private_state *priv_state;
+
+ priv_state = drm_atomic_get_new_private_obj_state(state, &priv->private_obj);
+ if (!priv_state)
+ return NULL;
+
+ return to_ingenic_drm_priv_state(priv_state);
+}
+
+static bool ingenic_drm_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case JZ_REG_LCD_IID:
+ case JZ_REG_LCD_SA0:
+ case JZ_REG_LCD_FID0:
+ case JZ_REG_LCD_CMD0:
+ case JZ_REG_LCD_SA1:
+ case JZ_REG_LCD_FID1:
+ case JZ_REG_LCD_CMD1:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config ingenic_drm_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+
+ .writeable_reg = ingenic_drm_writeable_reg,
+};
+
+static inline struct ingenic_drm *drm_device_get_priv(struct drm_device *drm)
+{
+ return container_of(drm, struct ingenic_drm, drm);
+}
+
+static inline struct ingenic_drm *drm_crtc_get_priv(struct drm_crtc *crtc)
+{
+ return container_of(crtc, struct ingenic_drm, crtc);
+}
+
+static inline struct ingenic_drm *drm_nb_get_priv(struct notifier_block *nb)
+{
+ return container_of(nb, struct ingenic_drm, clock_nb);
+}
+
+static inline dma_addr_t dma_hwdesc_addr(const struct ingenic_drm *priv,
+ unsigned int idx)
+{
+ u32 offset = offsetof(struct ingenic_dma_hwdescs, hwdesc[idx]);
+
+ return priv->dma_hwdescs_phys + offset;
+}
+
+static int ingenic_drm_update_pixclk(struct notifier_block *nb,
+ unsigned long action,
+ void *data)
+{
+ struct ingenic_drm *priv = drm_nb_get_priv(nb);
+
+ switch (action) {
+ case PRE_RATE_CHANGE:
+ mutex_lock(&priv->clk_mutex);
+ priv->update_clk_rate = true;
+ drm_crtc_wait_one_vblank(&priv->crtc);
+ return NOTIFY_OK;
+ default:
+ mutex_unlock(&priv->clk_mutex);
+ return NOTIFY_OK;
+ }
+}
+
+static void ingenic_drm_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(bridge->dev);
+
+ regmap_write(priv->map, JZ_REG_LCD_STATE, 0);
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
+ JZ_LCD_CTRL_ENABLE | JZ_LCD_CTRL_DISABLE,
+ JZ_LCD_CTRL_ENABLE);
+}
+
+static void ingenic_drm_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
+ struct ingenic_drm_private_state *priv_state;
+ unsigned int next_id;
+
+ priv_state = ingenic_drm_get_priv_state(priv, state);
+ if (WARN_ON(IS_ERR(priv_state)))
+ return;
+
+ /* Set addresses of our DMA descriptor chains */
+ next_id = priv_state->use_palette ? HWDESC_PALETTE : 0;
+ regmap_write(priv->map, JZ_REG_LCD_DA0, dma_hwdesc_addr(priv, next_id));
+ regmap_write(priv->map, JZ_REG_LCD_DA1, dma_hwdesc_addr(priv, 1));
+
+ drm_crtc_vblank_on(crtc);
+}
+
+static void ingenic_drm_bridge_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(bridge->dev);
+ unsigned int var;
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
+ JZ_LCD_CTRL_DISABLE, JZ_LCD_CTRL_DISABLE);
+
+ regmap_read_poll_timeout(priv->map, JZ_REG_LCD_STATE, var,
+ var & JZ_LCD_STATE_DISABLED,
+ 1000, 0);
+}
+
+static void ingenic_drm_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ drm_crtc_vblank_off(crtc);
+}
+
+static void ingenic_drm_crtc_update_timings(struct ingenic_drm *priv,
+ struct drm_display_mode *mode)
+{
+ unsigned int vpe, vds, vde, vt, hpe, hds, hde, ht;
+
+ vpe = mode->crtc_vsync_end - mode->crtc_vsync_start;
+ vds = mode->crtc_vtotal - mode->crtc_vsync_start;
+ vde = vds + mode->crtc_vdisplay;
+ vt = vde + mode->crtc_vsync_start - mode->crtc_vdisplay;
+
+ hpe = mode->crtc_hsync_end - mode->crtc_hsync_start;
+ hds = mode->crtc_htotal - mode->crtc_hsync_start;
+ hde = hds + mode->crtc_hdisplay;
+ ht = hde + mode->crtc_hsync_start - mode->crtc_hdisplay;
+
+ regmap_write(priv->map, JZ_REG_LCD_VSYNC,
+ 0 << JZ_LCD_VSYNC_VPS_OFFSET |
+ vpe << JZ_LCD_VSYNC_VPE_OFFSET);
+
+ regmap_write(priv->map, JZ_REG_LCD_HSYNC,
+ 0 << JZ_LCD_HSYNC_HPS_OFFSET |
+ hpe << JZ_LCD_HSYNC_HPE_OFFSET);
+
+ regmap_write(priv->map, JZ_REG_LCD_VAT,
+ ht << JZ_LCD_VAT_HT_OFFSET |
+ vt << JZ_LCD_VAT_VT_OFFSET);
+
+ regmap_write(priv->map, JZ_REG_LCD_DAH,
+ hds << JZ_LCD_DAH_HDS_OFFSET |
+ hde << JZ_LCD_DAH_HDE_OFFSET);
+ regmap_write(priv->map, JZ_REG_LCD_DAV,
+ vds << JZ_LCD_DAV_VDS_OFFSET |
+ vde << JZ_LCD_DAV_VDE_OFFSET);
+
+ if (priv->panel_is_sharp) {
+ regmap_write(priv->map, JZ_REG_LCD_PS, hde << 16 | (hde + 1));
+ regmap_write(priv->map, JZ_REG_LCD_CLS, hde << 16 | (hde + 1));
+ regmap_write(priv->map, JZ_REG_LCD_SPL, hpe << 16 | (hpe + 1));
+ regmap_write(priv->map, JZ_REG_LCD_REV, mode->htotal << 16);
+ }
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
+ JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_MASK,
+ JZ_LCD_CTRL_OFUP | priv->soc_info->max_burst);
+
+ /*
+ * IPU restart - specify how much time the LCDC will wait before
+ * transferring a new frame from the IPU. The value is the one
+ * suggested in the programming manual.
+ */
+ regmap_write(priv->map, JZ_REG_LCD_IPUR, JZ_LCD_IPUR_IPUREN |
+ (ht * vpe / 3) << JZ_LCD_IPUR_IPUR_LSB);
+}
+
+static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+ crtc);
+ struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
+ struct drm_plane_state *f1_state, *f0_state, *ipu_state = NULL;
+
+ if (crtc_state->gamma_lut &&
+ drm_color_lut_size(crtc_state->gamma_lut) != ARRAY_SIZE(priv->dma_hwdescs->palette)) {
+ dev_dbg(priv->dev, "Invalid palette size\n");
+ return -EINVAL;
+ }
+
+ if (drm_atomic_crtc_needs_modeset(crtc_state) && priv->soc_info->has_osd) {
+ f1_state = drm_atomic_get_plane_state(crtc_state->state,
+ &priv->f1);
+ if (IS_ERR(f1_state))
+ return PTR_ERR(f1_state);
+
+ f0_state = drm_atomic_get_plane_state(crtc_state->state,
+ &priv->f0);
+ if (IS_ERR(f0_state))
+ return PTR_ERR(f0_state);
+
+ if (IS_ENABLED(CONFIG_DRM_INGENIC_IPU) && priv->ipu_plane) {
+ ipu_state = drm_atomic_get_plane_state(crtc_state->state,
+ priv->ipu_plane);
+ if (IS_ERR(ipu_state))
+ return PTR_ERR(ipu_state);
+
+ /* IPU and F1 planes cannot be enabled at the same time. */
+ if (f1_state->fb && ipu_state->fb) {
+ dev_dbg(priv->dev, "Cannot enable both F1 and IPU\n");
+ return -EINVAL;
+ }
+ }
+
+ /* If all the planes are disabled, we won't get a VBLANK IRQ */
+ priv->no_vblank = !f1_state->fb && !f0_state->fb &&
+ !(ipu_state && ipu_state->fb);
+ }
+
+ return 0;
+}
+
+static enum drm_mode_status
+ingenic_drm_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
+{
+ struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
+ long rate;
+
+ if (mode->hdisplay > priv->soc_info->max_width)
+ return MODE_BAD_HVALUE;
+ if (mode->vdisplay > priv->soc_info->max_height)
+ return MODE_BAD_VVALUE;
+
+ rate = clk_round_rate(priv->pix_clk, mode->clock * 1000);
+ if (rate < 0)
+ return MODE_CLOCK_RANGE;
+
+ return MODE_OK;
+}
+
+static void ingenic_drm_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+ crtc);
+ struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
+ u32 ctrl = 0;
+
+ if (priv->soc_info->has_osd &&
+ drm_atomic_crtc_needs_modeset(crtc_state)) {
+ /*
+ * If IPU plane is enabled, enable IPU as source for the F1
+ * plane; otherwise use regular DMA.
+ */
+ if (priv->ipu_plane && priv->ipu_plane->state->fb)
+ ctrl |= JZ_LCD_OSDCTRL_IPU;
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_OSDCTRL,
+ JZ_LCD_OSDCTRL_IPU, ctrl);
+ }
+}
+
+static void ingenic_drm_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+ crtc);
+ struct drm_pending_vblank_event *event = crtc_state->event;
+
+ if (drm_atomic_crtc_needs_modeset(crtc_state)) {
+ ingenic_drm_crtc_update_timings(priv, &crtc_state->adjusted_mode);
+ priv->update_clk_rate = true;
+ }
+
+ if (priv->update_clk_rate) {
+ mutex_lock(&priv->clk_mutex);
+ clk_set_rate(priv->pix_clk,
+ crtc_state->adjusted_mode.crtc_clock * 1000);
+ priv->update_clk_rate = false;
+ mutex_unlock(&priv->clk_mutex);
+ }
+
+ if (event) {
+ crtc_state->event = NULL;
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (drm_crtc_vblank_get(crtc) == 0)
+ drm_crtc_arm_vblank_event(crtc, event);
+ else
+ drm_crtc_send_vblank_event(crtc, event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+ }
+}
+
+static int ingenic_drm_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state,
+ plane);
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
+ struct ingenic_drm_private_state *priv_state;
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc;
+ int ret;
+
+ if (!crtc)
+ return 0;
+
+ if (priv->soc_info->plane_f0_not_working && plane == &priv->f0)
+ return -EINVAL;
+
+ crtc_state = drm_atomic_get_existing_crtc_state(state,
+ crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ priv_state = ingenic_drm_get_priv_state(priv, state);
+ if (IS_ERR(priv_state))
+ return PTR_ERR(priv_state);
+
+ ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
+ DRM_PLANE_NO_SCALING,
+ DRM_PLANE_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 &&
+ (new_plane_state->src_x != 0 ||
+ (new_plane_state->src_w >> 16) != new_plane_state->crtc_w ||
+ (new_plane_state->src_h >> 16) != new_plane_state->crtc_h))
+ return -EINVAL;
+
+ priv_state->use_palette = new_plane_state->fb &&
+ new_plane_state->fb->format->format == DRM_FORMAT_C8;
+
+ /*
+ * Require full modeset if enabling or disabling a plane, or changing
+ * its position, size or depth.
+ */
+ if (priv->soc_info->has_osd &&
+ (!old_plane_state->fb || !new_plane_state->fb ||
+ old_plane_state->crtc_x != new_plane_state->crtc_x ||
+ old_plane_state->crtc_y != new_plane_state->crtc_y ||
+ old_plane_state->crtc_w != new_plane_state->crtc_w ||
+ old_plane_state->crtc_h != new_plane_state->crtc_h ||
+ old_plane_state->fb->format->format != new_plane_state->fb->format->format))
+ crtc_state->mode_changed = true;
+
+ if (priv->soc_info->map_noncoherent)
+ drm_atomic_helper_check_plane_damage(state, new_plane_state);
+
+ 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 != &priv->f0)
+ en_bit = JZ_LCD_OSDC_F1EN;
+ else
+ en_bit = JZ_LCD_OSDC_F0EN;
+
+ regmap_set_bits(priv->map, JZ_REG_LCD_OSDC, en_bit);
+ }
+}
+
+void ingenic_drm_plane_disable(struct device *dev, struct drm_plane *plane)
+{
+ struct ingenic_drm *priv = dev_get_drvdata(dev);
+ unsigned int en_bit;
+
+ if (priv->soc_info->has_osd) {
+ if (plane != &priv->f0)
+ 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_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
+
+ ingenic_drm_plane_disable(priv->dev, plane);
+}
+
+void ingenic_drm_plane_config(struct device *dev,
+ struct drm_plane *plane, u32 fourcc)
+{
+ struct ingenic_drm *priv = dev_get_drvdata(dev);
+ 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 != &priv->f0) {
+ 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_RGB888:
+ ctrl |= JZ_LCD_OSDCTRL_BPP_24_COMP;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ ctrl |= JZ_LCD_OSDCTRL_BPP_18_24;
+ break;
+ case DRM_FORMAT_XRGB2101010:
+ ctrl |= JZ_LCD_OSDCTRL_BPP_30;
+ break;
+ }
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_OSDCTRL,
+ JZ_LCD_OSDCTRL_BPP_MASK, ctrl);
+ } else {
+ switch (fourcc) {
+ case DRM_FORMAT_C8:
+ ctrl |= JZ_LCD_CTRL_BPP_8;
+ break;
+ 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_RGB888:
+ ctrl |= JZ_LCD_CTRL_BPP_24_COMP;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ ctrl |= JZ_LCD_CTRL_BPP_18_24;
+ break;
+ case DRM_FORMAT_XRGB2101010:
+ ctrl |= JZ_LCD_CTRL_BPP_30;
+ break;
+ }
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
+ JZ_LCD_CTRL_BPP_MASK, ctrl);
+ }
+
+ if (priv->soc_info->has_osd) {
+ if (plane != &priv->f0) {
+ 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);
+ }
+}
+
+bool ingenic_drm_map_noncoherent(const struct device *dev)
+{
+ const struct ingenic_drm *priv = dev_get_drvdata(dev);
+
+ return priv->soc_info->map_noncoherent;
+}
+
+static void ingenic_drm_update_palette(struct ingenic_drm *priv,
+ const struct drm_color_lut *lut)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(priv->dma_hwdescs->palette); i++) {
+ u16 color = drm_color_lut_extract(lut[i].red, 5) << 11
+ | drm_color_lut_extract(lut[i].green, 6) << 5
+ | drm_color_lut_extract(lut[i].blue, 5);
+
+ priv->dma_hwdescs->palette[i] = color;
+ }
+}
+
+static void ingenic_drm_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
+ struct drm_plane_state *newstate = drm_atomic_get_new_plane_state(state, plane);
+ struct drm_plane_state *oldstate = drm_atomic_get_old_plane_state(state, plane);
+ unsigned int width, height, cpp, next_id, plane_id;
+ struct ingenic_drm_private_state *priv_state;
+ struct drm_crtc_state *crtc_state;
+ struct ingenic_dma_hwdesc *hwdesc;
+ dma_addr_t addr;
+ u32 fourcc;
+
+ if (newstate && newstate->fb) {
+ if (priv->soc_info->map_noncoherent)
+ drm_fb_dma_sync_non_coherent(&priv->drm, oldstate, newstate);
+
+ crtc_state = newstate->crtc->state;
+ plane_id = !!(priv->soc_info->has_osd && plane != &priv->f0);
+
+ addr = drm_fb_dma_get_gem_addr(newstate->fb, newstate, 0);
+ width = newstate->src_w >> 16;
+ height = newstate->src_h >> 16;
+ cpp = newstate->fb->format->cpp[0];
+
+ priv_state = ingenic_drm_get_new_priv_state(priv, state);
+ next_id = (priv_state && priv_state->use_palette) ? HWDESC_PALETTE : plane_id;
+
+ hwdesc = &priv->dma_hwdescs->hwdesc[plane_id];
+ hwdesc->addr = addr;
+ hwdesc->cmd = JZ_LCD_CMD_EOF_IRQ | (width * height * cpp / 4);
+ hwdesc->next = dma_hwdesc_addr(priv, next_id);
+
+ if (priv->soc_info->use_extended_hwdesc) {
+ hwdesc->cmd |= JZ_LCD_CMD_FRM_ENABLE;
+
+ /* Extended 8-byte descriptor */
+ hwdesc->cpos = 0;
+ hwdesc->offsize = 0;
+ hwdesc->pagewidth = 0;
+
+ switch (newstate->fb->format->format) {
+ case DRM_FORMAT_XRGB1555:
+ hwdesc->cpos |= JZ_LCD_CPOS_RGB555;
+ fallthrough;
+ case DRM_FORMAT_RGB565:
+ hwdesc->cpos |= JZ_LCD_CPOS_BPP_15_16;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ hwdesc->cpos |= JZ_LCD_CPOS_BPP_18_24;
+ break;
+ }
+ hwdesc->cpos |= (JZ_LCD_CPOS_COEFFICIENT_1 <<
+ JZ_LCD_CPOS_COEFFICIENT_OFFSET);
+ hwdesc->dessize =
+ (0xff << JZ_LCD_DESSIZE_ALPHA_OFFSET) |
+ FIELD_PREP(JZ_LCD_DESSIZE_HEIGHT_MASK, height - 1) |
+ FIELD_PREP(JZ_LCD_DESSIZE_WIDTH_MASK, width - 1);
+ }
+
+ if (drm_atomic_crtc_needs_modeset(crtc_state)) {
+ fourcc = newstate->fb->format->format;
+
+ ingenic_drm_plane_config(priv->dev, plane, fourcc);
+
+ crtc_state->color_mgmt_changed = fourcc == DRM_FORMAT_C8;
+ }
+
+ if (crtc_state->color_mgmt_changed)
+ ingenic_drm_update_palette(priv, crtc_state->gamma_lut->data);
+ }
+}
+
+static void ingenic_drm_encoder_atomic_mode_set(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(encoder->dev);
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ struct ingenic_drm_bridge *bridge = to_ingenic_drm_bridge(encoder);
+ unsigned int cfg, rgbcfg = 0;
+
+ priv->panel_is_sharp = bridge->bus_cfg.flags & DRM_BUS_FLAG_SHARP_SIGNALS;
+
+ if (priv->panel_is_sharp) {
+ cfg = JZ_LCD_CFG_MODE_SPECIAL_TFT_1 | JZ_LCD_CFG_REV_POLARITY;
+ } else {
+ cfg = JZ_LCD_CFG_PS_DISABLE | JZ_LCD_CFG_CLS_DISABLE
+ | JZ_LCD_CFG_SPL_DISABLE | JZ_LCD_CFG_REV_DISABLE;
+ }
+
+ if (priv->soc_info->use_extended_hwdesc)
+ cfg |= JZ_LCD_CFG_DESCRIPTOR_8;
+
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW;
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW;
+ if (bridge->bus_cfg.flags & DRM_BUS_FLAG_DE_LOW)
+ cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW;
+ if (bridge->bus_cfg.flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+ cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE;
+
+ if (!priv->panel_is_sharp) {
+ if (conn_state->connector->connector_type == DRM_MODE_CONNECTOR_TV) {
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ cfg |= JZ_LCD_CFG_MODE_TV_OUT_I;
+ else
+ cfg |= JZ_LCD_CFG_MODE_TV_OUT_P;
+ } else {
+ switch (bridge->bus_cfg.format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ cfg |= JZ_LCD_CFG_MODE_GENERIC_16BIT;
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ cfg |= JZ_LCD_CFG_MODE_GENERIC_18BIT;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ cfg |= JZ_LCD_CFG_MODE_GENERIC_24BIT;
+ break;
+ case MEDIA_BUS_FMT_RGB888_3X8_DELTA:
+ rgbcfg = JZ_LCD_RGBC_EVEN_GBR | JZ_LCD_RGBC_ODD_RGB;
+ fallthrough;
+ case MEDIA_BUS_FMT_RGB888_3X8:
+ cfg |= JZ_LCD_CFG_MODE_8BIT_SERIAL;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ regmap_write(priv->map, JZ_REG_LCD_CFG, cfg);
+ regmap_write(priv->map, JZ_REG_LCD_RGBC, rgbcfg);
+}
+
+static int ingenic_drm_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct ingenic_drm_bridge *ib = to_ingenic_drm_bridge(bridge->encoder);
+
+ return drm_bridge_attach(bridge->encoder, ib->next_bridge,
+ &ib->bridge, flags);
+}
+
+static int ingenic_drm_bridge_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ struct ingenic_drm_bridge *ib = to_ingenic_drm_bridge(bridge->encoder);
+
+ ib->bus_cfg = bridge_state->output_bus_cfg;
+
+ if (conn_state->connector->connector_type == DRM_MODE_CONNECTOR_TV)
+ return 0;
+
+ switch (bridge_state->output_bus_cfg.format) {
+ case MEDIA_BUS_FMT_RGB888_3X8:
+ case MEDIA_BUS_FMT_RGB888_3X8_DELTA:
+ /*
+ * The LCD controller expects timing values in dot-clock ticks,
+ * which is 3x the timing values in pixels when using a 3x8-bit
+ * display; but it will count the display area size in pixels
+ * either way. Go figure.
+ */
+ mode->crtc_clock = mode->clock * 3;
+ mode->crtc_hsync_start = mode->hsync_start * 3 - mode->hdisplay * 2;
+ mode->crtc_hsync_end = mode->hsync_end * 3 - mode->hdisplay * 2;
+ mode->crtc_hdisplay = mode->hdisplay;
+ mode->crtc_htotal = mode->htotal * 3 - mode->hdisplay * 2;
+ return 0;
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static u32 *
+ingenic_drm_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state,
+ u32 output_fmt,
+ unsigned int *num_input_fmts)
+{
+ switch (output_fmt) {
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ case MEDIA_BUS_FMT_RGB888_3X8:
+ case MEDIA_BUS_FMT_RGB888_3X8_DELTA:
+ break;
+ default:
+ *num_input_fmts = 0;
+ return NULL;
+ }
+
+ return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state,
+ crtc_state, conn_state,
+ output_fmt,
+ num_input_fmts);
+}
+
+static irqreturn_t ingenic_drm_irq_handler(int irq, void *arg)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(arg);
+ unsigned int state;
+
+ regmap_read(priv->map, JZ_REG_LCD_STATE, &state);
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_STATE,
+ JZ_LCD_STATE_EOF_IRQ, 0);
+
+ if (state & JZ_LCD_STATE_EOF_IRQ)
+ drm_crtc_handle_vblank(&priv->crtc);
+
+ return IRQ_HANDLED;
+}
+
+static int ingenic_drm_enable_vblank(struct drm_crtc *crtc)
+{
+ struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
+
+ if (priv->no_vblank)
+ return -EINVAL;
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
+ JZ_LCD_CTRL_EOF_IRQ, JZ_LCD_CTRL_EOF_IRQ);
+
+ return 0;
+}
+
+static void ingenic_drm_disable_vblank(struct drm_crtc *crtc)
+{
+ struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_CTRL, JZ_LCD_CTRL_EOF_IRQ, 0);
+}
+
+static struct drm_framebuffer *
+ingenic_drm_gem_fb_create(struct drm_device *drm, struct drm_file *file,
+ const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(drm);
+
+ if (priv->soc_info->map_noncoherent)
+ return drm_gem_fb_create_with_dirty(drm, file, mode_cmd);
+
+ return drm_gem_fb_create(drm, file, mode_cmd);
+}
+
+static struct drm_gem_object *
+ingenic_drm_gem_create_object(struct drm_device *drm, size_t size)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(drm);
+ struct drm_gem_dma_object *obj;
+
+ obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return ERR_PTR(-ENOMEM);
+
+ obj->map_noncoherent = priv->soc_info->map_noncoherent;
+
+ return &obj->base;
+}
+
+static struct drm_private_state *
+ingenic_drm_duplicate_state(struct drm_private_obj *obj)
+{
+ struct ingenic_drm_private_state *state = to_ingenic_drm_priv_state(obj->state);
+
+ state = kmemdup(state, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
+
+ return &state->base;
+}
+
+static void ingenic_drm_destroy_state(struct drm_private_obj *obj,
+ struct drm_private_state *state)
+{
+ struct ingenic_drm_private_state *priv_state = to_ingenic_drm_priv_state(state);
+
+ kfree(priv_state);
+}
+
+DEFINE_DRM_GEM_DMA_FOPS(ingenic_drm_fops);
+
+static const struct drm_driver ingenic_drm_driver_data = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+ .name = "ingenic-drm",
+ .desc = "DRM module for Ingenic SoCs",
+ .date = "20200716",
+ .major = 1,
+ .minor = 1,
+ .patchlevel = 0,
+
+ .fops = &ingenic_drm_fops,
+ .gem_create_object = ingenic_drm_gem_create_object,
+ DRM_GEM_DMA_DRIVER_OPS,
+};
+
+static const struct drm_plane_funcs ingenic_drm_primary_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .reset = drm_atomic_helper_plane_reset,
+ .destroy = drm_plane_cleanup,
+
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static const struct drm_crtc_funcs ingenic_drm_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = drm_atomic_helper_crtc_reset,
+ .destroy = drm_crtc_cleanup,
+
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+
+ .enable_vblank = ingenic_drm_enable_vblank,
+ .disable_vblank = ingenic_drm_disable_vblank,
+};
+
+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,
+};
+
+static const struct drm_crtc_helper_funcs ingenic_drm_crtc_helper_funcs = {
+ .atomic_enable = ingenic_drm_crtc_atomic_enable,
+ .atomic_disable = ingenic_drm_crtc_atomic_disable,
+ .atomic_begin = ingenic_drm_crtc_atomic_begin,
+ .atomic_flush = ingenic_drm_crtc_atomic_flush,
+ .atomic_check = ingenic_drm_crtc_atomic_check,
+ .mode_valid = ingenic_drm_crtc_mode_valid,
+};
+
+static const struct drm_encoder_helper_funcs ingenic_drm_encoder_helper_funcs = {
+ .atomic_mode_set = ingenic_drm_encoder_atomic_mode_set,
+};
+
+static const struct drm_bridge_funcs ingenic_drm_bridge_funcs = {
+ .attach = ingenic_drm_bridge_attach,
+ .atomic_enable = ingenic_drm_bridge_atomic_enable,
+ .atomic_disable = ingenic_drm_bridge_atomic_disable,
+ .atomic_check = ingenic_drm_bridge_atomic_check,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_get_input_bus_fmts = ingenic_drm_bridge_atomic_get_input_bus_fmts,
+};
+
+static const struct drm_mode_config_funcs ingenic_drm_mode_config_funcs = {
+ .fb_create = ingenic_drm_gem_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+static struct drm_mode_config_helper_funcs ingenic_drm_mode_config_helpers = {
+ .atomic_commit_tail = drm_atomic_helper_commit_tail,
+};
+
+static const struct drm_private_state_funcs ingenic_drm_private_state_funcs = {
+ .atomic_duplicate_state = ingenic_drm_duplicate_state,
+ .atomic_destroy_state = ingenic_drm_destroy_state,
+};
+
+static void ingenic_drm_unbind_all(void *d)
+{
+ struct ingenic_drm *priv = d;
+
+ component_unbind_all(priv->dev, &priv->drm);
+}
+
+static void __maybe_unused ingenic_drm_release_rmem(void *d)
+{
+ of_reserved_mem_device_release(d);
+}
+
+static void ingenic_drm_configure_hwdesc(struct ingenic_drm *priv,
+ unsigned int hwdesc,
+ unsigned int next_hwdesc, u32 id)
+{
+ struct ingenic_dma_hwdesc *desc = &priv->dma_hwdescs->hwdesc[hwdesc];
+
+ desc->next = dma_hwdesc_addr(priv, next_hwdesc);
+ desc->id = id;
+}
+
+static void ingenic_drm_configure_hwdesc_palette(struct ingenic_drm *priv)
+{
+ struct ingenic_dma_hwdesc *desc;
+
+ ingenic_drm_configure_hwdesc(priv, HWDESC_PALETTE, 0, 0xc0);
+
+ desc = &priv->dma_hwdescs->hwdesc[HWDESC_PALETTE];
+ desc->addr = priv->dma_hwdescs_phys
+ + offsetof(struct ingenic_dma_hwdescs, palette);
+ desc->cmd = JZ_LCD_CMD_ENABLE_PAL
+ | (sizeof(priv->dma_hwdescs->palette) / 4);
+}
+
+static void ingenic_drm_configure_hwdesc_plane(struct ingenic_drm *priv,
+ unsigned int plane)
+{
+ ingenic_drm_configure_hwdesc(priv, plane, plane, 0xf0 | plane);
+}
+
+static void ingenic_drm_atomic_private_obj_fini(struct drm_device *drm, void *private_obj)
+{
+ drm_atomic_private_obj_fini(private_obj);
+}
+
+static int ingenic_drm_bind(struct device *dev, bool has_components)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ingenic_drm_private_state *private_state;
+ const struct jz_soc_info *soc_info;
+ struct ingenic_drm *priv;
+ struct clk *parent_clk;
+ struct drm_plane *primary;
+ struct drm_bridge *bridge;
+ struct drm_panel *panel;
+ struct drm_connector *connector;
+ struct drm_encoder *encoder;
+ struct ingenic_drm_bridge *ib;
+ struct drm_device *drm;
+ void __iomem *base;
+ struct resource *res;
+ struct regmap_config regmap_config;
+ long parent_rate;
+ unsigned int i, clone_mask = 0;
+ int ret, irq;
+ u32 osdc = 0;
+
+ soc_info = of_device_get_match_data(dev);
+ if (!soc_info) {
+ dev_err(dev, "Missing platform data\n");
+ return -EINVAL;
+ }
+
+ if (IS_ENABLED(CONFIG_OF_RESERVED_MEM)) {
+ ret = of_reserved_mem_device_init(dev);
+
+ if (ret && ret != -ENODEV)
+ dev_warn(dev, "Failed to get reserved memory: %d\n", ret);
+
+ if (!ret) {
+ ret = devm_add_action_or_reset(dev, ingenic_drm_release_rmem, dev);
+ if (ret)
+ return ret;
+ }
+ }
+
+ priv = devm_drm_dev_alloc(dev, &ingenic_drm_driver_data,
+ struct ingenic_drm, drm);
+ if (IS_ERR(priv))
+ return PTR_ERR(priv);
+
+ priv->soc_info = soc_info;
+ priv->dev = dev;
+ drm = &priv->drm;
+
+ platform_set_drvdata(pdev, priv);
+
+ ret = drmm_mode_config_init(drm);
+ if (ret)
+ return ret;
+
+ drm->mode_config.min_width = 0;
+ drm->mode_config.min_height = 0;
+ 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_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(base)) {
+ dev_err(dev, "Failed to get memory resource\n");
+ return PTR_ERR(base);
+ }
+
+ regmap_config = ingenic_drm_regmap_config;
+ regmap_config.max_register = res->end - res->start;
+ priv->map = devm_regmap_init_mmio(dev, base,
+ &regmap_config);
+ if (IS_ERR(priv->map)) {
+ dev_err(dev, "Failed to create regmap\n");
+ return PTR_ERR(priv->map);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ if (soc_info->needs_dev_clk) {
+ priv->lcd_clk = devm_clk_get(dev, "lcd");
+ if (IS_ERR(priv->lcd_clk)) {
+ dev_err(dev, "Failed to get lcd clock\n");
+ return PTR_ERR(priv->lcd_clk);
+ }
+ }
+
+ priv->pix_clk = devm_clk_get(dev, "lcd_pclk");
+ if (IS_ERR(priv->pix_clk)) {
+ dev_err(dev, "Failed to get pixel clock\n");
+ return PTR_ERR(priv->pix_clk);
+ }
+
+ priv->dma_hwdescs = dmam_alloc_coherent(dev,
+ sizeof(*priv->dma_hwdescs),
+ &priv->dma_hwdescs_phys,
+ GFP_KERNEL);
+ if (!priv->dma_hwdescs)
+ return -ENOMEM;
+
+ /* Configure DMA hwdesc for foreground0 plane */
+ ingenic_drm_configure_hwdesc_plane(priv, 0);
+
+ /* Configure DMA hwdesc for foreground1 plane */
+ ingenic_drm_configure_hwdesc_plane(priv, 1);
+
+ /* Configure DMA hwdesc for palette */
+ ingenic_drm_configure_hwdesc_palette(priv);
+
+ primary = priv->soc_info->has_osd ? &priv->f1 : &priv->f0;
+
+ drm_plane_helper_add(primary, &ingenic_drm_plane_helper_funcs);
+
+ ret = drm_universal_plane_init(drm, primary, 1,
+ &ingenic_drm_primary_plane_funcs,
+ priv->soc_info->formats_f1,
+ priv->soc_info->num_formats_f1,
+ NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (ret) {
+ dev_err(dev, "Failed to register plane: %i\n", ret);
+ return ret;
+ }
+
+ if (soc_info->map_noncoherent)
+ drm_plane_enable_fb_damage_clips(&priv->f1);
+
+ drm_crtc_helper_add(&priv->crtc, &ingenic_drm_crtc_helper_funcs);
+
+ ret = drm_crtc_init_with_planes(drm, &priv->crtc, primary,
+ NULL, &ingenic_drm_crtc_funcs, NULL);
+ if (ret) {
+ dev_err(dev, "Failed to init CRTC: %i\n", ret);
+ return ret;
+ }
+
+ drm_crtc_enable_color_mgmt(&priv->crtc, 0, false,
+ ARRAY_SIZE(priv->dma_hwdescs->palette));
+
+ 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,
+ priv->soc_info->formats_f0,
+ priv->soc_info->num_formats_f0,
+ NULL, DRM_PLANE_TYPE_OVERLAY,
+ NULL);
+ if (ret) {
+ dev_err(dev, "Failed to register overlay plane: %i\n",
+ ret);
+ return ret;
+ }
+
+ if (soc_info->map_noncoherent)
+ drm_plane_enable_fb_damage_clips(&priv->f0);
+
+ if (IS_ENABLED(CONFIG_DRM_INGENIC_IPU) && has_components) {
+ ret = component_bind_all(dev, drm);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to bind components: %i\n", ret);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, ingenic_drm_unbind_all, priv);
+ if (ret)
+ return ret;
+
+ priv->ipu_plane = drm_plane_from_index(drm, 2);
+ if (!priv->ipu_plane) {
+ dev_err(dev, "Failed to retrieve IPU plane\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ for (i = 0; ; i++) {
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 0, i, &panel, &bridge);
+ if (ret) {
+ if (ret == -ENODEV)
+ break; /* we're done */
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get bridge handle\n");
+ return ret;
+ }
+
+ if (panel)
+ bridge = devm_drm_panel_bridge_add_typed(dev, panel,
+ DRM_MODE_CONNECTOR_DPI);
+
+ ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder,
+ NULL, DRM_MODE_ENCODER_DPI, NULL);
+ if (IS_ERR(ib)) {
+ ret = PTR_ERR(ib);
+ dev_err(dev, "Failed to init encoder: %d\n", ret);
+ return ret;
+ }
+
+ encoder = &ib->encoder;
+ encoder->possible_crtcs = drm_crtc_mask(&priv->crtc);
+
+ drm_encoder_helper_add(encoder, &ingenic_drm_encoder_helper_funcs);
+
+ ib->bridge.funcs = &ingenic_drm_bridge_funcs;
+ ib->next_bridge = bridge;
+
+ ret = drm_bridge_attach(encoder, &ib->bridge, NULL,
+ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret) {
+ dev_err(dev, "Unable to attach bridge\n");
+ return ret;
+ }
+
+ connector = drm_bridge_connector_init(drm, encoder);
+ if (IS_ERR(connector)) {
+ dev_err(dev, "Unable to init connector\n");
+ return PTR_ERR(connector);
+ }
+
+ drm_connector_attach_encoder(connector, encoder);
+ }
+
+ drm_for_each_encoder(encoder, drm) {
+ clone_mask |= BIT(drm_encoder_index(encoder));
+ }
+
+ drm_for_each_encoder(encoder, drm) {
+ encoder->possible_clones = clone_mask;
+ }
+
+ ret = devm_request_irq(dev, irq, ingenic_drm_irq_handler, 0, drm->driver->name, drm);
+ if (ret) {
+ dev_err(dev, "Unable to install IRQ handler\n");
+ return ret;
+ }
+
+ ret = drm_vblank_init(drm, 1);
+ if (ret) {
+ dev_err(dev, "Failed calling drm_vblank_init()\n");
+ return ret;
+ }
+
+ drm_mode_config_reset(drm);
+
+ ret = clk_prepare_enable(priv->pix_clk);
+ if (ret) {
+ dev_err(dev, "Unable to start pixel clock\n");
+ return ret;
+ }
+
+ if (priv->lcd_clk) {
+ parent_clk = clk_get_parent(priv->lcd_clk);
+ parent_rate = clk_get_rate(parent_clk);
+
+ /* LCD Device clock must be 3x the pixel clock for STN panels,
+ * or 1.5x the pixel clock for TFT panels. To avoid having to
+ * check for the LCD device clock everytime we do a mode change,
+ * we set the LCD device clock to the highest rate possible.
+ */
+ ret = clk_set_rate(priv->lcd_clk, parent_rate);
+ if (ret) {
+ dev_err(dev, "Unable to set LCD clock rate\n");
+ goto err_pixclk_disable;
+ }
+
+ ret = clk_prepare_enable(priv->lcd_clk);
+ if (ret) {
+ dev_err(dev, "Unable to start lcd clock\n");
+ goto err_pixclk_disable;
+ }
+ }
+
+ /* Enable OSD if available */
+ if (soc_info->has_osd)
+ osdc |= JZ_LCD_OSDC_OSDEN;
+ if (soc_info->has_alpha)
+ osdc |= JZ_LCD_OSDC_ALPHAEN;
+ regmap_write(priv->map, JZ_REG_LCD_OSDC, osdc);
+
+ mutex_init(&priv->clk_mutex);
+ priv->clock_nb.notifier_call = ingenic_drm_update_pixclk;
+
+ parent_clk = clk_get_parent(priv->pix_clk);
+ ret = clk_notifier_register(parent_clk, &priv->clock_nb);
+ if (ret) {
+ dev_err(dev, "Unable to register clock notifier\n");
+ goto err_devclk_disable;
+ }
+
+ private_state = kzalloc(sizeof(*private_state), GFP_KERNEL);
+ if (!private_state) {
+ ret = -ENOMEM;
+ goto err_clk_notifier_unregister;
+ }
+
+ drm_atomic_private_obj_init(drm, &priv->private_obj, &private_state->base,
+ &ingenic_drm_private_state_funcs);
+
+ ret = drmm_add_action_or_reset(drm, ingenic_drm_atomic_private_obj_fini,
+ &priv->private_obj);
+ if (ret)
+ goto err_private_state_free;
+
+ ret = drm_dev_register(drm, 0);
+ if (ret) {
+ dev_err(dev, "Failed to register DRM driver\n");
+ goto err_clk_notifier_unregister;
+ }
+
+ drm_fbdev_generic_setup(drm, 32);
+
+ return 0;
+
+err_private_state_free:
+ kfree(private_state);
+err_clk_notifier_unregister:
+ clk_notifier_unregister(parent_clk, &priv->clock_nb);
+err_devclk_disable:
+ if (priv->lcd_clk)
+ clk_disable_unprepare(priv->lcd_clk);
+err_pixclk_disable:
+ clk_disable_unprepare(priv->pix_clk);
+ return ret;
+}
+
+static int ingenic_drm_bind_with_components(struct device *dev)
+{
+ return ingenic_drm_bind(dev, true);
+}
+
+static void ingenic_drm_unbind(struct device *dev)
+{
+ struct ingenic_drm *priv = dev_get_drvdata(dev);
+ struct clk *parent_clk = clk_get_parent(priv->pix_clk);
+
+ clk_notifier_unregister(parent_clk, &priv->clock_nb);
+ if (priv->lcd_clk)
+ clk_disable_unprepare(priv->lcd_clk);
+ clk_disable_unprepare(priv->pix_clk);
+
+ drm_dev_unregister(&priv->drm);
+ drm_atomic_helper_shutdown(&priv->drm);
+}
+
+static const struct component_master_ops ingenic_master_ops = {
+ .bind = ingenic_drm_bind_with_components,
+ .unbind = ingenic_drm_unbind,
+};
+
+static int ingenic_drm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct component_match *match = NULL;
+ struct device_node *np;
+
+ if (!IS_ENABLED(CONFIG_DRM_INGENIC_IPU))
+ return ingenic_drm_bind(dev, false);
+
+ /* IPU is at port address 8 */
+ np = of_graph_get_remote_node(dev->of_node, 8, 0);
+ if (!np)
+ return ingenic_drm_bind(dev, false);
+
+ drm_of_component_match_add(dev, &match, component_compare_of, np);
+ of_node_put(np);
+
+ return component_master_add_with_match(dev, &ingenic_master_ops, match);
+}
+
+static int ingenic_drm_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!IS_ENABLED(CONFIG_DRM_INGENIC_IPU))
+ ingenic_drm_unbind(dev);
+ else
+ component_master_del(dev, &ingenic_master_ops);
+
+ return 0;
+}
+
+static int ingenic_drm_suspend(struct device *dev)
+{
+ struct ingenic_drm *priv = dev_get_drvdata(dev);
+
+ return drm_mode_config_helper_suspend(&priv->drm);
+}
+
+static int ingenic_drm_resume(struct device *dev)
+{
+ struct ingenic_drm *priv = dev_get_drvdata(dev);
+
+ return drm_mode_config_helper_resume(&priv->drm);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(ingenic_drm_pm_ops,
+ ingenic_drm_suspend, ingenic_drm_resume);
+
+static const u32 jz4740_formats[] = {
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB8888,
+};
+
+static const u32 jz4725b_formats_f1[] = {
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB8888,
+};
+
+static const u32 jz4725b_formats_f0[] = {
+ DRM_FORMAT_C8,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB8888,
+};
+
+static const u32 jz4770_formats_f1[] = {
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XRGB2101010,
+};
+
+static const u32 jz4770_formats_f0[] = {
+ DRM_FORMAT_C8,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XRGB2101010,
+};
+
+static const struct jz_soc_info jz4740_soc_info = {
+ .needs_dev_clk = true,
+ .has_osd = false,
+ .map_noncoherent = false,
+ .max_width = 800,
+ .max_height = 600,
+ .max_burst = JZ_LCD_CTRL_BURST_16,
+ .formats_f1 = jz4740_formats,
+ .num_formats_f1 = ARRAY_SIZE(jz4740_formats),
+ /* JZ4740 has only one plane */
+};
+
+static const struct jz_soc_info jz4725b_soc_info = {
+ .needs_dev_clk = false,
+ .has_osd = true,
+ .map_noncoherent = false,
+ .max_width = 800,
+ .max_height = 600,
+ .max_burst = JZ_LCD_CTRL_BURST_16,
+ .formats_f1 = jz4725b_formats_f1,
+ .num_formats_f1 = ARRAY_SIZE(jz4725b_formats_f1),
+ .formats_f0 = jz4725b_formats_f0,
+ .num_formats_f0 = ARRAY_SIZE(jz4725b_formats_f0),
+};
+
+static const struct jz_soc_info jz4760_soc_info = {
+ .needs_dev_clk = false,
+ .has_osd = true,
+ .map_noncoherent = false,
+ .max_width = 1280,
+ .max_height = 720,
+ .max_burst = JZ_LCD_CTRL_BURST_32,
+ .formats_f1 = jz4770_formats_f1,
+ .num_formats_f1 = ARRAY_SIZE(jz4770_formats_f1),
+ .formats_f0 = jz4770_formats_f0,
+ .num_formats_f0 = ARRAY_SIZE(jz4770_formats_f0),
+};
+
+static const struct jz_soc_info jz4760b_soc_info = {
+ .needs_dev_clk = false,
+ .has_osd = true,
+ .map_noncoherent = false,
+ .max_width = 1280,
+ .max_height = 720,
+ .max_burst = JZ_LCD_CTRL_BURST_64,
+ .formats_f1 = jz4770_formats_f1,
+ .num_formats_f1 = ARRAY_SIZE(jz4770_formats_f1),
+ .formats_f0 = jz4770_formats_f0,
+ .num_formats_f0 = ARRAY_SIZE(jz4770_formats_f0),
+};
+
+static const struct jz_soc_info jz4770_soc_info = {
+ .needs_dev_clk = false,
+ .has_osd = true,
+ .map_noncoherent = true,
+ .max_width = 1280,
+ .max_height = 720,
+ .max_burst = JZ_LCD_CTRL_BURST_64,
+ .formats_f1 = jz4770_formats_f1,
+ .num_formats_f1 = ARRAY_SIZE(jz4770_formats_f1),
+ .formats_f0 = jz4770_formats_f0,
+ .num_formats_f0 = ARRAY_SIZE(jz4770_formats_f0),
+};
+
+static const struct jz_soc_info jz4780_soc_info = {
+ .needs_dev_clk = true,
+ .has_osd = true,
+ .has_alpha = true,
+ .use_extended_hwdesc = true,
+ .plane_f0_not_working = true, /* REVISIT */
+ .max_width = 4096,
+ .max_height = 2048,
+ .max_burst = JZ_LCD_CTRL_BURST_64,
+ .formats_f1 = jz4770_formats_f1,
+ .num_formats_f1 = ARRAY_SIZE(jz4770_formats_f1),
+ .formats_f0 = jz4770_formats_f0,
+ .num_formats_f0 = ARRAY_SIZE(jz4770_formats_f0),
+};
+
+static const struct of_device_id ingenic_drm_of_match[] = {
+ { .compatible = "ingenic,jz4740-lcd", .data = &jz4740_soc_info },
+ { .compatible = "ingenic,jz4725b-lcd", .data = &jz4725b_soc_info },
+ { .compatible = "ingenic,jz4760-lcd", .data = &jz4760_soc_info },
+ { .compatible = "ingenic,jz4760b-lcd", .data = &jz4760b_soc_info },
+ { .compatible = "ingenic,jz4770-lcd", .data = &jz4770_soc_info },
+ { .compatible = "ingenic,jz4780-lcd", .data = &jz4780_soc_info },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ingenic_drm_of_match);
+
+static struct platform_driver ingenic_drm_driver = {
+ .driver = {
+ .name = "ingenic-drm",
+ .pm = pm_sleep_ptr(&ingenic_drm_pm_ops),
+ .of_match_table = of_match_ptr(ingenic_drm_of_match),
+ },
+ .probe = ingenic_drm_probe,
+ .remove = ingenic_drm_remove,
+};
+
+static int ingenic_drm_init(void)
+{
+ int err;
+
+ if (drm_firmware_drivers_only())
+ return -ENODEV;
+
+ if (IS_ENABLED(CONFIG_DRM_INGENIC_IPU)) {
+ err = platform_driver_register(ingenic_ipu_driver_ptr);
+ if (err)
+ return err;
+ }
+
+ err = platform_driver_register(&ingenic_drm_driver);
+ if (IS_ENABLED(CONFIG_DRM_INGENIC_IPU) && err)
+ platform_driver_unregister(ingenic_ipu_driver_ptr);
+
+ return err;
+}
+module_init(ingenic_drm_init);
+
+static void ingenic_drm_exit(void)
+{
+ platform_driver_unregister(&ingenic_drm_driver);
+
+ if (IS_ENABLED(CONFIG_DRM_INGENIC_IPU))
+ platform_driver_unregister(ingenic_ipu_driver_ptr);
+}
+module_exit(ingenic_drm_exit);
+
+MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
+MODULE_DESCRIPTION("DRM driver for the Ingenic SoCs\n");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.h b/drivers/gpu/drm/ingenic/ingenic-drm.h
new file mode 100644
index 000000000..e5bd007ea
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/ingenic-drm.h
@@ -0,0 +1,232 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+//
+// Ingenic JZ47xx KMS driver - Register definitions and private API
+//
+// Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net>
+
+#ifndef DRIVERS_GPU_DRM_INGENIC_INGENIC_DRM_H
+#define DRIVERS_GPU_DRM_INGENIC_INGENIC_DRM_H
+
+#include <linux/bitops.h>
+#include <linux/types.h>
+
+#define JZ_REG_LCD_CFG 0x00
+#define JZ_REG_LCD_VSYNC 0x04
+#define JZ_REG_LCD_HSYNC 0x08
+#define JZ_REG_LCD_VAT 0x0C
+#define JZ_REG_LCD_DAH 0x10
+#define JZ_REG_LCD_DAV 0x14
+#define JZ_REG_LCD_PS 0x18
+#define JZ_REG_LCD_CLS 0x1C
+#define JZ_REG_LCD_SPL 0x20
+#define JZ_REG_LCD_REV 0x24
+#define JZ_REG_LCD_CTRL 0x30
+#define JZ_REG_LCD_STATE 0x34
+#define JZ_REG_LCD_IID 0x38
+#define JZ_REG_LCD_DA0 0x40
+#define JZ_REG_LCD_SA0 0x44
+#define JZ_REG_LCD_FID0 0x48
+#define JZ_REG_LCD_CMD0 0x4C
+#define JZ_REG_LCD_DA1 0x50
+#define JZ_REG_LCD_SA1 0x54
+#define JZ_REG_LCD_FID1 0x58
+#define JZ_REG_LCD_CMD1 0x5C
+#define JZ_REG_LCD_RGBC 0x90
+#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_REG_LCD_PCFG 0x2c0
+
+#define JZ_LCD_CFG_SLCD BIT(31)
+#define JZ_LCD_CFG_DESCRIPTOR_8 BIT(28)
+#define JZ_LCD_CFG_RECOVER_FIFO_UNDERRUN BIT(25)
+#define JZ_LCD_CFG_PS_DISABLE BIT(23)
+#define JZ_LCD_CFG_CLS_DISABLE BIT(22)
+#define JZ_LCD_CFG_SPL_DISABLE BIT(21)
+#define JZ_LCD_CFG_REV_DISABLE BIT(20)
+#define JZ_LCD_CFG_HSYNCM BIT(19)
+#define JZ_LCD_CFG_PCLKM BIT(18)
+#define JZ_LCD_CFG_INV BIT(17)
+#define JZ_LCD_CFG_SYNC_DIR BIT(16)
+#define JZ_LCD_CFG_PS_POLARITY BIT(15)
+#define JZ_LCD_CFG_CLS_POLARITY BIT(14)
+#define JZ_LCD_CFG_SPL_POLARITY BIT(13)
+#define JZ_LCD_CFG_REV_POLARITY BIT(12)
+#define JZ_LCD_CFG_HSYNC_ACTIVE_LOW BIT(11)
+#define JZ_LCD_CFG_PCLK_FALLING_EDGE BIT(10)
+#define JZ_LCD_CFG_DE_ACTIVE_LOW BIT(9)
+#define JZ_LCD_CFG_VSYNC_ACTIVE_LOW BIT(8)
+#define JZ_LCD_CFG_18_BIT BIT(7)
+#define JZ_LCD_CFG_24_BIT BIT(6)
+#define JZ_LCD_CFG_PDW (BIT(5) | BIT(4))
+
+#define JZ_LCD_CFG_MODE_GENERIC_16BIT 0
+#define JZ_LCD_CFG_MODE_GENERIC_18BIT BIT(7)
+#define JZ_LCD_CFG_MODE_GENERIC_24BIT BIT(6)
+
+#define JZ_LCD_CFG_MODE_SPECIAL_TFT_1 1
+#define JZ_LCD_CFG_MODE_SPECIAL_TFT_2 2
+#define JZ_LCD_CFG_MODE_SPECIAL_TFT_3 3
+
+#define JZ_LCD_CFG_MODE_TV_OUT_P 4
+#define JZ_LCD_CFG_MODE_TV_OUT_I 6
+
+#define JZ_LCD_CFG_MODE_SINGLE_COLOR_STN 8
+#define JZ_LCD_CFG_MODE_SINGLE_MONOCHROME_STN 9
+#define JZ_LCD_CFG_MODE_DUAL_COLOR_STN 10
+#define JZ_LCD_CFG_MODE_DUAL_MONOCHROME_STN 11
+
+#define JZ_LCD_CFG_MODE_8BIT_SERIAL 12
+#define JZ_LCD_CFG_MODE_LCM 13
+
+#define JZ_LCD_VSYNC_VPS_OFFSET 16
+#define JZ_LCD_VSYNC_VPE_OFFSET 0
+
+#define JZ_LCD_HSYNC_HPS_OFFSET 16
+#define JZ_LCD_HSYNC_HPE_OFFSET 0
+
+#define JZ_LCD_VAT_HT_OFFSET 16
+#define JZ_LCD_VAT_VT_OFFSET 0
+
+#define JZ_LCD_DAH_HDS_OFFSET 16
+#define JZ_LCD_DAH_HDE_OFFSET 0
+
+#define JZ_LCD_DAV_VDS_OFFSET 16
+#define JZ_LCD_DAV_VDE_OFFSET 0
+
+#define JZ_LCD_CTRL_BURST_4 (0x0 << 28)
+#define JZ_LCD_CTRL_BURST_8 (0x1 << 28)
+#define JZ_LCD_CTRL_BURST_16 (0x2 << 28)
+#define JZ_LCD_CTRL_BURST_32 (0x3 << 28)
+#define JZ_LCD_CTRL_BURST_64 (0x4 << 28)
+#define JZ_LCD_CTRL_BURST_MASK (0x7 << 28)
+#define JZ_LCD_CTRL_RGB555 BIT(27)
+#define JZ_LCD_CTRL_OFUP BIT(26)
+#define JZ_LCD_CTRL_FRC_GRAYSCALE_16 (0x0 << 24)
+#define JZ_LCD_CTRL_FRC_GRAYSCALE_4 (0x1 << 24)
+#define JZ_LCD_CTRL_FRC_GRAYSCALE_2 (0x2 << 24)
+#define JZ_LCD_CTRL_PDD_MASK (0xff << 16)
+#define JZ_LCD_CTRL_EOF_IRQ BIT(13)
+#define JZ_LCD_CTRL_SOF_IRQ BIT(12)
+#define JZ_LCD_CTRL_OFU_IRQ BIT(11)
+#define JZ_LCD_CTRL_IFU0_IRQ BIT(10)
+#define JZ_LCD_CTRL_IFU1_IRQ BIT(9)
+#define JZ_LCD_CTRL_DD_IRQ BIT(8)
+#define JZ_LCD_CTRL_QDD_IRQ BIT(7)
+#define JZ_LCD_CTRL_REVERSE_ENDIAN BIT(6)
+#define JZ_LCD_CTRL_LSB_FISRT BIT(5)
+#define JZ_LCD_CTRL_DISABLE BIT(4)
+#define JZ_LCD_CTRL_ENABLE BIT(3)
+#define JZ_LCD_CTRL_BPP_1 0x0
+#define JZ_LCD_CTRL_BPP_2 0x1
+#define JZ_LCD_CTRL_BPP_4 0x2
+#define JZ_LCD_CTRL_BPP_8 0x3
+#define JZ_LCD_CTRL_BPP_15_16 0x4
+#define JZ_LCD_CTRL_BPP_18_24 0x5
+#define JZ_LCD_CTRL_BPP_24_COMP 0x6
+#define JZ_LCD_CTRL_BPP_30 0x7
+#define JZ_LCD_CTRL_BPP_MASK (JZ_LCD_CTRL_RGB555 | 0x7)
+
+#define JZ_LCD_CMD_SOF_IRQ BIT(31)
+#define JZ_LCD_CMD_EOF_IRQ BIT(30)
+#define JZ_LCD_CMD_ENABLE_PAL BIT(28)
+#define JZ_LCD_CMD_FRM_ENABLE BIT(26)
+
+#define JZ_LCD_SYNC_MASK 0x3ff
+
+#define JZ_LCD_STATE_EOF_IRQ BIT(5)
+#define JZ_LCD_STATE_SOF_IRQ BIT(4)
+#define JZ_LCD_STATE_DISABLED BIT(0)
+
+#define JZ_LCD_RGBC_ODD_RGB (0x0 << 4)
+#define JZ_LCD_RGBC_ODD_RBG (0x1 << 4)
+#define JZ_LCD_RGBC_ODD_GRB (0x2 << 4)
+#define JZ_LCD_RGBC_ODD_GBR (0x3 << 4)
+#define JZ_LCD_RGBC_ODD_BRG (0x4 << 4)
+#define JZ_LCD_RGBC_ODD_BGR (0x5 << 4)
+#define JZ_LCD_RGBC_EVEN_RGB (0x0 << 0)
+#define JZ_LCD_RGBC_EVEN_RBG (0x1 << 0)
+#define JZ_LCD_RGBC_EVEN_GRB (0x2 << 0)
+#define JZ_LCD_RGBC_EVEN_GBR (0x3 << 0)
+#define JZ_LCD_RGBC_EVEN_BRG (0x4 << 0)
+#define JZ_LCD_RGBC_EVEN_BGR (0x5 << 0)
+
+#define JZ_LCD_OSDC_OSDEN BIT(0)
+#define JZ_LCD_OSDC_ALPHAEN BIT(2)
+#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_24_COMP 0x6
+#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
+
+#define JZ_LCD_DESSIZE_ALPHA_OFFSET 24
+#define JZ_LCD_DESSIZE_HEIGHT_MASK GENMASK(23, 12)
+#define JZ_LCD_DESSIZE_WIDTH_MASK GENMASK(11, 0)
+
+#define JZ_LCD_CPOS_BPP_15_16 (4 << 27)
+#define JZ_LCD_CPOS_BPP_18_24 (5 << 27)
+#define JZ_LCD_CPOS_BPP_30 (7 << 27)
+#define JZ_LCD_CPOS_RGB555 BIT(30)
+#define JZ_LCD_CPOS_PREMULTIPLY_LCD BIT(26)
+#define JZ_LCD_CPOS_COEFFICIENT_OFFSET 24
+#define JZ_LCD_CPOS_COEFFICIENT_0 0
+#define JZ_LCD_CPOS_COEFFICIENT_1 1
+#define JZ_LCD_CPOS_COEFFICIENT_ALPHA1 2
+#define JZ_LCD_CPOS_COEFFICIENT_1_ALPHA1 3
+
+#define JZ_LCD_RGBC_RGB_PADDING BIT(15)
+#define JZ_LCD_RGBC_RGB_PADDING_FIRST BIT(14)
+#define JZ_LCD_RGBC_422 BIT(8)
+#define JZ_LCD_RGBC_RGB_FORMAT_ENABLE BIT(7)
+
+#define JZ_LCD_PCFG_PRI_MODE BIT(31)
+#define JZ_LCD_PCFG_HP_BST_4 (0 << 28)
+#define JZ_LCD_PCFG_HP_BST_8 (1 << 28)
+#define JZ_LCD_PCFG_HP_BST_16 (2 << 28)
+#define JZ_LCD_PCFG_HP_BST_32 (3 << 28)
+#define JZ_LCD_PCFG_HP_BST_64 (4 << 28)
+#define JZ_LCD_PCFG_HP_BST_16_CONT (5 << 28)
+#define JZ_LCD_PCFG_HP_BST_DISABLE (7 << 28)
+#define JZ_LCD_PCFG_THRESHOLD2_OFFSET 18
+#define JZ_LCD_PCFG_THRESHOLD1_OFFSET 9
+#define JZ_LCD_PCFG_THRESHOLD0_OFFSET 0
+
+struct device;
+struct drm_plane;
+struct drm_plane_state;
+struct platform_driver;
+
+void ingenic_drm_plane_config(struct device *dev,
+ struct drm_plane *plane, u32 fourcc);
+void ingenic_drm_plane_disable(struct device *dev, struct drm_plane *plane);
+bool ingenic_drm_map_noncoherent(const struct device *dev);
+
+extern struct platform_driver *ingenic_ipu_driver_ptr;
+
+#endif /* DRIVERS_GPU_DRM_INGENIC_INGENIC_DRM_H */
diff --git a/drivers/gpu/drm/ingenic/ingenic-dw-hdmi.c b/drivers/gpu/drm/ingenic/ingenic-dw-hdmi.c
new file mode 100644
index 000000000..72f8b4499
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/ingenic-dw-hdmi.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2019, 2020 Paul Boddie <paul@boddie.org.uk>
+ *
+ * Derived from dw_hdmi-imx.c with i.MX portions removed.
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+
+static const struct dw_hdmi_mpll_config ingenic_mpll_cfg[] = {
+ { 45250000, { { 0x01e0, 0x0000 }, { 0x21e1, 0x0000 }, { 0x41e2, 0x0000 } } },
+ { 92500000, { { 0x0140, 0x0005 }, { 0x2141, 0x0005 }, { 0x4142, 0x0005 } } },
+ { 148500000, { { 0x00a0, 0x000a }, { 0x20a1, 0x000a }, { 0x40a2, 0x000a } } },
+ { 216000000, { { 0x00a0, 0x000a }, { 0x2001, 0x000f }, { 0x4002, 0x000f } } },
+ { ~0UL, { { 0x0000, 0x0000 }, { 0x0000, 0x0000 }, { 0x0000, 0x0000 } } }
+};
+
+static const struct dw_hdmi_curr_ctrl ingenic_cur_ctr[] = {
+ /*pixelclk bpp8 bpp10 bpp12 */
+ { 54000000, { 0x091c, 0x091c, 0x06dc } },
+ { 58400000, { 0x091c, 0x06dc, 0x06dc } },
+ { 72000000, { 0x06dc, 0x06dc, 0x091c } },
+ { 74250000, { 0x06dc, 0x0b5c, 0x091c } },
+ { 118800000, { 0x091c, 0x091c, 0x06dc } },
+ { 216000000, { 0x06dc, 0x0b5c, 0x091c } },
+ { ~0UL, { 0x0000, 0x0000, 0x0000 } },
+};
+
+/*
+ * Resistance term 133Ohm Cfg
+ * PREEMP config 0.00
+ * TX/CK level 10
+ */
+static const struct dw_hdmi_phy_config ingenic_phy_config[] = {
+ /*pixelclk symbol term vlev */
+ { 216000000, 0x800d, 0x0005, 0x01ad},
+ { ~0UL, 0x0000, 0x0000, 0x0000}
+};
+
+static enum drm_mode_status
+ingenic_dw_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ if (mode->clock < 13500)
+ return MODE_CLOCK_LOW;
+ /* FIXME: Hardware is capable of 270MHz, but setup data is missing. */
+ if (mode->clock > 216000)
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static struct dw_hdmi_plat_data ingenic_dw_hdmi_plat_data = {
+ .mpll_cfg = ingenic_mpll_cfg,
+ .cur_ctr = ingenic_cur_ctr,
+ .phy_config = ingenic_phy_config,
+ .mode_valid = ingenic_dw_hdmi_mode_valid,
+ .output_port = 1,
+};
+
+static const struct of_device_id ingenic_dw_hdmi_dt_ids[] = {
+ { .compatible = "ingenic,jz4780-dw-hdmi" },
+ { /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ingenic_dw_hdmi_dt_ids);
+
+static void ingenic_dw_hdmi_cleanup(void *data)
+{
+ struct dw_hdmi *hdmi = (struct dw_hdmi *)data;
+
+ dw_hdmi_remove(hdmi);
+}
+
+static int ingenic_dw_hdmi_probe(struct platform_device *pdev)
+{
+ struct dw_hdmi *hdmi;
+
+ hdmi = dw_hdmi_probe(pdev, &ingenic_dw_hdmi_plat_data);
+ if (IS_ERR(hdmi))
+ return PTR_ERR(hdmi);
+
+ return devm_add_action_or_reset(&pdev->dev, ingenic_dw_hdmi_cleanup, hdmi);
+}
+
+static struct platform_driver ingenic_dw_hdmi_driver = {
+ .probe = ingenic_dw_hdmi_probe,
+ .driver = {
+ .name = "dw-hdmi-ingenic",
+ .of_match_table = ingenic_dw_hdmi_dt_ids,
+ },
+};
+module_platform_driver(ingenic_dw_hdmi_driver);
+
+MODULE_DESCRIPTION("JZ4780 Specific DW-HDMI Driver Extension");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:dw-hdmi-ingenic");
diff --git a/drivers/gpu/drm/ingenic/ingenic-ipu.c b/drivers/gpu/drm/ingenic/ingenic-ipu.c
new file mode 100644
index 000000000..6d236547f
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/ingenic-ipu.c
@@ -0,0 +1,998 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Ingenic JZ47xx IPU driver
+//
+// Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net>
+// Copyright (C) 2020, Daniel Silsby <dansilsby@gmail.com>
+
+#include "ingenic-drm.h"
+#include "ingenic-ipu.h"
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/gcd.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_property.h>
+#include <drm/drm_vblank.h>
+
+struct ingenic_ipu;
+
+struct soc_info {
+ const u32 *formats;
+ size_t num_formats;
+ bool has_bicubic;
+ bool manual_restart;
+
+ void (*set_coefs)(struct ingenic_ipu *ipu, unsigned int reg,
+ unsigned int sharpness, bool downscale,
+ unsigned int weight, unsigned int offset);
+};
+
+struct ingenic_ipu_private_state {
+ struct drm_private_state base;
+
+ unsigned int num_w, num_h, denom_w, denom_h;
+};
+
+struct ingenic_ipu {
+ struct drm_plane plane;
+ struct drm_device *drm;
+ struct device *dev, *master;
+ struct regmap *map;
+ struct clk *clk;
+ const struct soc_info *soc_info;
+ bool clk_enabled;
+
+ dma_addr_t addr_y, addr_u, addr_v;
+
+ struct drm_property *sharpness_prop;
+ unsigned int sharpness;
+
+ struct drm_private_obj private_obj;
+};
+
+/* Signed 15.16 fixed-point math (for bicubic scaling coefficients) */
+#define I2F(i) ((s32)(i) * 65536)
+#define F2I(f) ((f) / 65536)
+#define FMUL(fa, fb) ((s32)(((s64)(fa) * (s64)(fb)) / 65536))
+#define SHARPNESS_INCR (I2F(-1) / 8)
+
+static inline struct ingenic_ipu *plane_to_ingenic_ipu(struct drm_plane *plane)
+{
+ return container_of(plane, struct ingenic_ipu, plane);
+}
+
+static inline struct ingenic_ipu_private_state *
+to_ingenic_ipu_priv_state(struct drm_private_state *state)
+{
+ return container_of(state, struct ingenic_ipu_private_state, base);
+}
+
+static struct ingenic_ipu_private_state *
+ingenic_ipu_get_priv_state(struct ingenic_ipu *priv, struct drm_atomic_state *state)
+{
+ struct drm_private_state *priv_state;
+
+ priv_state = drm_atomic_get_private_obj_state(state, &priv->private_obj);
+ if (IS_ERR(priv_state))
+ return ERR_CAST(priv_state);
+
+ return to_ingenic_ipu_priv_state(priv_state);
+}
+
+static struct ingenic_ipu_private_state *
+ingenic_ipu_get_new_priv_state(struct ingenic_ipu *priv, struct drm_atomic_state *state)
+{
+ struct drm_private_state *priv_state;
+
+ priv_state = drm_atomic_get_new_private_obj_state(state, &priv->private_obj);
+ if (!priv_state)
+ return NULL;
+
+ return to_ingenic_ipu_priv_state(priv_state);
+}
+
+/*
+ * Apply conventional cubic convolution kernel. Both parameters
+ * and return value are 15.16 signed fixed-point.
+ *
+ * @f_a: Sharpness factor, typically in range [-4.0, -0.25].
+ * A larger magnitude increases perceived sharpness, but going past
+ * -2.0 might cause ringing artifacts to outweigh any improvement.
+ * Nice values on a 320x240 LCD are between -0.75 and -2.0.
+ *
+ * @f_x: Absolute distance in pixels from 'pixel 0' sample position
+ * along horizontal (or vertical) source axis. Range is [0, +2.0].
+ *
+ * returns: Weight of this pixel within 4-pixel sample group. Range is
+ * [-2.0, +2.0]. For moderate (i.e. > -3.0) sharpness factors,
+ * range is within [-1.0, +1.0].
+ */
+static inline s32 cubic_conv(s32 f_a, s32 f_x)
+{
+ const s32 f_1 = I2F(1);
+ const s32 f_2 = I2F(2);
+ const s32 f_3 = I2F(3);
+ const s32 f_4 = I2F(4);
+ const s32 f_x2 = FMUL(f_x, f_x);
+ const s32 f_x3 = FMUL(f_x, f_x2);
+
+ if (f_x <= f_1)
+ return FMUL((f_a + f_2), f_x3) - FMUL((f_a + f_3), f_x2) + f_1;
+ else if (f_x <= f_2)
+ return FMUL(f_a, (f_x3 - 5 * f_x2 + 8 * f_x - f_4));
+ else
+ return 0;
+}
+
+/*
+ * On entry, "weight" is a coefficient suitable for bilinear mode,
+ * which is converted to a set of four suitable for bicubic mode.
+ *
+ * "weight 512" means all of pixel 0;
+ * "weight 256" means half of pixel 0 and half of pixel 1;
+ * "weight 0" means all of pixel 1;
+ *
+ * "offset" is increment to next source pixel sample location.
+ */
+static void jz4760_set_coefs(struct ingenic_ipu *ipu, unsigned int reg,
+ unsigned int sharpness, bool downscale,
+ unsigned int weight, unsigned int offset)
+{
+ u32 val;
+ s32 w0, w1, w2, w3; /* Pixel weights at X (or Y) offsets -1,0,1,2 */
+
+ weight = clamp_val(weight, 0, 512);
+
+ if (sharpness < 2) {
+ /*
+ * When sharpness setting is 0, emulate nearest-neighbor.
+ * When sharpness setting is 1, emulate bilinear.
+ */
+
+ if (sharpness == 0)
+ weight = weight >= 256 ? 512 : 0;
+ w0 = 0;
+ w1 = weight;
+ w2 = 512 - weight;
+ w3 = 0;
+ } else {
+ const s32 f_a = SHARPNESS_INCR * sharpness;
+ const s32 f_h = I2F(1) / 2; /* Round up 0.5 */
+
+ /*
+ * Note that always rounding towards +infinity here is intended.
+ * The resulting coefficients match a round-to-nearest-int
+ * double floating-point implementation.
+ */
+
+ weight = 512 - weight;
+ w0 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512 + weight) / 512));
+ w1 = F2I(f_h + 512 * cubic_conv(f_a, I2F(0 + weight) / 512));
+ w2 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512 - weight) / 512));
+ w3 = F2I(f_h + 512 * cubic_conv(f_a, I2F(1024 - weight) / 512));
+ w0 = clamp_val(w0, -1024, 1023);
+ w1 = clamp_val(w1, -1024, 1023);
+ w2 = clamp_val(w2, -1024, 1023);
+ w3 = clamp_val(w3, -1024, 1023);
+ }
+
+ val = ((w1 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) |
+ ((w0 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB);
+ regmap_write(ipu->map, reg, val);
+
+ val = ((w3 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) |
+ ((w2 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB) |
+ ((offset & JZ4760_IPU_RSZ_OFFSET_MASK) << JZ4760_IPU_RSZ_OFFSET_LSB);
+ regmap_write(ipu->map, reg, val);
+}
+
+static void jz4725b_set_coefs(struct ingenic_ipu *ipu, unsigned int reg,
+ unsigned int sharpness, bool downscale,
+ unsigned int weight, unsigned int offset)
+{
+ u32 val = JZ4725B_IPU_RSZ_LUT_OUT_EN;
+ unsigned int i;
+
+ weight = clamp_val(weight, 0, 512);
+
+ if (sharpness == 0)
+ weight = weight >= 256 ? 512 : 0;
+
+ val |= (weight & JZ4725B_IPU_RSZ_LUT_COEF_MASK) << JZ4725B_IPU_RSZ_LUT_COEF_LSB;
+ if (downscale || !!offset)
+ val |= JZ4725B_IPU_RSZ_LUT_IN_EN;
+
+ regmap_write(ipu->map, reg, val);
+
+ if (downscale) {
+ for (i = 1; i < offset; i++)
+ regmap_write(ipu->map, reg, JZ4725B_IPU_RSZ_LUT_IN_EN);
+ }
+}
+
+static void ingenic_ipu_set_downscale_coefs(struct ingenic_ipu *ipu,
+ unsigned int reg,
+ unsigned int num,
+ unsigned int denom)
+{
+ unsigned int i, offset, weight, weight_num = denom;
+
+ for (i = 0; i < num; i++) {
+ weight_num = num + (weight_num - num) % (num * 2);
+ weight = 512 - 512 * (weight_num - num) / (num * 2);
+ weight_num += denom * 2;
+ offset = (weight_num - num) / (num * 2);
+
+ ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness,
+ true, weight, offset);
+ }
+}
+
+static void ingenic_ipu_set_integer_upscale_coefs(struct ingenic_ipu *ipu,
+ unsigned int reg,
+ unsigned int num)
+{
+ /*
+ * Force nearest-neighbor scaling and use simple math when upscaling
+ * by an integer ratio. It looks better, and fixes a few problem cases.
+ */
+ unsigned int i;
+
+ for (i = 0; i < num; i++)
+ ipu->soc_info->set_coefs(ipu, reg, 0, false, 512, i == num - 1);
+}
+
+static void ingenic_ipu_set_upscale_coefs(struct ingenic_ipu *ipu,
+ unsigned int reg,
+ unsigned int num,
+ unsigned int denom)
+{
+ unsigned int i, offset, weight, weight_num = 0;
+
+ for (i = 0; i < num; i++) {
+ weight = 512 - 512 * weight_num / num;
+ weight_num += denom;
+ offset = weight_num >= num;
+
+ if (offset)
+ weight_num -= num;
+
+ ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness,
+ false, weight, offset);
+ }
+}
+
+static void ingenic_ipu_set_coefs(struct ingenic_ipu *ipu, unsigned int reg,
+ unsigned int num, unsigned int denom)
+{
+ /* Begin programming the LUT */
+ regmap_write(ipu->map, reg, -1);
+
+ if (denom > num)
+ ingenic_ipu_set_downscale_coefs(ipu, reg, num, denom);
+ else if (denom == 1)
+ ingenic_ipu_set_integer_upscale_coefs(ipu, reg, num);
+ else
+ ingenic_ipu_set_upscale_coefs(ipu, reg, num, denom);
+}
+
+static int reduce_fraction(unsigned int *num, unsigned int *denom)
+{
+ unsigned long d = gcd(*num, *denom);
+
+ /* The scaling table has only 31 entries */
+ if (*num > 31 * d)
+ return -EINVAL;
+
+ *num /= d;
+ *denom /= d;
+ return 0;
+}
+
+static inline bool osd_changed(struct drm_plane_state *state,
+ struct drm_plane_state *oldstate)
+{
+ return state->src_x != oldstate->src_x ||
+ state->src_y != oldstate->src_y ||
+ state->src_w != oldstate->src_w ||
+ state->src_h != oldstate->src_h ||
+ state->crtc_x != oldstate->crtc_x ||
+ state->crtc_y != oldstate->crtc_y ||
+ state->crtc_w != oldstate->crtc_w ||
+ state->crtc_h != oldstate->crtc_h;
+}
+
+static void ingenic_ipu_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+ struct drm_plane_state *newstate = drm_atomic_get_new_plane_state(state, plane);
+ struct drm_plane_state *oldstate = drm_atomic_get_old_plane_state(state, plane);
+ const struct drm_format_info *finfo;
+ u32 ctrl, stride = 0, coef_index = 0, format = 0;
+ bool needs_modeset, upscaling_w, upscaling_h;
+ struct ingenic_ipu_private_state *ipu_state;
+ int err;
+
+ if (!newstate || !newstate->fb)
+ return;
+
+ ipu_state = ingenic_ipu_get_new_priv_state(ipu, state);
+ if (WARN_ON(!ipu_state))
+ return;
+
+ finfo = drm_format_info(newstate->fb->format->format);
+
+ if (!ipu->clk_enabled) {
+ err = clk_enable(ipu->clk);
+ if (err) {
+ dev_err(ipu->dev, "Unable to enable clock: %d\n", err);
+ return;
+ }
+
+ ipu->clk_enabled = true;
+ }
+
+ /* Reset all the registers if needed */
+ needs_modeset = drm_atomic_crtc_needs_modeset(newstate->crtc->state);
+ if (needs_modeset) {
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RST);
+
+ /* Enable the chip */
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL,
+ JZ_IPU_CTRL_CHIP_EN | JZ_IPU_CTRL_LCDC_SEL);
+ }
+
+ if (ingenic_drm_map_noncoherent(ipu->master))
+ drm_fb_dma_sync_non_coherent(ipu->drm, oldstate, newstate);
+
+ /* New addresses will be committed in vblank handler... */
+ ipu->addr_y = drm_fb_dma_get_gem_addr(newstate->fb, newstate, 0);
+ if (finfo->num_planes > 1)
+ ipu->addr_u = drm_fb_dma_get_gem_addr(newstate->fb, newstate,
+ 1);
+ if (finfo->num_planes > 2)
+ ipu->addr_v = drm_fb_dma_get_gem_addr(newstate->fb, newstate,
+ 2);
+
+ if (!needs_modeset)
+ return;
+
+ /* Or right here if we're doing a full modeset. */
+ regmap_write(ipu->map, JZ_REG_IPU_Y_ADDR, ipu->addr_y);
+ regmap_write(ipu->map, JZ_REG_IPU_U_ADDR, ipu->addr_u);
+ regmap_write(ipu->map, JZ_REG_IPU_V_ADDR, ipu->addr_v);
+
+ if (finfo->num_planes == 1)
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_SPKG_SEL);
+
+ ingenic_drm_plane_config(ipu->master, plane, DRM_FORMAT_XRGB8888);
+
+ /* Set the input height/width/strides */
+ if (finfo->num_planes > 2)
+ stride = ((newstate->src_w >> 16) * finfo->cpp[2] / finfo->hsub)
+ << JZ_IPU_UV_STRIDE_V_LSB;
+
+ if (finfo->num_planes > 1)
+ stride |= ((newstate->src_w >> 16) * finfo->cpp[1] / finfo->hsub)
+ << JZ_IPU_UV_STRIDE_U_LSB;
+
+ regmap_write(ipu->map, JZ_REG_IPU_UV_STRIDE, stride);
+
+ stride = ((newstate->src_w >> 16) * finfo->cpp[0]) << JZ_IPU_Y_STRIDE_Y_LSB;
+ regmap_write(ipu->map, JZ_REG_IPU_Y_STRIDE, stride);
+
+ regmap_write(ipu->map, JZ_REG_IPU_IN_GS,
+ (stride << JZ_IPU_IN_GS_W_LSB) |
+ ((newstate->src_h >> 16) << JZ_IPU_IN_GS_H_LSB));
+
+ switch (finfo->format) {
+ case DRM_FORMAT_XRGB1555:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB555 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_RGB;
+ break;
+ case DRM_FORMAT_XBGR1555:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB555 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_BGR;
+ break;
+ case DRM_FORMAT_RGB565:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB565 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_RGB;
+ break;
+ case DRM_FORMAT_BGR565:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB565 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_BGR;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_XYUV8888:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB888 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_RGB;
+ break;
+ case DRM_FORMAT_XBGR8888:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB888 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_BGR;
+ break;
+ case DRM_FORMAT_YUYV:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
+ JZ_IPU_D_FMT_YUV_VY1UY0;
+ break;
+ case DRM_FORMAT_YVYU:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
+ JZ_IPU_D_FMT_YUV_UY1VY0;
+ break;
+ case DRM_FORMAT_UYVY:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
+ JZ_IPU_D_FMT_YUV_Y1VY0U;
+ break;
+ case DRM_FORMAT_VYUY:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
+ JZ_IPU_D_FMT_YUV_Y1UY0V;
+ break;
+ case DRM_FORMAT_YUV411:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV411;
+ break;
+ case DRM_FORMAT_YUV420:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV420;
+ break;
+ case DRM_FORMAT_YUV422:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422;
+ break;
+ case DRM_FORMAT_YUV444:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV444;
+ break;
+ default:
+ WARN_ONCE(1, "Unsupported format");
+ break;
+ }
+
+ /* Fix output to RGB888 */
+ format |= JZ_IPU_D_FMT_OUT_FMT_RGB888;
+
+ /* Set pixel format */
+ regmap_write(ipu->map, JZ_REG_IPU_D_FMT, format);
+
+ /* Set the output height/width/stride */
+ regmap_write(ipu->map, JZ_REG_IPU_OUT_GS,
+ ((newstate->crtc_w * 4) << JZ_IPU_OUT_GS_W_LSB)
+ | newstate->crtc_h << JZ_IPU_OUT_GS_H_LSB);
+ regmap_write(ipu->map, JZ_REG_IPU_OUT_STRIDE, newstate->crtc_w * 4);
+
+ if (finfo->is_yuv) {
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CSC_EN);
+
+ /*
+ * Offsets for Chroma/Luma.
+ * y = source Y - LUMA,
+ * u = source Cb - CHROMA,
+ * v = source Cr - CHROMA
+ */
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_OFFSET,
+ 128 << JZ_IPU_CSC_OFFSET_CHROMA_LSB |
+ 0 << JZ_IPU_CSC_OFFSET_LUMA_LSB);
+
+ /*
+ * YUV422 to RGB conversion table.
+ * R = C0 / 0x400 * y + C1 / 0x400 * v
+ * G = C0 / 0x400 * y - C2 / 0x400 * u - C3 / 0x400 * v
+ * B = C0 / 0x400 * y + C4 / 0x400 * u
+ */
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C0_COEF, 0x4a8);
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C1_COEF, 0x662);
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C2_COEF, 0x191);
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C3_COEF, 0x341);
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C4_COEF, 0x811);
+ }
+
+ ctrl = 0;
+
+ /*
+ * Must set ZOOM_SEL before programming bicubic LUTs.
+ * If the IPU supports bicubic, we enable it unconditionally, since it
+ * can do anything bilinear can and more.
+ */
+ if (ipu->soc_info->has_bicubic)
+ ctrl |= JZ_IPU_CTRL_ZOOM_SEL;
+
+ upscaling_w = ipu_state->num_w > ipu_state->denom_w;
+ if (upscaling_w)
+ ctrl |= JZ_IPU_CTRL_HSCALE;
+
+ if (ipu_state->num_w != 1 || ipu_state->denom_w != 1) {
+ if (!ipu->soc_info->has_bicubic && !upscaling_w)
+ coef_index |= (ipu_state->denom_w - 1) << 16;
+ else
+ coef_index |= (ipu_state->num_w - 1) << 16;
+ ctrl |= JZ_IPU_CTRL_HRSZ_EN;
+ }
+
+ upscaling_h = ipu_state->num_h > ipu_state->denom_h;
+ if (upscaling_h)
+ ctrl |= JZ_IPU_CTRL_VSCALE;
+
+ if (ipu_state->num_h != 1 || ipu_state->denom_h != 1) {
+ if (!ipu->soc_info->has_bicubic && !upscaling_h)
+ coef_index |= ipu_state->denom_h - 1;
+ else
+ coef_index |= ipu_state->num_h - 1;
+ ctrl |= JZ_IPU_CTRL_VRSZ_EN;
+ }
+
+ regmap_update_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_ZOOM_SEL |
+ JZ_IPU_CTRL_HRSZ_EN | JZ_IPU_CTRL_VRSZ_EN |
+ JZ_IPU_CTRL_HSCALE | JZ_IPU_CTRL_VSCALE, ctrl);
+
+ /* Set the LUT index register */
+ regmap_write(ipu->map, JZ_REG_IPU_RSZ_COEF_INDEX, coef_index);
+
+ if (ipu_state->num_w != 1 || ipu_state->denom_w != 1)
+ ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_HRSZ_COEF_LUT,
+ ipu_state->num_w, ipu_state->denom_w);
+
+ if (ipu_state->num_h != 1 || ipu_state->denom_h != 1)
+ ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_VRSZ_COEF_LUT,
+ ipu_state->num_h, ipu_state->denom_h);
+
+ /* Clear STATUS register */
+ regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0);
+
+ /* Start IPU */
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL,
+ JZ_IPU_CTRL_RUN | JZ_IPU_CTRL_FM_IRQ_EN);
+
+ dev_dbg(ipu->dev, "Scaling %ux%u to %ux%u (%u:%u horiz, %u:%u vert)\n",
+ newstate->src_w >> 16, newstate->src_h >> 16,
+ newstate->crtc_w, newstate->crtc_h,
+ ipu_state->num_w, ipu_state->denom_w,
+ ipu_state->num_h, ipu_state->denom_h);
+}
+
+static int ingenic_ipu_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state,
+ plane);
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ unsigned int num_w, denom_w, num_h, denom_h, xres, yres, max_w, max_h;
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+ struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc;
+ struct drm_crtc_state *crtc_state;
+ struct ingenic_ipu_private_state *ipu_state;
+
+ if (!crtc)
+ return 0;
+
+ crtc_state = drm_atomic_get_existing_crtc_state(state, crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ ipu_state = ingenic_ipu_get_priv_state(ipu, state);
+ if (IS_ERR(ipu_state))
+ return PTR_ERR(ipu_state);
+
+ /* Request a full modeset if we are enabling or disabling the IPU. */
+ if (!old_plane_state->crtc ^ !new_plane_state->crtc)
+ crtc_state->mode_changed = true;
+
+ if (!new_plane_state->crtc ||
+ !crtc_state->mode.hdisplay || !crtc_state->mode.vdisplay)
+ goto out_check_damage;
+
+ /* Plane must be fully visible */
+ if (new_plane_state->crtc_x < 0 || new_plane_state->crtc_y < 0 ||
+ new_plane_state->crtc_x + new_plane_state->crtc_w > crtc_state->mode.hdisplay ||
+ new_plane_state->crtc_y + new_plane_state->crtc_h > crtc_state->mode.vdisplay)
+ return -EINVAL;
+
+ /* Minimum size is 4x4 */
+ if ((new_plane_state->src_w >> 16) < 4 || (new_plane_state->src_h >> 16) < 4)
+ return -EINVAL;
+
+ /* Input and output lines must have an even number of pixels. */
+ if (((new_plane_state->src_w >> 16) & 1) || (new_plane_state->crtc_w & 1))
+ return -EINVAL;
+
+ if (!osd_changed(new_plane_state, old_plane_state))
+ goto out_check_damage;
+
+ crtc_state->mode_changed = true;
+
+ xres = new_plane_state->src_w >> 16;
+ yres = new_plane_state->src_h >> 16;
+
+ /*
+ * Increase the scaled image's theorical width/height until we find a
+ * configuration that has valid scaling coefficients, up to 102% of the
+ * screen's resolution. This makes sure that we can scale from almost
+ * every resolution possible at the cost of a very small distorsion.
+ * The CRTC_W / CRTC_H are not modified.
+ */
+ max_w = crtc_state->mode.hdisplay * 102 / 100;
+ max_h = crtc_state->mode.vdisplay * 102 / 100;
+
+ for (denom_w = xres, num_w = new_plane_state->crtc_w; num_w <= max_w; num_w++)
+ if (!reduce_fraction(&num_w, &denom_w))
+ break;
+ if (num_w > max_w)
+ return -EINVAL;
+
+ for (denom_h = yres, num_h = new_plane_state->crtc_h; num_h <= max_h; num_h++)
+ if (!reduce_fraction(&num_h, &denom_h))
+ break;
+ if (num_h > max_h)
+ return -EINVAL;
+
+ ipu_state->num_w = num_w;
+ ipu_state->num_h = num_h;
+ ipu_state->denom_w = denom_w;
+ ipu_state->denom_h = denom_h;
+
+out_check_damage:
+ if (ingenic_drm_map_noncoherent(ipu->master))
+ drm_atomic_helper_check_plane_damage(state, new_plane_state);
+
+ return 0;
+}
+
+static void ingenic_ipu_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_STOP);
+ regmap_clear_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CHIP_EN);
+
+ ingenic_drm_plane_disable(ipu->master, plane);
+
+ if (ipu->clk_enabled) {
+ clk_disable(ipu->clk);
+ ipu->clk_enabled = false;
+ }
+}
+
+static const struct drm_plane_helper_funcs ingenic_ipu_plane_helper_funcs = {
+ .atomic_update = ingenic_ipu_plane_atomic_update,
+ .atomic_check = ingenic_ipu_plane_atomic_check,
+ .atomic_disable = ingenic_ipu_plane_atomic_disable,
+};
+
+static int
+ingenic_ipu_plane_atomic_get_property(struct drm_plane *plane,
+ const struct drm_plane_state *state,
+ struct drm_property *property, u64 *val)
+{
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+
+ if (property != ipu->sharpness_prop)
+ return -EINVAL;
+
+ *val = ipu->sharpness;
+
+ return 0;
+}
+
+static int
+ingenic_ipu_plane_atomic_set_property(struct drm_plane *plane,
+ struct drm_plane_state *state,
+ struct drm_property *property, u64 val)
+{
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+ struct drm_crtc_state *crtc_state;
+ bool mode_changed;
+
+ if (property != ipu->sharpness_prop)
+ return -EINVAL;
+
+ mode_changed = val != ipu->sharpness;
+ ipu->sharpness = val;
+
+ if (state->crtc) {
+ crtc_state = drm_atomic_get_existing_crtc_state(state->state, state->crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ crtc_state->mode_changed |= mode_changed;
+ }
+
+ return 0;
+}
+
+static const struct drm_plane_funcs ingenic_ipu_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .reset = drm_atomic_helper_plane_reset,
+ .destroy = drm_plane_cleanup,
+
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+
+ .atomic_get_property = ingenic_ipu_plane_atomic_get_property,
+ .atomic_set_property = ingenic_ipu_plane_atomic_set_property,
+};
+
+static struct drm_private_state *
+ingenic_ipu_duplicate_state(struct drm_private_obj *obj)
+{
+ struct ingenic_ipu_private_state *state = to_ingenic_ipu_priv_state(obj->state);
+
+ state = kmemdup(state, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
+
+ return &state->base;
+}
+
+static void ingenic_ipu_destroy_state(struct drm_private_obj *obj,
+ struct drm_private_state *state)
+{
+ struct ingenic_ipu_private_state *priv_state = to_ingenic_ipu_priv_state(state);
+
+ kfree(priv_state);
+}
+
+static const struct drm_private_state_funcs ingenic_ipu_private_state_funcs = {
+ .atomic_duplicate_state = ingenic_ipu_duplicate_state,
+ .atomic_destroy_state = ingenic_ipu_destroy_state,
+};
+
+static irqreturn_t ingenic_ipu_irq_handler(int irq, void *arg)
+{
+ struct ingenic_ipu *ipu = arg;
+ struct drm_crtc *crtc = drm_crtc_from_index(ipu->drm, 0);
+ unsigned int dummy;
+
+ /* dummy read allows CPU to reconfigure IPU */
+ if (ipu->soc_info->manual_restart)
+ regmap_read(ipu->map, JZ_REG_IPU_STATUS, &dummy);
+
+ /* ACK interrupt */
+ regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0);
+
+ /* Set previously cached addresses */
+ regmap_write(ipu->map, JZ_REG_IPU_Y_ADDR, ipu->addr_y);
+ regmap_write(ipu->map, JZ_REG_IPU_U_ADDR, ipu->addr_u);
+ regmap_write(ipu->map, JZ_REG_IPU_V_ADDR, ipu->addr_v);
+
+ /* Run IPU for the new frame */
+ if (ipu->soc_info->manual_restart)
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RUN);
+
+ drm_crtc_handle_vblank(crtc);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config ingenic_ipu_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+
+ .max_register = JZ_REG_IPU_OUT_PHY_T_ADDR,
+};
+
+static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ingenic_ipu_private_state *private_state;
+ const struct soc_info *soc_info;
+ struct drm_device *drm = d;
+ struct drm_plane *plane;
+ struct ingenic_ipu *ipu;
+ void __iomem *base;
+ unsigned int sharpness_max;
+ int err, irq;
+
+ ipu = devm_kzalloc(dev, sizeof(*ipu), GFP_KERNEL);
+ if (!ipu)
+ return -ENOMEM;
+
+ soc_info = of_device_get_match_data(dev);
+ if (!soc_info) {
+ dev_err(dev, "Missing platform data\n");
+ return -EINVAL;
+ }
+
+ ipu->dev = dev;
+ ipu->drm = drm;
+ ipu->master = master;
+ ipu->soc_info = soc_info;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base)) {
+ dev_err(dev, "Failed to get memory resource\n");
+ return PTR_ERR(base);
+ }
+
+ ipu->map = devm_regmap_init_mmio(dev, base, &ingenic_ipu_regmap_config);
+ if (IS_ERR(ipu->map)) {
+ dev_err(dev, "Failed to create regmap\n");
+ return PTR_ERR(ipu->map);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ipu->clk = devm_clk_get(dev, "ipu");
+ if (IS_ERR(ipu->clk)) {
+ dev_err(dev, "Failed to get pixel clock\n");
+ return PTR_ERR(ipu->clk);
+ }
+
+ err = devm_request_irq(dev, irq, ingenic_ipu_irq_handler, 0,
+ dev_name(dev), ipu);
+ if (err) {
+ dev_err(dev, "Unable to request IRQ\n");
+ return err;
+ }
+
+ plane = &ipu->plane;
+ dev_set_drvdata(dev, plane);
+
+ drm_plane_helper_add(plane, &ingenic_ipu_plane_helper_funcs);
+
+ err = drm_universal_plane_init(drm, plane, 1, &ingenic_ipu_plane_funcs,
+ soc_info->formats, soc_info->num_formats,
+ NULL, DRM_PLANE_TYPE_OVERLAY, NULL);
+ if (err) {
+ dev_err(dev, "Failed to init plane: %i\n", err);
+ return err;
+ }
+
+ if (ingenic_drm_map_noncoherent(master))
+ drm_plane_enable_fb_damage_clips(plane);
+
+ /*
+ * Sharpness settings range is [0,32]
+ * 0 : nearest-neighbor
+ * 1 : bilinear
+ * 2 .. 32 : bicubic (translated to sharpness factor -0.25 .. -4.0)
+ */
+ sharpness_max = soc_info->has_bicubic ? 32 : 1;
+ ipu->sharpness_prop = drm_property_create_range(drm, 0, "sharpness",
+ 0, sharpness_max);
+ if (!ipu->sharpness_prop) {
+ dev_err(dev, "Unable to create sharpness property\n");
+ return -ENOMEM;
+ }
+
+ /* Default sharpness factor: -0.125 * 8 = -1.0 */
+ ipu->sharpness = soc_info->has_bicubic ? 8 : 1;
+ drm_object_attach_property(&plane->base, ipu->sharpness_prop,
+ ipu->sharpness);
+
+ err = clk_prepare(ipu->clk);
+ if (err) {
+ dev_err(dev, "Unable to prepare clock\n");
+ return err;
+ }
+
+ private_state = kzalloc(sizeof(*private_state), GFP_KERNEL);
+ if (!private_state) {
+ err = -ENOMEM;
+ goto err_clk_unprepare;
+ }
+
+ drm_atomic_private_obj_init(drm, &ipu->private_obj, &private_state->base,
+ &ingenic_ipu_private_state_funcs);
+
+ return 0;
+
+err_clk_unprepare:
+ clk_unprepare(ipu->clk);
+ return err;
+}
+
+static void ingenic_ipu_unbind(struct device *dev,
+ struct device *master, void *d)
+{
+ struct ingenic_ipu *ipu = dev_get_drvdata(dev);
+
+ drm_atomic_private_obj_fini(&ipu->private_obj);
+ clk_unprepare(ipu->clk);
+}
+
+static const struct component_ops ingenic_ipu_ops = {
+ .bind = ingenic_ipu_bind,
+ .unbind = ingenic_ipu_unbind,
+};
+
+static int ingenic_ipu_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &ingenic_ipu_ops);
+}
+
+static int ingenic_ipu_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &ingenic_ipu_ops);
+ return 0;
+}
+
+static const u32 jz4725b_ipu_formats[] = {
+ /*
+ * While officially supported, packed YUV 4:2:2 formats can cause
+ * random hardware crashes on JZ4725B under certain circumstances.
+ * It seems to happen with some specific resize ratios.
+ * Until a proper workaround or fix is found, disable these formats.
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_VYUY,
+ */
+ DRM_FORMAT_YUV411,
+ DRM_FORMAT_YUV420,
+ DRM_FORMAT_YUV422,
+ DRM_FORMAT_YUV444,
+};
+
+static const struct soc_info jz4725b_soc_info = {
+ .formats = jz4725b_ipu_formats,
+ .num_formats = ARRAY_SIZE(jz4725b_ipu_formats),
+ .has_bicubic = false,
+ .manual_restart = true,
+ .set_coefs = jz4725b_set_coefs,
+};
+
+static const u32 jz4760_ipu_formats[] = {
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_VYUY,
+ DRM_FORMAT_YUV411,
+ DRM_FORMAT_YUV420,
+ DRM_FORMAT_YUV422,
+ DRM_FORMAT_YUV444,
+ DRM_FORMAT_XYUV8888,
+};
+
+static const struct soc_info jz4760_soc_info = {
+ .formats = jz4760_ipu_formats,
+ .num_formats = ARRAY_SIZE(jz4760_ipu_formats),
+ .has_bicubic = true,
+ .manual_restart = false,
+ .set_coefs = jz4760_set_coefs,
+};
+
+static const struct of_device_id ingenic_ipu_of_match[] = {
+ { .compatible = "ingenic,jz4725b-ipu", .data = &jz4725b_soc_info },
+ { .compatible = "ingenic,jz4760-ipu", .data = &jz4760_soc_info },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ingenic_ipu_of_match);
+
+static struct platform_driver ingenic_ipu_driver = {
+ .driver = {
+ .name = "ingenic-ipu",
+ .of_match_table = ingenic_ipu_of_match,
+ },
+ .probe = ingenic_ipu_probe,
+ .remove = ingenic_ipu_remove,
+};
+
+struct platform_driver *ingenic_ipu_driver_ptr = &ingenic_ipu_driver;
diff --git a/drivers/gpu/drm/ingenic/ingenic-ipu.h b/drivers/gpu/drm/ingenic/ingenic-ipu.h
new file mode 100644
index 000000000..eab6fab8c
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/ingenic-ipu.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+//
+// Ingenic JZ47xx IPU - Register definitions and private API
+//
+// Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net>
+
+#ifndef DRIVERS_GPU_DRM_INGENIC_INGENIC_IPU_H
+#define DRIVERS_GPU_DRM_INGENIC_INGENIC_IPU_H
+
+#include <linux/bitops.h>
+
+#define JZ_REG_IPU_CTRL 0x00
+#define JZ_REG_IPU_STATUS 0x04
+#define JZ_REG_IPU_D_FMT 0x08
+#define JZ_REG_IPU_Y_ADDR 0x0c
+#define JZ_REG_IPU_U_ADDR 0x10
+#define JZ_REG_IPU_V_ADDR 0x14
+#define JZ_REG_IPU_IN_GS 0x18
+#define JZ_REG_IPU_Y_STRIDE 0x1c
+#define JZ_REG_IPU_UV_STRIDE 0x20
+#define JZ_REG_IPU_OUT_ADDR 0x24
+#define JZ_REG_IPU_OUT_GS 0x28
+#define JZ_REG_IPU_OUT_STRIDE 0x2c
+#define JZ_REG_IPU_RSZ_COEF_INDEX 0x30
+#define JZ_REG_IPU_CSC_C0_COEF 0x34
+#define JZ_REG_IPU_CSC_C1_COEF 0x38
+#define JZ_REG_IPU_CSC_C2_COEF 0x3c
+#define JZ_REG_IPU_CSC_C3_COEF 0x40
+#define JZ_REG_IPU_CSC_C4_COEF 0x44
+#define JZ_REG_IPU_HRSZ_COEF_LUT 0x48
+#define JZ_REG_IPU_VRSZ_COEF_LUT 0x4c
+#define JZ_REG_IPU_CSC_OFFSET 0x50
+#define JZ_REG_IPU_Y_PHY_T_ADDR 0x54
+#define JZ_REG_IPU_U_PHY_T_ADDR 0x58
+#define JZ_REG_IPU_V_PHY_T_ADDR 0x5c
+#define JZ_REG_IPU_OUT_PHY_T_ADDR 0x60
+
+#define JZ_IPU_CTRL_ADDR_SEL BIT(20)
+#define JZ_IPU_CTRL_ZOOM_SEL BIT(18)
+#define JZ_IPU_CTRL_DFIX_SEL BIT(17)
+#define JZ_IPU_CTRL_LCDC_SEL BIT(11)
+#define JZ_IPU_CTRL_SPKG_SEL BIT(10)
+#define JZ_IPU_CTRL_VSCALE BIT(9)
+#define JZ_IPU_CTRL_HSCALE BIT(8)
+#define JZ_IPU_CTRL_STOP BIT(7)
+#define JZ_IPU_CTRL_RST BIT(6)
+#define JZ_IPU_CTRL_FM_IRQ_EN BIT(5)
+#define JZ_IPU_CTRL_CSC_EN BIT(4)
+#define JZ_IPU_CTRL_VRSZ_EN BIT(3)
+#define JZ_IPU_CTRL_HRSZ_EN BIT(2)
+#define JZ_IPU_CTRL_RUN BIT(1)
+#define JZ_IPU_CTRL_CHIP_EN BIT(0)
+
+#define JZ_IPU_STATUS_OUT_END BIT(0)
+
+#define JZ_IPU_IN_GS_H_LSB 0x0
+#define JZ_IPU_IN_GS_W_LSB 0x10
+#define JZ_IPU_OUT_GS_H_LSB 0x0
+#define JZ_IPU_OUT_GS_W_LSB 0x10
+
+#define JZ_IPU_Y_STRIDE_Y_LSB 0
+#define JZ_IPU_UV_STRIDE_U_LSB 16
+#define JZ_IPU_UV_STRIDE_V_LSB 0
+
+#define JZ_IPU_D_FMT_IN_FMT_LSB 0
+#define JZ_IPU_D_FMT_IN_FMT_RGB555 (0x0 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_YUV420 (0x0 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_YUV422 (0x1 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_RGB888 (0x2 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_YUV444 (0x2 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_RGB565 (0x3 << JZ_IPU_D_FMT_IN_FMT_LSB)
+
+#define JZ_IPU_D_FMT_YUV_FMT_LSB 2
+#define JZ_IPU_D_FMT_YUV_Y1UY0V (0x0 << JZ_IPU_D_FMT_YUV_FMT_LSB)
+#define JZ_IPU_D_FMT_YUV_Y1VY0U (0x1 << JZ_IPU_D_FMT_YUV_FMT_LSB)
+#define JZ_IPU_D_FMT_YUV_UY1VY0 (0x2 << JZ_IPU_D_FMT_YUV_FMT_LSB)
+#define JZ_IPU_D_FMT_YUV_VY1UY0 (0x3 << JZ_IPU_D_FMT_YUV_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_YUV411 (0x3 << JZ_IPU_D_FMT_IN_FMT_LSB)
+
+#define JZ_IPU_D_FMT_OUT_FMT_LSB 19
+#define JZ_IPU_D_FMT_OUT_FMT_RGB555 (0x0 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+#define JZ_IPU_D_FMT_OUT_FMT_RGB565 (0x1 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+#define JZ_IPU_D_FMT_OUT_FMT_RGB888 (0x2 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+#define JZ_IPU_D_FMT_OUT_FMT_YUV422 (0x3 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+#define JZ_IPU_D_FMT_OUT_FMT_RGBAAA (0x4 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_LSB 22
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_RGB (0x0 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_RBG (0x1 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_GBR (0x2 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_GRB (0x3 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_BRG (0x4 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_BGR (0x5 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+
+#define JZ4725B_IPU_RSZ_LUT_COEF_LSB 2
+#define JZ4725B_IPU_RSZ_LUT_COEF_MASK 0x7ff
+#define JZ4725B_IPU_RSZ_LUT_IN_EN BIT(1)
+#define JZ4725B_IPU_RSZ_LUT_OUT_EN BIT(0)
+
+#define JZ4760_IPU_RSZ_COEF20_LSB 6
+#define JZ4760_IPU_RSZ_COEF31_LSB 17
+#define JZ4760_IPU_RSZ_COEF_MASK 0x7ff
+#define JZ4760_IPU_RSZ_OFFSET_LSB 1
+#define JZ4760_IPU_RSZ_OFFSET_MASK 0x1f
+
+#define JZ_IPU_CSC_OFFSET_CHROMA_LSB 16
+#define JZ_IPU_CSC_OFFSET_LUMA_LSB 16
+
+#endif /* DRIVERS_GPU_DRM_INGENIC_INGENIC_IPU_H */