Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>

+96 -11
+1 -1
drivers/gpu/drm/Makefile
··· 81 81 obj-$(CONFIG_DRM_AST) += ast/ 82 82 obj-$(CONFIG_DRM_ARMADA) += armada/ 83 83 obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/ 84 - obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ 84 + obj-y += rcar-du/ 85 85 obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ 86 86 obj-y += omapdrm/ 87 87 obj-$(CONFIG_DRM_SUN4I) += sun4i/
+1
drivers/gpu/drm/rcar-du/Kconfig
··· 4 4 depends on DRM && OF 5 5 depends on ARM || ARM64 6 6 depends on ARCH_RENESAS || COMPILE_TEST 7 + imply DRM_RCAR_LVDS 7 8 select DRM_KMS_HELPER 8 9 select DRM_KMS_CMA_HELPER 9 10 select DRM_GEM_CMA_HELPER
+67 -10
drivers/gpu/drm/rcar-du/rcar_lvds.c
··· 23 23 #include <drm/drm_panel.h> 24 24 #include <drm/drm_probe_helper.h> 25 25 26 + #include "rcar_lvds.h" 26 27 #include "rcar_lvds_regs.h" 27 28 28 29 struct rcar_lvds; ··· 184 183 185 184 static void rcar_lvds_d3_e3_pll_calc(struct rcar_lvds *lvds, struct clk *clk, 186 185 unsigned long target, struct pll_info *pll, 187 - u32 clksel) 186 + u32 clksel, bool dot_clock_only) 188 187 { 188 + unsigned int div7 = dot_clock_only ? 1 : 7; 189 189 unsigned long output; 190 190 unsigned long fin; 191 191 unsigned int m_min; ··· 220 218 * `------------> | | 221 219 * |/ 222 220 * 223 - * The /7 divider is optional when the LVDS PLL is used to generate a 224 - * dot clock for the DU RGB output, without using the LVDS encoder. We 225 - * don't support this configuration yet. 221 + * The /7 divider is optional, it is enabled when the LVDS PLL is used 222 + * to drive the LVDS encoder, and disabled when used to generate a dot 223 + * clock for the DU RGB output, without using the LVDS encoder. 226 224 * 227 225 * The PLL allowed input frequency range is 12 MHz to 192 MHz. 228 226 */ ··· 282 280 * the PLL, followed by a an optional fixed /7 283 281 * divider. 284 282 */ 285 - fout = fvco / (1 << e) / 7; 283 + fout = fvco / (1 << e) / div7; 286 284 div = DIV_ROUND_CLOSEST(fout, target); 287 285 diff = abs(fout / div - target); 288 286 ··· 303 301 304 302 done: 305 303 output = fin * pll->pll_n / pll->pll_m / (1 << pll->pll_e) 306 - / 7 / pll->div; 304 + / div7 / pll->div; 307 305 error = (long)(output - target) * 10000 / (long)target; 308 306 309 307 dev_dbg(lvds->dev, ··· 313 311 pll->pll_m, pll->pll_n, pll->pll_e, pll->div); 314 312 } 315 313 316 - static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq) 314 + static void __rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, 315 + unsigned int freq, bool dot_clock_only) 317 316 { 318 317 struct pll_info pll = { .diff = (unsigned long)-1 }; 319 318 u32 lvdpllcr; 320 319 321 320 rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[0], freq, &pll, 322 - LVDPLLCR_CKSEL_DU_DOTCLKIN(0)); 321 + LVDPLLCR_CKSEL_DU_DOTCLKIN(0), dot_clock_only); 323 322 rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.dotclkin[1], freq, &pll, 324 - LVDPLLCR_CKSEL_DU_DOTCLKIN(1)); 323 + LVDPLLCR_CKSEL_DU_DOTCLKIN(1), dot_clock_only); 325 324 rcar_lvds_d3_e3_pll_calc(lvds, lvds->clocks.extal, freq, &pll, 326 - LVDPLLCR_CKSEL_EXTAL); 325 + LVDPLLCR_CKSEL_EXTAL, dot_clock_only); 327 326 328 327 lvdpllcr = LVDPLLCR_PLLON | pll.clksel | LVDPLLCR_CLKOUT 329 328 | LVDPLLCR_PLLN(pll.pll_n - 1) | LVDPLLCR_PLLM(pll.pll_m - 1); ··· 332 329 if (pll.pll_e > 0) 333 330 lvdpllcr |= LVDPLLCR_STP_CLKOUTE | LVDPLLCR_OUTCLKSEL 334 331 | LVDPLLCR_PLLE(pll.pll_e - 1); 332 + 333 + if (dot_clock_only) 334 + lvdpllcr |= LVDPLLCR_OCKSEL; 335 335 336 336 rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr); 337 337 ··· 348 342 else 349 343 rcar_lvds_write(lvds, LVDDIV, 0); 350 344 } 345 + 346 + static void rcar_lvds_pll_setup_d3_e3(struct rcar_lvds *lvds, unsigned int freq) 347 + { 348 + __rcar_lvds_pll_setup_d3_e3(lvds, freq, false); 349 + } 350 + 351 + /* ----------------------------------------------------------------------------- 352 + * Clock - D3/E3 only 353 + */ 354 + 355 + int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq) 356 + { 357 + struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge); 358 + int ret; 359 + 360 + if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL))) 361 + return -ENODEV; 362 + 363 + dev_dbg(lvds->dev, "enabling LVDS PLL, freq=%luHz\n", freq); 364 + 365 + WARN_ON(lvds->enabled); 366 + 367 + ret = clk_prepare_enable(lvds->clocks.mod); 368 + if (ret < 0) 369 + return ret; 370 + 371 + __rcar_lvds_pll_setup_d3_e3(lvds, freq, true); 372 + 373 + lvds->enabled = true; 374 + return 0; 375 + } 376 + EXPORT_SYMBOL_GPL(rcar_lvds_clk_enable); 377 + 378 + void rcar_lvds_clk_disable(struct drm_bridge *bridge) 379 + { 380 + struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge); 381 + 382 + if (WARN_ON(!(lvds->info->quirks & RCAR_LVDS_QUIRK_EXT_PLL))) 383 + return; 384 + 385 + dev_dbg(lvds->dev, "disabling LVDS PLL\n"); 386 + 387 + WARN_ON(!lvds->enabled); 388 + 389 + rcar_lvds_write(lvds, LVDPLLCR, 0); 390 + 391 + clk_disable_unprepare(lvds->clocks.mod); 392 + 393 + lvds->enabled = false; 394 + } 395 + EXPORT_SYMBOL_GPL(rcar_lvds_clk_disable); 351 396 352 397 /* ----------------------------------------------------------------------------- 353 398 * Bridge
+27
drivers/gpu/drm/rcar-du/rcar_lvds.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * rcar_lvds.h -- R-Car LVDS Encoder 4 + * 5 + * Copyright (C) 2013-2018 Renesas Electronics Corporation 6 + * 7 + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) 8 + */ 9 + 10 + #ifndef __RCAR_LVDS_H__ 11 + #define __RCAR_LVDS_H__ 12 + 13 + struct drm_bridge; 14 + 15 + #if IS_ENABLED(CONFIG_DRM_RCAR_LVDS) 16 + int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq); 17 + void rcar_lvds_clk_disable(struct drm_bridge *bridge); 18 + #else 19 + static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge, 20 + unsigned long freq) 21 + { 22 + return -ENOSYS; 23 + } 24 + static inline void rcar_lvds_clk_disable(struct drm_bridge *bridge) { } 25 + #endif /* CONFIG_DRM_RCAR_LVDS */ 26 + 27 + #endif /* __RCAR_LVDS_H__ */