Commit d795fd32 authored by Linus Walleij's avatar Linus Walleij

drm/mcde: Support DPI output

This implements support for DPI output using the port node
in the device tree to connect a DPI LCD display to the
MCDE. The block also supports TV-out but we leave that
for another day when we have a hardware using it.

We implement parsing and handling of the "port" node,
and follow that to the DPI endpoint.

The clock divider used by the MCDE to divide down the
"lcdclk" (this has been designed for TV-like frequencies)
is represented by an ordinary clock provider internally
in the MCDE. This idea was inspired by the PL111 solution
by Eric Anholt: the divider also works very similar to
the Pl111 clock divider.

We take care to clear up some errors regarding the number
of available formatters and their type. We have 6 DSI
formatters and 2 DPI formatters.

Tested on the Samsung GT-I9070 Janice mobile phone.
Signed-off-by: default avatarLinus Walleij <linus.walleij@linaro.org>
Acked-by: default avatarSam Ravnborg <sam@ravnborg.org>
Cc: Stephan Gerhold <stephan@gerhold.net>
Cc: phone-devel@vger.kernel.org
Cc: upstreaming@lists.sr.ht
Link: https://patchwork.freedesktop.org/patch/msgid/20201112142925.2571179-2-linus.walleij@linaro.org
parent bfbc5e3b
......@@ -4,6 +4,7 @@ config DRM_MCDE
depends on CMA
depends on ARM || COMPILE_TEST
depends on OF
depends on COMMON_CLK
select MFD_SYSCON
select DRM_MIPI_DSI
select DRM_BRIDGE
......
mcde_drm-y += mcde_drv.o mcde_dsi.o mcde_display.o
mcde_drm-y += mcde_drv.o mcde_dsi.o mcde_clk_div.o mcde_display.o
obj-$(CONFIG_DRM_MCDE) += mcde_drm.o
// SPDX-License-Identifier: GPL-2.0
#include <linux/clk-provider.h>
#include <linux/regulator/consumer.h>
#include "mcde_drm.h"
#include "mcde_display_regs.h"
/* The MCDE internal clock dividers for FIFO A and B */
struct mcde_clk_div {
struct clk_hw hw;
struct mcde *mcde;
u32 cr;
u32 cr_div;
};
static int mcde_clk_div_enable(struct clk_hw *hw)
{
struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
struct mcde *mcde = cdiv->mcde;
u32 val;
spin_lock(&mcde->fifo_crx1_lock);
val = readl(mcde->regs + cdiv->cr);
/*
* Select the PLL72 (LCD) clock as parent
* FIXME: implement other parents.
*/
val &= ~MCDE_CRX1_CLKSEL_MASK;
val |= MCDE_CRX1_CLKSEL_CLKPLL72 << MCDE_CRX1_CLKSEL_SHIFT;
/* Internal clock */
val |= MCDE_CRA1_CLKTYPE_TVXCLKSEL1;
/* Clear then set the divider */
val &= ~(MCDE_CRX1_BCD | MCDE_CRX1_PCD_MASK);
val |= cdiv->cr_div;
writel(val, mcde->regs + cdiv->cr);
spin_unlock(&mcde->fifo_crx1_lock);
return 0;
}
static int mcde_clk_div_choose_div(struct clk_hw *hw, unsigned long rate,
unsigned long *prate, bool set_parent)
{
int best_div = 1, div;
struct clk_hw *parent = clk_hw_get_parent(hw);
unsigned long best_prate = 0;
unsigned long best_diff = ~0ul;
int max_div = (1 << MCDE_CRX1_PCD_BITS) - 1;
for (div = 1; div < max_div; div++) {
unsigned long this_prate, div_rate, diff;
if (set_parent)
this_prate = clk_hw_round_rate(parent, rate * div);
else
this_prate = *prate;
div_rate = DIV_ROUND_UP_ULL(this_prate, div);
diff = abs(rate - div_rate);
if (diff < best_diff) {
best_div = div;
best_diff = diff;
best_prate = this_prate;
}
}
*prate = best_prate;
return best_div;
}
static long mcde_clk_div_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
int div = mcde_clk_div_choose_div(hw, rate, prate, true);
return DIV_ROUND_UP_ULL(*prate, div);
}
static unsigned long mcde_clk_div_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
struct mcde *mcde = cdiv->mcde;
u32 cr;
int div;
/*
* If the MCDE is not powered we can't access registers.
* It will come up with 0 in the divider register bits, which
* means "divide by 2".
*/
if (!regulator_is_enabled(mcde->epod))
return DIV_ROUND_UP_ULL(prate, 2);
cr = readl(mcde->regs + cdiv->cr);
if (cr & MCDE_CRX1_BCD)
return prate;
/* 0 in the PCD means "divide by 2", 1 means "divide by 3" etc */
div = cr & MCDE_CRX1_PCD_MASK;
div += 2;
return DIV_ROUND_UP_ULL(prate, div);
}
static int mcde_clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long prate)
{
struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
int div = mcde_clk_div_choose_div(hw, rate, &prate, false);
u32 cr = 0;
/*
* We cache the CR bits to set the divide in the state so that
* we can call this before we can even write to the hardware.
*/
if (div == 1) {
/* Bypass clock divider */
cr |= MCDE_CRX1_BCD;
} else {
div -= 2;
cr |= div & MCDE_CRX1_PCD_MASK;
}
cdiv->cr_div = cr;
return 0;
}
static const struct clk_ops mcde_clk_div_ops = {
.enable = mcde_clk_div_enable,
.recalc_rate = mcde_clk_div_recalc_rate,
.round_rate = mcde_clk_div_round_rate,
.set_rate = mcde_clk_div_set_rate,
};
int mcde_init_clock_divider(struct mcde *mcde)
{
struct device *dev = mcde->dev;
struct mcde_clk_div *fifoa;
struct mcde_clk_div *fifob;
const char *parent_name;
struct clk_init_data fifoa_init = {
.name = "fifoa",
.ops = &mcde_clk_div_ops,
.parent_names = &parent_name,
.num_parents = 1,
.flags = CLK_SET_RATE_PARENT,
};
struct clk_init_data fifob_init = {
.name = "fifob",
.ops = &mcde_clk_div_ops,
.parent_names = &parent_name,
.num_parents = 1,
.flags = CLK_SET_RATE_PARENT,
};
int ret;
spin_lock_init(&mcde->fifo_crx1_lock);
parent_name = __clk_get_name(mcde->lcd_clk);
/* Allocate 2 clocks */
fifoa = devm_kzalloc(dev, sizeof(*fifoa), GFP_KERNEL);
if (!fifoa)
return -ENOMEM;
fifob = devm_kzalloc(dev, sizeof(*fifob), GFP_KERNEL);
if (!fifob)
return -ENOMEM;
fifoa->mcde = mcde;
fifoa->cr = MCDE_CRA1;
fifoa->hw.init = &fifoa_init;
ret = devm_clk_hw_register(dev, &fifoa->hw);
if (ret) {
dev_err(dev, "error registering FIFO A clock divider\n");
return ret;
}
mcde->fifoa_clk = fifoa->hw.clk;
fifob->mcde = mcde;
fifob->cr = MCDE_CRB1;
fifob->hw.init = &fifob_init;
ret = devm_clk_hw_register(dev, &fifob->hw);
if (ret) {
dev_err(dev, "error registering FIFO B clock divider\n");
return ret;
}
mcde->fifob_clk = fifob->hw.clk;
return 0;
}
This diff is collapsed.
......@@ -215,6 +215,80 @@
#define MCDE_OVLXCOMP_Z_SHIFT 27
#define MCDE_OVLXCOMP_Z_MASK 0x78000000
/* DPI/TV configuration registers, channel A and B */
#define MCDE_TVCRA 0x00000838
#define MCDE_TVCRB 0x00000A38
#define MCDE_TVCR_MOD_TV BIT(0) /* 0 = LCD mode */
#define MCDE_TVCR_INTEREN BIT(1)
#define MCDE_TVCR_IFIELD BIT(2)
#define MCDE_TVCR_TVMODE_SDTV_656P (0 << 3)
#define MCDE_TVCR_TVMODE_SDTV_656P_LE (3 << 3)
#define MCDE_TVCR_TVMODE_SDTV_656P_BE (4 << 3)
#define MCDE_TVCR_SDTVMODE_Y0CBY1CR (0 << 6)
#define MCDE_TVCR_SDTVMODE_CBY0CRY1 (1 << 6)
#define MCDE_TVCR_AVRGEN BIT(8)
#define MCDE_TVCR_CKINV BIT(9)
/* TV blanking control register 1, channel A and B */
#define MCDE_TVBL1A 0x0000083C
#define MCDE_TVBL1B 0x00000A3C
#define MCDE_TVBL1_BEL1_SHIFT 0 /* VFP vertical front porch 11 bits */
#define MCDE_TVBL1_BSL1_SHIFT 16 /* VSW vertical sync pulse width 11 bits */
/* Pixel processing TV start line, channel A and B */
#define MCDE_TVISLA 0x00000840
#define MCDE_TVISLB 0x00000A40
#define MCDE_TVISL_FSL1_SHIFT 0 /* Field 1 identification start line 11 bits */
#define MCDE_TVISL_FSL2_SHIFT 16 /* Field 2 identification start line 11 bits */
/* Pixel processing TV DVO offset */
#define MCDE_TVDVOA 0x00000844
#define MCDE_TVDVOB 0x00000A44
#define MCDE_TVDVO_DVO1_SHIFT 0 /* VBP vertical back porch 0 = 0 */
#define MCDE_TVDVO_DVO2_SHIFT 16
/*
* Pixel processing TV Timing 1
* HBP horizontal back porch 11 bits horizontal offset
* 0 = 1 pixel HBP, 255 = 256 pixels, so actual value - 1
*/
#define MCDE_TVTIM1A 0x0000084C
#define MCDE_TVTIM1B 0x00000A4C
/* Pixel processing TV LBALW */
/* 0 = 1 clock cycle, 255 = 256 clock cycles */
#define MCDE_TVLBALWA 0x00000850
#define MCDE_TVLBALWB 0x00000A50
#define MCDE_TVLBALW_LBW_SHIFT 0 /* HSW horizonal sync width, line blanking width 11 bits */
#define MCDE_TVLBALW_ALW_SHIFT 16 /* HFP horizontal front porch, active line width 11 bits */
/* TV blanking control register 1, channel A and B */
#define MCDE_TVBL2A 0x00000854
#define MCDE_TVBL2B 0x00000A54
#define MCDE_TVBL2_BEL2_SHIFT 0 /* Field 2 blanking end line 11 bits */
#define MCDE_TVBL2_BSL2_SHIFT 16 /* Field 2 blanking start line 11 bits */
/* Pixel processing TV background */
#define MCDE_TVBLUA 0x00000858
#define MCDE_TVBLUB 0x00000A58
#define MCDE_TVBLU_TVBLU_SHIFT 0 /* 8 bits luminance */
#define MCDE_TVBLU_TVBCB_SHIFT 8 /* 8 bits Cb chrominance */
#define MCDE_TVBLU_TVBCR_SHIFT 16 /* 8 bits Cr chrominance */
/* Pixel processing LCD timing 1 */
#define MCDE_LCDTIM1A 0x00000860
#define MCDE_LCDTIM1B 0x00000A60
/* inverted vertical sync pulse for HRTFT 0 = active low, 1 active high */
#define MCDE_LCDTIM1B_IVP BIT(19)
/* inverted vertical sync, 0 = active high (the normal), 1 = active low */
#define MCDE_LCDTIM1B_IVS BIT(20)
/* inverted horizontal sync, 0 = active high (the normal), 1 = active low */
#define MCDE_LCDTIM1B_IHS BIT(21)
/* inverted panel clock 0 = rising edge data out, 1 = falling edge data out */
#define MCDE_LCDTIM1B_IPC BIT(22)
/* invert output enable 0 = active high, 1 = active low */
#define MCDE_LCDTIM1B_IOE BIT(23)
#define MCDE_CRC 0x00000C00
#define MCDE_CRC_C1EN BIT(2)
#define MCDE_CRC_C2EN BIT(3)
......@@ -360,6 +434,7 @@
#define MCDE_CRB1 0x00000A04
#define MCDE_CRX1_PCD_SHIFT 0
#define MCDE_CRX1_PCD_MASK 0x000003FF
#define MCDE_CRX1_PCD_BITS 10
#define MCDE_CRX1_CLKSEL_SHIFT 10
#define MCDE_CRX1_CLKSEL_MASK 0x00001C00
#define MCDE_CRX1_CLKSEL_CLKPLL72 0
......@@ -421,8 +496,20 @@
#define MCDE_ROTACONF 0x0000087C
#define MCDE_ROTBCONF 0x00000A7C
/* Synchronization event configuration */
#define MCDE_SYNCHCONFA 0x00000880
#define MCDE_SYNCHCONFB 0x00000A80
#define MCDE_SYNCHCONF_HWREQVEVENT_SHIFT 0
#define MCDE_SYNCHCONF_HWREQVEVENT_VSYNC (0 << 0)
#define MCDE_SYNCHCONF_HWREQVEVENT_BACK_PORCH (1 << 0)
#define MCDE_SYNCHCONF_HWREQVEVENT_ACTIVE_VIDEO (2 << 0)
#define MCDE_SYNCHCONF_HWREQVEVENT_FRONT_PORCH (3 << 0)
#define MCDE_SYNCHCONF_HWREQVCNT_SHIFT 2 /* 14 bits */
#define MCDE_SYNCHCONF_SWINTVEVENT_VSYNC (0 << 16)
#define MCDE_SYNCHCONF_SWINTVEVENT_BACK_PORCH (1 << 16)
#define MCDE_SYNCHCONF_SWINTVEVENT_ACTIVE_VIDEO (2 << 16)
#define MCDE_SYNCHCONF_SWINTVEVENT_FRONT_PORCH (3 << 16)
#define MCDE_SYNCHCONF_SWINTVCNT_SHIFT 18 /* 14 bits */
/* Channel A+B control registers */
#define MCDE_CTRLA 0x00000884
......
......@@ -62,6 +62,8 @@ enum mcde_flow_mode {
MCDE_VIDEO_TE_FLOW,
/* Video mode with the formatter itself as sync source */
MCDE_VIDEO_FORMATTER_FLOW,
/* DPI video with the formatter itsels as sync source */
MCDE_DPI_FORMATTER_FLOW,
};
struct mcde {
......@@ -72,6 +74,7 @@ struct mcde {
struct drm_connector *connector;
struct drm_simple_display_pipe pipe;
struct mipi_dsi_device *mdsi;
bool dpi_output;
s16 stride;
enum mcde_flow_mode flow_mode;
unsigned int flow_active;
......@@ -82,6 +85,11 @@ struct mcde {
struct clk *mcde_clk;
struct clk *lcd_clk;
struct clk *hdmi_clk;
/* Handles to the clock dividers for FIFO A and B */
struct clk *fifoa_clk;
struct clk *fifob_clk;
/* Locks the MCDE FIFO control register A and B */
spinlock_t fifo_crx1_lock;
struct regulator *epod;
struct regulator *vana;
......@@ -105,4 +113,6 @@ void mcde_display_irq(struct mcde *mcde);
void mcde_display_disable_irqs(struct mcde *mcde);
int mcde_display_init(struct drm_device *drm);
int mcde_init_clock_divider(struct mcde *mcde);
#endif /* _MCDE_DRM_H_ */
......@@ -22,13 +22,13 @@
* The hardware has four display pipes, and the layout is a little
* bit like this::
*
* Memory -> Overlay -> Channel -> FIFO -> 5 formatters -> DSI/DPI
* External 0..5 0..3 A,B, 3 x DSI bridge
* Memory -> Overlay -> Channel -> FIFO -> 8 formatters -> DSI/DPI
* External 0..5 0..3 A,B, 6 x DSI bridge
* source 0..9 C0,C1 2 x DPI
*
* FIFOs A and B are for LCD and HDMI while FIFO CO/C1 are for
* panels with embedded buffer.
* 3 of the formatters are for DSI.
* 6 of the formatters are for DSI, 3 pairs for VID/CMD respectively.
* 2 of the formatters are for DPI.
*
* Behind the formatters are the DSI or DPI ports that route to
......@@ -130,9 +130,37 @@ static int mcde_modeset_init(struct drm_device *drm)
struct mcde *mcde = to_mcde(drm);
int ret;
/*
* If no other bridge was found, check if we have a DPI panel or
* any other bridge connected directly to the MCDE DPI output.
* If a DSI bridge is found, DSI will take precedence.
*
* TODO: more elaborate bridge selection if we have more than one
* thing attached to the system.
*/
if (!mcde->bridge) {
dev_err(drm->dev, "no display output bridge yet\n");
return -EPROBE_DEFER;
struct drm_panel *panel;
struct drm_bridge *bridge;
ret = drm_of_find_panel_or_bridge(drm->dev->of_node,
0, 0, &panel, &bridge);
if (ret) {
dev_err(drm->dev,
"Could not locate any output bridge or panel\n");
return ret;
}
if (panel) {
bridge = drm_panel_bridge_add_typed(panel,
DRM_MODE_CONNECTOR_DPI);
if (IS_ERR(bridge)) {
dev_err(drm->dev,
"Could not connect panel bridge\n");
return PTR_ERR(bridge);
}
}
mcde->dpi_output = true;
mcde->bridge = bridge;
mcde->flow_mode = MCDE_DPI_FORMATTER_FLOW;
}
mode_config = &drm->mode_config;
......@@ -156,13 +184,7 @@ static int mcde_modeset_init(struct drm_device *drm)
return ret;
}
/*
* Attach the DSI bridge
*
* TODO: when adding support for the DPI bridge or several DSI bridges,
* we selectively connect the bridge(s) here instead of this simple
* attachment.
*/
/* Attach the bridge. */
ret = drm_simple_display_pipe_attach_bridge(&mcde->pipe,
mcde->bridge);
if (ret) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment