Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
  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
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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
161
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
187
188
189
190
191
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
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
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
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
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
/*
 * Copyright (C) 2015 Free Electrons
 * Copyright (C) 2015 NextThing Co
 *
 * Maxime Ripard <maxime.ripard@free-electrons.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 */

#include <linux/clk.h>
#include <linux/component.h>
#include <linux/of_address.h>
#include <linux/regmap.h>
#include <linux/reset.h>

#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_panel.h>

#include "sun4i_backend.h"
#include "sun4i_drv.h"
#include "sun4i_tcon.h"

#define SUN4I_TVE_EN_REG		0x000
#define SUN4I_TVE_EN_DAC_MAP_MASK		GENMASK(19, 4)
#define SUN4I_TVE_EN_DAC_MAP(dac, out)		(((out) & 0xf) << (dac + 1) * 4)
#define SUN4I_TVE_EN_ENABLE			BIT(0)

#define SUN4I_TVE_CFG0_REG		0x004
#define SUN4I_TVE_CFG0_DAC_CONTROL_54M		BIT(26)
#define SUN4I_TVE_CFG0_CORE_DATAPATH_54M	BIT(25)
#define SUN4I_TVE_CFG0_CORE_CONTROL_54M		BIT(24)
#define SUN4I_TVE_CFG0_YC_EN			BIT(17)
#define SUN4I_TVE_CFG0_COMP_EN			BIT(16)
#define SUN4I_TVE_CFG0_RES(x)			((x) & 0xf)
#define SUN4I_TVE_CFG0_RES_480i			SUN4I_TVE_CFG0_RES(0)
#define SUN4I_TVE_CFG0_RES_576i			SUN4I_TVE_CFG0_RES(1)

#define SUN4I_TVE_DAC0_REG		0x008
#define SUN4I_TVE_DAC0_CLOCK_INVERT		BIT(24)
#define SUN4I_TVE_DAC0_LUMA(x)			(((x) & 3) << 20)
#define SUN4I_TVE_DAC0_LUMA_0_4			SUN4I_TVE_DAC0_LUMA(3)
#define SUN4I_TVE_DAC0_CHROMA(x)		(((x) & 3) << 18)
#define SUN4I_TVE_DAC0_CHROMA_0_75		SUN4I_TVE_DAC0_CHROMA(3)
#define SUN4I_TVE_DAC0_INTERNAL_DAC(x)		(((x) & 3) << 16)
#define SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS	SUN4I_TVE_DAC0_INTERNAL_DAC(3)
#define SUN4I_TVE_DAC0_DAC_EN(dac)		BIT(dac)

#define SUN4I_TVE_NOTCH_REG		0x00c
#define SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(dac, x)	((4 - (x)) << (dac * 3))

#define SUN4I_TVE_CHROMA_FREQ_REG	0x010

#define SUN4I_TVE_PORCH_REG		0x014
#define SUN4I_TVE_PORCH_BACK(x)			((x) << 16)
#define SUN4I_TVE_PORCH_FRONT(x)		(x)

#define SUN4I_TVE_LINE_REG		0x01c
#define SUN4I_TVE_LINE_FIRST(x)			((x) << 16)
#define SUN4I_TVE_LINE_NUMBER(x)		(x)

#define SUN4I_TVE_LEVEL_REG		0x020
#define SUN4I_TVE_LEVEL_BLANK(x)		((x) << 16)
#define SUN4I_TVE_LEVEL_BLACK(x)		(x)

#define SUN4I_TVE_DAC1_REG		0x024
#define SUN4I_TVE_DAC1_AMPLITUDE(dac, x)	((x) << (dac * 8))

#define SUN4I_TVE_DETECT_STA_REG	0x038
#define SUN4I_TVE_DETECT_STA_DAC(dac)		BIT((dac * 8))
#define SUN4I_TVE_DETECT_STA_UNCONNECTED		0
#define SUN4I_TVE_DETECT_STA_CONNECTED			1
#define SUN4I_TVE_DETECT_STA_GROUND			2

#define SUN4I_TVE_CB_CR_LVL_REG		0x10c
#define SUN4I_TVE_CB_CR_LVL_CR_BURST(x)		((x) << 8)
#define SUN4I_TVE_CB_CR_LVL_CB_BURST(x)		(x)

#define SUN4I_TVE_TINT_BURST_PHASE_REG	0x110
#define SUN4I_TVE_TINT_BURST_PHASE_CHROMA(x)	(x)

#define SUN4I_TVE_BURST_WIDTH_REG	0x114
#define SUN4I_TVE_BURST_WIDTH_BREEZEWAY(x)	((x) << 16)
#define SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(x)	((x) << 8)
#define SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(x)	(x)

#define SUN4I_TVE_CB_CR_GAIN_REG	0x118
#define SUN4I_TVE_CB_CR_GAIN_CR(x)		((x) << 8)
#define SUN4I_TVE_CB_CR_GAIN_CB(x)		(x)

#define SUN4I_TVE_SYNC_VBI_REG		0x11c
#define SUN4I_TVE_SYNC_VBI_SYNC(x)		((x) << 16)
#define SUN4I_TVE_SYNC_VBI_VBLANK(x)		(x)

#define SUN4I_TVE_ACTIVE_LINE_REG	0x124
#define SUN4I_TVE_ACTIVE_LINE(x)		(x)

#define SUN4I_TVE_CHROMA_REG		0x128
#define SUN4I_TVE_CHROMA_COMP_GAIN(x)		((x) & 3)
#define SUN4I_TVE_CHROMA_COMP_GAIN_50		SUN4I_TVE_CHROMA_COMP_GAIN(2)

#define SUN4I_TVE_12C_REG		0x12c
#define SUN4I_TVE_12C_NOTCH_WIDTH_WIDE		BIT(8)
#define SUN4I_TVE_12C_COMP_YUV_EN		BIT(0)

#define SUN4I_TVE_RESYNC_REG		0x130
#define SUN4I_TVE_RESYNC_FIELD			BIT(31)
#define SUN4I_TVE_RESYNC_LINE(x)		((x) << 16)
#define SUN4I_TVE_RESYNC_PIXEL(x)		(x)

#define SUN4I_TVE_SLAVE_REG		0x134

#define SUN4I_TVE_WSS_DATA2_REG		0x244

struct color_gains {
	u16	cb;
	u16	cr;
};

struct burst_levels {
	u16	cb;
	u16	cr;
};

struct video_levels {
	u16	black;
	u16	blank;
};

struct resync_parameters {
	bool	field;
	u16	line;
	u16	pixel;
};

struct tv_mode {
	char		*name;

	u32		mode;
	u32		chroma_freq;
	u16		back_porch;
	u16		front_porch;
	u16		line_number;
	u16		vblank_level;

	u32		hdisplay;
	u16		hfront_porch;
	u16		hsync_len;
	u16		hback_porch;

	u32		vdisplay;
	u16		vfront_porch;
	u16		vsync_len;
	u16		vback_porch;

	bool		yc_en;
	bool		dac3_en;
	bool		dac_bit25_en;

	struct color_gains		*color_gains;
	struct burst_levels		*burst_levels;
	struct video_levels		*video_levels;
	struct resync_parameters	*resync_params;
};

struct sun4i_tv {
	struct drm_connector	connector;
	struct drm_encoder	encoder;

	struct clk		*clk;
	struct regmap		*regs;
	struct reset_control	*reset;

	struct sun4i_drv	*drv;
};

struct video_levels ntsc_video_levels = {
	.black = 282,	.blank = 240,
};

struct video_levels pal_video_levels = {
	.black = 252,	.blank = 252,
};

struct burst_levels ntsc_burst_levels = {
	.cb = 79,	.cr = 0,
};

struct burst_levels pal_burst_levels = {
	.cb = 40,	.cr = 40,
};

struct color_gains ntsc_color_gains = {
	.cb = 160,	.cr = 160,
};

struct color_gains pal_color_gains = {
	.cb = 224,	.cr = 224,
};

struct resync_parameters ntsc_resync_parameters = {
	.field = false,	.line = 14,	.pixel = 12,
};

struct resync_parameters pal_resync_parameters = {
	.field = true,	.line = 13,	.pixel = 12,
};

struct tv_mode tv_modes[] = {
	{
		.name		= "NTSC",
		.mode		= SUN4I_TVE_CFG0_RES_480i,
		.chroma_freq	= 0x21f07c1f,
		.yc_en		= true,
		.dac3_en	= true,
		.dac_bit25_en	= true,

		.back_porch	= 118,
		.front_porch	= 32,
		.line_number	= 525,

		.hdisplay	= 720,
		.hfront_porch	= 18,
		.hsync_len	= 2,
		.hback_porch	= 118,

		.vdisplay	= 480,
		.vfront_porch	= 26,
		.vsync_len	= 2,
		.vback_porch	= 17,

		.vblank_level	= 240,

		.color_gains	= &ntsc_color_gains,
		.burst_levels	= &ntsc_burst_levels,
		.video_levels	= &ntsc_video_levels,
		.resync_params	= &ntsc_resync_parameters,
	},
	{
		.name		= "PAL",
		.mode		= SUN4I_TVE_CFG0_RES_576i,
		.chroma_freq	= 0x2a098acb,

		.back_porch	= 138,
		.front_porch	= 24,
		.line_number	= 625,

		.hdisplay	= 720,
		.hfront_porch	= 3,
		.hsync_len	= 2,
		.hback_porch	= 139,

		.vdisplay	= 576,
		.vfront_porch	= 28,
		.vsync_len	= 2,
		.vback_porch	= 19,

		.vblank_level	= 252,

		.color_gains	= &pal_color_gains,
		.burst_levels	= &pal_burst_levels,
		.video_levels	= &pal_video_levels,
		.resync_params	= &pal_resync_parameters,
	},
};

static inline struct sun4i_tv *
drm_encoder_to_sun4i_tv(struct drm_encoder *encoder)
{
	return container_of(encoder, struct sun4i_tv,
			    encoder);
}

static inline struct sun4i_tv *
drm_connector_to_sun4i_tv(struct drm_connector *connector)
{
	return container_of(connector, struct sun4i_tv,
			    connector);
}

/*
 * FIXME: If only the drm_display_mode private field was usable, this
 * could go away...
 *
 * So far, it doesn't seem to be preserved when the mode is passed by
 * to mode_set for some reason.
 */
static struct tv_mode *sun4i_tv_find_tv_by_mode(struct drm_display_mode *mode)
{
	int i;

	/* First try to identify the mode by name */
	for (i = 0; i < ARRAY_SIZE(tv_modes); i++) {
		struct tv_mode *tv_mode = &tv_modes[i];

		DRM_DEBUG_DRIVER("Comparing mode %s vs %s",
				 mode->name, tv_mode->name);

		if (!strcmp(mode->name, tv_mode->name))
			return tv_mode;
	}

	/* Then by number of lines */
	for (i = 0; i < ARRAY_SIZE(tv_modes); i++) {
		struct tv_mode *tv_mode = &tv_modes[i];

		DRM_DEBUG_DRIVER("Comparing mode %s vs %s (X: %d vs %d)",
				 mode->name, tv_mode->name,
				 mode->vdisplay, tv_mode->vdisplay);

		if (mode->vdisplay == tv_mode->vdisplay)
			return tv_mode;
	}

	return NULL;
}

static void sun4i_tv_mode_to_drm_mode(struct tv_mode *tv_mode,
				      struct drm_display_mode *mode)
{
	DRM_DEBUG_DRIVER("Creating mode %s\n", mode->name);

	mode->type = DRM_MODE_TYPE_DRIVER;
	mode->clock = 13500;
	mode->flags = DRM_MODE_FLAG_INTERLACE;

	mode->hdisplay = tv_mode->hdisplay;
	mode->hsync_start = mode->hdisplay + tv_mode->hfront_porch;
	mode->hsync_end = mode->hsync_start + tv_mode->hsync_len;
	mode->htotal = mode->hsync_end  + tv_mode->hback_porch;

	mode->vdisplay = tv_mode->vdisplay;
	mode->vsync_start = mode->vdisplay + tv_mode->vfront_porch;
	mode->vsync_end = mode->vsync_start + tv_mode->vsync_len;
	mode->vtotal = mode->vsync_end  + tv_mode->vback_porch;
}

static int sun4i_tv_atomic_check(struct drm_encoder *encoder,
				 struct drm_crtc_state *crtc_state,
				 struct drm_connector_state *conn_state)
{
	return 0;
}

static void sun4i_tv_disable(struct drm_encoder *encoder)
{
	struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
	struct sun4i_drv *drv = tv->drv;
	struct sun4i_tcon *tcon = drv->tcon;

	DRM_DEBUG_DRIVER("Disabling the TV Output\n");

	sun4i_tcon_channel_disable(tcon, 1);

	regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
			   SUN4I_TVE_EN_ENABLE,
			   0);
	sun4i_backend_disable_color_correction(drv->backend);
}

static void sun4i_tv_enable(struct drm_encoder *encoder)
{
	struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
	struct sun4i_drv *drv = tv->drv;
	struct sun4i_tcon *tcon = drv->tcon;

	DRM_DEBUG_DRIVER("Enabling the TV Output\n");

	sun4i_backend_apply_color_correction(drv->backend);

	regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
			   SUN4I_TVE_EN_ENABLE,
			   SUN4I_TVE_EN_ENABLE);

	sun4i_tcon_channel_enable(tcon, 1);
}

static void sun4i_tv_mode_set(struct drm_encoder *encoder,
			      struct drm_display_mode *mode,
			      struct drm_display_mode *adjusted_mode)
{
	struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
	struct sun4i_drv *drv = tv->drv;
	struct sun4i_tcon *tcon = drv->tcon;
	struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);

	sun4i_tcon1_mode_set(tcon, mode);

	/* Enable and map the DAC to the output */
	regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
			   SUN4I_TVE_EN_DAC_MAP_MASK,
			   SUN4I_TVE_EN_DAC_MAP(0, 1) |
			   SUN4I_TVE_EN_DAC_MAP(1, 2) |
			   SUN4I_TVE_EN_DAC_MAP(2, 3) |
			   SUN4I_TVE_EN_DAC_MAP(3, 4));

	/* Set PAL settings */
	regmap_write(tv->regs, SUN4I_TVE_CFG0_REG,
		     tv_mode->mode |
		     (tv_mode->yc_en ? SUN4I_TVE_CFG0_YC_EN : 0) |
		     SUN4I_TVE_CFG0_COMP_EN |
		     SUN4I_TVE_CFG0_DAC_CONTROL_54M |
		     SUN4I_TVE_CFG0_CORE_DATAPATH_54M |
		     SUN4I_TVE_CFG0_CORE_CONTROL_54M);

	/* Configure the DAC for a composite output */
	regmap_write(tv->regs, SUN4I_TVE_DAC0_REG,
		     SUN4I_TVE_DAC0_DAC_EN(0) |
		     (tv_mode->dac3_en ? SUN4I_TVE_DAC0_DAC_EN(3) : 0) |
		     SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS |
		     SUN4I_TVE_DAC0_CHROMA_0_75 |
		     SUN4I_TVE_DAC0_LUMA_0_4 |
		     SUN4I_TVE_DAC0_CLOCK_INVERT |
		     (tv_mode->dac_bit25_en ? BIT(25) : 0) |
		     BIT(30));

	/* Configure the sample delay between DAC0 and the other DAC */
	regmap_write(tv->regs, SUN4I_TVE_NOTCH_REG,
		     SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(1, 0) |
		     SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(2, 0));

	regmap_write(tv->regs, SUN4I_TVE_CHROMA_FREQ_REG,
		     tv_mode->chroma_freq);

	/* Set the front and back porch */
	regmap_write(tv->regs, SUN4I_TVE_PORCH_REG,
		     SUN4I_TVE_PORCH_BACK(tv_mode->back_porch) |
		     SUN4I_TVE_PORCH_FRONT(tv_mode->front_porch));

	/* Set the lines setup */
	regmap_write(tv->regs, SUN4I_TVE_LINE_REG,
		     SUN4I_TVE_LINE_FIRST(22) |
		     SUN4I_TVE_LINE_NUMBER(tv_mode->line_number));

	regmap_write(tv->regs, SUN4I_TVE_LEVEL_REG,
		     SUN4I_TVE_LEVEL_BLANK(tv_mode->video_levels->blank) |
		     SUN4I_TVE_LEVEL_BLACK(tv_mode->video_levels->black));

	regmap_write(tv->regs, SUN4I_TVE_DAC1_REG,
		     SUN4I_TVE_DAC1_AMPLITUDE(0, 0x18) |
		     SUN4I_TVE_DAC1_AMPLITUDE(1, 0x18) |
		     SUN4I_TVE_DAC1_AMPLITUDE(2, 0x18) |
		     SUN4I_TVE_DAC1_AMPLITUDE(3, 0x18));

	regmap_write(tv->regs, SUN4I_TVE_CB_CR_LVL_REG,
		     SUN4I_TVE_CB_CR_LVL_CB_BURST(tv_mode->burst_levels->cb) |
		     SUN4I_TVE_CB_CR_LVL_CR_BURST(tv_mode->burst_levels->cr));

	/* Set burst width for a composite output */
	regmap_write(tv->regs, SUN4I_TVE_BURST_WIDTH_REG,
		     SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(126) |
		     SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(68) |
		     SUN4I_TVE_BURST_WIDTH_BREEZEWAY(22));

	regmap_write(tv->regs, SUN4I_TVE_CB_CR_GAIN_REG,
		     SUN4I_TVE_CB_CR_GAIN_CB(tv_mode->color_gains->cb) |
		     SUN4I_TVE_CB_CR_GAIN_CR(tv_mode->color_gains->cr));

	regmap_write(tv->regs, SUN4I_TVE_SYNC_VBI_REG,
		     SUN4I_TVE_SYNC_VBI_SYNC(0x10) |
		     SUN4I_TVE_SYNC_VBI_VBLANK(tv_mode->vblank_level));

	regmap_write(tv->regs, SUN4I_TVE_ACTIVE_LINE_REG,
		     SUN4I_TVE_ACTIVE_LINE(1440));

	/* Set composite chroma gain to 50 % */
	regmap_write(tv->regs, SUN4I_TVE_CHROMA_REG,
		     SUN4I_TVE_CHROMA_COMP_GAIN_50);

	regmap_write(tv->regs, SUN4I_TVE_12C_REG,
		     SUN4I_TVE_12C_COMP_YUV_EN |
		     SUN4I_TVE_12C_NOTCH_WIDTH_WIDE);

	regmap_write(tv->regs, SUN4I_TVE_RESYNC_REG,
		     SUN4I_TVE_RESYNC_PIXEL(tv_mode->resync_params->pixel) |
		     SUN4I_TVE_RESYNC_LINE(tv_mode->resync_params->line) |
		     (tv_mode->resync_params->field ?
		      SUN4I_TVE_RESYNC_FIELD : 0));

	regmap_write(tv->regs, SUN4I_TVE_SLAVE_REG, 0);

	clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
}

static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = {
	.atomic_check	= sun4i_tv_atomic_check,
	.disable	= sun4i_tv_disable,
	.enable		= sun4i_tv_enable,
	.mode_set	= sun4i_tv_mode_set,
};

static void sun4i_tv_destroy(struct drm_encoder *encoder)
{
	drm_encoder_cleanup(encoder);
}

static struct drm_encoder_funcs sun4i_tv_funcs = {
	.destroy	= sun4i_tv_destroy,
};

static int sun4i_tv_comp_get_modes(struct drm_connector *connector)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(tv_modes); i++) {
		struct drm_display_mode *mode = drm_mode_create(connector->dev);
		struct tv_mode *tv_mode = &tv_modes[i];

		strcpy(mode->name, tv_mode->name);

		sun4i_tv_mode_to_drm_mode(tv_mode, mode);
		drm_mode_probed_add(connector, mode);
	}

	return i;
}

static int sun4i_tv_comp_mode_valid(struct drm_connector *connector,
				    struct drm_display_mode *mode)
{
	/* TODO */
	return MODE_OK;
}

static struct drm_encoder *
sun4i_tv_comp_best_encoder(struct drm_connector *connector)
{
	struct sun4i_tv *tv = drm_connector_to_sun4i_tv(connector);

	return &tv->encoder;
}

static struct drm_connector_helper_funcs sun4i_tv_comp_connector_helper_funcs = {
	.get_modes	= sun4i_tv_comp_get_modes,
	.mode_valid	= sun4i_tv_comp_mode_valid,
	.best_encoder	= sun4i_tv_comp_best_encoder,
};

static enum drm_connector_status
sun4i_tv_comp_connector_detect(struct drm_connector *connector, bool force)
{
	return connector_status_connected;
}

static void
sun4i_tv_comp_connector_destroy(struct drm_connector *connector)
{
	drm_connector_cleanup(connector);
}

static struct drm_connector_funcs sun4i_tv_comp_connector_funcs = {
	.dpms			= drm_atomic_helper_connector_dpms,
	.detect			= sun4i_tv_comp_connector_detect,
	.fill_modes		= drm_helper_probe_single_connector_modes,
	.destroy		= sun4i_tv_comp_connector_destroy,
	.reset			= drm_atomic_helper_connector_reset,
	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
};

static struct regmap_config sun4i_tv_regmap_config = {
	.reg_bits	= 32,
	.val_bits	= 32,
	.reg_stride	= 4,
	.max_register	= SUN4I_TVE_WSS_DATA2_REG,
	.name		= "tv-encoder",
};

static int sun4i_tv_bind(struct device *dev, struct device *master,
			 void *data)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct drm_device *drm = data;
	struct sun4i_drv *drv = drm->dev_private;
	struct sun4i_tv *tv;
	struct resource *res;
	void __iomem *regs;
	int ret;

	tv = devm_kzalloc(dev, sizeof(*tv), GFP_KERNEL);
	if (!tv)
		return -ENOMEM;
	tv->drv = drv;
	dev_set_drvdata(dev, tv);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	regs = devm_ioremap_resource(dev, res);
	if (IS_ERR(regs)) {
		dev_err(dev, "Couldn't map the TV encoder registers\n");
		return PTR_ERR(regs);
	}

	tv->regs = devm_regmap_init_mmio(dev, regs,
					 &sun4i_tv_regmap_config);
	if (IS_ERR(tv->regs)) {
		dev_err(dev, "Couldn't create the TV encoder regmap\n");
		return PTR_ERR(tv->regs);
	}

	tv->reset = devm_reset_control_get(dev, NULL);
	if (IS_ERR(tv->reset)) {
		dev_err(dev, "Couldn't get our reset line\n");
		return PTR_ERR(tv->reset);
	}

	ret = reset_control_deassert(tv->reset);
	if (ret) {
		dev_err(dev, "Couldn't deassert our reset line\n");
		return ret;
	}

	tv->clk = devm_clk_get(dev, NULL);
	if (IS_ERR(tv->clk)) {
		dev_err(dev, "Couldn't get the TV encoder clock\n");
		ret = PTR_ERR(tv->clk);
		goto err_assert_reset;
	}
	clk_prepare_enable(tv->clk);

	drm_encoder_helper_add(&tv->encoder,
			       &sun4i_tv_helper_funcs);
	ret = drm_encoder_init(drm,
			       &tv->encoder,
			       &sun4i_tv_funcs,
			       DRM_MODE_ENCODER_TVDAC,
			       NULL);
	if (ret) {
		dev_err(dev, "Couldn't initialise the TV encoder\n");
		goto err_disable_clk;
	}

	tv->encoder.possible_crtcs = BIT(0);

	drm_connector_helper_add(&tv->connector,
				 &sun4i_tv_comp_connector_helper_funcs);
	ret = drm_connector_init(drm, &tv->connector,
				 &sun4i_tv_comp_connector_funcs,
				 DRM_MODE_CONNECTOR_Composite);
	if (ret) {
		dev_err(dev,
			"Couldn't initialise the Composite connector\n");
		goto err_cleanup_connector;
	}
	tv->connector.interlace_allowed = true;

	drm_mode_connector_attach_encoder(&tv->connector, &tv->encoder);

	return 0;

err_cleanup_connector:
	drm_encoder_cleanup(&tv->encoder);
err_disable_clk:
	clk_disable_unprepare(tv->clk);
err_assert_reset:
	reset_control_assert(tv->reset);
	return ret;
}

static void sun4i_tv_unbind(struct device *dev, struct device *master,
			    void *data)
{
	struct sun4i_tv *tv = dev_get_drvdata(dev);

	drm_connector_cleanup(&tv->connector);
	drm_encoder_cleanup(&tv->encoder);
	clk_disable_unprepare(tv->clk);
}

static struct component_ops sun4i_tv_ops = {
	.bind	= sun4i_tv_bind,
	.unbind	= sun4i_tv_unbind,
};

static int sun4i_tv_probe(struct platform_device *pdev)
{
	return component_add(&pdev->dev, &sun4i_tv_ops);
}

static int sun4i_tv_remove(struct platform_device *pdev)
{
	component_del(&pdev->dev, &sun4i_tv_ops);

	return 0;
}

static const struct of_device_id sun4i_tv_of_table[] = {
	{ .compatible = "allwinner,sun4i-a10-tv-encoder" },
	{ }
};
MODULE_DEVICE_TABLE(of, sun4i_tv_of_table);

static struct platform_driver sun4i_tv_platform_driver = {
	.probe		= sun4i_tv_probe,
	.remove		= sun4i_tv_remove,
	.driver		= {
		.name		= "sun4i-tve",
		.of_match_table	= sun4i_tv_of_table,
	},
};
module_platform_driver(sun4i_tv_platform_driver);

MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Allwinner A10 TV Encoder Driver");
MODULE_LICENSE("GPL");