dpi.c 14.8 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
25
26
/*
 * linux/drivers/video/omap2/dss/dpi.c
 *
 * Copyright (C) 2009 Nokia Corporation
 * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
 *
 * Some code and ideas taken from drivers/video/omap/ driver
 * by Imre Deak.
 *
 * 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 "DPI"

#include <linux/kernel.h>
#include <linux/delay.h>
27
#include <linux/export.h>
28
#include <linux/err.h>
Tomi Valkeinen's avatar
Tomi Valkeinen committed
29
#include <linux/errno.h>
30
31
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
32
#include <linux/string.h>
Tomi Valkeinen's avatar
Tomi Valkeinen committed
33

34
#include <video/omapdss.h>
Tomi Valkeinen's avatar
Tomi Valkeinen committed
35
36

#include "dss.h"
37
#include "dss_features.h"
Tomi Valkeinen's avatar
Tomi Valkeinen committed
38
39

static struct {
40
41
	struct platform_device *pdev;

42
	struct regulator *vdds_dsi_reg;
43
	struct platform_device *dsidev;
44

45
46
	struct mutex lock;

47
	struct omap_video_timings timings;
48
	struct dss_lcd_mgr_config mgr_config;
49
	int data_lines;
50

51
	struct omap_dss_device output;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
52
53
} dpi;

54
static struct platform_device *dpi_get_dsidev(enum omap_channel channel)
55
{
56
57
58
59
60
61
62
63
64
65
66
67
68
	/*
	 * XXX we can't currently use DSI PLL for DPI with OMAP3, as the DSI PLL
	 * would also be used for DISPC fclk. Meaning, when the DPI output is
	 * disabled, DISPC clock will be disabled, and TV out will stop.
	 */
	switch (omapdss_get_version()) {
	case OMAPDSS_VER_OMAP24xx:
	case OMAPDSS_VER_OMAP34xx_ES1:
	case OMAPDSS_VER_OMAP34xx_ES3:
	case OMAPDSS_VER_OMAP3630:
	case OMAPDSS_VER_AM35xx:
		return NULL;

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
	case OMAPDSS_VER_OMAP4430_ES1:
	case OMAPDSS_VER_OMAP4430_ES2:
	case OMAPDSS_VER_OMAP4:
		switch (channel) {
		case OMAP_DSS_CHANNEL_LCD:
			return dsi_get_dsidev_from_id(0);
		case OMAP_DSS_CHANNEL_LCD2:
			return dsi_get_dsidev_from_id(1);
		default:
			return NULL;
		}

	case OMAPDSS_VER_OMAP5:
		switch (channel) {
		case OMAP_DSS_CHANNEL_LCD:
			return dsi_get_dsidev_from_id(0);
		case OMAP_DSS_CHANNEL_LCD3:
			return dsi_get_dsidev_from_id(1);
		default:
			return NULL;
		}

91
92
93
	default:
		return NULL;
	}
94
95
}

96
static enum omap_dss_clk_source dpi_get_alt_clk_src(enum omap_channel channel)
97
{
98
99
100
101
102
103
104
105
106
107
	switch (channel) {
	case OMAP_DSS_CHANNEL_LCD:
		return OMAP_DSS_CLK_SRC_DSI_PLL_HSDIV_DISPC;
	case OMAP_DSS_CHANNEL_LCD2:
		return OMAP_DSS_CLK_SRC_DSI2_PLL_HSDIV_DISPC;
	default:
		/* this shouldn't happen */
		WARN_ON(1);
		return OMAP_DSS_CLK_SRC_FCK;
	}
108
109
}

110
111
112
113
114
115
116
117
118
119
struct dpi_clk_calc_ctx {
	struct platform_device *dsidev;

	/* inputs */

	unsigned long pck_min, pck_max;

	/* outputs */

	struct dsi_clock_info dsi_cinfo;
120
	unsigned long long fck;
121
122
123
124
125
126
127
128
129
130
131
132
133
	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 dpi_clk_calc_ctx *ctx = data;

	/*
	 * Odd dividers give us uneven duty cycle, causing problem when level
	 * shifted. So skip all odd dividers when the pixel clock is on the
	 * higher side.
	 */
134
	if (ctx->pck_min >= 100000000) {
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
		if (lckd > 1 && lckd % 2 != 0)
			return false;

		if (pckd > 1 && pckd % 2 != 0)
			return false;
	}

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

	return true;
}


static bool dpi_calc_hsdiv_cb(int regm_dispc, unsigned long dispc,
		void *data)
{
	struct dpi_clk_calc_ctx *ctx = data;

	/*
	 * Odd dividers give us uneven duty cycle, causing problem when level
	 * shifted. So skip all odd dividers when the pixel clock is on the
	 * higher side.
	 */
161
	if (regm_dispc > 1 && regm_dispc % 2 != 0 && ctx->pck_min >= 100000000)
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
		return false;

	ctx->dsi_cinfo.regm_dispc = regm_dispc;
	ctx->dsi_cinfo.dsi_pll_hsdiv_dispc_clk = dispc;

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


static bool dpi_calc_pll_cb(int regn, int regm, unsigned long fint,
		unsigned long pll,
		void *data)
{
	struct dpi_clk_calc_ctx *ctx = data;

	ctx->dsi_cinfo.regn = regn;
	ctx->dsi_cinfo.regm = regm;
	ctx->dsi_cinfo.fint = fint;
	ctx->dsi_cinfo.clkin4ddr = pll;

	return dsi_hsdiv_calc(ctx->dsidev, pll, ctx->pck_min,
			dpi_calc_hsdiv_cb, ctx);
}

187
static bool dpi_calc_dss_cb(unsigned long fck, void *data)
188
189
190
{
	struct dpi_clk_calc_ctx *ctx = data;

191
	ctx->fck = fck;
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

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

static bool dpi_dsi_clk_calc(unsigned long pck, struct dpi_clk_calc_ctx *ctx)
{
	unsigned long clkin;
	unsigned long pll_min, pll_max;

	clkin = dsi_get_pll_clkin(dpi.dsidev);

	memset(ctx, 0, sizeof(*ctx));
	ctx->dsidev = dpi.dsidev;
	ctx->pck_min = pck - 1000;
	ctx->pck_max = pck + 1000;
	ctx->dsi_cinfo.clkin = clkin;

	pll_min = 0;
	pll_max = 0;

	return dsi_pll_calc(dpi.dsidev, clkin,
			pll_min, pll_max,
			dpi_calc_pll_cb, ctx);
}

static bool dpi_dss_clk_calc(unsigned long pck, struct dpi_clk_calc_ctx *ctx)
{
	int i;

	/*
	 * DSS fck 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
226
	 * +/- ~15MHz.
227
228
	 */

229
	for (i = 0; i < 25; ++i) {
230
231
232
233
234
235
236
237
238
		bool ok;

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

239
		ok = dss_div_calc(pck, ctx->pck_min, dpi_calc_dss_cb, ctx);
240
241
242
243
244
245
246
247
248
		if (ok)
			return ok;
	}

	return false;
}



249
static int dpi_set_dsi_clk(enum omap_channel channel,
250
251
		unsigned long pck_req, unsigned long *fck, int *lck_div,
		int *pck_div)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
252
{
253
	struct dpi_clk_calc_ctx ctx;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
254
	int r;
255
	bool ok;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
256

257
258
259
	ok = dpi_dsi_clk_calc(pck_req, &ctx);
	if (!ok)
		return -EINVAL;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
260

261
	r = dsi_pll_set_clock_div(dpi.dsidev, &ctx.dsi_cinfo);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
262
263
264
	if (r)
		return r;

265
266
	dss_select_lcd_clk_source(channel,
			dpi_get_alt_clk_src(channel));
Tomi Valkeinen's avatar
Tomi Valkeinen committed
267

268
	dpi.mgr_config.clock_info = ctx.dispc_cinfo;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
269

270
271
272
	*fck = ctx.dsi_cinfo.dsi_pll_hsdiv_dispc_clk;
	*lck_div = ctx.dispc_cinfo.lck_div;
	*pck_div = ctx.dispc_cinfo.pck_div;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
273
274
275

	return 0;
}
276

277
278
static int dpi_set_dispc_clk(unsigned long pck_req, unsigned long *fck,
		int *lck_div, int *pck_div)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
279
{
280
	struct dpi_clk_calc_ctx ctx;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
281
	int r;
282
	bool ok;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
283

284
285
286
	ok = dpi_dss_clk_calc(pck_req, &ctx);
	if (!ok)
		return -EINVAL;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
287

288
	r = dss_set_fck_rate(ctx.fck);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
289
290
291
	if (r)
		return r;

292
	dpi.mgr_config.clock_info = ctx.dispc_cinfo;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
293

294
	*fck = ctx.fck;
295
296
	*lck_div = ctx.dispc_cinfo.lck_div;
	*pck_div = ctx.dispc_cinfo.pck_div;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
297
298
299
300

	return 0;
}

301
static int dpi_set_mode(struct omap_overlay_manager *mgr)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
302
{
303
	struct omap_video_timings *t = &dpi.timings;
304
305
	int lck_div = 0, pck_div = 0;
	unsigned long fck = 0;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
306
307
308
	unsigned long pck;
	int r = 0;

309
	if (dpi.dsidev)
310
		r = dpi_set_dsi_clk(mgr->id, t->pixel_clock * 1000, &fck,
311
				&lck_div, &pck_div);
312
	else
313
		r = dpi_set_dispc_clk(t->pixel_clock * 1000, &fck,
314
				&lck_div, &pck_div);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
315
	if (r)
316
		return r;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
317
318
319
320
321
322
323
324
325
326
327

	pck = fck / lck_div / pck_div / 1000;

	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;
	}

328
	dss_mgr_set_timings(mgr, t);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
329

330
	return 0;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
331
332
}

333
static void dpi_config_lcd_manager(struct omap_overlay_manager *mgr)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
334
{
335
	dpi.mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS;
336

337
338
339
	dpi.mgr_config.stallmode = false;
	dpi.mgr_config.fifohandcheck = false;

340
	dpi.mgr_config.video_port_width = dpi.data_lines;
341
342
343

	dpi.mgr_config.lcden_sig_polarity = 0;

344
	dss_mgr_set_lcd_config(mgr, &dpi.mgr_config);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
345
346
}

347
static int dpi_display_enable(struct omap_dss_device *dssdev)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
348
{
349
	struct omap_dss_device *out = &dpi.output;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
350
351
	int r;

352
353
	mutex_lock(&dpi.lock);

354
	if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI) && !dpi.vdds_dsi_reg) {
355
		DSSERR("no VDSS_DSI regulator\n");
356
357
		r = -ENODEV;
		goto err_no_reg;
358
359
	}

360
361
	if (out == NULL || out->manager == NULL) {
		DSSERR("failed to enable display: no output/manager\n");
362
		r = -ENODEV;
363
		goto err_no_out_mgr;
364
365
	}

366
	if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI)) {
367
368
		r = regulator_enable(dpi.vdds_dsi_reg);
		if (r)
369
			goto err_reg_enable;
370
371
	}

372
	r = dispc_runtime_get();
Tomi Valkeinen's avatar
Tomi Valkeinen committed
373
	if (r)
374
375
		goto err_get_dispc;

376
	r = dss_dpi_select_source(out->manager->id);
377
378
379
	if (r)
		goto err_src_sel;

380
	if (dpi.dsidev) {
381
382
383
384
		r = dsi_runtime_get(dpi.dsidev);
		if (r)
			goto err_get_dsi;

385
		r = dsi_pll_init(dpi.dsidev, 0, 1);
386
		if (r)
387
			goto err_dsi_pll_init;
388
389
	}

390
	r = dpi_set_mode(out->manager);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
391
	if (r)
392
		goto err_set_mode;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
393

394
	dpi_config_lcd_manager(out->manager);
395

Tomi Valkeinen's avatar
Tomi Valkeinen committed
396
397
	mdelay(2);

398
	r = dss_mgr_enable(out->manager);
399
400
	if (r)
		goto err_mgr_enable;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
401

402
403
	mutex_unlock(&dpi.lock);

Tomi Valkeinen's avatar
Tomi Valkeinen committed
404
405
	return 0;

406
err_mgr_enable:
407
err_set_mode:
408
	if (dpi.dsidev)
409
		dsi_pll_uninit(dpi.dsidev, true);
410
err_dsi_pll_init:
411
	if (dpi.dsidev)
412
413
		dsi_runtime_put(dpi.dsidev);
err_get_dsi:
414
err_src_sel:
415
416
	dispc_runtime_put();
err_get_dispc:
417
	if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI))
418
		regulator_disable(dpi.vdds_dsi_reg);
419
err_reg_enable:
420
err_no_out_mgr:
421
422
err_no_reg:
	mutex_unlock(&dpi.lock);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
423
424
425
	return r;
}

426
static void dpi_display_disable(struct omap_dss_device *dssdev)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
427
{
428
	struct omap_overlay_manager *mgr = dpi.output.manager;
429

430
431
	mutex_lock(&dpi.lock);

432
	dss_mgr_disable(mgr);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
433

434
	if (dpi.dsidev) {
435
		dss_select_lcd_clk_source(mgr->id, OMAP_DSS_CLK_SRC_FCK);
436
		dsi_pll_uninit(dpi.dsidev, true);
437
		dsi_runtime_put(dpi.dsidev);
438
	}
Tomi Valkeinen's avatar
Tomi Valkeinen committed
439

440
	dispc_runtime_put();
Tomi Valkeinen's avatar
Tomi Valkeinen committed
441

442
	if (dss_has_feature(FEAT_DPI_USES_VDDS_DSI))
443
444
		regulator_disable(dpi.vdds_dsi_reg);

445
	mutex_unlock(&dpi.lock);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
446
447
}

448
static void dpi_set_timings(struct omap_dss_device *dssdev,
449
		struct omap_video_timings *timings)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
450
451
{
	DSSDBG("dpi_set_timings\n");
452
453
454

	mutex_lock(&dpi.lock);

455
456
	dpi.timings = *timings;

457
	mutex_unlock(&dpi.lock);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
458
459
}

Tomi Valkeinen's avatar
Tomi Valkeinen committed
460
461
462
463
464
465
466
467
468
469
static void dpi_get_timings(struct omap_dss_device *dssdev,
		struct omap_video_timings *timings)
{
	mutex_lock(&dpi.lock);

	*timings = dpi.timings;

	mutex_unlock(&dpi.lock);
}

470
static int dpi_check_timings(struct omap_dss_device *dssdev,
Tomi Valkeinen's avatar
Tomi Valkeinen committed
471
472
			struct omap_video_timings *timings)
{
473
	struct omap_overlay_manager *mgr = dpi.output.manager;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
474
475
476
	int lck_div, pck_div;
	unsigned long fck;
	unsigned long pck;
477
478
	struct dpi_clk_calc_ctx ctx;
	bool ok;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
479

480
	if (mgr && !dispc_mgr_timings_ok(mgr->id, timings))
Tomi Valkeinen's avatar
Tomi Valkeinen committed
481
482
483
484
485
		return -EINVAL;

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

486
	if (dpi.dsidev) {
487
488
489
		ok = dpi_dsi_clk_calc(timings->pixel_clock * 1000, &ctx);
		if (!ok)
			return -EINVAL;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
490

491
		fck = ctx.dsi_cinfo.dsi_pll_hsdiv_dispc_clk;
492
	} else {
493
494
495
		ok = dpi_dss_clk_calc(timings->pixel_clock * 1000, &ctx);
		if (!ok)
			return -EINVAL;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
496

497
		fck = ctx.fck;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
498
	}
499

500
501
	lck_div = ctx.dispc_cinfo.lck_div;
	pck_div = ctx.dispc_cinfo.pck_div;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
502
503
504
505
506
507
508
509

	pck = fck / lck_div / pck_div / 1000;

	timings->pixel_clock = pck;

	return 0;
}

510
static void dpi_set_data_lines(struct omap_dss_device *dssdev, int data_lines)
511
512
513
514
515
516
517
518
{
	mutex_lock(&dpi.lock);

	dpi.data_lines = data_lines;

	mutex_unlock(&dpi.lock);
}

519
static int dpi_verify_dsi_pll(struct platform_device *dsidev)
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
{
	int r;

	/* do initial setup with the PLL to see if it is operational */

	r = dsi_runtime_get(dsidev);
	if (r)
		return r;

	r = dsi_pll_init(dsidev, 0, 1);
	if (r) {
		dsi_runtime_put(dsidev);
		return r;
	}

	dsi_pll_uninit(dsidev, true);
	dsi_runtime_put(dsidev);

	return 0;
}

541
542
543
544
545
546
547
548
549
550
static int dpi_init_regulator(void)
{
	struct regulator *vdds_dsi;

	if (!dss_has_feature(FEAT_DPI_USES_VDDS_DSI))
		return 0;

	if (dpi.vdds_dsi_reg)
		return 0;

551
	vdds_dsi = devm_regulator_get(&dpi.pdev->dev, "vdds_dsi");
552
	if (IS_ERR(vdds_dsi)) {
553
554
		DSSERR("can't get VDDS_DSI regulator\n");
		return PTR_ERR(vdds_dsi);
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
	}

	dpi.vdds_dsi_reg = vdds_dsi;

	return 0;
}

static void dpi_init_pll(void)
{
	struct platform_device *dsidev;

	if (dpi.dsidev)
		return;

	dsidev = dpi_get_dsidev(dpi.output.dispc_channel);
	if (!dsidev)
		return;

	if (dpi_verify_dsi_pll(dsidev)) {
		DSSWARN("DSI PLL not operational\n");
		return;
	}

	dpi.dsidev = dsidev;
}

581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
/*
 * Return a hardcoded channel for the DPI output. This should work for
 * current use cases, but this can be later expanded to either resolve
 * the channel in some more dynamic manner, or get the channel as a user
 * parameter.
 */
static enum omap_channel dpi_get_channel(void)
{
	switch (omapdss_get_version()) {
	case OMAPDSS_VER_OMAP24xx:
	case OMAPDSS_VER_OMAP34xx_ES1:
	case OMAPDSS_VER_OMAP34xx_ES3:
	case OMAPDSS_VER_OMAP3630:
	case OMAPDSS_VER_AM35xx:
		return OMAP_DSS_CHANNEL_LCD;

	case OMAPDSS_VER_OMAP4430_ES1:
	case OMAPDSS_VER_OMAP4430_ES2:
	case OMAPDSS_VER_OMAP4:
		return OMAP_DSS_CHANNEL_LCD2;

	case OMAPDSS_VER_OMAP5:
		return OMAP_DSS_CHANNEL_LCD3;

	default:
		DSSWARN("unsupported DSS version\n");
		return OMAP_DSS_CHANNEL_LCD;
	}
}

Tomi Valkeinen's avatar
Tomi Valkeinen committed
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
static int dpi_connect(struct omap_dss_device *dssdev,
		struct omap_dss_device *dst)
{
	struct omap_overlay_manager *mgr;
	int r;

	r = dpi_init_regulator();
	if (r)
		return r;

	dpi_init_pll();

	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 dpi_disconnect(struct omap_dss_device *dssdev,
		struct omap_dss_device *dst)
{
645
	WARN_ON(dst != dssdev->dst);
Tomi Valkeinen's avatar
Tomi Valkeinen committed
646

647
	if (dst != dssdev->dst)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
648
649
650
651
652
653
654
655
656
657
658
659
		return;

	omapdss_output_unset_device(dssdev);

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

static const struct omapdss_dpi_ops dpi_ops = {
	.connect = dpi_connect,
	.disconnect = dpi_disconnect,

660
661
	.enable = dpi_display_enable,
	.disable = dpi_display_disable,
Tomi Valkeinen's avatar
Tomi Valkeinen committed
662
663

	.check_timings = dpi_check_timings,
664
	.set_timings = dpi_set_timings,
Tomi Valkeinen's avatar
Tomi Valkeinen committed
665
666
	.get_timings = dpi_get_timings,

667
	.set_data_lines = dpi_set_data_lines,
Tomi Valkeinen's avatar
Tomi Valkeinen committed
668
669
};

670
static void dpi_init_output(struct platform_device *pdev)
671
{
672
	struct omap_dss_device *out = &dpi.output;
673

674
	out->dev = &pdev->dev;
675
	out->id = OMAP_DSS_OUTPUT_DPI;
676
	out->output_type = OMAP_DISPLAY_TYPE_DPI;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
677
	out->name = "dpi.0";
678
	out->dispc_channel = dpi_get_channel();
Tomi Valkeinen's avatar
Tomi Valkeinen committed
679
	out->ops.dpi = &dpi_ops;
680
	out->owner = THIS_MODULE;
681

682
	omapdss_register_output(out);
683
684
685
686
}

static void __exit dpi_uninit_output(struct platform_device *pdev)
{
687
	struct omap_dss_device *out = &dpi.output;
688

689
	omapdss_unregister_output(out);
690
691
}

692
static int omap_dpi_probe(struct platform_device *pdev)
693
{
694
695
	dpi.pdev = pdev;

696
697
	mutex_init(&dpi.lock);

698
699
	dpi_init_output(pdev);

700
701
702
	return 0;
}

Tomi Valkeinen's avatar
Tomi Valkeinen committed
703
static int __exit omap_dpi_remove(struct platform_device *pdev)
Tomi Valkeinen's avatar
Tomi Valkeinen committed
704
{
705
706
	dpi_uninit_output(pdev);

707
	return 0;
Tomi Valkeinen's avatar
Tomi Valkeinen committed
708
709
}

710
static struct platform_driver omap_dpi_driver = {
711
	.probe		= omap_dpi_probe,
Tomi Valkeinen's avatar
Tomi Valkeinen committed
712
	.remove         = __exit_p(omap_dpi_remove),
713
714
715
716
717
718
	.driver         = {
		.name   = "omapdss_dpi",
		.owner  = THIS_MODULE,
	},
};

Tomi Valkeinen's avatar
Tomi Valkeinen committed
719
int __init dpi_init_platform_driver(void)
720
{
721
	return platform_driver_register(&omap_dpi_driver);
722
723
}

Tomi Valkeinen's avatar
Tomi Valkeinen committed
724
void __exit dpi_uninit_platform_driver(void)
725
726
727
{
	platform_driver_unregister(&omap_dpi_driver);
}