drm: rcar-du: lvds: D3/E3 support
The LVDS encoders in the D3 and E3 SoCs differ significantly from those in the other R-Car Gen3 family members: - The LVDS PLL architecture is more complex and requires computing PLL parameters manually. - The PLL uses external clocks as inputs, which need to be retrieved from DT. - In addition to the different PLL setup, the startup sequence has changed *again* (seems someone had trouble making his/her mind). Supporting all this requires DT bindings extensions for external clocks, brand new PLL setup code, and a few quirks to handle the differences in the startup sequence. The implementation doesn't support all hardware features yet, namely - Using the LV[01] clocks generated by the CPG as PLL input. - Providing the LVDS PLL clock to the DU for use with the RGB output. Those features can be added later when the need will arise. Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> Tested-by: Jacopo Mondi <jacopo+renesas@jmondi.org> Reviewed-by: Ulrich Hecht <uli+renesas@fpond.eu> Reviewed-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
This commit is contained in:
parent
399d9f2f19
commit
c25c013611
@ -24,6 +24,8 @@
|
|||||||
|
|
||||||
#include "rcar_lvds_regs.h"
|
#include "rcar_lvds_regs.h"
|
||||||
|
|
||||||
|
struct rcar_lvds;
|
||||||
|
|
||||||
/* Keep in sync with the LVDCR0.LVMD hardware register values. */
|
/* Keep in sync with the LVDCR0.LVMD hardware register values. */
|
||||||
enum rcar_lvds_mode {
|
enum rcar_lvds_mode {
|
||||||
RCAR_LVDS_MODE_JEIDA = 0,
|
RCAR_LVDS_MODE_JEIDA = 0,
|
||||||
@ -31,14 +33,16 @@ enum rcar_lvds_mode {
|
|||||||
RCAR_LVDS_MODE_VESA = 4,
|
RCAR_LVDS_MODE_VESA = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define RCAR_LVDS_QUIRK_LANES (1 << 0) /* LVDS lanes 1 and 3 inverted */
|
#define RCAR_LVDS_QUIRK_LANES BIT(0) /* LVDS lanes 1 and 3 inverted */
|
||||||
#define RCAR_LVDS_QUIRK_GEN2_PLLCR (1 << 1) /* LVDPLLCR has gen2 layout */
|
#define RCAR_LVDS_QUIRK_GEN3_LVEN BIT(1) /* LVEN bit needs to be set on R8A77970/R8A7799x */
|
||||||
#define RCAR_LVDS_QUIRK_GEN3_LVEN (1 << 2) /* LVEN bit needs to be set */
|
#define RCAR_LVDS_QUIRK_PWD BIT(2) /* PWD bit available (all of Gen3 but E3) */
|
||||||
/* on R8A77970/R8A7799x */
|
#define RCAR_LVDS_QUIRK_EXT_PLL BIT(3) /* Has extended PLL */
|
||||||
|
#define RCAR_LVDS_QUIRK_DUAL_LINK BIT(4) /* Supports dual-link operation */
|
||||||
|
|
||||||
struct rcar_lvds_device_info {
|
struct rcar_lvds_device_info {
|
||||||
unsigned int gen;
|
unsigned int gen;
|
||||||
unsigned int quirks;
|
unsigned int quirks;
|
||||||
|
void (*pll_setup)(struct rcar_lvds *lvds, unsigned int freq);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct rcar_lvds {
|
struct rcar_lvds {
|
||||||
@ -52,7 +56,11 @@ struct rcar_lvds {
|
|||||||
struct drm_panel *panel;
|
struct drm_panel *panel;
|
||||||
|
|
||||||
void __iomem *mmio;
|
void __iomem *mmio;
|
||||||
struct clk *clock;
|
struct {
|
||||||
|
struct clk *mod; /* CPG module clock */
|
||||||
|
struct clk *extal; /* External clock */
|
||||||
|
struct clk *dotclkin[2]; /* External DU clocks */
|
||||||
|
} clocks;
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
|
||||||
struct drm_display_mode display_mode;
|
struct drm_display_mode display_mode;
|
||||||
@ -128,33 +136,216 @@ static const struct drm_connector_funcs rcar_lvds_conn_funcs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------------
|
/* -----------------------------------------------------------------------------
|
||||||
* Bridge
|
* PLL Setup
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
|
static void rcar_lvds_pll_setup_gen2(struct rcar_lvds *lvds, unsigned int freq)
|
||||||
{
|
{
|
||||||
if (freq < 39000)
|
u32 val;
|
||||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
|
|
||||||
else if (freq < 61000)
|
if (freq < 39000000)
|
||||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
|
val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
|
||||||
else if (freq < 121000)
|
else if (freq < 61000000)
|
||||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
|
val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
|
||||||
|
else if (freq < 121000000)
|
||||||
|
val = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
|
||||||
else
|
else
|
||||||
return LVDPLLCR_PLLDLYCNT_150M;
|
val = LVDPLLCR_PLLDLYCNT_150M;
|
||||||
|
|
||||||
|
rcar_lvds_write(lvds, LVDPLLCR, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
|
static void rcar_lvds_pll_setup_gen3(struct rcar_lvds *lvds, unsigned int freq)
|
||||||
{
|
{
|
||||||
if (freq < 42000)
|
u32 val;
|
||||||
return LVDPLLCR_PLLDIVCNT_42M;
|
|
||||||
else if (freq < 85000)
|
if (freq < 42000000)
|
||||||
return LVDPLLCR_PLLDIVCNT_85M;
|
val = LVDPLLCR_PLLDIVCNT_42M;
|
||||||
else if (freq < 128000)
|
else if (freq < 85000000)
|
||||||
return LVDPLLCR_PLLDIVCNT_128M;
|
val = LVDPLLCR_PLLDIVCNT_85M;
|
||||||
|
else if (freq < 128000000)
|
||||||
|
val = LVDPLLCR_PLLDIVCNT_128M;
|
||||||
else
|
else
|
||||||
return LVDPLLCR_PLLDIVCNT_148M;
|
val = LVDPLLCR_PLLDIVCNT_148M;
|
||||||
|
|
||||||
|
rcar_lvds_write(lvds, LVDPLLCR, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct pll_info {
|
||||||
|
unsigned long diff;
|
||||||
|
unsigned int pll_m;
|
||||||
|
unsigned int pll_n;
|
||||||
|
unsigned int pll_e;
|
||||||
|
unsigned int div;
|
||||||
|
u32 clksel;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk,
|
||||||
|
unsigned long target, struct pll_info *pll,
|
||||||
|
u32 clksel)
|
||||||
|
{
|
||||||
|
unsigned long output;
|
||||||
|
unsigned long fin;
|
||||||
|
unsigned int m_min;
|
||||||
|
unsigned int m_max;
|
||||||
|
unsigned int m;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (!clk)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The LVDS PLL is made of a pre-divider and a multiplier (strangely
|
||||||
|
* enough called M and N respectively), followed by a post-divider E.
|
||||||
|
*
|
||||||
|
* ,-----. ,-----. ,-----. ,-----.
|
||||||
|
* Fin --> | 1/M | -Fpdf-> | PFD | --> | VCO | -Fvco-> | 1/E | --> Fout
|
||||||
|
* `-----' ,-> | | `-----' | `-----'
|
||||||
|
* | `-----' |
|
||||||
|
* | ,-----. |
|
||||||
|
* `-------- | 1/N | <-------'
|
||||||
|
* `-----'
|
||||||
|
*
|
||||||
|
* The clock output by the PLL is then further divided by a programmable
|
||||||
|
* divider DIV to achieve the desired target frequency. Finally, an
|
||||||
|
* optional fixed /7 divider is used to convert the bit clock to a pixel
|
||||||
|
* clock (as LVDS transmits 7 bits per lane per clock sample).
|
||||||
|
*
|
||||||
|
* ,-------. ,-----. |\
|
||||||
|
* Fout --> | 1/DIV | --> | 1/7 | --> | |
|
||||||
|
* `-------' | `-----' | | --> dot clock
|
||||||
|
* `------------> | |
|
||||||
|
* |/
|
||||||
|
*
|
||||||
|
* The /7 divider is optional when the LVDS PLL is used to generate a
|
||||||
|
* dot clock for the DU RGB output, without using the LVDS encoder. We
|
||||||
|
* don't support this configuration yet.
|
||||||
|
*
|
||||||
|
* The PLL allowed input frequency range is 12 MHz to 192 MHz.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fin = clk_get_rate(clk);
|
||||||
|
if (fin < 12000000 || fin > 192000000)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The comparison frequency range is 12 MHz to 24 MHz, which limits the
|
||||||
|
* allowed values for the pre-divider M (normal range 1-8).
|
||||||
|
*
|
||||||
|
* Fpfd = Fin / M
|
||||||
|
*/
|
||||||
|
m_min = max_t(unsigned int, 1, DIV_ROUND_UP(fin, 24000000));
|
||||||
|
m_max = min_t(unsigned int, 8, fin / 12000000);
|
||||||
|
|
||||||
|
for (m = m_min; m <= m_max; ++m) {
|
||||||
|
unsigned long fpfd;
|
||||||
|
unsigned int n_min;
|
||||||
|
unsigned int n_max;
|
||||||
|
unsigned int n;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The VCO operating range is 900 Mhz to 1800 MHz, which limits
|
||||||
|
* the allowed values for the multiplier N (normal range
|
||||||
|
* 60-120).
|
||||||
|
*
|
||||||
|
* Fvco = Fin * N / M
|
||||||
|
*/
|
||||||
|
fpfd = fin / m;
|
||||||
|
n_min = max_t(unsigned int, 60, DIV_ROUND_UP(900000000, fpfd));
|
||||||
|
n_max = min_t(unsigned int, 120, 1800000000 / fpfd);
|
||||||
|
|
||||||
|
for (n = n_min; n < n_max; ++n) {
|
||||||
|
unsigned long fvco;
|
||||||
|
unsigned int e_min;
|
||||||
|
unsigned int e;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The output frequency is limited to 1039.5 MHz,
|
||||||
|
* limiting again the allowed values for the
|
||||||
|
* post-divider E (normal value 1, 2 or 4).
|
||||||
|
*
|
||||||
|
* Fout = Fvco / E
|
||||||
|
*/
|
||||||
|
fvco = fpfd * n;
|
||||||
|
e_min = fvco > 1039500000 ? 1 : 0;
|
||||||
|
|
||||||
|
for (e = e_min; e < 3; ++e) {
|
||||||
|
unsigned long fout;
|
||||||
|
unsigned long diff;
|
||||||
|
unsigned int div;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Finally we have a programable divider after
|
||||||
|
* the PLL, followed by a an optional fixed /7
|
||||||
|
* divider.
|
||||||
|
*/
|
||||||
|
fout = fvco / (1 << e) / 7;
|
||||||
|
div = DIV_ROUND_CLOSEST(fout, target);
|
||||||
|
diff = abs(fout / div - target);
|
||||||
|
|
||||||
|
if (diff < pll->diff) {
|
||||||
|
pll->diff = diff;
|
||||||
|
pll->pll_m = m;
|
||||||
|
pll->pll_n = n;
|
||||||
|
pll->pll_e = e;
|
||||||
|
pll->div = div;
|
||||||
|
pll->clksel = clksel;
|
||||||
|
|
||||||
|
if (diff == 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
output = fin * pll->pll_n / pll->pll_m / (1 << pll->pll_e)
|
||||||
|
/ 7 / pll->div;
|
||||||
|
error = (long)(output - target) * 10000 / (long)target;
|
||||||
|
|
||||||
|
dev_dbg(lvds->dev,
|
||||||
|
"%pC %lu Hz -> Fout %lu Hz (target %lu Hz, error %d.%02u%%), PLL M/N/E/DIV %u/%u/%u/%u\n",
|
||||||
|
clk, fin, output, target, error / 100,
|
||||||
|
error < 0 ? -error % 100 : error % 100,
|
||||||
|
pll->pll_m, pll->pll_n, pll->pll_e, pll->div);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq)
|
||||||
|
{
|
||||||
|
struct pll_info pll = { .diff = (unsigned long)-1 };
|
||||||
|
u32 lvdpllcr;
|
||||||
|
|
||||||
|
rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[0], freq, &pll,
|
||||||
|
LVDPLLCR_CKSEL_DU_DOTCLKIN(0));
|
||||||
|
rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[1], freq, &pll,
|
||||||
|
LVDPLLCR_CKSEL_DU_DOTCLKIN(1));
|
||||||
|
rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.extal, freq, &pll,
|
||||||
|
LVDPLLCR_CKSEL_EXTAL);
|
||||||
|
|
||||||
|
lvdpllcr = LVDPLLCR_PLLON | pll.clksel | LVDPLLCR_CLKOUT
|
||||||
|
| LVDPLLCR_PLLN(pll.pll_n - 1) | LVDPLLCR_PLLM(pll.pll_m - 1);
|
||||||
|
|
||||||
|
if (pll.pll_e > 0)
|
||||||
|
lvdpllcr |= LVDPLLCR_STP_CLKOUTE | LVDPLLCR_OUTCLKSEL
|
||||||
|
| LVDPLLCR_PLLE(pll.pll_e - 1);
|
||||||
|
|
||||||
|
rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
|
||||||
|
|
||||||
|
if (pll.div > 1)
|
||||||
|
/*
|
||||||
|
* The DIVRESET bit is a misnomer, setting it to 1 deasserts the
|
||||||
|
* divisor reset.
|
||||||
|
*/
|
||||||
|
rcar_lvds_write(lvds, LVDDIV, LVDDIV_DIVSEL |
|
||||||
|
LVDDIV_DIVRESET | LVDDIV_DIV(pll.div - 1));
|
||||||
|
else
|
||||||
|
rcar_lvds_write(lvds, LVDDIV, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------------------------------------------------
|
||||||
|
* Bridge
|
||||||
|
*/
|
||||||
|
|
||||||
static void rcar_lvds_enable(struct drm_bridge *bridge)
|
static void rcar_lvds_enable(struct drm_bridge *bridge)
|
||||||
{
|
{
|
||||||
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
|
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
|
||||||
@ -164,14 +355,13 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
|
|||||||
* do we get a state pointer?
|
* do we get a state pointer?
|
||||||
*/
|
*/
|
||||||
struct drm_crtc *crtc = lvds->bridge.encoder->crtc;
|
struct drm_crtc *crtc = lvds->bridge.encoder->crtc;
|
||||||
u32 lvdpllcr;
|
|
||||||
u32 lvdhcr;
|
u32 lvdhcr;
|
||||||
u32 lvdcr0;
|
u32 lvdcr0;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
WARN_ON(lvds->enabled);
|
WARN_ON(lvds->enabled);
|
||||||
|
|
||||||
ret = clk_prepare_enable(lvds->clock);
|
ret = clk_prepare_enable(lvds->clocks.mod);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -196,12 +386,13 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
|
|||||||
|
|
||||||
rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
|
rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
|
||||||
|
|
||||||
|
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
|
||||||
|
/* Disable dual-link mode. */
|
||||||
|
rcar_lvds_write(lvds, LVDSTRIPE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/* PLL clock configuration. */
|
/* PLL clock configuration. */
|
||||||
if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN2_PLLCR)
|
lvds->info->pll_setup(lvds, mode->clock * 1000);
|
||||||
lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
|
|
||||||
else
|
|
||||||
lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
|
|
||||||
rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
|
|
||||||
|
|
||||||
/* Set the LVDS mode and select the input. */
|
/* Set the LVDS mode and select the input. */
|
||||||
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
|
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
|
||||||
@ -220,11 +411,16 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
|
|||||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Turn the PLL on. */
|
if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
|
||||||
|
/*
|
||||||
|
* Turn the PLL on (simple PLL only, extended PLL is fully
|
||||||
|
* controlled through LVDPLLCR).
|
||||||
|
*/
|
||||||
lvdcr0 |= LVDCR0_PLLON;
|
lvdcr0 |= LVDCR0_PLLON;
|
||||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||||
|
}
|
||||||
|
|
||||||
if (lvds->info->gen > 2) {
|
if (lvds->info->quirks & RCAR_LVDS_QUIRK_PWD) {
|
||||||
/* Set LVDS normal mode. */
|
/* Set LVDS normal mode. */
|
||||||
lvdcr0 |= LVDCR0_PWD;
|
lvdcr0 |= LVDCR0_PWD;
|
||||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||||
@ -236,8 +432,10 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
|
|||||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wait for the startup delay. */
|
if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)) {
|
||||||
|
/* Wait for the PLL startup delay (simple PLL only). */
|
||||||
usleep_range(100, 150);
|
usleep_range(100, 150);
|
||||||
|
}
|
||||||
|
|
||||||
/* Turn the output on. */
|
/* Turn the output on. */
|
||||||
lvdcr0 |= LVDCR0_LVRES;
|
lvdcr0 |= LVDCR0_LVRES;
|
||||||
@ -264,8 +462,9 @@ static void rcar_lvds_disable(struct drm_bridge *bridge)
|
|||||||
|
|
||||||
rcar_lvds_write(lvds, LVDCR0, 0);
|
rcar_lvds_write(lvds, LVDCR0, 0);
|
||||||
rcar_lvds_write(lvds, LVDCR1, 0);
|
rcar_lvds_write(lvds, LVDCR1, 0);
|
||||||
|
rcar_lvds_write(lvds, LVDPLLCR, 0);
|
||||||
|
|
||||||
clk_disable_unprepare(lvds->clock);
|
clk_disable_unprepare(lvds->clocks.mod);
|
||||||
|
|
||||||
lvds->enabled = false;
|
lvds->enabled = false;
|
||||||
}
|
}
|
||||||
@ -446,6 +645,60 @@ done:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct clk *rcar_lvds_get_clock(struct rcar_lvds *lvds, const char *name,
|
||||||
|
bool optional)
|
||||||
|
{
|
||||||
|
struct clk *clk;
|
||||||
|
|
||||||
|
clk = devm_clk_get(lvds->dev, name);
|
||||||
|
if (!IS_ERR(clk))
|
||||||
|
return clk;
|
||||||
|
|
||||||
|
if (PTR_ERR(clk) == -ENOENT && optional)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (PTR_ERR(clk) != -EPROBE_DEFER)
|
||||||
|
dev_err(lvds->dev, "failed to get %s clock\n",
|
||||||
|
name ? name : "module");
|
||||||
|
|
||||||
|
return clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rcar_lvds_get_clocks(struct rcar_lvds *lvds)
|
||||||
|
{
|
||||||
|
lvds->clocks.mod = rcar_lvds_get_clock(lvds, NULL, false);
|
||||||
|
if (IS_ERR(lvds->clocks.mod))
|
||||||
|
return PTR_ERR(lvds->clocks.mod);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LVDS encoders without an extended PLL have no external clock inputs.
|
||||||
|
*/
|
||||||
|
if (!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
lvds->clocks.extal = rcar_lvds_get_clock(lvds, "extal", true);
|
||||||
|
if (IS_ERR(lvds->clocks.extal))
|
||||||
|
return PTR_ERR(lvds->clocks.extal);
|
||||||
|
|
||||||
|
lvds->clocks.dotclkin[0] = rcar_lvds_get_clock(lvds, "dclkin.0", true);
|
||||||
|
if (IS_ERR(lvds->clocks.dotclkin[0]))
|
||||||
|
return PTR_ERR(lvds->clocks.dotclkin[0]);
|
||||||
|
|
||||||
|
lvds->clocks.dotclkin[1] = rcar_lvds_get_clock(lvds, "dclkin.1", true);
|
||||||
|
if (IS_ERR(lvds->clocks.dotclkin[1]))
|
||||||
|
return PTR_ERR(lvds->clocks.dotclkin[1]);
|
||||||
|
|
||||||
|
/* At least one input to the PLL must be available. */
|
||||||
|
if (!lvds->clocks.extal && !lvds->clocks.dotclkin[0] &&
|
||||||
|
!lvds->clocks.dotclkin[1]) {
|
||||||
|
dev_err(lvds->dev,
|
||||||
|
"no input clock (extal, dclkin.0 or dclkin.1)\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int rcar_lvds_probe(struct platform_device *pdev)
|
static int rcar_lvds_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct rcar_lvds *lvds;
|
struct rcar_lvds *lvds;
|
||||||
@ -475,11 +728,9 @@ static int rcar_lvds_probe(struct platform_device *pdev)
|
|||||||
if (IS_ERR(lvds->mmio))
|
if (IS_ERR(lvds->mmio))
|
||||||
return PTR_ERR(lvds->mmio);
|
return PTR_ERR(lvds->mmio);
|
||||||
|
|
||||||
lvds->clock = devm_clk_get(&pdev->dev, NULL);
|
ret = rcar_lvds_get_clocks(lvds);
|
||||||
if (IS_ERR(lvds->clock)) {
|
if (ret < 0)
|
||||||
dev_err(&pdev->dev, "failed to get clock\n");
|
return ret;
|
||||||
return PTR_ERR(lvds->clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
drm_bridge_add(&lvds->bridge);
|
drm_bridge_add(&lvds->bridge);
|
||||||
|
|
||||||
@ -497,21 +748,39 @@ static int rcar_lvds_remove(struct platform_device *pdev)
|
|||||||
|
|
||||||
static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
|
static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
|
||||||
.gen = 2,
|
.gen = 2,
|
||||||
.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR,
|
.pll_setup = rcar_lvds_pll_setup_gen2,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = {
|
static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = {
|
||||||
.gen = 2,
|
.gen = 2,
|
||||||
.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR | RCAR_LVDS_QUIRK_LANES,
|
.quirks = RCAR_LVDS_QUIRK_LANES,
|
||||||
|
.pll_setup = rcar_lvds_pll_setup_gen2,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
|
static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
|
||||||
.gen = 3,
|
.gen = 3,
|
||||||
|
.quirks = RCAR_LVDS_QUIRK_PWD,
|
||||||
|
.pll_setup = rcar_lvds_pll_setup_gen3,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct rcar_lvds_device_info rcar_lvds_r8a77970_info = {
|
static const struct rcar_lvds_device_info rcar_lvds_r8a77970_info = {
|
||||||
.gen = 3,
|
.gen = 3,
|
||||||
.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR | RCAR_LVDS_QUIRK_GEN3_LVEN,
|
.quirks = RCAR_LVDS_QUIRK_PWD | RCAR_LVDS_QUIRK_GEN3_LVEN,
|
||||||
|
.pll_setup = rcar_lvds_pll_setup_gen2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct rcar_lvds_device_info rcar_lvds_r8a77990_info = {
|
||||||
|
.gen = 3,
|
||||||
|
.quirks = RCAR_LVDS_QUIRK_GEN3_LVEN | RCAR_LVDS_QUIRK_EXT_PLL
|
||||||
|
| RCAR_LVDS_QUIRK_DUAL_LINK,
|
||||||
|
.pll_setup = rcar_lvds_pll_setup_d3_e3,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct rcar_lvds_device_info rcar_lvds_r8a77995_info = {
|
||||||
|
.gen = 3,
|
||||||
|
.quirks = RCAR_LVDS_QUIRK_GEN3_LVEN | RCAR_LVDS_QUIRK_PWD
|
||||||
|
| RCAR_LVDS_QUIRK_EXT_PLL | RCAR_LVDS_QUIRK_DUAL_LINK,
|
||||||
|
.pll_setup = rcar_lvds_pll_setup_d3_e3,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct of_device_id rcar_lvds_of_table[] = {
|
static const struct of_device_id rcar_lvds_of_table[] = {
|
||||||
@ -523,6 +792,8 @@ static const struct of_device_id rcar_lvds_of_table[] = {
|
|||||||
{ .compatible = "renesas,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
|
{ .compatible = "renesas,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
|
||||||
{ .compatible = "renesas,r8a77970-lvds", .data = &rcar_lvds_r8a77970_info },
|
{ .compatible = "renesas,r8a77970-lvds", .data = &rcar_lvds_r8a77970_info },
|
||||||
{ .compatible = "renesas,r8a77980-lvds", .data = &rcar_lvds_gen3_info },
|
{ .compatible = "renesas,r8a77980-lvds", .data = &rcar_lvds_gen3_info },
|
||||||
|
{ .compatible = "renesas,r8a77990-lvds", .data = &rcar_lvds_r8a77990_info },
|
||||||
|
{ .compatible = "renesas,r8a77995-lvds", .data = &rcar_lvds_r8a77995_info },
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
#define LVDCR0_PLLON (1 << 4)
|
#define LVDCR0_PLLON (1 << 4)
|
||||||
#define LVDCR0_PWD (1 << 2) /* Gen3 only */
|
#define LVDCR0_PWD (1 << 2) /* Gen3 only */
|
||||||
#define LVDCR0_BEN (1 << 2) /* Gen2 only */
|
#define LVDCR0_BEN (1 << 2) /* Gen2 only */
|
||||||
#define LVDCR0_LVEN (1 << 1) /* Gen2 only */
|
#define LVDCR0_LVEN (1 << 1)
|
||||||
#define LVDCR0_LVRES (1 << 0)
|
#define LVDCR0_LVRES (1 << 0)
|
||||||
|
|
||||||
#define LVDCR1 0x0004
|
#define LVDCR1 0x0004
|
||||||
@ -27,21 +27,36 @@
|
|||||||
#define LVDCR1_CLKSTBY (3 << 0)
|
#define LVDCR1_CLKSTBY (3 << 0)
|
||||||
|
|
||||||
#define LVDPLLCR 0x0008
|
#define LVDPLLCR 0x0008
|
||||||
|
/* Gen2 & V3M */
|
||||||
#define LVDPLLCR_CEEN (1 << 14)
|
#define LVDPLLCR_CEEN (1 << 14)
|
||||||
#define LVDPLLCR_FBEN (1 << 13)
|
#define LVDPLLCR_FBEN (1 << 13)
|
||||||
#define LVDPLLCR_COSEL (1 << 12)
|
#define LVDPLLCR_COSEL (1 << 12)
|
||||||
/* Gen2 */
|
|
||||||
#define LVDPLLCR_PLLDLYCNT_150M (0x1bf << 0)
|
#define LVDPLLCR_PLLDLYCNT_150M (0x1bf << 0)
|
||||||
#define LVDPLLCR_PLLDLYCNT_121M (0x22c << 0)
|
#define LVDPLLCR_PLLDLYCNT_121M (0x22c << 0)
|
||||||
#define LVDPLLCR_PLLDLYCNT_60M (0x77b << 0)
|
#define LVDPLLCR_PLLDLYCNT_60M (0x77b << 0)
|
||||||
#define LVDPLLCR_PLLDLYCNT_38M (0x69a << 0)
|
#define LVDPLLCR_PLLDLYCNT_38M (0x69a << 0)
|
||||||
#define LVDPLLCR_PLLDLYCNT_MASK (0x7ff << 0)
|
#define LVDPLLCR_PLLDLYCNT_MASK (0x7ff << 0)
|
||||||
/* Gen3 */
|
/* Gen3 but V3M,D3 and E3 */
|
||||||
#define LVDPLLCR_PLLDIVCNT_42M (0x014cb << 0)
|
#define LVDPLLCR_PLLDIVCNT_42M (0x014cb << 0)
|
||||||
#define LVDPLLCR_PLLDIVCNT_85M (0x00a45 << 0)
|
#define LVDPLLCR_PLLDIVCNT_85M (0x00a45 << 0)
|
||||||
#define LVDPLLCR_PLLDIVCNT_128M (0x006c3 << 0)
|
#define LVDPLLCR_PLLDIVCNT_128M (0x006c3 << 0)
|
||||||
#define LVDPLLCR_PLLDIVCNT_148M (0x046c1 << 0)
|
#define LVDPLLCR_PLLDIVCNT_148M (0x046c1 << 0)
|
||||||
#define LVDPLLCR_PLLDIVCNT_MASK (0x7ffff << 0)
|
#define LVDPLLCR_PLLDIVCNT_MASK (0x7ffff << 0)
|
||||||
|
/* D3 and E3 */
|
||||||
|
#define LVDPLLCR_PLLON (1 << 22)
|
||||||
|
#define LVDPLLCR_PLLSEL_PLL0 (0 << 20)
|
||||||
|
#define LVDPLLCR_PLLSEL_LVX (1 << 20)
|
||||||
|
#define LVDPLLCR_PLLSEL_PLL1 (2 << 20)
|
||||||
|
#define LVDPLLCR_CKSEL_LVX (1 << 17)
|
||||||
|
#define LVDPLLCR_CKSEL_EXTAL (3 << 17)
|
||||||
|
#define LVDPLLCR_CKSEL_DU_DOTCLKIN(n) ((5 + (n) * 2) << 17)
|
||||||
|
#define LVDPLLCR_OCKSEL (1 << 16)
|
||||||
|
#define LVDPLLCR_STP_CLKOUTE (1 << 14)
|
||||||
|
#define LVDPLLCR_OUTCLKSEL (1 << 12)
|
||||||
|
#define LVDPLLCR_CLKOUT (1 << 11)
|
||||||
|
#define LVDPLLCR_PLLE(n) ((n) << 10)
|
||||||
|
#define LVDPLLCR_PLLN(n) ((n) << 3)
|
||||||
|
#define LVDPLLCR_PLLM(n) ((n) << 0)
|
||||||
|
|
||||||
#define LVDCTRCR 0x000c
|
#define LVDCTRCR 0x000c
|
||||||
#define LVDCTRCR_CTR3SEL_ZERO (0 << 12)
|
#define LVDCTRCR_CTR3SEL_ZERO (0 << 12)
|
||||||
@ -71,4 +86,26 @@
|
|||||||
#define LVDCHCR_CHSEL_CH(n, c) ((((c) - (n)) & 3) << ((n) * 4))
|
#define LVDCHCR_CHSEL_CH(n, c) ((((c) - (n)) & 3) << ((n) * 4))
|
||||||
#define LVDCHCR_CHSEL_MASK(n) (3 << ((n) * 4))
|
#define LVDCHCR_CHSEL_MASK(n) (3 << ((n) * 4))
|
||||||
|
|
||||||
|
/* All registers below are specific to D3 and E3 */
|
||||||
|
#define LVDSTRIPE 0x0014
|
||||||
|
#define LVDSTRIPE_ST_TRGSEL_DISP (0 << 2)
|
||||||
|
#define LVDSTRIPE_ST_TRGSEL_HSYNC_R (1 << 2)
|
||||||
|
#define LVDSTRIPE_ST_TRGSEL_HSYNC_F (2 << 2)
|
||||||
|
#define LVDSTRIPE_ST_SWAP (1 << 1)
|
||||||
|
#define LVDSTRIPE_ST_ON (1 << 0)
|
||||||
|
|
||||||
|
#define LVDSCR 0x0018
|
||||||
|
#define LVDSCR_DEPTH(n) (((n) - 1) << 29)
|
||||||
|
#define LVDSCR_BANDSET (1 << 28)
|
||||||
|
#define LVDSCR_TWGCNT(n) ((((n) - 256) / 16) << 24)
|
||||||
|
#define LVDSCR_SDIV(n) ((n) << 22)
|
||||||
|
#define LVDSCR_MODE (1 << 21)
|
||||||
|
#define LVDSCR_RSTN (1 << 20)
|
||||||
|
|
||||||
|
#define LVDDIV 0x001c
|
||||||
|
#define LVDDIV_DIVSEL (1 << 8)
|
||||||
|
#define LVDDIV_DIVRESET (1 << 7)
|
||||||
|
#define LVDDIV_DIVSTP (1 << 6)
|
||||||
|
#define LVDDIV_DIV(n) ((n) << 0)
|
||||||
|
|
||||||
#endif /* __RCAR_LVDS_REGS_H__ */
|
#endif /* __RCAR_LVDS_REGS_H__ */
|
||||||
|
Loading…
Reference in New Issue
Block a user