Linux Audio

Check our new training course

Embedded Linux training

Mar 31-Apr 8, 2025
Register
Loading...
Note: File does not exist in v6.2.
  1/*
  2 * rcar_du_lvdsenc.c  --  R-Car Display Unit LVDS Encoder
  3 *
  4 * Copyright (C) 2013-2014 Renesas Electronics Corporation
  5 *
  6 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
  7 *
  8 * This program is free software; you can redistribute it and/or modify
  9 * it under the terms of the GNU General Public License as published by
 10 * the Free Software Foundation; either version 2 of the License, or
 11 * (at your option) any later version.
 12 */
 13
 14#include <linux/clk.h>
 15#include <linux/delay.h>
 16#include <linux/io.h>
 17#include <linux/platform_device.h>
 18#include <linux/slab.h>
 19
 20#include "rcar_du_drv.h"
 21#include "rcar_du_encoder.h"
 22#include "rcar_du_lvdsenc.h"
 23#include "rcar_lvds_regs.h"
 24
 25struct rcar_du_lvdsenc {
 26	struct rcar_du_device *dev;
 27
 28	unsigned int index;
 29	void __iomem *mmio;
 30	struct clk *clock;
 31	bool enabled;
 32
 33	enum rcar_lvds_input input;
 34};
 35
 36static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
 37{
 38	iowrite32(data, lvds->mmio + reg);
 39}
 40
 41static void rcar_du_lvdsenc_start_gen2(struct rcar_du_lvdsenc *lvds,
 42				       struct rcar_du_crtc *rcrtc)
 43{
 44	const struct drm_display_mode *mode = &rcrtc->crtc.mode;
 45	unsigned int freq = mode->clock;
 46	u32 lvdcr0;
 47	u32 pllcr;
 48
 49	/* PLL clock configuration */
 50	if (freq < 39000)
 51		pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
 52	else if (freq < 61000)
 53		pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
 54	else if (freq < 121000)
 55		pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
 56	else
 57		pllcr = LVDPLLCR_PLLDLYCNT_150M;
 58
 59	rcar_lvds_write(lvds, LVDPLLCR, pllcr);
 60
 61	/* Select the input, hardcode mode 0, enable LVDS operation and turn
 62	 * bias circuitry on.
 63	 */
 64	lvdcr0 = LVDCR0_BEN | LVDCR0_LVEN;
 65	if (rcrtc->index == 2)
 66		lvdcr0 |= LVDCR0_DUSEL;
 67	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
 68
 69	/* Turn all the channels on. */
 70	rcar_lvds_write(lvds, LVDCR1,
 71			LVDCR1_CHSTBY_GEN2(3) | LVDCR1_CHSTBY_GEN2(2) |
 72			LVDCR1_CHSTBY_GEN2(1) | LVDCR1_CHSTBY_GEN2(0) |
 73			LVDCR1_CLKSTBY_GEN2);
 74
 75	/* Turn the PLL on, wait for the startup delay, and turn the output
 76	 * on.
 77	 */
 78	lvdcr0 |= LVDCR0_PLLON;
 79	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
 80
 81	usleep_range(100, 150);
 82
 83	lvdcr0 |= LVDCR0_LVRES;
 84	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
 85}
 86
 87static void rcar_du_lvdsenc_start_gen3(struct rcar_du_lvdsenc *lvds,
 88				       struct rcar_du_crtc *rcrtc)
 89{
 90	const struct drm_display_mode *mode = &rcrtc->crtc.mode;
 91	unsigned int freq = mode->clock;
 92	u32 lvdcr0;
 93	u32 pllcr;
 94
 95	/* PLL clock configuration */
 96	if (freq < 42000)
 97		pllcr = LVDPLLCR_PLLDIVCNT_42M;
 98	else if (freq < 85000)
 99		pllcr = LVDPLLCR_PLLDIVCNT_85M;
100	else if (freq < 128000)
101		pllcr = LVDPLLCR_PLLDIVCNT_128M;
102	else
103		pllcr = LVDPLLCR_PLLDIVCNT_148M;
104
105	rcar_lvds_write(lvds, LVDPLLCR, pllcr);
106
107	/* Turn the PLL on, set it to LVDS normal mode, wait for the startup
108	 * delay and turn the output on.
109	 */
110	lvdcr0 = LVDCR0_PLLON;
111	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
112
113	lvdcr0 |= LVDCR0_PWD;
114	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
115
116	usleep_range(100, 150);
117
118	lvdcr0 |= LVDCR0_LVRES;
119	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
120
121	/* Turn all the channels on. */
122	rcar_lvds_write(lvds, LVDCR1,
123			LVDCR1_CHSTBY_GEN3(3) | LVDCR1_CHSTBY_GEN3(2) |
124			LVDCR1_CHSTBY_GEN3(1) | LVDCR1_CHSTBY_GEN3(0) |
125			LVDCR1_CLKSTBY_GEN3);
126}
127
128static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
129				 struct rcar_du_crtc *rcrtc)
130{
131	u32 lvdhcr;
132	int ret;
133
134	if (lvds->enabled)
135		return 0;
136
137	ret = clk_prepare_enable(lvds->clock);
138	if (ret < 0)
139		return ret;
140
141	/* Hardcode the channels and control signals routing for now.
142	 *
143	 * HSYNC -> CTRL0
144	 * VSYNC -> CTRL1
145	 * DISP  -> CTRL2
146	 * 0     -> CTRL3
147	 */
148	rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
149			LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
150			LVDCTRCR_CTR0SEL_HSYNC);
151
152	if (rcar_du_needs(lvds->dev, RCAR_DU_QUIRK_LVDS_LANES))
153		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
154		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
155	else
156		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
157		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
158
159	rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
160
161	/* Perform generation-specific initialization. */
162	if (lvds->dev->info->gen < 3)
163		rcar_du_lvdsenc_start_gen2(lvds, rcrtc);
164	else
165		rcar_du_lvdsenc_start_gen3(lvds, rcrtc);
166
167	lvds->enabled = true;
168
169	return 0;
170}
171
172static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
173{
174	if (!lvds->enabled)
175		return;
176
177	rcar_lvds_write(lvds, LVDCR0, 0);
178	rcar_lvds_write(lvds, LVDCR1, 0);
179
180	clk_disable_unprepare(lvds->clock);
181
182	lvds->enabled = false;
183}
184
185int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc,
186			   bool enable)
187{
188	if (!enable) {
189		rcar_du_lvdsenc_stop(lvds);
190		return 0;
191	} else if (crtc) {
192		struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
193		return rcar_du_lvdsenc_start(lvds, rcrtc);
194	} else
195		return -EINVAL;
196}
197
198void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
199				  struct drm_display_mode *mode)
200{
201	struct rcar_du_device *rcdu = lvds->dev;
202
203	/* The internal LVDS encoder has a restricted clock frequency operating
204	 * range (30MHz to 150MHz on Gen2, 25.175MHz to 148.5MHz on Gen3). Clamp
205	 * the clock accordingly.
206	 */
207	if (rcdu->info->gen < 3)
208		mode->clock = clamp(mode->clock, 30000, 150000);
209	else
210		mode->clock = clamp(mode->clock, 25175, 148500);
211}
212
213static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds,
214					 struct platform_device *pdev)
215{
216	struct resource *mem;
217	char name[7];
218
219	sprintf(name, "lvds.%u", lvds->index);
220
221	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
222	lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
223	if (IS_ERR(lvds->mmio))
224		return PTR_ERR(lvds->mmio);
225
226	lvds->clock = devm_clk_get(&pdev->dev, name);
227	if (IS_ERR(lvds->clock)) {
228		dev_err(&pdev->dev, "failed to get clock for %s\n", name);
229		return PTR_ERR(lvds->clock);
230	}
231
232	return 0;
233}
234
235int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
236{
237	struct platform_device *pdev = to_platform_device(rcdu->dev);
238	struct rcar_du_lvdsenc *lvds;
239	unsigned int i;
240	int ret;
241
242	for (i = 0; i < rcdu->info->num_lvds; ++i) {
243		lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
244		if (lvds == NULL) {
245			dev_err(&pdev->dev, "failed to allocate private data\n");
246			return -ENOMEM;
247		}
248
249		lvds->dev = rcdu;
250		lvds->index = i;
251		lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0;
252		lvds->enabled = false;
253
254		ret = rcar_du_lvdsenc_get_resources(lvds, pdev);
255		if (ret < 0)
256			return ret;
257
258		rcdu->lvds[i] = lvds;
259	}
260
261	return 0;
262}