From 106b4a2a78b2a2f14885604891957b84bba502db Mon Sep 17 00:00:00 2001 From: Adrian Betschart Date: Fri, 11 Sep 2026 17:13:14 +0200 Subject: drm/amd/display: Try RGB before YCbCr 4:4:4 in stream validation amdgpu_dm_create_validate_stream_for_sink() walks encoding_order[] and uses the first encoding that validates. YCbCr 4:4:4 is listed before RGB, so an HDMI sink that advertises 4:4:4 gets YCbCr 4:4:4 whenever the "color format" property is left at AUTO, even though RGB fits the same link. That contradicts the documented AUTO behaviour for HDMI in enum drm_connector_color_format (RGB, falling back to YCbCr 4:2:0 only when the bandwidth is not available or the mode is 4:2:0-only), which the amdgpu implementation of the property also describes. It also leaves the "Broadcast RGB" property without effect on such sinks, since the quantization range it selects only applies to RGB output. Try RGB first. The mask still holds every encoding the sink supports, so a mode that cannot carry RGB falls back exactly as before. For reference, v7.2 picked RGB here unless YCbCr 4:4:4 was forced through debugfs, while earlier kernels picked YCbCr 4:4:4 for any HDMI sink that advertised it. Fixes: 0b0ff65d3ca1 ("drm/amd/display: Refactor stream validation") Suggested-by: Adolfo Rodrigues Assisted-by: Claude Code:claude-fable-5-1 Signed-off-by: Adrian Betschart Reviewed-by: Fangzhi Zuo Tested-by: Adolfo Rodrigues Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 7de432bc753133764dc40d37f9388da56de251e7) --- drivers/gpu/drm/tilcdc/Kconfig | 34 + drivers/gpu/drm/tilcdc/Makefile | 14 + drivers/gpu/drm/tilcdc/tilcdc_crtc.c | 1009 +++++++++++++++++++++++ drivers/gpu/drm/tilcdc/tilcdc_drv.c | 501 +++++++++++ drivers/gpu/drm/tilcdc/tilcdc_drv.h | 108 +++ drivers/gpu/drm/tilcdc/tilcdc_encoder.c | 63 ++ drivers/gpu/drm/tilcdc/tilcdc_encoder.h | 11 + drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c | 185 +++++ drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.dtso | 29 + drivers/gpu/drm/tilcdc/tilcdc_plane.c | 116 +++ drivers/gpu/drm/tilcdc/tilcdc_regs.h | 173 ++++ 11 files changed, 2243 insertions(+) create mode 100644 drivers/gpu/drm/tilcdc/Kconfig create mode 100644 drivers/gpu/drm/tilcdc/Makefile create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_encoder.c create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_encoder.h create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.dtso create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_plane.c create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h (limited to 'drivers/gpu/drm/tilcdc') diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig new file mode 100644 index 000000000..89df75287 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_TILCDC + tristate "DRM Support for TI LCDC Display Controller" + depends on DRM && OF && ARM + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + select DRM_BRIDGE + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + select DRM_PANEL_BRIDGE + select VIDEOMODE_HELPERS + select BACKLIGHT_CLASS_DEVICE + help + Choose this option if you have an TI SoC with LCDC display + controller, for example AM33xx in beagle-bone, DA8xx, or + OMAP-L1xx. This driver replaces the FB_DA8XX fbdev driver. + +config DRM_TILCDC_PANEL_LEGACY + bool "Support device tree blobs using TI LCDC Panel binding" + default y + depends on DRM_TILCDC + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + depends on PM + select OF_OVERLAY + select DRM_PANEL_SIMPLE + help + Modifies the live device tree at early boot to convert the legacy + "ti,tilcdc,panel" devicetree node to the standard panel-dpi node. + This allows to maintain backward compatibility for boards which + were using the deprecated tilcdc_panel driver. + If you find "ti,tilcdc,panel"-string from your DTB, you probably + need this. Otherwise you do not. diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile new file mode 100644 index 000000000..c6b484dad --- /dev/null +++ b/drivers/gpu/drm/tilcdc/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +ifeq (, $(findstring -W,$(KCFLAGS))) + ccflags-y += -Werror +endif + +tilcdc-y := \ + tilcdc_plane.o \ + tilcdc_crtc.o \ + tilcdc_encoder.o \ + tilcdc_drv.o + +obj-$(CONFIG_DRM_TILCDC) += tilcdc.o +obj-$(CONFIG_DRM_TILCDC_PANEL_LEGACY) += tilcdc_panel_legacy.o \ + tilcdc_panel_legacy.dtbo.o diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c new file mode 100644 index 000000000..e07ecb36a --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c @@ -0,0 +1,1009 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tilcdc_drv.h" +#include "tilcdc_regs.h" + +#define TILCDC_VBLANK_SAFETY_THRESHOLD_US 1000 +#define TILCDC_PALETTE_SIZE 32 +#define TILCDC_PALETTE_FIRST_ENTRY 0x4000 + +struct tilcdc_crtc { + struct drm_crtc base; + + struct tilcdc_plane *primary; + struct drm_pending_vblank_event *event; + struct mutex enable_lock; + bool enabled; + bool shutdown; + wait_queue_head_t frame_done_wq; + bool frame_done; + spinlock_t irq_lock; + + unsigned int lcd_fck_rate; + + ktime_t last_vblank; + unsigned int hvtotal_us; + + struct drm_framebuffer *next_fb; + + int sync_lost_count; + bool frame_intact; + struct work_struct recover_work; + + dma_addr_t palette_dma_handle; + u16 *palette_base; + struct completion palette_loaded; +}; +#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base) + +static void set_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb) +{ + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + struct drm_gem_dma_object *gem; + dma_addr_t start, end; + u64 dma_base_and_ceiling; + + gem = drm_fb_dma_get_gem_obj(fb, 0); + + start = gem->dma_addr + fb->offsets[0] + + crtc->y * fb->pitches[0] + + crtc->x * fb->format->cpp[0]; + + end = start + (crtc->mode.vdisplay * fb->pitches[0]); + + /* Write LCDC_DMA_FB_BASE_ADDR_0_REG and LCDC_DMA_FB_CEILING_ADDR_0_REG + * with a single insruction, if available. This should make it more + * unlikely that LCDC would fetch the DMA addresses in the middle of + * an update. + */ + if (priv->rev == 1) + end -= 1; + + dma_base_and_ceiling = (u64)end << 32 | start; + tilcdc_write64(dev, LCDC_DMA_FB_BASE_ADDR_0_REG, dma_base_and_ceiling); +} + +/* + * The driver currently only supports only true color formats. For + * true color the palette block is bypassed, but a 32 byte palette + * should still be loaded. The first 16-bit entry must be 0x4000 while + * all other entries must be zeroed. + */ +static void tilcdc_crtc_load_palette(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + int ret; + + reinit_completion(&tilcdc_crtc->palette_loaded); + + /* Tell the LCDC where the palette is located. */ + tilcdc_write(dev, LCDC_DMA_FB_BASE_ADDR_0_REG, + tilcdc_crtc->palette_dma_handle); + tilcdc_write(dev, LCDC_DMA_FB_CEILING_ADDR_0_REG, + (u32) tilcdc_crtc->palette_dma_handle + + TILCDC_PALETTE_SIZE - 1); + + /* Set dma load mode for palette loading only. */ + tilcdc_write_mask(dev, LCDC_RASTER_CTRL_REG, + LCDC_PALETTE_LOAD_MODE(PALETTE_ONLY), + LCDC_PALETTE_LOAD_MODE_MASK); + + /* Enable DMA Palette Loaded Interrupt */ + if (priv->rev == 1) + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_PL_INT_ENA); + else + tilcdc_write(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_PL_INT_ENA); + + /* Enable LCDC DMA and wait for palette to be loaded. */ + tilcdc_clear_irqstatus(dev, 0xffffffff); + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE); + + ret = wait_for_completion_timeout(&tilcdc_crtc->palette_loaded, + msecs_to_jiffies(50)); + if (ret == 0) + drm_err(dev, "%s: Palette loading timeout", __func__); + + /* Disable LCDC DMA and DMA Palette Loaded Interrupt. */ + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE); + if (priv->rev == 1) + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_PL_INT_ENA); + else + tilcdc_write(dev, LCDC_INT_ENABLE_CLR_REG, LCDC_V2_PL_INT_ENA); +} + +static void tilcdc_crtc_enable_irqs(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + + tilcdc_clear_irqstatus(dev, 0xffffffff); + + if (priv->rev == 1) { + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, + LCDC_V1_SYNC_LOST_INT_ENA | LCDC_V1_FRAME_DONE_INT_ENA | + LCDC_V1_UNDERFLOW_INT_ENA); + } else { + tilcdc_write(dev, LCDC_INT_ENABLE_SET_REG, + LCDC_V2_UNDERFLOW_INT_ENA | + LCDC_FRAME_DONE | LCDC_SYNC_LOST); + } +} + +static void tilcdc_crtc_disable_irqs(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + + /* disable irqs that we might have enabled: */ + if (priv->rev == 1) { + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, + LCDC_V1_SYNC_LOST_INT_ENA | LCDC_V1_FRAME_DONE_INT_ENA | + LCDC_V1_UNDERFLOW_INT_ENA | LCDC_V1_PL_INT_ENA); + tilcdc_clear(dev, LCDC_DMA_CTRL_REG, + LCDC_V1_END_OF_FRAME_INT_ENA); + } else { + tilcdc_write(dev, LCDC_INT_ENABLE_CLR_REG, + LCDC_V2_UNDERFLOW_INT_ENA | LCDC_V2_PL_INT_ENA | + LCDC_V2_END_OF_FRAME0_INT_ENA | + LCDC_FRAME_DONE | LCDC_SYNC_LOST); + } +} + +static void reset(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + + if (priv->rev != 2) + return; + + tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET); + usleep_range(250, 1000); + tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET); +} + +/* + * Calculate the percentage difference between the requested pixel clock rate + * and the effective rate resulting from calculating the clock divider value. + */ +static unsigned int tilcdc_pclk_diff(unsigned long rate, + unsigned long real_rate) +{ + int r = rate / 100, rr = real_rate / 100; + + return (unsigned int)(abs(((rr - r) * 100) / r)); +} + +static void tilcdc_crtc_set_clk(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + unsigned long clk_rate, real_pclk_rate, pclk_rate; + unsigned int clkdiv; + int ret; + + clkdiv = 2; /* first try using a standard divider of 2 */ + + /* mode.clock is in KHz, set_rate wants parameter in Hz */ + pclk_rate = crtc->mode.clock * 1000; + + ret = clk_set_rate(priv->clk, pclk_rate * clkdiv); + clk_rate = clk_get_rate(priv->clk); + real_pclk_rate = clk_rate / clkdiv; + if (ret < 0 || tilcdc_pclk_diff(pclk_rate, real_pclk_rate) > 5) { + /* + * If we fail to set the clock rate (some architectures don't + * use the common clock framework yet and may not implement + * all the clk API calls for every clock), try the next best + * thing: adjusting the clock divider, unless clk_get_rate() + * failed as well. + */ + if (!clk_rate) { + /* Nothing more we can do. Just bail out. */ + drm_err(dev, + "failed to set the pixel clock - unable to read current lcdc clock rate\n"); + return; + } + + clkdiv = DIV_ROUND_CLOSEST(clk_rate, pclk_rate); + + /* + * Emit a warning if the real clock rate resulting from the + * calculated divider differs much from the requested rate. + * + * 5% is an arbitrary value - LCDs are usually quite tolerant + * about pixel clock rates. + */ + real_pclk_rate = clk_rate / clkdiv; + + if (tilcdc_pclk_diff(pclk_rate, real_pclk_rate) > 5) { + drm_warn(dev, + "effective pixel clock rate (%luHz) differs from the requested rate (%luHz)\n", + real_pclk_rate, pclk_rate); + } + } + + tilcdc_crtc->lcd_fck_rate = clk_rate; + + DBG("lcd_clk=%u, mode clock=%d, div=%u", + tilcdc_crtc->lcd_fck_rate, crtc->mode.clock, clkdiv); + + /* Configure the LCD clock divisor. */ + tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(clkdiv) | + LCDC_RASTER_MODE); + + if (priv->rev == 2) + tilcdc_set(dev, LCDC_CLK_ENABLE_REG, + LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN | + LCDC_V2_CORE_CLK_EN); +} + +static uint tilcdc_mode_hvtotal(const struct drm_display_mode *mode) +{ + return (uint) div_u64(1000llu * mode->htotal * mode->vtotal, + mode->clock); +} + +static void tilcdc_crtc_set_mode(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw; + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct drm_framebuffer *fb = crtc->primary->state->fb; + + if (WARN_ON(!fb)) + return; + + /* Configure the Burst Size and fifo threshold of DMA: */ + reg = tilcdc_read(dev, LCDC_DMA_CTRL_REG) & ~0x00000770; + /* Use 16 bit DMA burst size by default */ + reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_16); + + if (priv->fifo_th) { + int fifo_th_val = ilog2(priv->fifo_th) - 3; + + reg |= (fifo_th_val << 8); + } + tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg); + + /* Configure timings: */ + hbp = mode->htotal - mode->hsync_end; + hfp = mode->hsync_start - mode->hdisplay; + hsw = mode->hsync_end - mode->hsync_start; + vbp = mode->vtotal - mode->vsync_end; + vfp = mode->vsync_start - mode->vdisplay; + vsw = mode->vsync_end - mode->vsync_start; + + DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u", + mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw); + + /* Set AC Bias Period and Number of Transitions per Interrupt: */ + reg = tilcdc_read(dev, LCDC_RASTER_TIMING_2_REG) & ~0x000fff00; + /* Use 255 AC Bias Pin Frequency by default */ + reg |= LCDC_AC_BIAS_FREQUENCY(255); + + /* + * subtract one from hfp, hbp, hsw because the hardware uses + * a value of 0 as 1 + */ + if (priv->rev == 2) { + /* clear bits we're going to set */ + reg &= ~0x78000033; + reg |= ((hfp-1) & 0x300) >> 8; + reg |= ((hbp-1) & 0x300) >> 4; + reg |= ((hsw-1) & 0x3c0) << 21; + } + tilcdc_write(dev, LCDC_RASTER_TIMING_2_REG, reg); + + reg = (((mode->hdisplay >> 4) - 1) << 4) | + (((hbp-1) & 0xff) << 24) | + (((hfp-1) & 0xff) << 16) | + (((hsw-1) & 0x3f) << 10); + if (priv->rev == 2) + reg |= (((mode->hdisplay >> 4) - 1) & 0x40) >> 3; + tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg); + + reg = ((mode->vdisplay - 1) & 0x3ff) | + ((vbp & 0xff) << 24) | + ((vfp & 0xff) << 16) | + (((vsw-1) & 0x3f) << 10); + tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg); + + /* + * be sure to set Bit 10 for the V2 LCDC controller, + * otherwise limited to 1024 pixels width, stopping + * 1920x1080 being supported. + */ + if (priv->rev == 2) { + if ((mode->vdisplay - 1) & 0x400) { + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, + LCDC_LPP_B10); + } else { + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, + LCDC_LPP_B10); + } + } + + /* Configure display type: */ + reg = tilcdc_read(dev, LCDC_RASTER_CTRL_REG) & + ~(LCDC_TFT_MODE | LCDC_MONO_8BIT_MODE | LCDC_MONOCHROME_MODE | + LCDC_V2_TFT_24BPP_MODE | LCDC_V2_TFT_24BPP_UNPACK | + 0x000ff000 /* Palette Loading Delay bits */); + reg |= LCDC_TFT_MODE; /* no monochrome/passive support */ + if (priv->rev == 2) { + switch (fb->format->format) { + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB565: + break; + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_XRGB8888: + reg |= LCDC_V2_TFT_24BPP_UNPACK; + fallthrough; + case DRM_FORMAT_BGR888: + case DRM_FORMAT_RGB888: + reg |= LCDC_V2_TFT_24BPP_MODE; + break; + default: + drm_err(dev, "invalid pixel format\n"); + return; + } + } + /* Use 128 FIFO DMA Request Delay by default */ + reg |= 128 << 12; + tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg); + + if (mode->flags == DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK); + else + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK); + + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL); + if (mode->flags == DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE) + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE); + else + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE); + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC); + else + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC); + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC); + else + tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC); + + tilcdc_crtc_set_clk(crtc); + + tilcdc_crtc_load_palette(crtc); + + set_scanout(crtc, fb); + + drm_mode_copy(&crtc->hwmode, &crtc->state->adjusted_mode); + + tilcdc_crtc->hvtotal_us = + tilcdc_mode_hvtotal(&crtc->hwmode); +} + +static void tilcdc_crtc_enable(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + unsigned long flags; + + mutex_lock(&tilcdc_crtc->enable_lock); + if (tilcdc_crtc->enabled || tilcdc_crtc->shutdown) { + mutex_unlock(&tilcdc_crtc->enable_lock); + return; + } + + pm_runtime_get_sync(dev->dev); + + reset(crtc); + + tilcdc_crtc_set_mode(crtc); + + tilcdc_crtc_enable_irqs(dev); + + tilcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE); + tilcdc_write_mask(dev, LCDC_RASTER_CTRL_REG, + LCDC_PALETTE_LOAD_MODE(DATA_ONLY), + LCDC_PALETTE_LOAD_MODE_MASK); + + /* There is no real chance for a race here as the time stamp + * is taken before the raster DMA is started. The spin-lock is + * taken to have a memory barrier after taking the time-stamp + * and to avoid a context switch between taking the stamp and + * enabling the raster. + */ + spin_lock_irqsave(&tilcdc_crtc->irq_lock, flags); + tilcdc_crtc->last_vblank = ktime_get(); + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE); + spin_unlock_irqrestore(&tilcdc_crtc->irq_lock, flags); + + drm_crtc_vblank_on(crtc); + + tilcdc_crtc->enabled = true; + mutex_unlock(&tilcdc_crtc->enable_lock); +} + +static void tilcdc_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + tilcdc_crtc_enable(crtc); +} + +static void tilcdc_crtc_off(struct drm_crtc *crtc, bool shutdown) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + int ret; + + mutex_lock(&tilcdc_crtc->enable_lock); + if (shutdown) + tilcdc_crtc->shutdown = true; + if (!tilcdc_crtc->enabled) { + mutex_unlock(&tilcdc_crtc->enable_lock); + return; + } + tilcdc_crtc->frame_done = false; + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE); + + /* + * Wait for framedone irq which will still come before putting + * things to sleep.. + */ + ret = wait_event_timeout(tilcdc_crtc->frame_done_wq, + tilcdc_crtc->frame_done, + msecs_to_jiffies(500)); + if (ret == 0) + drm_err(dev, "%s: timeout waiting for framedone\n", + __func__); + + drm_crtc_vblank_off(crtc); + + spin_lock_irq(&crtc->dev->event_lock); + + if (crtc->state->event) { + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + + spin_unlock_irq(&crtc->dev->event_lock); + + tilcdc_crtc_disable_irqs(dev); + + pm_runtime_put_sync(dev->dev); + + tilcdc_crtc->enabled = false; + mutex_unlock(&tilcdc_crtc->enable_lock); +} + +static void tilcdc_crtc_disable(struct drm_crtc *crtc) +{ + tilcdc_crtc_off(crtc, false); +} + +static void tilcdc_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + tilcdc_crtc_disable(crtc); +} + +static void tilcdc_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + if (!crtc->state->event) + return; + + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irq(&crtc->dev->event_lock); +} + +void tilcdc_crtc_shutdown(struct drm_crtc *crtc) +{ + tilcdc_crtc_off(crtc, true); +} + +static bool tilcdc_crtc_is_on(struct drm_crtc *crtc) +{ + return crtc->state && crtc->state->enable && crtc->state->active; +} + +static void tilcdc_crtc_recover_work(struct work_struct *work) +{ + struct tilcdc_crtc *tilcdc_crtc = + container_of(work, struct tilcdc_crtc, recover_work); + struct drm_crtc *crtc = &tilcdc_crtc->base; + + drm_info(crtc->dev, "%s: Reset CRTC", __func__); + + drm_modeset_lock(&crtc->mutex, NULL); + + if (!tilcdc_crtc_is_on(crtc)) + goto out; + + tilcdc_crtc_disable(crtc); + tilcdc_crtc_enable(crtc); +out: + drm_modeset_unlock(&crtc->mutex); +} + +static void tilcdc_crtc_destroy(struct drm_device *dev, void *data) +{ + struct tilcdc_drm_private *priv = (struct tilcdc_drm_private *)data; + + tilcdc_crtc_shutdown(priv->crtc); + + flush_workqueue(priv->wq); + + of_node_put(priv->crtc->port); +} + +int tilcdc_crtc_update_fb(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + + if (tilcdc_crtc->event) { + drm_err(dev, "already pending page flip!\n"); + return -EBUSY; + } + + tilcdc_crtc->event = event; + + mutex_lock(&tilcdc_crtc->enable_lock); + + if (tilcdc_crtc->enabled) { + unsigned long flags; + ktime_t next_vblank; + s64 tdiff; + + spin_lock_irqsave(&tilcdc_crtc->irq_lock, flags); + + next_vblank = ktime_add_us(tilcdc_crtc->last_vblank, + tilcdc_crtc->hvtotal_us); + tdiff = ktime_to_us(ktime_sub(next_vblank, ktime_get())); + + if (tdiff < TILCDC_VBLANK_SAFETY_THRESHOLD_US) + tilcdc_crtc->next_fb = fb; + else + set_scanout(crtc, fb); + + spin_unlock_irqrestore(&tilcdc_crtc->irq_lock, flags); + } + + mutex_unlock(&tilcdc_crtc->enable_lock); + + return 0; +} + +static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* + * tilcdc does not generate VESA-compliant sync but aligns + * VS on the second edge of HS instead of first edge. + * We use adjusted_mode, to fixup sync by aligning both rising + * edges and add HSKEW offset to fix the sync. + */ + adjusted_mode->hskew = mode->hsync_end - mode->hsync_start; + adjusted_mode->flags |= DRM_MODE_FLAG_HSKEW; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) { + adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC; + adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC; + } else { + adjusted_mode->flags |= DRM_MODE_FLAG_NHSYNC; + adjusted_mode->flags &= ~DRM_MODE_FLAG_PHSYNC; + } + + return true; +} + +static int tilcdc_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + /* If we are not active we don't care */ + if (!crtc_state->active) + return 0; + + return drm_atomic_helper_check_crtc_primary_plane(crtc_state); +} + +static int tilcdc_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + unsigned long flags; + + spin_lock_irqsave(&tilcdc_crtc->irq_lock, flags); + + tilcdc_clear_irqstatus(dev, LCDC_END_OF_FRAME0); + + if (priv->rev == 1) + tilcdc_set(dev, LCDC_DMA_CTRL_REG, + LCDC_V1_END_OF_FRAME_INT_ENA); + else + tilcdc_set(dev, LCDC_INT_ENABLE_SET_REG, + LCDC_V2_END_OF_FRAME0_INT_ENA); + + spin_unlock_irqrestore(&tilcdc_crtc->irq_lock, flags); + + return 0; +} + +static void tilcdc_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + unsigned long flags; + + spin_lock_irqsave(&tilcdc_crtc->irq_lock, flags); + + if (priv->rev == 1) + tilcdc_clear(dev, LCDC_DMA_CTRL_REG, + LCDC_V1_END_OF_FRAME_INT_ENA); + else + tilcdc_clear(dev, LCDC_INT_ENABLE_SET_REG, + LCDC_V2_END_OF_FRAME0_INT_ENA); + + spin_unlock_irqrestore(&tilcdc_crtc->irq_lock, flags); +} + +static void tilcdc_crtc_reset(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + int ret; + + drm_atomic_helper_crtc_reset(crtc); + + /* Turn the raster off if it for some reason is on. */ + pm_runtime_get_sync(dev->dev); + if (tilcdc_read(dev, LCDC_RASTER_CTRL_REG) & LCDC_RASTER_ENABLE) { + /* Enable DMA Frame Done Interrupt */ + tilcdc_write(dev, LCDC_INT_ENABLE_SET_REG, LCDC_FRAME_DONE); + tilcdc_clear_irqstatus(dev, 0xffffffff); + + tilcdc_crtc->frame_done = false; + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE); + + ret = wait_event_timeout(tilcdc_crtc->frame_done_wq, + tilcdc_crtc->frame_done, + msecs_to_jiffies(500)); + if (ret == 0) + drm_err(dev, "%s: timeout waiting for framedone\n", + __func__); + } + pm_runtime_put_sync(dev->dev); +} + +static const struct drm_crtc_funcs tilcdc_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = tilcdc_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = tilcdc_crtc_enable_vblank, + .disable_vblank = tilcdc_crtc_disable_vblank, +}; + +static enum drm_mode_status +tilcdc_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(crtc->dev); + unsigned int bandwidth; + uint32_t hbp, hfp, hsw, vbp, vfp, vsw; + + /* + * check to see if the width is within the range that + * the LCD Controller physically supports + */ + if (mode->hdisplay > priv->max_width) + return MODE_VIRTUAL_X; + + /* width must be multiple of 16 */ + if (mode->hdisplay & 0xf) + return MODE_VIRTUAL_X; + + if (mode->vdisplay > 2048) + return MODE_VIRTUAL_Y; + + DBG("Processing mode %dx%d@%d with pixel clock %d", + mode->hdisplay, mode->vdisplay, + drm_mode_vrefresh(mode), mode->clock); + + hbp = mode->htotal - mode->hsync_end; + hfp = mode->hsync_start - mode->hdisplay; + hsw = mode->hsync_end - mode->hsync_start; + vbp = mode->vtotal - mode->vsync_end; + vfp = mode->vsync_start - mode->vdisplay; + vsw = mode->vsync_end - mode->vsync_start; + + if ((hbp-1) & ~0x3ff) { + DBG("Pruning mode: Horizontal Back Porch out of range"); + return MODE_HBLANK_WIDE; + } + + if ((hfp-1) & ~0x3ff) { + DBG("Pruning mode: Horizontal Front Porch out of range"); + return MODE_HBLANK_WIDE; + } + + if ((hsw-1) & ~0x3ff) { + DBG("Pruning mode: Horizontal Sync Width out of range"); + return MODE_HSYNC_WIDE; + } + + if (vbp & ~0xff) { + DBG("Pruning mode: Vertical Back Porch out of range"); + return MODE_VBLANK_WIDE; + } + + if (vfp & ~0xff) { + DBG("Pruning mode: Vertical Front Porch out of range"); + return MODE_VBLANK_WIDE; + } + + if ((vsw-1) & ~0x3f) { + DBG("Pruning mode: Vertical Sync Width out of range"); + return MODE_VSYNC_WIDE; + } + + /* + * some devices have a maximum allowed pixel clock + * configured from the DT + */ + if (mode->clock > priv->max_pixelclock) { + DBG("Pruning mode: pixel clock too high"); + return MODE_CLOCK_HIGH; + } + + /* + * some devices further limit the max horizontal resolution + * configured from the DT + */ + if (mode->hdisplay > priv->max_width) + return MODE_BAD_WIDTH; + + /* filter out modes that would require too much memory bandwidth: */ + bandwidth = mode->hdisplay * mode->vdisplay * + drm_mode_vrefresh(mode); + if (bandwidth > priv->max_bandwidth) { + DBG("Pruning mode: exceeds defined bandwidth limit"); + return MODE_BAD; + } + + return MODE_OK; +} + +static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = { + .mode_valid = tilcdc_crtc_mode_valid, + .mode_fixup = tilcdc_crtc_mode_fixup, + .atomic_check = tilcdc_crtc_atomic_check, + .atomic_enable = tilcdc_crtc_atomic_enable, + .atomic_disable = tilcdc_crtc_atomic_disable, + .atomic_flush = tilcdc_crtc_atomic_flush, +}; + +void tilcdc_crtc_update_clk(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + + drm_modeset_lock(&crtc->mutex, NULL); + if (tilcdc_crtc->lcd_fck_rate != clk_get_rate(priv->clk)) { + if (tilcdc_crtc_is_on(crtc)) { + pm_runtime_get_sync(dev->dev); + tilcdc_crtc_disable(crtc); + + tilcdc_crtc_set_clk(crtc); + + tilcdc_crtc_enable(crtc); + pm_runtime_put_sync(dev->dev); + } + } + drm_modeset_unlock(&crtc->mutex); +} + +#define SYNC_LOST_COUNT_LIMIT 50 + +irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + uint32_t stat, reg; + + stat = tilcdc_read_irqstatus(dev); + tilcdc_clear_irqstatus(dev, stat); + + if (stat & LCDC_END_OF_FRAME0) { + bool skip_event = false; + ktime_t now; + + now = ktime_get(); + + spin_lock(&tilcdc_crtc->irq_lock); + + tilcdc_crtc->last_vblank = now; + + if (tilcdc_crtc->next_fb) { + set_scanout(crtc, tilcdc_crtc->next_fb); + tilcdc_crtc->next_fb = NULL; + skip_event = true; + } + + spin_unlock(&tilcdc_crtc->irq_lock); + + drm_crtc_handle_vblank(crtc); + + if (!skip_event) { + struct drm_pending_vblank_event *event; + + spin_lock(&dev->event_lock); + + event = tilcdc_crtc->event; + tilcdc_crtc->event = NULL; + if (event) + drm_crtc_send_vblank_event(crtc, event); + + spin_unlock(&dev->event_lock); + } + + if (tilcdc_crtc->frame_intact) + tilcdc_crtc->sync_lost_count = 0; + else + tilcdc_crtc->frame_intact = true; + } + + if (stat & LCDC_FIFO_UNDERFLOW) + drm_err_ratelimited(dev, "%s(0x%08x): FIFO underflow", + __func__, stat); + + if (stat & LCDC_PL_LOAD_DONE) { + complete(&tilcdc_crtc->palette_loaded); + if (priv->rev == 1) + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, + LCDC_V1_PL_INT_ENA); + else + tilcdc_write(dev, LCDC_INT_ENABLE_CLR_REG, + LCDC_V2_PL_INT_ENA); + } + + if (stat & LCDC_SYNC_LOST) { + drm_err_ratelimited(dev, "%s(0x%08x): Sync lost", + __func__, stat); + tilcdc_crtc->frame_intact = false; + if (priv->rev == 1) { + reg = tilcdc_read(dev, LCDC_RASTER_CTRL_REG); + if (reg & LCDC_RASTER_ENABLE) { + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, + LCDC_RASTER_ENABLE); + tilcdc_set(dev, LCDC_RASTER_CTRL_REG, + LCDC_RASTER_ENABLE); + } + } else { + if (tilcdc_crtc->sync_lost_count++ > + SYNC_LOST_COUNT_LIMIT) { + drm_err(dev, + "%s(0x%08x): Sync lost flood detected, recovering", + __func__, stat); + queue_work(system_percpu_wq, + &tilcdc_crtc->recover_work); + tilcdc_write(dev, LCDC_INT_ENABLE_CLR_REG, + LCDC_SYNC_LOST); + tilcdc_crtc->sync_lost_count = 0; + } + } + } + + if (stat & LCDC_FRAME_DONE) { + tilcdc_crtc->frame_done = true; + wake_up(&tilcdc_crtc->frame_done_wq); + /* rev 1 lcdc appears to hang if irq is not disabled here */ + if (priv->rev == 1) + tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, + LCDC_V1_FRAME_DONE_INT_ENA); + } + + /* For revision 2 only */ + if (priv->rev == 2) { + /* Indicate to LCDC that the interrupt service routine has + * completed, see 13.3.6.1.6 in AM335x TRM. + */ + tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0); + } + + return IRQ_HANDLED; +} + +int tilcdc_crtc_create(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + struct tilcdc_crtc *tilcdc_crtc; + struct tilcdc_plane *primary; + struct drm_crtc *crtc; + int ret; + + primary = tilcdc_plane_init(dev); + if (IS_ERR(primary)) { + drm_err(dev, "Failed to initialize plane: %pe\n", primary); + return PTR_ERR(primary); + } + + tilcdc_crtc = drmm_crtc_alloc_with_planes(dev, struct tilcdc_crtc, base, + &primary->base, + NULL, + &tilcdc_crtc_funcs, + "tilcdc crtc"); + if (IS_ERR(tilcdc_crtc)) { + drm_err(dev, "Failed to init CRTC: %pe\n", tilcdc_crtc); + return PTR_ERR(tilcdc_crtc); + } + + tilcdc_crtc->primary = primary; + priv->crtc = &tilcdc_crtc->base; + ret = drmm_add_action_or_reset(dev, tilcdc_crtc_destroy, priv); + if (ret) + return ret; + + init_completion(&tilcdc_crtc->palette_loaded); + tilcdc_crtc->palette_base = dmam_alloc_coherent(dev->dev, + TILCDC_PALETTE_SIZE, + &tilcdc_crtc->palette_dma_handle, + GFP_KERNEL | __GFP_ZERO); + if (!tilcdc_crtc->palette_base) + return -ENOMEM; + *tilcdc_crtc->palette_base = TILCDC_PALETTE_FIRST_ENTRY; + + crtc = &tilcdc_crtc->base; + + mutex_init(&tilcdc_crtc->enable_lock); + + init_waitqueue_head(&tilcdc_crtc->frame_done_wq); + + spin_lock_init(&tilcdc_crtc->irq_lock); + INIT_WORK(&tilcdc_crtc->recover_work, tilcdc_crtc_recover_work); + + drm_crtc_helper_add(crtc, &tilcdc_crtc_helper_funcs); + + return 0; +} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c new file mode 100644 index 000000000..1d6c9a423 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark + */ + +/* LCDC DRM driver, based on da8xx-fb */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "tilcdc_drv.h" +#include "tilcdc_encoder.h" +#include "tilcdc_regs.h" + +enum tilcdc_variant { + AM33XX_TILCDC, + DA850_TILCDC, +}; + +static const u32 tilcdc_rev1_formats[] = { DRM_FORMAT_RGB565 }; + +static const u32 tilcdc_straight_formats[] = { DRM_FORMAT_RGB565, + DRM_FORMAT_BGR888, + DRM_FORMAT_XBGR8888 }; + +static const u32 tilcdc_crossed_formats[] = { DRM_FORMAT_BGR565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888 }; + +static const u32 tilcdc_legacy_formats[] = { DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888 }; + +static int tilcdc_atomic_check(struct drm_device *dev, + struct drm_atomic_commit *state) +{ + int ret; + + ret = drm_atomic_helper_check_modeset(dev, state); + if (ret) + return ret; + + ret = drm_atomic_helper_check_planes(dev, state); + if (ret) + return ret; + + /* + * tilcdc ->atomic_check can update ->mode_changed if pixel format + * changes, hence will we check modeset changes again. + */ + ret = drm_atomic_helper_check_modeset(dev, state); + if (ret) + return ret; + + return ret; +} + +static const struct drm_mode_config_funcs mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = tilcdc_atomic_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void modeset_init(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = priv->max_width; + dev->mode_config.max_height = 2048; + dev->mode_config.funcs = &mode_config_funcs; +} + +#ifdef CONFIG_CPU_FREQ +static int cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct tilcdc_drm_private *priv = container_of(nb, + struct tilcdc_drm_private, freq_transition); + + if (val == CPUFREQ_POSTCHANGE) + tilcdc_crtc_update_clk(priv->crtc); + + return 0; +} +#endif + +static irqreturn_t tilcdc_irq(int irq, void *arg) +{ + struct drm_device *dev = arg; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + + return tilcdc_crtc_irq(priv->crtc); +} + +static int tilcdc_irq_install(struct drm_device *dev, unsigned int irq) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + int ret; + + ret = request_irq(irq, tilcdc_irq, 0, dev->driver->name, dev); + if (ret) + return ret; + + priv->irq_enabled = true; + + return 0; +} + +static void tilcdc_irq_uninstall(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + + if (!priv->irq_enabled) + return; + + free_irq(priv->irq, dev); + priv->irq_enabled = false; +} + +/* + * DRM operations: + */ + +#if defined(CONFIG_DEBUG_FS) +static const struct { + const char *name; + uint8_t rev; + uint8_t save; + uint32_t reg; +} registers[] = { +#define REG(rev, save, reg) { #reg, rev, save, reg } + /* exists in revision 1: */ + REG(1, false, LCDC_PID_REG), + REG(1, true, LCDC_CTRL_REG), + REG(1, false, LCDC_STAT_REG), + REG(1, true, LCDC_RASTER_CTRL_REG), + REG(1, true, LCDC_RASTER_TIMING_0_REG), + REG(1, true, LCDC_RASTER_TIMING_1_REG), + REG(1, true, LCDC_RASTER_TIMING_2_REG), + REG(1, true, LCDC_DMA_CTRL_REG), + REG(1, true, LCDC_DMA_FB_BASE_ADDR_0_REG), + REG(1, true, LCDC_DMA_FB_CEILING_ADDR_0_REG), + REG(1, true, LCDC_DMA_FB_BASE_ADDR_1_REG), + REG(1, true, LCDC_DMA_FB_CEILING_ADDR_1_REG), + /* new in revision 2: */ + REG(2, false, LCDC_RAW_STAT_REG), + REG(2, false, LCDC_MASKED_STAT_REG), + REG(2, true, LCDC_INT_ENABLE_SET_REG), + REG(2, false, LCDC_INT_ENABLE_CLR_REG), + REG(2, false, LCDC_END_OF_INT_IND_REG), + REG(2, true, LCDC_CLK_ENABLE_REG), +#undef REG +}; + +static int tilcdc_regs_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + unsigned i; + + pm_runtime_get_sync(dev->dev); + + seq_printf(m, "revision: %d\n", priv->rev); + + for (i = 0; i < ARRAY_SIZE(registers); i++) + if (priv->rev >= registers[i].rev) + seq_printf(m, "%s:\t %08x\n", registers[i].name, + tilcdc_read(dev, registers[i].reg)); + + pm_runtime_put_sync(dev->dev); + + return 0; +} + +static int tilcdc_mm_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct drm_printer p = drm_seq_file_printer(m); + drm_mm_print(&dev->vma_offset_manager->vm_addr_space_mm, &p); + return 0; +} + +static struct drm_info_list tilcdc_debugfs_list[] = { + { "regs", tilcdc_regs_show, 0, NULL }, + { "mm", tilcdc_mm_show, 0, NULL }, +}; + +static void tilcdc_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(tilcdc_debugfs_list, + ARRAY_SIZE(tilcdc_debugfs_list), + minor->debugfs_root, minor); +} +#endif + +DEFINE_DRM_GEM_DMA_FOPS(fops); + +static const struct drm_driver tilcdc_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + DRM_GEM_DMA_DRIVER_OPS, + DRM_FBDEV_DMA_DRIVER_OPS, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = tilcdc_debugfs_init, +#endif + .fops = &fops, + .name = "tilcdc", + .desc = "TI LCD Controller DRM", + .major = 1, + .minor = 0, +}; + +/* + * Power management: + */ + +static int tilcdc_pm_suspend(struct device *dev) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + int ret = 0; + + ret = drm_mode_config_helper_suspend(ddev); + + /* Select sleep pin state */ + pinctrl_pm_select_sleep_state(dev); + + return ret; +} + +static int tilcdc_pm_resume(struct device *dev) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + + /* Select default pin state */ + pinctrl_pm_select_default_state(dev); + return drm_mode_config_helper_resume(ddev); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(tilcdc_pm_ops, + tilcdc_pm_suspend, tilcdc_pm_resume); + +static int tilcdc_pdev_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct tilcdc_drm_private *priv; + struct device *dev = &pdev->dev; + enum tilcdc_variant variant; + struct drm_device *ddev; + u32 bpp = 0; + int ret; + + priv = devm_drm_dev_alloc(dev, &tilcdc_driver, + struct tilcdc_drm_private, ddev); + if (IS_ERR(priv)) + return PTR_ERR(priv); + + variant = (uintptr_t)of_device_get_match_data(dev); + + platform_set_drvdata(pdev, priv); + ddev = &priv->ddev; + ret = drmm_mode_config_init(ddev); + if (ret) + return ret; + + priv->wq = alloc_ordered_workqueue("tilcdc", 0); + if (!priv->wq) + return -ENOMEM; + + priv->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->mmio)) { + drm_err(ddev, "failed to request / ioremap\n"); + ret = PTR_ERR(priv->mmio); + goto free_wq; + } + + priv->clk = clk_get(dev, "fck"); + if (IS_ERR(priv->clk)) { + drm_err(ddev, "failed to get functional clock\n"); + ret = -ENODEV; + goto free_wq; + } + + pm_runtime_enable(dev); + + /* Determine LCD IP Version */ + pm_runtime_get_sync(dev); + switch (tilcdc_read(ddev, LCDC_PID_REG)) { + case 0x4c100102: + priv->rev = 1; + break; + case 0x4f200800: + case 0x4f201000: + priv->rev = 2; + break; + default: + drm_warn(ddev, "Unknown PID Reg value 0x%08x, " + "defaulting to LCD revision 1\n", + tilcdc_read(ddev, LCDC_PID_REG)); + priv->rev = 1; + break; + } + + pm_runtime_put_sync(dev); + + if (priv->rev == 1) { + DBG("Revision 1 LCDC supports only RGB565 format"); + priv->pixelformats = tilcdc_rev1_formats; + priv->num_pixelformats = ARRAY_SIZE(tilcdc_rev1_formats); + bpp = 16; + } else { + const char *str = "\0"; + + of_property_read_string(node, "blue-and-red-wiring", &str); + if (0 == strcmp(str, "crossed")) { + DBG("Configured for crossed blue and red wires"); + priv->pixelformats = tilcdc_crossed_formats; + priv->num_pixelformats = + ARRAY_SIZE(tilcdc_crossed_formats); + bpp = 32; /* Choose bpp with RGB support for fbdef */ + } else if (0 == strcmp(str, "straight")) { + DBG("Configured for straight blue and red wires"); + priv->pixelformats = tilcdc_straight_formats; + priv->num_pixelformats = + ARRAY_SIZE(tilcdc_straight_formats); + bpp = 16; /* Choose bpp with RGB support for fbdef */ + } else { + DBG("Blue and red wiring '%s' unknown, use legacy mode", + str); + priv->pixelformats = tilcdc_legacy_formats; + priv->num_pixelformats = + ARRAY_SIZE(tilcdc_legacy_formats); + bpp = 16; /* This is just a guess */ + } + } + + if (of_property_read_u32(node, "max-bandwidth", &priv->max_bandwidth)) + priv->max_bandwidth = TILCDC_DEFAULT_MAX_BANDWIDTH; + + DBG("Maximum Bandwidth Value %d", priv->max_bandwidth); + + if (of_property_read_u32(node, "max-width", &priv->max_width)) { + if (priv->rev == 1) + priv->max_width = TILCDC_DEFAULT_MAX_WIDTH_V1; + else + priv->max_width = TILCDC_DEFAULT_MAX_WIDTH_V2; + } + + DBG("Maximum Horizontal Pixel Width Value %dpixels", priv->max_width); + + if (of_property_read_u32(node, "max-pixelclock", + &priv->max_pixelclock)) + priv->max_pixelclock = TILCDC_DEFAULT_MAX_PIXELCLOCK; + + DBG("Maximum Pixel Clock Value %dKHz", priv->max_pixelclock); + + if (variant == DA850_TILCDC) + priv->fifo_th = 16; + else + priv->fifo_th = 8; + + ret = tilcdc_crtc_create(ddev); + if (ret < 0) { + drm_err(ddev, "failed to create crtc\n"); + goto disable_pm; + } + modeset_init(ddev); + +#ifdef CONFIG_CPU_FREQ + priv->freq_transition.notifier_call = cpufreq_transition; + ret = cpufreq_register_notifier(&priv->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); + if (ret) { + drm_err(ddev, "failed to register cpufreq notifier\n"); + priv->freq_transition.notifier_call = NULL; + goto disable_pm; + } +#endif + + ret = tilcdc_encoder_create(ddev); + if (ret) + goto unregister_cpufreq_notif; + + if (!priv->connector) { + drm_err(ddev, "no encoders/connectors found\n"); + ret = -EPROBE_DEFER; + goto unregister_cpufreq_notif; + } + + ret = drm_vblank_init(ddev, 1); + if (ret < 0) { + drm_err(ddev, "failed to initialize vblank\n"); + goto unregister_cpufreq_notif; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto unregister_cpufreq_notif; + priv->irq = ret; + + ret = tilcdc_irq_install(ddev, priv->irq); + if (ret < 0) { + drm_err(ddev, "failed to install IRQ handler\n"); + goto unregister_cpufreq_notif; + } + + drm_mode_config_reset(ddev); + + drm_kms_helper_poll_init(ddev); + + ret = drm_dev_register(ddev, 0); + if (ret) + goto stop_poll; + + drm_client_setup_with_color_mode(ddev, bpp); + + return 0; + +stop_poll: + drm_kms_helper_poll_fini(ddev); + tilcdc_irq_uninstall(ddev); +unregister_cpufreq_notif: +#ifdef CONFIG_CPU_FREQ + cpufreq_unregister_notifier(&priv->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +#endif +disable_pm: + pm_runtime_disable(dev); + clk_put(priv->clk); +free_wq: + destroy_workqueue(priv->wq); + + return ret; +} + +static void tilcdc_pdev_remove(struct platform_device *pdev) +{ + struct tilcdc_drm_private *priv = platform_get_drvdata(pdev); + struct drm_device *ddev = &priv->ddev; + + drm_dev_unregister(ddev); + drm_kms_helper_poll_fini(ddev); + tilcdc_irq_uninstall(ddev); +#ifdef CONFIG_CPU_FREQ + cpufreq_unregister_notifier(&priv->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +#endif + pm_runtime_disable(&pdev->dev); + clk_put(priv->clk); + destroy_workqueue(priv->wq); +} + +static void tilcdc_pdev_shutdown(struct platform_device *pdev) +{ + struct tilcdc_drm_private *priv = platform_get_drvdata(pdev); + struct drm_device *ddev = &priv->ddev; + + drm_atomic_helper_shutdown(ddev); +} + +static const struct of_device_id tilcdc_of_match[] = { + { .compatible = "ti,am33xx-tilcdc", .data = (void *)AM33XX_TILCDC}, + { .compatible = "ti,da850-tilcdc", .data = (void *)DA850_TILCDC}, + { }, +}; +MODULE_DEVICE_TABLE(of, tilcdc_of_match); + +static struct platform_driver tilcdc_platform_driver = { + .probe = tilcdc_pdev_probe, + .remove = tilcdc_pdev_remove, + .shutdown = tilcdc_pdev_shutdown, + .driver = { + .name = "tilcdc", + .pm = pm_sleep_ptr(&tilcdc_pm_ops), + .of_match_table = tilcdc_of_match, + }, +}; + +drm_module_platform_driver(tilcdc_platform_driver); + +MODULE_AUTHOR("Rob Clark + */ + +#ifndef __TILCDC_DRV_H__ +#define __TILCDC_DRV_H__ + +#include +#include + +#include + +struct clk; +struct workqueue_struct; + +struct drm_connector; +struct drm_connector_helper_funcs; +struct drm_crtc; +struct drm_device; +struct drm_display_mode; +struct drm_encoder; +struct drm_framebuffer; +struct drm_minor; +struct drm_pending_vblank_event; +struct drm_plane; + +/* Defaulting to pixel clock defined on AM335x */ +#define TILCDC_DEFAULT_MAX_PIXELCLOCK 126000 +/* Maximum display width for LCDC V1 */ +#define TILCDC_DEFAULT_MAX_WIDTH_V1 1024 +/* ... and for LCDC V2 found on AM335x: */ +#define TILCDC_DEFAULT_MAX_WIDTH_V2 2048 +/* + * This may need some tweaking, but want to allow at least 1280x1024@60 + * with optimized DDR & EMIF settings tweaked 1920x1080@24 appears to + * be supportable + */ +#define TILCDC_DEFAULT_MAX_BANDWIDTH (1280*1024*60) + + +struct tilcdc_drm_private { + void __iomem *mmio; + + struct clk *clk; /* functional clock */ + int rev; /* IP revision */ + + unsigned int irq; + + struct drm_device ddev; + + /* don't attempt resolutions w/ higher W * H * Hz: */ + uint32_t max_bandwidth; + /* + * Pixel Clock will be restricted to some value as + * defined in the device datasheet measured in KHz + */ + uint32_t max_pixelclock; + /* + * Max allowable width is limited on a per device basis + * measured in pixels + */ + uint32_t max_width; + + u32 fifo_th; + + /* Supported pixel formats */ + const uint32_t *pixelformats; + uint32_t num_pixelformats; + +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; +#endif + + struct workqueue_struct *wq; + + struct drm_crtc *crtc; + + struct tilcdc_encoder *encoder; + struct drm_connector *connector; + + bool irq_enabled; +}; + +#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) + +#define ddev_to_tilcdc_priv(x) container_of(x, struct tilcdc_drm_private, ddev) + +int tilcdc_crtc_create(struct drm_device *dev); +irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc); +void tilcdc_crtc_update_clk(struct drm_crtc *crtc); +void tilcdc_crtc_shutdown(struct drm_crtc *crtc); +int tilcdc_crtc_update_fb(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event); + +struct tilcdc_plane { + struct drm_plane base; +}; + +struct tilcdc_encoder { + struct drm_encoder base; +}; + +struct tilcdc_plane *tilcdc_plane_init(struct drm_device *dev); + +#endif /* __TILCDC_DRV_H__ */ diff --git a/drivers/gpu/drm/tilcdc/tilcdc_encoder.c b/drivers/gpu/drm/tilcdc/tilcdc_encoder.c new file mode 100644 index 000000000..ac4b5beb2 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_encoder.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha + */ + +#include + +#include +#include +#include +#include +#include + +#include "tilcdc_drv.h" +#include "tilcdc_encoder.h" + +static +int tilcdc_attach_bridge(struct drm_device *ddev, struct drm_bridge *bridge) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(ddev); + struct drm_connector *connector; + int ret; + + priv->encoder->base.possible_crtcs = BIT(0); + + ret = drm_bridge_attach(&priv->encoder->base, bridge, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + + connector = drm_bridge_connector_init(ddev, &priv->encoder->base); + if (IS_ERR(connector)) { + drm_err(ddev, "bridge_connector create failed\n"); + return PTR_ERR(connector); + } + + priv->connector = connector; + return 0; +} + +int tilcdc_encoder_create(struct drm_device *ddev) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(ddev); + struct tilcdc_encoder *encoder; + struct drm_bridge *bridge; + + bridge = devm_drm_of_get_bridge(ddev->dev, ddev->dev->of_node, 0, 0); + if (PTR_ERR(bridge) == -ENODEV) + return 0; + else if (IS_ERR(bridge)) + return PTR_ERR(bridge); + + encoder = drmm_simple_encoder_alloc(ddev, struct tilcdc_encoder, + base, DRM_MODE_ENCODER_NONE); + if (IS_ERR(encoder)) { + drm_err(ddev, "drm_encoder_init() failed %pe\n", encoder); + return PTR_ERR(encoder); + } + priv->encoder = encoder; + + return tilcdc_attach_bridge(ddev, bridge); +} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_encoder.h b/drivers/gpu/drm/tilcdc/tilcdc_encoder.h new file mode 100644 index 000000000..c8f87f590 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_encoder.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha + */ + +#ifndef __TILCDC_EXTERNAL_H__ +#define __TILCDC_EXTERNAL_H__ + +int tilcdc_encoder_create(struct drm_device *ddev); +#endif /* __TILCDC_SLAVE_H__ */ diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c b/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c new file mode 100644 index 000000000..7583b6ebb --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 Bootlin + * Author: Kory Maincent + * + * To support the legacy "ti,tilcdc,panel" binding, the devicetree has to + * be transformed to the new panel-dpi binding with the endpoint associated. + */ + +#include +#include +#include +#include + +/* Embedded dtbo symbols created by cmd_wrap_S_dtb in scripts/Makefile.lib */ +extern char __dtbo_tilcdc_panel_legacy_begin[]; +extern char __dtbo_tilcdc_panel_legacy_end[]; + +static int __init +tilcdc_panel_update_prop(struct of_changeset *ocs, struct device_node *node, + char *name, void *val, int length) +{ + struct property *prop; + + prop = kzalloc_obj(*prop); + if (!prop) + return -ENOMEM; + + prop->name = kstrdup(name, GFP_KERNEL); + prop->length = length; + prop->value = kmemdup(val, length, GFP_KERNEL); + if (!prop->name || !prop->value) { + kfree(prop->name); + kfree(prop->value); + kfree(prop); + return -ENOMEM; + } + + return of_changeset_update_property(ocs, node, prop); +} + +static int __init tilcdc_panel_copy_props(struct device_node *old_panel, + struct device_node *new_panel) +{ + struct device_node *old_timing __free(device_node) = NULL; + struct device_node *new_timing __free(device_node) = NULL; + struct device_node *panel_info __free(device_node) = NULL; + struct device_node *child __free(device_node) = NULL; + u32 invert_pxl_clk = 0, sync_edge = 0; + struct of_changeset ocs; + struct property *prop; + int ret; + + child = of_get_child_by_name(old_panel, "display-timings"); + if (!child) + return -EINVAL; + + /* The default display timing is the one specified as native-mode. + * If no native-mode is specified then the first node is assumed + * to be the native mode. + */ + old_timing = of_parse_phandle(child, "native-mode", 0); + if (!old_timing) { + old_timing = of_get_next_child(child, NULL); + if (!old_timing) + return -EINVAL; + } + + panel_info = of_get_child_by_name(old_panel, "panel-info"); + if (!panel_info) + return -EINVAL; + + of_changeset_init(&ocs); + + /* Copy all panel properties to the new panel node */ + for_each_property_of_node(old_panel, prop) { + if (!strncmp(prop->name, "compatible", sizeof("compatible"))) + continue; + + ret = tilcdc_panel_update_prop(&ocs, new_panel, prop->name, + prop->value, prop->length); + if (ret) + goto destroy_ocs; + } + + new_timing = of_changeset_create_node(&ocs, new_panel, "panel-timing"); + if (!new_timing) { + ret = -ENODEV; + goto destroy_ocs; + } + + /* Copy all panel timing properties to the new panel node */ + for_each_property_of_node(old_timing, prop) { + ret = tilcdc_panel_update_prop(&ocs, new_timing, prop->name, + prop->value, prop->length); + if (ret) + goto destroy_ocs; + } + + /* Looked only for these two parameter as all the other are always + * set to default and not related to common DRM properties. + */ + of_property_read_u32(panel_info, "invert-pxl-clk", &invert_pxl_clk); + of_property_read_u32(panel_info, "sync-edge", &sync_edge); + + if (!invert_pxl_clk) { + ret = tilcdc_panel_update_prop(&ocs, new_timing, "pixelclk-active", + &(__be32){cpu_to_be32(1)}, sizeof(__be32)); + if (ret) + goto destroy_ocs; + } + + if (!sync_edge) { + ret = tilcdc_panel_update_prop(&ocs, new_timing, "syncclk-active", + &(__be32){cpu_to_be32(1)}, sizeof(__be32)); + if (ret) + goto destroy_ocs; + } + + /* Remove compatible property to avoid any driver compatible match */ + of_changeset_remove_property(&ocs, old_panel, + of_find_property(old_panel, "compatible", NULL)); + + of_changeset_apply(&ocs); + return 0; + +destroy_ocs: + of_changeset_destroy(&ocs); + return ret; +} + +static const struct of_device_id tilcdc_panel_of_match[] __initconst = { + { .compatible = "ti,tilcdc,panel", }, + {}, +}; + +static const struct of_device_id tilcdc_of_match[] __initconst = { + { .compatible = "ti,am33xx-tilcdc", }, + { .compatible = "ti,da850-tilcdc", }, + {}, +}; + +static int __init tilcdc_panel_legacy_init(void) +{ + struct device_node *new_panel __free(device_node) = NULL; + struct device_node *panel __free(device_node) = NULL; + struct device_node *lcdc __free(device_node) = NULL; + void *dtbo_start; + u32 dtbo_size; + int ovcs_id; + int ret; + + lcdc = of_find_matching_node(NULL, tilcdc_of_match); + panel = of_find_matching_node(NULL, tilcdc_panel_of_match); + + if (!of_device_is_available(panel) || + !of_device_is_available(lcdc)) + return 0; + + dtbo_start = __dtbo_tilcdc_panel_legacy_begin; + dtbo_size = __dtbo_tilcdc_panel_legacy_end - + __dtbo_tilcdc_panel_legacy_begin; + + ret = of_overlay_fdt_apply(dtbo_start, dtbo_size, &ovcs_id, NULL); + if (ret) + return ret; + + new_panel = of_find_node_by_name(NULL, "tilcdc-panel-dpi"); + if (!new_panel) { + ret = -ENODEV; + goto overlay_remove; + } + + ret = tilcdc_panel_copy_props(panel, new_panel); + if (ret) + goto overlay_remove; + + return 0; + +overlay_remove: + of_overlay_remove(&ovcs_id); + return ret; +} + +subsys_initcall(tilcdc_panel_legacy_init); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.dtso b/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.dtso new file mode 100644 index 000000000..ae71d10f5 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_panel_legacy.dtso @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * DTS overlay for converting ti,tilcdc,panel binding to new binding. + * + * Copyright (C) 2025 Bootlin + * Author: Kory Maincent + */ + +/dts-v1/; +/plugin/; + +&{/} { + tilcdc-panel-dpi { + compatible = "panel-dpi"; + port { + panel_in: endpoint@0 { + remote-endpoint = <&lcd_0>; + }; + }; + }; +}; + +&lcdc { + port { + lcd_0: endpoint@0 { + remote-endpoint = <&panel_in>; + }; + }; +}; diff --git a/drivers/gpu/drm/tilcdc/tilcdc_plane.c b/drivers/gpu/drm/tilcdc/tilcdc_plane.c new file mode 100644 index 000000000..d2678af5d --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_plane.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha + */ + +#include +#include +#include +#include + +#include "tilcdc_drv.h" + +static const struct drm_plane_funcs tilcdc_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static int tilcdc_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_crtc_state *crtc_state; + struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, + plane); + unsigned int pitch; + + if (!new_state->crtc) + return 0; + + if (WARN_ON(!new_state->fb)) + return -EINVAL; + + if (new_state->crtc_x || new_state->crtc_y) { + drm_err(plane->dev, "%s: crtc position must be zero.", + __func__); + return -EINVAL; + } + + crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc); + /* we should have a crtc state if the plane is attached to a crtc */ + if (WARN_ON(!crtc_state)) + return 0; + + if (crtc_state->mode.hdisplay != new_state->crtc_w || + crtc_state->mode.vdisplay != new_state->crtc_h) { + drm_err(plane->dev, + "%s: Size must match mode (%dx%d == %dx%d)", __func__, + crtc_state->mode.hdisplay, crtc_state->mode.vdisplay, + new_state->crtc_w, new_state->crtc_h); + return -EINVAL; + } + + pitch = crtc_state->mode.hdisplay * + new_state->fb->format->cpp[0]; + if (new_state->fb->pitches[0] != pitch) { + drm_err(plane->dev, + "Invalid pitch: fb and crtc widths must be the same"); + return -EINVAL; + } + + if (old_state->fb && new_state->fb->format != old_state->fb->format) { + drm_dbg(plane->dev, + "%s(): pixel format change requires mode_change\n", + __func__); + crtc_state->mode_changed = true; + } + + return 0; +} + +static void tilcdc_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, + plane); + + if (!new_state->crtc) + return; + + if (WARN_ON(!new_state->fb || !new_state->crtc->state)) + return; + + if (tilcdc_crtc_update_fb(new_state->crtc, + new_state->fb, + new_state->crtc->state->event) == 0) { + new_state->crtc->state->event = NULL; + } +} + +static const struct drm_plane_helper_funcs plane_helper_funcs = { + .atomic_check = tilcdc_plane_atomic_check, + .atomic_update = tilcdc_plane_atomic_update, +}; + +struct tilcdc_plane *tilcdc_plane_init(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + struct tilcdc_plane *plane; + + plane = drmm_universal_plane_alloc(dev, struct tilcdc_plane, base, + 1, &tilcdc_plane_funcs, + priv->pixelformats, + priv->num_pixelformats, + NULL, DRM_PLANE_TYPE_PRIMARY, NULL); + if (IS_ERR(plane)) + return plane; + + drm_plane_helper_add(&plane->base, &plane_helper_funcs); + + return plane; +} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_regs.h b/drivers/gpu/drm/tilcdc/tilcdc_regs.h new file mode 100644 index 000000000..26ebaf1e0 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_regs.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark + */ + +#ifndef __TILCDC_REGS_H__ +#define __TILCDC_REGS_H__ + +/* LCDC register definitions, based on da8xx-fb */ + +#include + +#include "tilcdc_drv.h" + +/* LCDC Status Register */ +#define LCDC_END_OF_FRAME1 BIT(9) +#define LCDC_END_OF_FRAME0 BIT(8) +#define LCDC_PL_LOAD_DONE BIT(6) +#define LCDC_FIFO_UNDERFLOW BIT(5) +#define LCDC_SYNC_LOST BIT(2) +#define LCDC_FRAME_DONE BIT(0) + +/* LCDC DMA Control Register */ +#define LCDC_DMA_BURST_SIZE(x) ((x) << 4) +#define LCDC_DMA_BURST_SIZE_MASK ((0x7) << 4) +#define LCDC_DMA_BURST_1 0x0 +#define LCDC_DMA_BURST_2 0x1 +#define LCDC_DMA_BURST_4 0x2 +#define LCDC_DMA_BURST_8 0x3 +#define LCDC_DMA_BURST_16 0x4 +#define LCDC_DMA_FIFO_THRESHOLD(x) ((x) << 8) +#define LCDC_DMA_FIFO_THRESHOLD_MASK ((0x3) << 8) +#define LCDC_V1_END_OF_FRAME_INT_ENA BIT(2) +#define LCDC_V2_END_OF_FRAME0_INT_ENA BIT(8) +#define LCDC_V2_END_OF_FRAME1_INT_ENA BIT(9) +#define LCDC_DUAL_FRAME_BUFFER_ENABLE BIT(0) + +/* LCDC Control Register */ +#define LCDC_CLK_DIVISOR(x) ((x) << 8) +#define LCDC_CLK_DIVISOR_MASK ((0xFF) << 8) +#define LCDC_RASTER_MODE 0x01 + +/* LCDC Raster Control Register */ +#define LCDC_PALETTE_LOAD_MODE(x) ((x) << 20) +#define LCDC_PALETTE_LOAD_MODE_MASK ((0x3) << 20) +#define PALETTE_AND_DATA 0x00 +#define PALETTE_ONLY 0x01 +#define DATA_ONLY 0x02 + +#define LCDC_MONO_8BIT_MODE BIT(9) +#define LCDC_RASTER_ORDER BIT(8) +#define LCDC_TFT_MODE BIT(7) +#define LCDC_V1_UNDERFLOW_INT_ENA BIT(6) +#define LCDC_V2_UNDERFLOW_INT_ENA BIT(5) +#define LCDC_V1_PL_INT_ENA BIT(4) +#define LCDC_V2_PL_INT_ENA BIT(6) +#define LCDC_V1_SYNC_LOST_INT_ENA BIT(5) +#define LCDC_V1_FRAME_DONE_INT_ENA BIT(3) +#define LCDC_MONOCHROME_MODE BIT(1) +#define LCDC_RASTER_ENABLE BIT(0) +#define LCDC_TFT_ALT_ENABLE BIT(23) +#define LCDC_STN_565_ENABLE BIT(24) +#define LCDC_V2_DMA_CLK_EN BIT(2) +#define LCDC_V2_LIDD_CLK_EN BIT(1) +#define LCDC_V2_CORE_CLK_EN BIT(0) +#define LCDC_V2_LPP_B10 26 +#define LCDC_V2_TFT_24BPP_MODE BIT(25) +#define LCDC_V2_TFT_24BPP_UNPACK BIT(26) + +/* LCDC Raster Timing 2 Register */ +#define LCDC_AC_BIAS_TRANSITIONS_PER_INT(x) ((x) << 16) +#define LCDC_AC_BIAS_TRANSITIONS_PER_INT_MASK ((0xF) << 16) +#define LCDC_AC_BIAS_FREQUENCY(x) ((x) << 8) +#define LCDC_AC_BIAS_FREQUENCY_MASK ((0xFF) << 8) +#define LCDC_SYNC_CTRL BIT(25) +#define LCDC_SYNC_EDGE BIT(24) +#define LCDC_INVERT_PIXEL_CLOCK BIT(22) +#define LCDC_INVERT_HSYNC BIT(21) +#define LCDC_INVERT_VSYNC BIT(20) +#define LCDC_LPP_B10 BIT(26) + +/* LCDC Block */ +#define LCDC_PID_REG 0x0 +#define LCDC_CTRL_REG 0x4 +#define LCDC_STAT_REG 0x8 +#define LCDC_RASTER_CTRL_REG 0x28 +#define LCDC_RASTER_TIMING_0_REG 0x2c +#define LCDC_RASTER_TIMING_1_REG 0x30 +#define LCDC_RASTER_TIMING_2_REG 0x34 +#define LCDC_DMA_CTRL_REG 0x40 +#define LCDC_DMA_FB_BASE_ADDR_0_REG 0x44 +#define LCDC_DMA_FB_CEILING_ADDR_0_REG 0x48 +#define LCDC_DMA_FB_BASE_ADDR_1_REG 0x4c +#define LCDC_DMA_FB_CEILING_ADDR_1_REG 0x50 + +/* Interrupt Registers available only in Version 2 */ +#define LCDC_RAW_STAT_REG 0x58 +#define LCDC_MASKED_STAT_REG 0x5c +#define LCDC_INT_ENABLE_SET_REG 0x60 +#define LCDC_INT_ENABLE_CLR_REG 0x64 +#define LCDC_END_OF_INT_IND_REG 0x68 + +/* Clock registers available only on Version 2 */ +#define LCDC_CLK_ENABLE_REG 0x6c +#define LCDC_CLK_RESET_REG 0x70 +#define LCDC_CLK_MAIN_RESET BIT(3) + + +/* + * Helpers: + */ + +static inline void tilcdc_write(struct drm_device *dev, u32 reg, u32 data) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + iowrite32(data, priv->mmio + reg); +} + +static inline void tilcdc_write64(struct drm_device *dev, u32 reg, u64 data) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + volatile void __iomem *addr = priv->mmio + reg; + +#if defined(iowrite64) && !defined(iowrite64_is_nonatomic) + iowrite64(data, addr); +#else + __iowmb(); + /* This compiles to strd (=64-bit write) on ARM7 */ + *(volatile u64 __force *)addr = __cpu_to_le64(data); +#endif +} + +static inline u32 tilcdc_read(struct drm_device *dev, u32 reg) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + return ioread32(priv->mmio + reg); +} + +static inline void tilcdc_write_mask(struct drm_device *dev, u32 reg, + u32 val, u32 mask) +{ + tilcdc_write(dev, reg, (tilcdc_read(dev, reg) & ~mask) | (val & mask)); +} + +static inline void tilcdc_set(struct drm_device *dev, u32 reg, u32 mask) +{ + tilcdc_write(dev, reg, tilcdc_read(dev, reg) | mask); +} + +static inline void tilcdc_clear(struct drm_device *dev, u32 reg, u32 mask) +{ + tilcdc_write(dev, reg, tilcdc_read(dev, reg) & ~mask); +} + +/* the register to read/clear irqstatus differs between v1 and v2 of the IP */ +static inline u32 tilcdc_irqstatus_reg(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = ddev_to_tilcdc_priv(dev); + return (priv->rev == 2) ? LCDC_MASKED_STAT_REG : LCDC_STAT_REG; +} + +static inline u32 tilcdc_read_irqstatus(struct drm_device *dev) +{ + return tilcdc_read(dev, tilcdc_irqstatus_reg(dev)); +} + +static inline void tilcdc_clear_irqstatus(struct drm_device *dev, u32 mask) +{ + tilcdc_write(dev, tilcdc_irqstatus_reg(dev), mask); +} + +#endif /* __TILCDC_REGS_H__ */ -- cgit v1.3.1