Commit 02f2b300 authored by Laurent Pinchart's avatar Laurent Pinchart

drm: rcar-du: lvds: Add API to enable/disable clock output

On the D3 and E3 platforms, the LVDS internal PLL supplies the pixel
clock to the DU. This works automatically for LVDS outputs as the LVDS
encoder is enabled through the bridge API, enabling the internal PLL and
clock output. However, when using the DU DPAD output with the LVDS
outputs turned off, the LVDS PLL needs to be controlled manually. Add an
API to do so, to be called by the DU driver.

The drivers/gpu/drm/rcar-du/ directory has to be treated as obj-y
unconditionally, as the LVDS driver could be built-in while the DU
driver is compiled as a module.
Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
parent 6e1f8557
...@@ -81,7 +81,7 @@ obj-$(CONFIG_DRM_UDL) += udl/ ...@@ -81,7 +81,7 @@ obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/ obj-$(CONFIG_DRM_AST) += ast/
obj-$(CONFIG_DRM_ARMADA) += armada/ obj-$(CONFIG_DRM_ARMADA) += armada/
obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/ obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ obj-y += rcar-du/
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
obj-y += omapdrm/ obj-y += omapdrm/
obj-$(CONFIG_DRM_SUN4I) += sun4i/ obj-$(CONFIG_DRM_SUN4I) += sun4i/
......
...@@ -4,6 +4,7 @@ config DRM_RCAR_DU ...@@ -4,6 +4,7 @@ config DRM_RCAR_DU
depends on DRM && OF depends on DRM && OF
depends on ARM || ARM64 depends on ARM || ARM64
depends on ARCH_RENESAS || COMPILE_TEST depends on ARCH_RENESAS || COMPILE_TEST
imply DRM_RCAR_LVDS
select DRM_KMS_HELPER select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER select DRM_KMS_CMA_HELPER
select DRM_GEM_CMA_HELPER select DRM_GEM_CMA_HELPER
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <drm/drm_panel.h> #include <drm/drm_panel.h>
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
#include "rcar_lvds.h"
#include "rcar_lvds_regs.h" #include "rcar_lvds_regs.h"
struct rcar_lvds; struct rcar_lvds;
...@@ -183,8 +184,9 @@ struct pll_info { ...@@ -183,8 +184,9 @@ struct pll_info {
static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk, static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk,
unsigned long target, struct pll_info *pll, unsigned long target, struct pll_info *pll,
u32 clksel) u32 clksel, bool dot_clock_only)
{ {
unsigned int div7 = dot_clock_only ? 1 : 7;
unsigned long output; unsigned long output;
unsigned long fin; unsigned long fin;
unsigned int m_min; unsigned int m_min;
...@@ -218,9 +220,9 @@ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk, ...@@ -218,9 +220,9 @@ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk,
* `------------> | | * `------------> | |
* |/ * |/
* *
* The /7 divider is optional when the LVDS PLL is used to generate a * The /7 divider is optional, it is enabled when the LVDS PLL is used
* dot clock for the DU RGB output, without using the LVDS encoder. We * to drive the LVDS encoder, and disabled when used to generate a dot
* don't support this configuration yet. * clock for the DU RGB output, without using the LVDS encoder.
* *
* The PLL allowed input frequency range is 12 MHz to 192 MHz. * The PLL allowed input frequency range is 12 MHz to 192 MHz.
*/ */
...@@ -280,7 +282,7 @@ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk, ...@@ -280,7 +282,7 @@ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk,
* the PLL, followed by a an optional fixed /7 * the PLL, followed by a an optional fixed /7
* divider. * divider.
*/ */
fout = fvco / (1 << e) / 7; fout = fvco / (1 << e) / div7;
div = DIV_ROUND_CLOSEST(fout, target); div = DIV_ROUND_CLOSEST(fout, target);
diff = abs(fout / div - target); diff = abs(fout / div - target);
...@@ -301,7 +303,7 @@ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk, ...@@ -301,7 +303,7 @@ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk,
done: done:
output = fin * pll->pll_n / pll->pll_m / (1 << pll->pll_e) output = fin * pll->pll_n / pll->pll_m / (1 << pll->pll_e)
/ 7 / pll->div; / div7 / pll->div;
error = (long)(output - target) * 10000 / (long)target; error = (long)(output - target) * 10000 / (long)target;
dev_dbg(lvds->dev, dev_dbg(lvds->dev,
...@@ -311,17 +313,18 @@ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk, ...@@ -311,17 +313,18 @@ static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk,
pll->pll_m, pll->pll_n, pll->pll_e, pll->div); 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) static void __rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds,
unsigned int freq, bool dot_clock_only)
{ {
struct pll_info pll = { .diff = (unsigned long)-1 }; struct pll_info pll = { .diff = (unsigned long)-1 };
u32 lvdpllcr; u32 lvdpllcr;
rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[0], freq, &pll, rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[0], freq, &pll,
LVDPLLCR_CKSEL_DU_DOTCLKIN(0)); LVDPLLCR_CKSEL_DU_DOTCLKIN(0), dot_clock_only);
rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[1], freq, &pll, rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[1], freq, &pll,
LVDPLLCR_CKSEL_DU_DOTCLKIN(1)); LVDPLLCR_CKSEL_DU_DOTCLKIN(1), dot_clock_only);
rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.extal, freq, &pll, rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.extal, freq, &pll,
LVDPLLCR_CKSEL_EXTAL); LVDPLLCR_CKSEL_EXTAL, dot_clock_only);
lvdpllcr = LVDPLLCR_PLLON | pll.clksel | LVDPLLCR_CLKOUT lvdpllcr = LVDPLLCR_PLLON | pll.clksel | LVDPLLCR_CLKOUT
| LVDPLLCR_PLLN(pll.pll_n - 1) | LVDPLLCR_PLLM(pll.pll_m - 1); | LVDPLLCR_PLLN(pll.pll_n - 1) | LVDPLLCR_PLLM(pll.pll_m - 1);
...@@ -330,6 +333,9 @@ static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq) ...@@ -330,6 +333,9 @@ static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq)
lvdpllcr |= LVDPLLCR_STP_CLKOUTE | LVDPLLCR_OUTCLKSEL lvdpllcr |= LVDPLLCR_STP_CLKOUTE | LVDPLLCR_OUTCLKSEL
| LVDPLLCR_PLLE(pll.pll_e - 1); | LVDPLLCR_PLLE(pll.pll_e - 1);
if (dot_clock_only)
lvdpllcr |= LVDPLLCR_OCKSEL;
rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr); rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
if (pll.div > 1) if (pll.div > 1)
...@@ -343,6 +349,57 @@ static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq) ...@@ -343,6 +349,57 @@ static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq)
rcar_lvds_write(lvds, LVDDIV, 0); rcar_lvds_write(lvds, LVDDIV, 0);
} }
static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq)
{
__rcar_lvds_pll_setup_d3_e3(lvds, freq, false);
}
/* -----------------------------------------------------------------------------
* Clock - D3/E3 only
*/
int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
int ret;
if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)))
return -ENODEV;
dev_dbg(lvds->dev, "enabling LVDS PLL, freq=%luHz\n", freq);
WARN_ON(lvds->enabled);
ret = clk_prepare_enable(lvds->clocks.mod);
if (ret < 0)
return ret;
__rcar_lvds_pll_setup_d3_e3(lvds, freq, true);
lvds->enabled = true;
return 0;
}
EXPORT_SYMBOL_GPL(rcar_lvds_clk_enable);
void rcar_lvds_clk_disable(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL)))
return;
dev_dbg(lvds->dev, "disabling LVDS PLL\n");
WARN_ON(!lvds->enabled);
rcar_lvds_write(lvds, LVDPLLCR, 0);
clk_disable_unprepare(lvds->clocks.mod);
lvds->enabled = false;
}
EXPORT_SYMBOL_GPL(rcar_lvds_clk_disable);
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Bridge * Bridge
*/ */
......
/* SPDX-License-Identifier: GPL-2.0 */
/*
* rcar_lvds.h -- R-Car LVDS Encoder
*
* Copyright (C) 2013-2018 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __RCAR_LVDS_H__
#define __RCAR_LVDS_H__
struct drm_bridge;
#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq);
void rcar_lvds_clk_disable(struct drm_bridge *bridge);
#else
static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge,
unsigned long freq)
{
return -ENOSYS;
}
static inline void rcar_lvds_clk_disable(struct drm_bridge *bridge) { }
#endif /* CONFIG_DRM_RCAR_LVDS */
#endif /* __RCAR_LVDS_H__ */
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