// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/of_graph.h>
#include <linux/pm_runtime.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#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 drm_plane primary;
const struct tilcdc_panel_info *info;
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;
/* Only set if an external encoder is connected */
bool simulate_vesa_sync;
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 = dev->dev_private;
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 = dev->dev_private;
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)
dev_err(dev->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 = dev->dev_private;
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 = dev->dev_private;
/* 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;