aboutsummaryrefslogtreecommitdiff
path: root/sound/pci/ice1712/revo.c
blob: bcf114152dfd2da1bcc10dad3fbcb16c9dfb78b5 (plain) (blame)
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
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *   ALSA driver for ICEnsemble ICE1712 (Envy24)
 *
 *   Lowlevel functions for M-Audio Audiophile 192, Revolution 7.1 and 5.1
 *
 *	Copyright (c) 2003 Takashi Iwai <tiwai@suse.de>
 */      

#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <sound/core.h>

#include "ice1712.h"
#include "envy24ht.h"
#include "revo.h"

/* a non-standard I2C device for revo51 */
struct revo51_spec {
	struct snd_i2c_device *dev;
	struct snd_pt2258 *pt2258;
	struct ak4114 *ak4114;
};

static void revo_i2s_mclk_changed(struct snd_ice1712 *ice)
{
	/* assert PRST# to converters; MT05 bit 7 */
	outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD));
	mdelay(5);
	/* deassert PRST# */
	outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD));
}

/*
 * change the rate of Envy24HT, AK4355 and AK4381
 */
static void revo_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
{
	unsigned char old, tmp, dfs;
	int reg, shift;

	if (rate == 0)	/* no hint - S/PDIF input is master, simply return */
		return;

	/* adjust DFS on codecs */
	if (rate > 96000)
		dfs = 2;
	else if (rate > 48000)
		dfs = 1;
	else
		dfs = 0;

	if (ak->type == SND_AK4355 || ak->type == SND_AK4358) {
		reg = 2;
		shift = 4;
	} else {
		reg = 1;
		shift = 3;
	}
	tmp = snd_akm4xxx_get(ak, 0, reg);
	old = (tmp >> shift) & 0x03;
	if (old == dfs)
		return;

	/* reset DFS */
	snd_akm4xxx_reset(ak, 1);
	tmp = snd_akm4xxx_get(ak, 0, reg);
	tmp &= ~(0x03 << shift);
	tmp |= dfs << shift;
	/* snd_akm4xxx_write(ak, 0, reg, tmp); */
	snd_akm4xxx_set(ak, 0, reg, tmp); /* value is written in reset(0) */
	snd_akm4xxx_reset(ak, 0);
}

/*
 * I2C access to the PT2258 volume controller on GPIO 6/7 (Revolution 5.1)
 */

static void revo_i2c_start(struct snd_i2c_bus *bus)
{
	struct snd_ice1712 *ice = bus->private_data;
	snd_ice1712_save_gpio_status(ice);
}

static void revo_i2c_stop(struct snd_i2c_bus *bus)
{
	struct snd_ice1712 *ice = bus->private_data;
	snd_ice1712_restore_gpio_status(ice);
}

static void revo_i2c_direction(struct snd_i2c_bus *bus, int clock, int data)
{
	struct snd_ice1712 *ice = bus->private_data;
	unsigned int mask, val;

	val = 0;
	if (clock)
		val |= VT1724_REVO_I2C_CLOCK;	/* write SCL */
	if (data)
		val |= VT1724_REVO_I2C_DATA;	/* write SDA */
	mask = VT1724_REVO_I2C_CLOCK | VT1724_REVO_I2C_DATA;
	ice->gpio.direction &= ~mask;
	ice->gpio.direction |= val;
	snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
	snd_ice1712_gpio_set_mask(ice, ~mask);
}

static void revo_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data)
{
	struct snd_ice1712 *ice = bus->private_data;
	unsigned int val = 0;

	if (clk)
		val |= VT1724_REVO_I2C_CLOCK;
	if (data)
		val |= VT1724_REVO_I2C_DATA;
	snd_ice1712_gpio_write_bits(ice,
				    VT1724_REVO_I2C_DATA |
				    VT1724_REVO_I2C_CLOCK, val);
	udelay(5);
}

static int revo_i2c_getdata(struct snd_i2c_bus *bus, int ack)
{
	struct snd_ice1712 *ice = bus->private_data;
	int bit;

	if (ack)
		udelay(5);
	bit = snd_ice1712_gpio_read_bits(ice, VT1724_REVO_I2C_DATA) ? 1 : 0;
	return bit;
}

static struct snd_i2c_bit_ops revo51_bit_ops = {
	.start = revo_i2c_start,
	.stop = revo_i2c_stop,
	.direction = revo_i2c_direction,
	.setlines = revo_i2c_setlines,
	.getdata = revo_i2c_getdata,
};

static int revo51_i2c_init(struct snd_ice1712 *ice,
			   struct snd_pt2258 *pt)
{
	struct revo51_spec *spec;
	int err;

	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec)
		return -ENOMEM;
	ice->spec = spec;

	/* create the I2C bus */
	err = snd_i2c_bus_create(ice->card, "ICE1724 GPIO6", NULL, &ice->i2c);
	if (err < 0)
		return err;

	ice->i2c->private_data = ice;
	ice->i2c->hw_ops.bit = &revo51_bit_ops;

	/* create the I2C device */
	err = snd_i2c_device_create(ice->i2c, "PT2258", 0x40, &spec->dev);
	if (err < 0)
		return err;

	pt->card = ice->card;
	pt->i2c_bus = ice->i2c;
	pt->i2c_dev = spec->dev;
	spec->pt2258 = pt;

	snd_pt2258_reset(pt);

	return 0;
}

/*
 * initialize the chips on M-Audio Revolution cards
 */

#define AK_DAC(xname,xch) { .name = xname, .num_channels = xch }

static const struct snd_akm4xxx_dac_channel revo71_front[] = {
	{
		.name = "PCM Playback Volume",
		.num_channels = 2,
		/* front channels DAC supports muting */
		.switch_name = "PCM Playback Switch",
	},
};

static const struct snd_akm4xxx_dac_channel revo71_surround[] = {
	AK_DAC("PCM Center Playback Volume", 1),
	AK_DAC("PCM LFE Playback Volume", 1),
	AK_DAC("PCM Side Playback Volume", 2),
	AK_DAC("PCM Rear Playback Volume", 2),
};

static const struct snd_akm4xxx_dac_channel revo51_dac[] = {
	AK_DAC("PCM Playback Volume", 2),
	AK_DAC("PCM Center Playback Volume", 1),
	AK_DAC("PCM LFE Playback Volume", 1),
	AK_DAC("PCM Rear Playback Volume", 2),
	AK_DAC("PCM Headphone Volume", 2),
};

static const char *revo51_adc_input_names[] = {
	"Mic",
	"Line",
	"CD",
	NULL
};

static const struct snd_akm4xxx_adc_channel revo51_adc[] = {
	{
		.name = "PCM Capture Volume",
		.switch_name = "PCM Capture Switch",
		.num_channels = 2,
		.input_names = revo51_adc_input_names
	},
};

static const struct snd_akm4xxx akm_revo_front = {
	.type = SND_AK4381,
	.num_dacs = 2,
	.ops = {
		.set_rate_val = revo_set_rate_val
	},
	.dac_info = revo71_front,
};

static const struct snd_ak4xxx_private akm_revo_front_priv = {
	.caddr = 1,
	.cif = 0,
	.data_mask = VT1724_REVO_CDOUT,
	.clk_mask = VT1724_REVO_CCLK,
	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
	.cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS2,
	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
	.add_flags = VT1724_REVO_CCLK, /* high at init */
	.mask_flags = 0,
};

static const struct snd_akm4xxx akm_revo_surround = {
	.type = SND_AK4355,
	.idx_offset = 1,
	.num_dacs = 6,
	.ops = {
		.set_rate_val = revo_set_rate_val
	},
	.dac_info = revo71_surround,
};

static const struct snd_ak4xxx_private akm_revo_surround_priv = {
	.caddr = 3,
	.cif = 0,
	.data_mask = VT1724_REVO_CDOUT,
	.clk_mask = VT1724_REVO_CCLK,
	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
	.cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS1,
	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
	.add_flags = VT1724_REVO_CCLK, /* high at init */
	.mask_flags = 0,
};

static const struct snd_akm4xxx akm_revo51 = {
	.type = SND_AK4358,
	.num_dacs = 8,
	.ops = {
		.set_rate_val = revo_set_rate_val
	},
	.dac_info = revo51_dac,
};

static const struct snd_ak4xxx_private akm_revo51_priv = {
	.caddr = 2,
	.cif = 0,
	.data_mask = VT1724_REVO_CDOUT,
	.clk_mask = VT1724_REVO_CCLK,
	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
	.cs_addr = VT1724_REVO_CS1,
	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
	.add_flags = VT1724_REVO_CCLK, /* high at init */
	.mask_flags = 0,
};

static const struct snd_akm4xxx akm_revo51_adc = {
	.type = SND_AK5365,
	.num_adcs = 2,
	.adc_info = revo51_adc,
};

static const struct snd_ak4xxx_private akm_revo51_adc_priv = {
	.caddr = 2,
	.cif = 0,
	.data_mask = VT1724_REVO_CDOUT,
	.clk_mask = VT1724_REVO_CCLK,
	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
	.cs_addr = VT1724_REVO_CS0,
	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
	.add_flags = VT1724_REVO_CCLK, /* high at init */
	.mask_flags = 0,
};

static struct snd_pt2258 ptc_revo51_volume;

/* AK4358 for AP192 DAC, AK5385A for ADC */
static void ap192_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
{
	struct snd_ice1712 *ice = ak->private_data[0];
	int dfs;

	revo_set_rate_val(ak, rate);

	/* reset CKS */
	snd_ice1712_gpio_write_bits(ice, 1 << 8, rate > 96000 ? 1 << 8 : 0);
	/* reset DFS pins of AK5385A for ADC, too */
	if (rate > 96000)
		dfs = 2;
	else if (rate > 48000)
		dfs = 1;
	else
		dfs = 0;
	snd_ice1712_gpio_write_bits(ice, 3 << 9, dfs << 9);
	/* reset ADC */
	snd_ice1712_gpio_write_bits(ice, 1 << 11, 0);
	snd_ice1712_gpio_write_bits(ice, 1 << 11, 1 << 11);
}

static const struct snd_akm4xxx_dac_channel ap192_dac[] = {
	AK_DAC("PCM Playback Volume", 2)
};

static const struct snd_akm4xxx akm_ap192 = {
	.type = SND_AK4358,
	.num_dacs = 2,
	.ops = {
		.set_rate_val = ap192_set_rate_val
	},
	.dac_info = ap192_dac,
};

static const struct snd_ak4xxx_private akm_ap192_priv = {
	.caddr = 2,
	.cif = 0,
	.data_mask = VT1724_REVO_CDOUT,
	.clk_mask = VT1724_REVO_CCLK,
	.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS3,
	.cs_addr = VT1724_REVO_CS3,
	.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS3,
	.add_flags = VT1724_REVO_CCLK, /* high at init */
	.mask_flags = 0,
};

/* AK4114 support on Audiophile 192 */
/* CDTO (pin 32) -- GPIO2 pin 52
 * CDTI (pin 33) -- GPIO3 pin 53 (shared with AK4358)
 * CCLK (pin 34) -- GPIO1 pin 51 (shared with AK4358)
 * CSN  (pin 35) -- GPIO7 pin 59
 */
#define AK4114_ADDR	0x00

static void write_data(struct snd_ice1712 *ice, unsigned int gpio,
		       unsigned int data, int idx)
{
	for (; idx >= 0; idx--) {
		/* drop clock */
		gpio &= ~VT1724_REVO_CCLK;
		snd_ice1712_gpio_write(ice, gpio);
		udelay(1);
		/* set data */
		if (data & (1 << idx))
			gpio |= VT1724_REVO_CDOUT;
		else
			gpio &= ~VT1724_REVO_CDOUT;
		snd_ice1712_gpio_write(ice, gpio);
		udelay(1);
		/* raise clock */
		gpio |= VT1724_REVO_CCLK;
		snd_ice1712_gpio_write(ice, gpio);
		udelay(1);
	}
}

static unsigned char read_data(struct snd_ice1712 *ice, unsigned int gpio,
			       int idx)
{
	unsigned char data = 0;

	for (; idx >= 0; idx--) {
		/* drop clock */
		gpio &= ~VT1724_REVO_CCLK;
		snd_ice1712_gpio_write(ice, gpio);
		udelay(1);
		/* read data */
		if (snd_ice1712_gpio_read(ice) & VT1724_REVO_CDIN)
			data |= (1 << idx);
		udelay(1);
		/* raise clock */
		gpio |= VT1724_REVO_CCLK;
		snd_ice1712_gpio_write(ice, gpio);
		udelay(1);
	}
	return data;
}

static unsigned int ap192_4wire_start(struct snd_ice1712 *ice)
{
	unsigned int tmp;

	snd_ice1712_save_gpio_status(ice);
	tmp = snd_ice1712_gpio_read(ice);
	tmp |= VT1724_REVO_CCLK; /* high at init */
	tmp |= VT1724_REVO_CS0;
	tmp &= ~VT1724_REVO_CS3;
	snd_ice1712_gpio_write(ice, tmp);
	udelay(1);
	return tmp;
}

static void ap192_4wire_finish(struct snd_ice1712 *ice, unsigned int tmp)
{
	tmp |= VT1724_REVO_CS3;
	tmp |= VT1724_REVO_CS0;
	snd_ice1712_gpio_write(ice, tmp);
	udelay(1);
	snd_ice1712_restore_gpio_status(ice);
}

static void ap192_ak4114_write(void *private_data, unsigned char addr,
			       unsigned char data)
{
	struct snd_ice1712 *ice = private_data;
	unsigned int tmp, addrdata;

	tmp = ap192_4wire_start(ice);
	addrdata = (AK4114_ADDR << 6) | 0x20 | (addr & 0x1f);
	addrdata = (addrdata << 8) | data;
	write_data(ice, tmp, addrdata, 15);
	ap192_4wire_finish(ice, tmp);
}

static unsigned char ap192_ak4114_read(void *private_data, unsigned char addr)
{
	struct snd_ice1712 *ice = private_data;
	unsigned int tmp;
	unsigned char data;

	tmp = ap192_4wire_start(ice);
	write_data(ice, tmp, (AK4114_ADDR << 6) | (addr & 0x1f), 7);
	data = read_data(ice, tmp, 7);
	ap192_4wire_finish(ice, tmp);
	return data;
}

static int ap192_ak4114_init(struct snd_ice1712 *ice)
{
	static const unsigned char ak4114_init_vals[] = {
		AK4114_RST | AK4114_PWN | AK4114_OCKS0,
		AK4114_DIF_I24I2S,
		AK4114_TX1E,
		AK4114_EFH_1024 | AK4114_DIT | AK4114_IPS(0),
		0,
		0
	};
	static const unsigned char ak4114_init_txcsb[] = {
		0x41, 0x02, 0x2c, 0x00, 0x00
	};
	int err;

	struct revo51_spec *spec;
	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
	if (!spec)
		return -ENOMEM;
	ice->spec = spec;

	err = snd_ak4114_create(ice->card,
				 ap192_ak4114_read,
				 ap192_ak4114_write,
				 ak4114_init_vals, ak4114_init_txcsb,
				 ice, &spec->ak4114);
	if (err < 0)
		return err;
	/* AK4114 in Revo cannot detect external rate correctly.
	 * No reason to stop capture stream due to incorrect checks */
	spec->ak4114->check_flags = AK4114_CHECK_NO_RATE;

	return 0;
}

static int revo_init(struct snd_ice1712 *ice)
{
	struct snd_akm4xxx *ak;
	int err;

	/* determine I2C, DACs and ADCs */
	switch (ice->eeprom.subvendor) {
	case VT1724_SUBDEVICE_REVOLUTION71:
		ice->num_total_dacs = 8;
		ice->num_total_adcs = 2;
		ice->gpio.i2s_mclk_changed = revo_i2s_mclk_changed;
		break;
	case VT1724_SUBDEVICE_REVOLUTION51:
		ice->num_total_dacs = 8;
		ice->num_total_adcs = 2;
		break;
	case VT1724_SUBDEVICE_AUDIOPHILE192:
		ice->num_total_dacs = 2;
		ice->num_total_adcs = 2;
		break;
	default:
		snd_BUG();
		return -EINVAL;
	}

	/* second stage of initialization, analog parts and others */
	ak = ice->akm = kcalloc(2, sizeof(struct snd_akm4xxx), GFP_KERNEL);
	if (! ak)
		return -ENOMEM;
	switch (ice->eeprom.subvendor) {
	case VT1724_SUBDEVICE_REVOLUTION71:
		ice->akm_codecs = 2;
		err = snd_ice1712_akm4xxx_init(ak, &akm_revo_front,
						&akm_revo_front_priv, ice);
		if (err < 0)
			return err;
		err = snd_ice1712_akm4xxx_init(ak+1, &akm_revo_surround,
						&akm_revo_surround_priv, ice);
		if (err < 0)
			return err;
		/* unmute all codecs */
		snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
						VT1724_REVO_MUTE);
		break;
	case VT1724_SUBDEVICE_REVOLUTION51:
		ice->akm_codecs = 2;
		err = snd_ice1712_akm4xxx_init(ak, &akm_revo51,
					       &akm_revo51_priv, ice);
		if (err < 0)
			return err;
		err = snd_ice1712_akm4xxx_init(ak+1, &akm_revo51_adc,
					       &akm_revo51_adc_priv, ice);
		if (err < 0)
			return err;
		err = revo51_i2c_init(ice, &ptc_revo51_volume);
		if (err < 0)
			return err;
		/* unmute all codecs */
		snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
					    VT1724_REVO_MUTE);
		break;
	case VT1724_SUBDEVICE_AUDIOPHILE192:
		ice->akm_codecs = 1;
		err = snd_ice1712_akm4xxx_init(ak, &akm_ap192, &akm_ap192_priv,
					       ice);
		if (err < 0)
			return err;
		err = ap192_ak4114_init(ice);
		if (err < 0)
			return err;
		
		/* unmute all codecs */
		snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
					    VT1724_REVO_MUTE);
		break;
	}

	return 0;
}


static int revo_add_controls(struct snd_ice1712 *ice)
{
	struct revo51_spec *spec = ice->spec;
	int err;

	switch (ice->eeprom.subvendor) {
	case VT1724_SUBDEVICE_REVOLUTION71:
		err = snd_ice1712_akm4xxx_build_controls(ice);
		if (err < 0)
			return err;
		break;
	case VT1724_SUBDEVICE_REVOLUTION51:
		err = snd_ice1712_akm4xxx_build_controls(ice);
		if (err < 0)
			return err;
		spec = ice->spec;
		err = snd_pt2258_build_controls(spec->pt2258);
		if (err < 0)
			return err;
		break;
	case VT1724_SUBDEVICE_AUDIOPHILE192:
		err = snd_ice1712_akm4xxx_build_controls(ice);
		if (err < 0)
			return err;
		/* only capture SPDIF over AK4114 */
		err = snd_ak4114_build(spec->ak4114, NULL,
		   ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
		if (err < 0)
			return err;
		break;
	}
	return 0;
}

/* entry point */
struct snd_ice1712_card_info snd_vt1724_revo_cards[] = {
	{
		.subvendor = VT1724_SUBDEVICE_REVOLUTION71,
		.name = "M Audio Revolution-7.1",
		.model = "revo71",
		.chip_init = revo_init,
		.build_controls = revo_add_controls,
	},
	{
		.subvendor = VT1724_SUBDEVICE_REVOLUTION51,
		.name = "M Audio Revolution-5.1",
		.model = "revo51",
		.chip_init = revo_init,
		.build_controls = revo_add_controls,
	},
	{
		.subvendor = VT1724_SUBDEVICE_AUDIOPHILE192,
		.name = "M Audio Audiophile192",
		.model = "ap192",
		.chip_init = revo_init,
		.build_controls = revo_add_controls,
	},
	{ } /* terminator */
};