tuner-xc2028.c 16.5 KB
Newer Older
1 2 3
/* tuner-xc2028
 *
 * Copyright (c) 2007 Mauro Carvalho Chehab (mchehab@infradead.org)
4
 *
5 6
 * Copyright (c) 2007 Michel Ludwig (michel.ludwig@gmail.com)
 *       - frontend interface
7
 *
8 9 10 11 12 13
 * This code is placed under the terms of the GNU General Public License v2
 */

#include <linux/i2c.h>
#include <asm/div64.h>
#include <linux/firmware.h>
14
#include <linux/videodev2.h>
15
#include <linux/delay.h>
16
#include <media/tuner.h>
17
#include <linux/mutex.h>
18
#include "tuner-i2c.h"
19
#include "tuner-xc2028.h"
20
#include "tuner-xc2028-types.h"
21

22 23 24
#include <linux/dvb/frontend.h>
#include "dvb_frontend.h"

25
#define PREFIX "xc2028"
26 27

static LIST_HEAD(xc2028_list);
28 29 30 31 32 33 34
/* struct for storing firmware table */
struct firmware_description {
	unsigned int  type;
	v4l2_std_id   id;
	unsigned char *ptr;
	unsigned int  size;
};
35 36

struct xc2028_data {
37 38 39 40 41 42 43
	struct list_head        xc2028_list;
	struct tuner_i2c_props  i2c_props;
	int                     (*tuner_callback) (void *dev,
						   int command, int arg);
	struct device           *dev;
	void			*video_dev;
	int			count;
44 45 46 47 48 49 50 51
	__u32			frequency;

	struct firmware_description *firm;
	int			firm_size;

	__u16			version;

	struct xc2028_ctrl	ctrl;
52

53 54 55 56 57 58
	v4l2_std_id		firm_type;	   /* video stds supported
							by current firmware */
	fe_bandwidth_t		bandwidth;	   /* Firmware bandwidth:
							      6M, 7M or 8M */
	int			need_load_generic; /* The generic firmware
							      were loaded? */
59 60 61

	int			max_len;	/* Max firmware chunk */

62 63
	enum tuner_mode	mode;
	struct i2c_client	*i2c_client;
64 65

	struct mutex lock;
66 67
};

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
#define i2c_send(rc, priv, buf, size) do {				\
	rc = tuner_i2c_xfer_send(&priv->i2c_props, buf, size);		\
	if (size != rc)							\
		tuner_info("i2c output error: rc = %d (should be %d)\n",\
			   rc, (int)size);				\
} while (0)

#define i2c_rcv(rc, priv, buf, size) do {				\
	rc = tuner_i2c_xfer_recv(&priv->i2c_props, buf, size);		\
	if (size != rc)							\
		tuner_info("i2c input error: rc = %d (should be %d)\n",	\
			   rc, (int)size); 				\
} while (0)

#define send_seq(priv, data...)	do {					\
	int rc;								\
84
	static u8 _val[] = data;					\
85
	if (sizeof(_val) !=						\
86
			(rc = tuner_i2c_xfer_send(&priv->i2c_props,	\
87
						_val, sizeof(_val)))) {	\
88 89
		tuner_info("Error on line %d: %d\n", __LINE__, rc);	\
		return -EINVAL;						\
90
	}								\
91 92
	msleep(10);							\
} while (0)
93

94
static unsigned int xc2028_get_reg(struct xc2028_data *priv, u16 reg)
95 96
{
	int rc;
97
	unsigned char buf[2];
98 99

	tuner_info("%s called\n", __FUNCTION__);
100

101 102
	buf[0] = reg>>8;
	buf[1] = (unsigned char) reg;
103

104
	i2c_send(rc, priv, buf, 2);
105
	if (rc < 0)
106 107
		return rc;

108
	i2c_rcv(rc, priv, buf, 2);
109
	if (rc < 0)
110 111
		return rc;

112
	return (buf[1]) | (buf[0] << 8);
113 114
}

115
static void free_firmware(struct xc2028_data *priv)
116
{
117 118 119 120 121
	int i;

	if (!priv->firm)
		return;

122 123 124
	for (i = 0; i < priv->firm_size; i++)
		kfree(priv->firm[i].ptr);

125 126
	kfree(priv->firm);

127
	priv->firm = NULL;
128 129 130
	priv->need_load_generic = 1;
}

131
static int load_all_firmwares(struct dvb_frontend *fe)
132 133
{
	struct xc2028_data    *priv = fe->tuner_priv;
134
	const struct firmware *fw   = NULL;
135
	unsigned char         *p, *endp;
136 137
	int                   rc = 0;
	int		      n, n_array;
138
	char		      name[33];
139

140 141
	tuner_info("%s called\n", __FUNCTION__);

142 143
	tuner_info("Loading firmware %s\n", priv->ctrl.fname);
	rc = request_firmware(&fw, priv->ctrl.fname, priv->dev);
144
	if (rc < 0) {
145
		if (rc == -ENOENT)
146 147
			tuner_info("Error: firmware %s not found.\n",
				   priv->ctrl.fname);
148
		else
149 150
			tuner_info("Error %d while requesting firmware %s \n",
				   rc, priv->ctrl.fname);
151

152 153
		return rc;
	}
154 155
	p = fw->data;
	endp = p + fw->size;
156

157
	if (fw->size < sizeof(name) - 1 + 2) {
158
		tuner_info("Error: firmware size is zero!\n");
159
		rc = -EINVAL;
160
		goto done;
161
	}
162

163 164 165
	memcpy(name, p, sizeof(name) - 1);
	name[sizeof(name) - 1] = 0;
	p += sizeof(name) - 1;
166

167
	priv->version = le16_to_cpu(*(__u16 *) p);
168 169 170
	p += 2;

	tuner_info("firmware: %s, ver %d.%d\n", name,
171
		   priv->version >> 8, priv->version & 0xff);
172

173
	if (p + 2 > endp)
174 175
		goto corrupt;

176
	n_array = le16_to_cpu(*(__u16 *) p);
177 178 179 180
	p += 2;

	tuner_info("there are %d firmwares at %s\n", n_array, priv->ctrl.fname);

181
	priv->firm = kzalloc(sizeof(*priv->firm) * n_array, GFP_KERNEL);
182 183 184

	if (!fw) {
		tuner_info("Not enough memory for loading firmware.\n");
185
		rc = -ENOMEM;
186
		goto done;
187 188
	}

189
	priv->firm_size = n_array;
190 191
	n = -1;
	while (p < endp) {
192 193 194 195 196 197 198 199 200 201
		__u32 type, size;
		v4l2_std_id id;

		n++;
		if (n >= n_array) {
			tuner_info("Too much firmwares at the file\n");
			goto corrupt;
		}

		/* Checks if there's enough bytes to read */
202
		if (p + sizeof(type) + sizeof(id) + sizeof(size) > endp) {
203 204 205 206
			tuner_info("Lost firmware!\n");
			goto corrupt;
		}

207
		type = le32_to_cpu(*(__u32 *) p);
208 209
		p += sizeof(type);

210
		id = le64_to_cpu(*(v4l2_std_id *) p);
211 212
		p += sizeof(id);

213
		size = le32_to_cpu(*(v4l2_std_id *) p);
214 215
		p += sizeof(size);

216
		if ((!size) || (size + p > endp)) {
217
			tuner_info("Firmware type %x, id %lx corrupt\n",
218
				   type, (unsigned long)id);
219 220 221
			goto corrupt;
		}

222
		priv->firm[n].ptr = kzalloc(size, GFP_KERNEL);
223 224
		if (!priv->firm[n].ptr) {
			tuner_info("Not enough memory.\n");
225
			rc = -ENOMEM;
226 227 228
			goto err;
		}
		tuner_info("Loading firmware type %x, id %lx, size=%d.\n",
229
			   type, (unsigned long)id, size);
230 231 232 233 234 235 236 237 238

		memcpy(priv->firm[n].ptr, p, size);
		priv->firm[n].type = type;
		priv->firm[n].id   = id;
		priv->firm[n].size = size;

		p += size;
	}

239
	if (n + 1 != priv->firm_size) {
240 241 242 243 244 245 246
		tuner_info("Firmware file is incomplete!\n");
		goto corrupt;
	}

	goto done;

corrupt:
247
	rc = -EINVAL;
248 249 250 251 252 253 254 255 256 257 258 259 260 261
	tuner_info("Error: firmware file is corrupted!\n");

err:
	tuner_info("Releasing loaded firmware file.\n");

	free_firmware(priv);

done:
	release_firmware(fw);
	tuner_info("Firmware files loaded.\n");

	return rc;
}

262 263
static int load_firmware(struct dvb_frontend *fe, unsigned int type,
			 v4l2_std_id * id)
264 265
{
	struct xc2028_data *priv = fe->tuner_priv;
266 267
	int                i, rc;
	unsigned char      *p, *endp, buf[priv->max_len];
268 269 270 271

	tuner_info("%s called\n", __FUNCTION__);

	if (!priv->firm) {
272
		printk(KERN_ERR PREFIX "Error! firmware not loaded\n");
273 274 275 276
		return -EINVAL;
	}

	if ((type == 0) && (*id == 0))
277
		*id = V4L2_STD_PAL;
278 279

	/* Seek for exact match */
280 281
	for (i = 0; i < priv->firm_size; i++) {
		if ((type == priv->firm[i].type) && (*id == priv->firm[i].id))
282 283 284 285
			goto found;
	}

	/* Seek for generic video standard match */
286 287
	for (i = 0; i < priv->firm_size; i++) {
		if ((type == priv->firm[i].type) && (*id & priv->firm[i].id))
288 289 290 291 292
			goto found;
	}

	/*FIXME: Would make sense to seek for type "hint" match ? */

293 294
	tuner_info("Can't find firmware for type=%x, id=%lx\n", type,
		   (long int)*id);
295 296 297 298
	return -EINVAL;

found:
	*id = priv->firm[i].id;
299
	tuner_info("Found firmware for type=%x, id=%lx\n", type, (long int)*id);
300 301 302 303 304 305

	p = priv->firm[i].ptr;

	if (!p) {
		printk(KERN_ERR PREFIX "Firmware pointer were freed!");
		return -EINVAL;
306
	}
307
	endp = p + priv->firm[i].size;
308

309
	while (p < endp) {
310 311 312
		__u16 size;

		/* Checks if there's enough bytes to read */
313
		if (p + sizeof(size) > endp) {
314 315 316 317
			tuner_info("missing bytes\n");
			return -EINVAL;
		}

318
		size = le16_to_cpu(*(__u16 *) p);
319 320 321 322 323 324
		p += sizeof(size);

		if (size == 0xffff)
			return 0;

		if (!size) {
325
			/* Special callback command received */
326
			rc = priv->tuner_callback(priv->video_dev,
327 328
						  XC2028_TUNER_RESET, 0);
			if (rc < 0) {
329
				tuner_info("Error at RESET code %d\n",
330
					   (*p) & 0x7f);
331
				return -EINVAL;
332 333 334
			}
			continue;
		}
335 336 337

		/* Checks for a sleep command */
		if (size & 0x8000) {
338
			msleep(size & 0x7fff);
339
			continue;
340 341
		}

342 343
		if ((size + p > endp)) {
			tuner_info("missing bytes: need %d, have %d\n",
344
				   size, (int)(endp - p));
345 346
			return -EINVAL;
		}
347

348
		buf[0] = *p;
349
		p++;
350
		size--;
351

352
		/* Sends message chunks */
353 354 355
		while (size > 0) {
			int len = (size < priv->max_len - 1) ?
				   size : priv->max_len - 1;
356

357
			memcpy(buf + 1, p, len);
358

359 360 361
			i2c_send(rc, priv, buf, len + 1);
			if (rc < 0) {
				tuner_info("%d returned from send\n", rc);
362 363 364 365 366 367 368 369
				return -EINVAL;
			}

			p += len;
			size -= len;
		}
	}
	return -EINVAL;
370 371
}

372
static int check_firmware(struct dvb_frontend *fe, enum tuner_mode new_mode,
373
			  v4l2_std_id std, fe_bandwidth_t bandwidth)
374
{
375
	struct xc2028_data      *priv = fe->tuner_priv;
376
	int			rc, version, hwmodel;
377 378
	v4l2_std_id		std0 = 0;
	unsigned int		type0 = 0, type = 0;
379
	int			change_digital_bandwidth;
380

381
	tuner_info("%s called\n", __FUNCTION__);
382

383 384 385 386
	if (!priv->firm) {
		if (!priv->ctrl.fname)
			return -EINVAL;

387 388
		rc = load_all_firmwares(fe);
		if (rc < 0)
389 390 391
			return rc;
	}

392 393
	tuner_info("I am in mode %u and I should switch to mode %i\n",
		   priv->mode, new_mode);
394 395

	/* first of all, determine whether we have switched the mode */
396
	if (new_mode != priv->mode) {
397 398
		priv->mode = new_mode;
		priv->need_load_generic = 1;
399 400
	}

401
	change_digital_bandwidth = (priv->mode == T_DIGITAL_TV
402
				    && bandwidth != priv->bandwidth) ? 1 : 0;
403
	tuner_info("old bandwidth %u, new bandwidth %u\n", priv->bandwidth,
404
		   bandwidth);
405

406
	if (priv->need_load_generic) {
407
		/* Reset is needed before loading firmware */
408 409
		rc = priv->tuner_callback(priv->video_dev,
					  XC2028_TUNER_RESET, 0);
410
		if (rc < 0)
411 412
			return rc;

413
		type0 = BASE;
414 415 416 417

		if (priv->ctrl.type == XC2028_FIRM_MTS)
			type0 |= MTS;

418
		if (priv->bandwidth == 8)
419 420 421 422 423
			type0 |= F8MHZ;

		/* FIXME: How to load FM and FM|INPUT1 firmwares? */

		rc = load_firmware(fe, type0, &std0);
424
		if (rc < 0) {
425 426
			tuner_info("Error %d while loading generic firmware\n",
				   rc);
427
			return rc;
428
		}
429

430 431 432 433
		priv->need_load_generic = 0;
		priv->firm_type = 0;
		if (priv->mode == T_DIGITAL_TV)
			change_digital_bandwidth = 1;
434 435
	}

436
	tuner_info("I should change bandwidth %u\n", change_digital_bandwidth);
437 438

	if (change_digital_bandwidth) {
439 440 441 442 443 444

		/*FIXME: Should allow selecting between D2620 and D2633 */
		type |= D2620;

		/* FIXME: When should select a DTV78 firmware?
		 */
445
		switch (bandwidth) {
446 447
		case BANDWIDTH_8_MHZ:
			type |= DTV8;
448
			break;
449 450
		case BANDWIDTH_7_MHZ:
			type |= DTV7;
451
			break;
452 453 454
		case BANDWIDTH_6_MHZ:
			/* FIXME: Should allow select also ATSC */
			type |= DTV6_QAM;
455 456
			break;

457 458
		default:
			tuner_info("error: bandwidth not supported.\n");
459
		};
460
		priv->bandwidth = bandwidth;
461 462
	}

463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
	/* Load INIT1, if needed */
	tuner_info("Trying to load init1 firmware\n");
	type0 = BASE | INIT1 | priv->ctrl.type;
	if (priv->ctrl.type == XC2028_FIRM_MTS)
		type0 |= MTS;

	/* FIXME: Should handle errors - if INIT1 found */
	rc = load_firmware(fe, type0, &std0);

	/* FIXME: Should add support for FM radio
	 */

	if (priv->ctrl.type == XC2028_FIRM_MTS)
		type |= MTS;

478
	tuner_info("firmware standard to load: %08lx\n", (unsigned long)std);
479
	if (priv->firm_type & std) {
480
		tuner_info("no need to load a std-specific firmware.\n");
481
		return 0;
482
	}
483

484
	rc = load_firmware(fe, type, &std);
485
	if (rc < 0)
486 487
		return rc;

488 489 490 491 492 493 494
	version = xc2028_get_reg(priv, 0x0004);
	hwmodel = xc2028_get_reg(priv, 0x0008);

	tuner_info("Device is Xceive %d version %d.%d, "
		   "firmware version %d.%d\n",
		   hwmodel, (version & 0xf000) >> 12, (version & 0xf00) >> 8,
		   (version & 0xf0) >> 4, version & 0xf);
495

496
	priv->firm_type = std;
497 498 499 500

	return 0;
}

501
static int xc2028_signal(struct dvb_frontend *fe, u16 *strength)
502
{
503
	struct xc2028_data *priv = fe->tuner_priv;
504
	int                frq_lock, signal = 0;
505

506
	tuner_info("%s called\n", __FUNCTION__);
507

508
	mutex_lock(&priv->lock);
509

510 511
	*strength = 0;

512 513
	/* Sync Lock Indicator */
	frq_lock = xc2028_get_reg(priv, 0x0002);
514
	if (frq_lock <= 0)
515
		goto ret;
516 517 518

	/* Frequency is locked. Return signal quality */

519 520
	/* Get SNR of the video signal */
	signal = xc2028_get_reg(priv, 0x0040);
521

522 523
	if (signal <= 0)
		signal = frq_lock;
524 525

ret:
526 527 528
	mutex_unlock(&priv->lock);

	*strength = signal;
529

530
	return 0;
531 532 533 534
}

#define DIV 15625

535 536 537
static int generic_set_tv_freq(struct dvb_frontend *fe, u32 freq /* in Hz */ ,
			       enum tuner_mode new_mode,
			       v4l2_std_id std, fe_bandwidth_t bandwidth)
538
{
539
	struct xc2028_data *priv = fe->tuner_priv;
540 541 542
	int		   rc = -EINVAL;
	unsigned char	   buf[5];
	u32		   div, offset = 0;
543

544 545
	tuner_info("%s called\n", __FUNCTION__);

546 547
	mutex_lock(&priv->lock);

548 549
	/* HACK: It seems that specific firmware need to be reloaded
	   when freq is changed */
550

551
	priv->firm_type = 0;
552

553
	/* Reset GPIO 1 */
554
	rc = priv->tuner_callback(priv->video_dev, XC2028_TUNER_RESET, 0);
555
	if (rc < 0)
556 557
		goto ret;

558
	msleep(10);
559
	tuner_info("should set frequency %d kHz)\n", freq / 1000);
560

561
	if (check_firmware(fe, new_mode, std, bandwidth) < 0)
562
		goto ret;
563

564
	if (new_mode == T_DIGITAL_TV)
565
		offset = 2750000;
566

567
	div = (freq - offset + DIV / 2) / DIV;
568

569
	/* CMD= Set frequency */
570

571
	if (priv->version < 0x0202) {
572 573 574 575 576
		send_seq(priv, {0x00, 0x02, 0x00, 0x00});
	} else {
		send_seq(priv, {0x80, 0x02, 0x00, 0x00});
	}

577
	rc = priv->tuner_callback(priv->video_dev, XC2028_RESET_CLK, 1);
578
	if (rc < 0)
579
		goto ret;
580 581

	msleep(10);
582

583 584 585 586 587
	buf[0] = 0xff & (div >> 24);
	buf[1] = 0xff & (div >> 16);
	buf[2] = 0xff & (div >> 8);
	buf[3] = 0xff & (div);
	buf[4] = 0;
588

589
	i2c_send(rc, priv, buf, sizeof(buf));
590
	if (rc < 0)
591
		goto ret;
592 593
	msleep(100);

594
	priv->frequency = freq;
595

596
	printk("divider= %02x %02x %02x %02x (freq=%d.%02d)\n",
597 598
	       buf[1], buf[2], buf[3], buf[4],
	       freq / 1000000, (freq % 1000000) / 10000);
599

600
	rc = 0;
601

602 603
ret:
	mutex_unlock(&priv->lock);
604

605
	return rc;
606 607
}

608
static int xc2028_set_tv_freq(struct dvb_frontend *fe,
609
			      struct analog_parameters *p)
610
{
611
	struct xc2028_data *priv = fe->tuner_priv;
612

613
	tuner_info("%s called\n", __FUNCTION__);
614

615 616
	return generic_set_tv_freq(fe, 62500l * p->frequency, T_ANALOG_TV,
				   p->std, BANDWIDTH_8_MHZ /* NOT USED */);
617
}
618

619 620
static int xc2028_set_params(struct dvb_frontend *fe,
			     struct dvb_frontend_parameters *p)
621
{
622
	struct xc2028_data *priv = fe->tuner_priv;
623

624
	tuner_info("%s called\n", __FUNCTION__);
625

626 627
	/* FIXME: Only OFDM implemented */
	if (fe->ops.info.type != FE_OFDM) {
628
		tuner_info("DTV type not implemented.\n");
629
		return -EINVAL;
630 631
	}

632
	return generic_set_tv_freq(fe, p->frequency, T_DIGITAL_TV,
633 634
				   0 /* NOT USED */,
				   p->u.ofdm.bandwidth);
635 636

}
637

638
static int xc2028_dvb_release(struct dvb_frontend *fe)
639
{
640 641 642
	struct xc2028_data *priv = fe->tuner_priv;

	tuner_info("%s called\n", __FUNCTION__);
643

644
	priv->count--;
645

646
	if (!priv->count) {
647 648
		list_del(&priv->xc2028_list);

649
		kfree(priv->ctrl.fname);
650 651

		free_firmware(priv);
652
		kfree(priv);
653
	}
654 655 656 657

	return 0;
}

658
static int xc2028_get_frequency(struct dvb_frontend *fe, u32 *frequency)
659
{
660
	struct xc2028_data *priv = fe->tuner_priv;
661

662
	tuner_info("%s called\n", __FUNCTION__);
663

664
	*frequency = priv->frequency;
665 666 667 668

	return 0;
}

669
static int xc2028_set_config(struct dvb_frontend *fe, void *priv_cfg)
670 671 672 673 674 675 676 677 678
{
	struct xc2028_data *priv = fe->tuner_priv;
	struct xc2028_ctrl *p    = priv_cfg;

	tuner_info("%s called\n", __FUNCTION__);

	priv->ctrl.type = p->type;

	if (p->fname) {
679
		kfree(priv->ctrl.fname);
680

681
		priv->ctrl.fname = kmalloc(strlen(p->fname) + 1, GFP_KERNEL);
682 683 684 685 686 687 688
		if (!priv->ctrl.fname)
			return -ENOMEM;

		free_firmware(priv);
		strcpy(priv->ctrl.fname, p->fname);
	}

689
	if (p->max_len > 0)
690 691
		priv->max_len = p->max_len;

692 693 694 695 696
	tuner_info("%s OK\n", __FUNCTION__);

	return 0;
}

697
static const struct dvb_tuner_ops xc2028_dvb_tuner_ops = {
698
	.info = {
699 700 701 702 703
		 .name = "Xceive XC3028",
		 .frequency_min = 42000000,
		 .frequency_max = 864000000,
		 .frequency_step = 50000,
		 },
704

705
	.set_config	   = xc2028_set_config,
706 707 708 709 710
	.set_analog_params = xc2028_set_tv_freq,
	.release           = xc2028_dvb_release,
	.get_frequency     = xc2028_get_frequency,
	.get_rf_strength   = xc2028_signal,
	.set_params        = xc2028_set_params,
711 712 713

};

714
int xc2028_attach(struct dvb_frontend *fe, struct i2c_adapter *i2c_adap,
715
		  u8 i2c_addr, struct device *dev, void *video_dev,
716
		  int (*tuner_callback) (void *dev, int command, int arg))
717
{
718
	struct xc2028_data *priv;
719

720
	printk(KERN_INFO PREFIX "Xcv2028/3028 init called!\n");
721

722 723 724 725 726 727 728
	if (NULL == dev)
		return -ENODEV;

	if (NULL == video_dev)
		return -ENODEV;

	if (!tuner_callback) {
729
		printk(KERN_ERR PREFIX "No tuner callback!\n");
730 731 732 733
		return -EINVAL;
	}

	list_for_each_entry(priv, &xc2028_list, xc2028_list) {
734
		if (priv->dev == dev)
735 736 737 738 739 740 741
			dev = NULL;
	}

	if (dev) {
		priv = kzalloc(sizeof(*priv), GFP_KERNEL);
		if (priv == NULL)
			return -ENOMEM;
742

743
		fe->tuner_priv = priv;
744

745 746
		priv->bandwidth = BANDWIDTH_6_MHZ;
		priv->need_load_generic = 1;
747 748 749 750 751 752
		priv->mode = T_UNINITIALIZED;
		priv->i2c_props.addr = i2c_addr;
		priv->i2c_props.adap = i2c_adap;
		priv->dev = dev;
		priv->video_dev = video_dev;
		priv->tuner_callback = tuner_callback;
753 754
		priv->max_len = 13;

755 756 757

		mutex_init(&priv->lock);

758
		list_add_tail(&priv->xc2028_list, &xc2028_list);
759
	}
760
	priv->count++;
761 762

	memcpy(&fe->ops.tuner_ops, &xc2028_dvb_tuner_ops,
763
	       sizeof(xc2028_dvb_tuner_ops));
764 765 766 767 768

	tuner_info("type set to %s\n", "XCeive xc2028/xc3028 tuner");

	return 0;
}
769 770
EXPORT_SYMBOL(xc2028_attach);

771
MODULE_DESCRIPTION("Xceive xc2028/xc3028 tuner driver");
772
MODULE_AUTHOR("Michel Ludwig <michel.ludwig@gmail.com>");
773 774
MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>");
MODULE_LICENSE("GPL");