// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012 Russell King
* Rewritten from the dovefb driver, and Armada510 manuals.
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "armada_crtc.h"
#include "armada_drm.h"
#include "armada_fb.h"
#include "armada_gem.h"
#include "armada_hw.h"
#include "armada_plane.h"
#include "armada_trace.h"
/*
* A note about interlacing. Let's consider HDMI 1920x1080i.
* The timing parameters we have from X are:
* Hact HsyA HsyI Htot Vact VsyA VsyI Vtot
* 1920 2448 2492 2640 1080 1084 1094 1125
* Which get translated to:
* Hact HsyA HsyI Htot Vact VsyA VsyI Vtot
* 1920 2448 2492 2640 540 542 547 562
*
* This is how it is defined by CEA-861-D - line and pixel numbers are
* referenced to the rising edge of VSYNC and HSYNC. Total clocks per
* line: 2640. The odd frame, the first active line is at line 21, and
* the even frame, the first active line is 584.
*
* LN: 560 561 562 563 567 568 569
* DE: ~~~|____________________________//__________________________
* HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
* VSYNC: _________________________|~~~~~~//~~~~~~~~~~~~~~~|__________
* 22 blanking lines. VSYNC at 1320 (referenced to the HSYNC rising edge).
*
* LN: 1123 1124 1125 1 5 6 7
* DE: ~~~|____________________________//__________________________
* HSYNC: ____|~|_____|~|_____|~|_____|~|_//__|~|_____|~|_____|~|_____
* VSYNC: ____________________|~~~~~~~~~~~//~~~~~~~~~~|_______________
* 23 blanking lines
*
* The Armada LCD Controller line and pixel numbers are, like X timings,
* referenced to the top left of the active frame.
*
* So, translating these to our LCD controller:
* Odd frame, 563 total lines, VSYNC at line 543-548, pixel 1128.
* Even frame, 562 total lines, VSYNC at line 542-547, pixel 2448.
* Note: Vsync front porch remains constant!
*
* if (odd_frame) {
* vtotal = mode->crtc_vtotal + 1;
* vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay + 1;
* vhorizpos = mode->crtc_hsync_start - mode->crtc_htotal / 2
* } else {
* vtotal = mode->crtc_vtotal;
* vbackporch = mode->crtc_vsync_start - mode->crtc_vdisplay;
* vhorizpos = mode->crtc_hsync_start;
* }
* vfrontporch = mode->crtc_vtotal - mode->crtc_vsync_end;
*
* So, we need to reprogram these registers on each vsync event:
* LCD_SPU_V_PORCH, LCD_SPU_ADV_REG, LCD_SPUT_V_H_TOTAL
*
* Note: we do not use the frame done interrupts because these appear
* to happen too early, and lead to jitter on the display (presumably
* they occur at the end of the last active line, before the vsync back
* porch, which we're reprogramming.)
*/
void
armada_drm_crtc_update_regs(struct armada_crtc *dcrtc, struct armada_regs *regs)
{
while (regs->offset != ~0) {
void __iomem *reg = dcrtc->base + regs->offset;
uint32_t val;
val = regs->mask;
if (val != 0)
val &= readl_relaxed(reg);
writel_relaxed(val | regs->val, reg);
++regs;
}
}
static void armada_drm_crtc_update(struct armada_crtc *dcrtc, bool enable)
{
uint32_t dumb_ctrl;
dumb_ctrl = dcrtc->cfg_dumb_ctrl;
if (enable)
dumb_ctrl |= CFG_DUMB_ENA;
/*
* When the dumb interface isn't in DUMB24_RGB888_0 mode, it might
* be using SPI or GPIO. If we set this to DUMB_BLANK, we will
* force LCD_D[23:0] to output blank color, overriding the GPIO or
* SPI usage. So leave it as-is unless in DUMB24_RGB888_0 mode.
*/
if (!enable && (dumb_ctrl & DUMB_MASK) == DUMB24_RGB888_0) {
dumb_ctrl &= ~DUMB_MASK;
dumb_ctrl |= DUMB_BLANK;
}
armada_updatel(dumb_ctrl,
~(CFG_INV_CSYNC | CFG_INV_HSYNC | CFG_INV_VSYNC),
dcrtc->base + LCD_SPU_DUMB_CTRL);
}
static void armada_drm_crtc_queue_state_event(struct drm_crtc *crtc)
{
struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
struct drm_pending_vblank_event *event;
/* If we have an event, we need vblank events enabled */
event = xchg(&crtc->state->event, NULL);
if (event) {
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
dcrtc->event = event;
}
}
static void armada_drm_update_gamma(struct drm_crtc *crtc)
{
struct drm_property_blob *blob = crtc->state->gamma_lut;
void __iomem *base = drm_to_armada_crtc(crtc)->base;
int i;
if (blob) {
struct drm_color_lut *lut = blob->data;
armada_updatel(CFG_CSB_256x8, CFG_CSB_256x8 | CFG_PDWN256x8,
base + LCD_SPU_SRAM_PARA1);
for (i = 0; i < 256; i++) {
writel_relaxed(drm_color_lut_extract(lut[i].red, 8),
base + LCD_SPU_SRAM_WRDAT);
writel_relaxed(i | SRAM_WRITE | SRAM_GAMMA_YR,
base + LCD_SPU_SRAM_CTRL);
readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN);
writel_relaxed(drm_color_lut_extract(lut[i].green, 8),
base + LCD_SPU_SRAM_WRDAT);
writel_relaxed(i | SRAM_WRITE | SRAM_GAMMA_UG,
base + LCD_SPU_SRAM_CTRL);
readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN);
writel_relaxed(drm_color_lut_extract(lut[i].blue, 8),
base + LCD_SPU_SRAM_WRDAT);
writel_relaxed(i | SRAM_WRITE | SRAM_GAMMA_VB,
base + LCD_SPU_SRAM_CTRL);
readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN);
}
armada_updatel(CFG_GAMMA_ENA, CFG_GAMMA_ENA,
base + LCD_SPU_DMA_CTRL0);
} else {
armada_updatel(0, CFG_GAMMA_ENA, base + LCD_SPU_DMA_CTRL0