sdi.c 8.42 KB
Newer Older
Tomi Valkeinen's avatar
Tomi Valkeinen committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 * linux/drivers/video/omap2/dss/sdi.c
 *
 * Copyright (C) 2009 Nokia Corporation
 * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define DSS_SUBSYS_NAME "SDI"

#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/err.h>
25
#include <linux/regulator/consumer.h>
26
#include <linux/export.h>
27
#include <linux/platform_device.h>
28
#include <linux/string.h>
Tomi Valkeinen's avatar
Tomi Valkeinen committed
29

30
#include <video/omapdss.h>
Tomi Valkeinen's avatar
Tomi Valkeinen committed
31
32
33
#include "dss.h"

static struct {
34
35
	struct platform_device *pdev;

Tomi Valkeinen's avatar
Tomi Valkeinen committed
36
	bool update_enabled;
37
	struct regulator *vdds_sdi_reg;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
38

39
	struct dss_lcd_mgr_config mgr_config;
40
	struct omap_video_timings timings;
41
	int datapairs;
42

43
	struct omap_dss_device output;
44
} sdi;
45

46
47
48
struct sdi_clk_calc_ctx {
	unsigned long pck_min, pck_max;

Tomi Valkeinen's avatar
Tomi Valkeinen committed
49
	unsigned long fck;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
	struct dispc_clock_info dispc_cinfo;
};

static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck,
		unsigned long pck, void *data)
{
	struct sdi_clk_calc_ctx *ctx = data;

	ctx->dispc_cinfo.lck_div = lckd;
	ctx->dispc_cinfo.pck_div = pckd;
	ctx->dispc_cinfo.lck = lck;
	ctx->dispc_cinfo.pck = pck;

	return true;
}

66
static bool dpi_calc_dss_cb(unsigned long fck, void *data)
67
68
69
{
	struct sdi_clk_calc_ctx *ctx = data;

70
	ctx->fck = fck;
71
72
73
74
75
76

	return dispc_div_calc(fck, ctx->pck_min, ctx->pck_max,
			dpi_calc_dispc_cb, ctx);
}

static int sdi_calc_clock_div(unsigned long pclk,
77
		unsigned long *fck,
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
		struct dispc_clock_info *dispc_cinfo)
{
	int i;
	struct sdi_clk_calc_ctx ctx;

	/*
	 * DSS fclk gives us very few possibilities, so finding a good pixel
	 * clock may not be possible. We try multiple times to find the clock,
	 * each time widening the pixel clock range we look for, up to
	 * +/- 1MHz.
	 */

	for (i = 0; i < 10; ++i) {
		bool ok;

		memset(&ctx, 0, sizeof(ctx));
		if (pclk > 1000 * i * i * i)
			ctx.pck_min = max(pclk - 1000 * i * i * i, 0lu);
		else
			ctx.pck_min = 0;
		ctx.pck_max = pclk + 1000 * i * i * i;

100
		ok = dss_div_calc(pclk, ctx.pck_min, dpi_calc_dss_cb, &ctx);
101
		if (ok) {
102
			*fck = ctx.fck;
103
104
105
106
107
108
109
110
			*dispc_cinfo = ctx.dispc_cinfo;
			return 0;
		}
	}

	return -EINVAL;
}

111
static void sdi_config_lcd_manager(struct omap_dss_device *dssdev)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
112
{
113
	struct omap_overlay_manager *mgr = sdi.output.manager;
114

115
	sdi.mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS;
116

117
118
119
120
121
122
	sdi.mgr_config.stallmode = false;
	sdi.mgr_config.fifohandcheck = false;

	sdi.mgr_config.video_port_width = 24;
	sdi.mgr_config.lcden_sig_polarity = 1;

123
	dss_mgr_set_lcd_config(mgr, &sdi.mgr_config);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
124
125
}

126
static int sdi_display_enable(struct omap_dss_device *dssdev)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
127
{
128
	struct omap_dss_device *out = &sdi.output;
129
	struct omap_video_timings *t = &sdi.timings;
130
	unsigned long fck;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
131
132
133
134
	struct dispc_clock_info dispc_cinfo;
	unsigned long pck;
	int r;

135
136
	if (out == NULL || out->manager == NULL) {
		DSSERR("failed to enable display: no output/manager\n");
137
138
139
		return -ENODEV;
	}

140
141
	r = regulator_enable(sdi.vdds_sdi_reg);
	if (r)
142
		goto err_reg_enable;
143

144
145
146
	r = dispc_runtime_get();
	if (r)
		goto err_get_dispc;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
147
148

	/* 15.5.9.1.2 */
149
150
	t->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE;
	t->sync_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE;
151

152
	r = sdi_calc_clock_div(t->pixel_clock * 1000, &fck, &dispc_cinfo);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
153
	if (r)
154
		goto err_calc_clock_div;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
155

156
	sdi.mgr_config.clock_info = dispc_cinfo;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
157

158
	pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div / 1000;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
159
160
161
162
163
164
165
166
167
168

	if (pck != t->pixel_clock) {
		DSSWARN("Could not find exact pixel clock. Requested %d kHz, "
				"got %lu kHz\n",
				t->pixel_clock, pck);

		t->pixel_clock = pck;
	}


169
	dss_mgr_set_timings(out->manager, t);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
170

171
	r = dss_set_fck_rate(fck);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
172
	if (r)
173
		goto err_set_dss_clock_div;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
174

175
	sdi_config_lcd_manager(dssdev);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
176

Tomi Valkeinen's avatar
Tomi Valkeinen committed
177
178
179
180
181
182
183
184
185
186
187
	/*
	 * LCLK and PCLK divisors are located in shadow registers, and we
	 * normally write them to DISPC registers when enabling the output.
	 * However, SDI uses pck-free as source clock for its PLL, and pck-free
	 * is affected by the divisors. And as we need the PLL before enabling
	 * the output, we need to write the divisors early.
	 *
	 * It seems just writing to the DISPC register is enough, and we don't
	 * need to care about the shadow register mechanism for pck-free. The
	 * exact reason for this is unknown.
	 */
188
	dispc_mgr_set_clock_div(out->manager->id, &sdi.mgr_config.clock_info);
189

190
	dss_sdi_init(sdi.datapairs);
191
192
	r = dss_sdi_enable();
	if (r)
193
		goto err_sdi_enable;
194
	mdelay(2);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
195

196
	r = dss_mgr_enable(out->manager);
197
198
	if (r)
		goto err_mgr_enable;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
199
200

	return 0;
201

202
203
err_mgr_enable:
	dss_sdi_disable();
204
205
206
207
208
err_sdi_enable:
err_set_dss_clock_div:
err_calc_clock_div:
	dispc_runtime_put();
err_get_dispc:
209
	regulator_disable(sdi.vdds_sdi_reg);
210
err_reg_enable:
Tomi Valkeinen's avatar
Tomi Valkeinen committed
211
212
213
	return r;
}

214
static void sdi_display_disable(struct omap_dss_device *dssdev)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
215
{
216
	struct omap_overlay_manager *mgr = sdi.output.manager;
217
218

	dss_mgr_disable(mgr);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
219
220
221

	dss_sdi_disable();

222
	dispc_runtime_put();
Tomi Valkeinen's avatar
Tomi Valkeinen committed
223

224
	regulator_disable(sdi.vdds_sdi_reg);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
225
226
}

227
static void sdi_set_timings(struct omap_dss_device *dssdev,
228
229
		struct omap_video_timings *timings)
{
230
	sdi.timings = *timings;
231
232
}

Tomi Valkeinen's avatar
Tomi Valkeinen committed
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
static void sdi_get_timings(struct omap_dss_device *dssdev,
		struct omap_video_timings *timings)
{
	*timings = sdi.timings;
}

static int sdi_check_timings(struct omap_dss_device *dssdev,
			struct omap_video_timings *timings)
{
	struct omap_overlay_manager *mgr = sdi.output.manager;

	if (mgr && !dispc_mgr_timings_ok(mgr->id, timings))
		return -EINVAL;

	if (timings->pixel_clock == 0)
		return -EINVAL;

	return 0;
}

253
static void sdi_set_datapairs(struct omap_dss_device *dssdev, int datapairs)
254
255
256
257
{
	sdi.datapairs = datapairs;
}

258
static int sdi_init_regulator(void)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
259
{
260
	struct regulator *vdds_sdi;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
261

262
263
	if (sdi.vdds_sdi_reg)
		return 0;
264

265
	vdds_sdi = devm_regulator_get(&sdi.pdev->dev, "vdds_sdi");
266
	if (IS_ERR(vdds_sdi)) {
267
268
		if (PTR_ERR(vdds_sdi) != -EPROBE_DEFER)
			DSSERR("can't get VDDS_SDI regulator\n");
269
		return PTR_ERR(vdds_sdi);
270
271
	}

272
273
	sdi.vdds_sdi_reg = vdds_sdi;

Tomi Valkeinen's avatar
Tomi Valkeinen committed
274
275
276
	return 0;
}

Tomi Valkeinen's avatar
Tomi Valkeinen committed
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
static int sdi_connect(struct omap_dss_device *dssdev,
		struct omap_dss_device *dst)
{
	struct omap_overlay_manager *mgr;
	int r;

	r = sdi_init_regulator();
	if (r)
		return r;

	mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel);
	if (!mgr)
		return -ENODEV;

	r = dss_mgr_connect(mgr, dssdev);
	if (r)
		return r;

	r = omapdss_output_set_device(dssdev, dst);
	if (r) {
		DSSERR("failed to connect output to new device: %s\n",
				dst->name);
		dss_mgr_disconnect(mgr, dssdev);
		return r;
	}

	return 0;
}

static void sdi_disconnect(struct omap_dss_device *dssdev,
		struct omap_dss_device *dst)
{
309
	WARN_ON(dst != dssdev->dst);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
310

311
	if (dst != dssdev->dst)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
312
313
314
315
316
317
318
319
320
321
322
323
		return;

	omapdss_output_unset_device(dssdev);

	if (dssdev->manager)
		dss_mgr_disconnect(dssdev->manager, dssdev);
}

static const struct omapdss_sdi_ops sdi_ops = {
	.connect = sdi_connect,
	.disconnect = sdi_disconnect,

324
325
	.enable = sdi_display_enable,
	.disable = sdi_display_disable,
Tomi Valkeinen's avatar
Tomi Valkeinen committed
326
327

	.check_timings = sdi_check_timings,
328
	.set_timings = sdi_set_timings,
Tomi Valkeinen's avatar
Tomi Valkeinen committed
329
330
	.get_timings = sdi_get_timings,

331
	.set_datapairs = sdi_set_datapairs,
Tomi Valkeinen's avatar
Tomi Valkeinen committed
332
333
};

334
static void sdi_init_output(struct platform_device *pdev)
335
{
336
	struct omap_dss_device *out = &sdi.output;
337

338
	out->dev = &pdev->dev;
339
	out->id = OMAP_DSS_OUTPUT_SDI;
340
	out->output_type = OMAP_DISPLAY_TYPE_SDI;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
341
	out->name = "sdi.0";
342
	out->dispc_channel = OMAP_DSS_CHANNEL_LCD;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
343
	out->ops.sdi = &sdi_ops;
344
	out->owner = THIS_MODULE;
345

346
	omapdss_register_output(out);
347
348
349
350
}

static void __exit sdi_uninit_output(struct platform_device *pdev)
{
351
	struct omap_dss_device *out = &sdi.output;
352

353
	omapdss_unregister_output(out);
354
355
}

356
static int omap_sdi_probe(struct platform_device *pdev)
357
{
358
359
	sdi.pdev = pdev;

360
361
	sdi_init_output(pdev);

Tomi Valkeinen's avatar
Tomi Valkeinen committed
362
363
364
	return 0;
}

Tomi Valkeinen's avatar
Tomi Valkeinen committed
365
static int __exit omap_sdi_remove(struct platform_device *pdev)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
366
{
367
368
	sdi_uninit_output(pdev);

369
370
371
372
	return 0;
}

static struct platform_driver omap_sdi_driver = {
373
	.probe		= omap_sdi_probe,
Tomi Valkeinen's avatar
Tomi Valkeinen committed
374
	.remove         = __exit_p(omap_sdi_remove),
375
376
377
378
379
380
	.driver         = {
		.name   = "omapdss_sdi",
		.owner  = THIS_MODULE,
	},
};

Tomi Valkeinen's avatar
Tomi Valkeinen committed
381
int __init sdi_init_platform_driver(void)
382
{
383
	return platform_driver_register(&omap_sdi_driver);
384
385
}

Tomi Valkeinen's avatar
Tomi Valkeinen committed
386
void __exit sdi_uninit_platform_driver(void)
387
388
{
	platform_driver_unregister(&omap_sdi_driver);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
389
}