diff options
Diffstat (limited to 'drivers/gpu/drm/ingenic')
-rw-r--r-- | drivers/gpu/drm/ingenic/Kconfig | 36 | ||||
-rw-r--r-- | drivers/gpu/drm/ingenic/Makefile | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 1651 | ||||
-rw-r--r-- | drivers/gpu/drm/ingenic/ingenic-drm.h | 232 | ||||
-rw-r--r-- | drivers/gpu/drm/ingenic/ingenic-dw-hdmi.c | 103 | ||||
-rw-r--r-- | drivers/gpu/drm/ingenic/ingenic-ipu.c | 998 | ||||
-rw-r--r-- | drivers/gpu/drm/ingenic/ingenic-ipu.h | 109 |
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..a53f475d3 --- /dev/null +++ b/drivers/gpu/drm/ingenic/Kconfig @@ -0,0 +1,36 @@ +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 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..4499a04f7 --- /dev/null +++ b/drivers/gpu/drm/ingenic/ingenic-drm-drv.c @@ -0,0 +1,1651 @@ +// 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_device.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_crtc_helper.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_fb_helper.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, + .output_poll_changed = drm_fb_helper_output_poll_changed, + .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, + ®map_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..7a4350501 --- /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/of_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 */ |