summaryrefslogblamecommitdiffstats
path: root/drivers/media/tuners/r820t.c
blob: 79ab2b74bd5264739954880bb900dd001073d13f (plain) (tree)
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
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980




































                                                                             
                         
                      

                      



























































































































































































































































































                                                                                       













                                                                   














































































                                                                                  
                  















                                                                              

                                       






























































































































































































































































































































































































































































































































































                                                                                                    
                                       























































































































                                                                             











                                                                 







































































                                                                          












                                                                



                                                                




                                                                         


                                          




                                                




                                                              





                                                                 
    



















































































































                                                                            

                                                                     









                                                 
                                


                                             


















                                                                     


                                             
                                  
 

                                                           









                                                 
                                


                                             
                                 


                                             
                                  









                                                             
               











                                                                    











                                                                           











                                                                             
                                











                                                                            
                                  








                                                               
                   
 



                                             


                                           
                                 



                                                                        
                              

         




                                             



                                                              





























































































































                                                                              
/*
 * Rafael Micro R820T driver
 *
 * Copyright (C) 2013 Mauro Carvalho Chehab <mchehab@redhat.com>
 *
 * This driver was written from scratch, based on an existing driver
 * that it is part of rtl-sdr git tree, released under GPLv2:
 *	https://groups.google.com/forum/#!topic/ultra-cheap-sdr/Y3rBEOFtHug
 *	https://github.com/n1gp/gr-baz
 *
 * From what I understood from the threads, the original driver was converted
 * to userspace from a Realtek tree. I couldn't find the original tree.
 * However, the original driver look awkward on my eyes. So, I decided to
 * write a new version from it from the scratch, while trying to reproduce
 * everything found there.
 *
 * TODO:
 *	After locking, the original driver seems to have some routines to
 *		improve reception. This was not implemented here yet.
 *
 *	RF Gain set/get is not implemented.
 *
 *    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.
 *
 *    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.
 *
 */

#include <linux/videodev2.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/bitrev.h>
#include <asm/div64.h>

#include "tuner-i2c.h"
#include "r820t.h"

/*
 * FIXME: I think that there are only 32 registers, but better safe than
 *	  sorry. After finishing the driver, we may review it.
 */
#define REG_SHADOW_START	5
#define NUM_REGS		27

#define VER_NUM  49

static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable verbose debug messages");

/*
 * enums and structures
 */

enum xtal_cap_value {
	XTAL_LOW_CAP_30P = 0,
	XTAL_LOW_CAP_20P,
	XTAL_LOW_CAP_10P,
	XTAL_LOW_CAP_0P,
	XTAL_HIGH_CAP_0P
};

struct r820t_priv {
	struct list_head		hybrid_tuner_instance_list;
	const struct r820t_config	*cfg;
	struct tuner_i2c_props		i2c_props;
	struct mutex			lock;

	u8				regs[NUM_REGS];
	u8				buf[NUM_REGS + 1];
	enum xtal_cap_value		xtal_cap_sel;
	u16				pll;	/* kHz */
	u32				int_freq;
	u8				fil_cal_code;
	bool				imr_done;

	/* Store current mode */
	u32				delsys;
	enum v4l2_tuner_type		type;
	v4l2_std_id			std;
	u32				bw;	/* in MHz */

	bool				has_lock;
};

struct r820t_freq_range {
	u32	freq;
	u8	open_d;
	u8	rf_mux_ploy;
	u8	tf_c;
	u8	xtal_cap20p;
	u8	xtal_cap10p;
	u8	xtal_cap0p;
	u8	imr_mem;		/* Not used, currently */
};

#define VCO_POWER_REF   0x02

/*
 * Static constants
 */

static LIST_HEAD(hybrid_tuner_instance_list);
static DEFINE_MUTEX(r820t_list_mutex);

/* Those initial values start from REG_SHADOW_START */
static const u8 r820t_init_array[NUM_REGS] = {
	0x83, 0x32, 0x75,			/* 05 to 07 */
	0xc0, 0x40, 0xd6, 0x6c,			/* 08 to 0b */
	0xf5, 0x63, 0x75, 0x68,			/* 0c to 0f */
	0x6c, 0x83, 0x80, 0x00,			/* 10 to 13 */
	0x0f, 0x00, 0xc0, 0x30,			/* 14 to 17 */
	0x48, 0xcc, 0x60, 0x00,			/* 18 to 1b */
	0x54, 0xae, 0x4a, 0xc0			/* 1c to 1f */
};

/* Tuner frequency ranges */
static const struct r820t_freq_range freq_ranges[] = {
	{
		.freq = 0,
		.open_d = 0x08,		/* low */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0xdf,		/* R27[7:0]  band2,band0 */
		.xtal_cap20p = 0x02,	/* R16[1:0]  20pF (10)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 50,		/* Start freq, in MHz */
		.open_d = 0x08,		/* low */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0xbe,		/* R27[7:0]  band4,band1  */
		.xtal_cap20p = 0x02,	/* R16[1:0]  20pF (10)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 55,		/* Start freq, in MHz */
		.open_d = 0x08,		/* low */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x8b,		/* R27[7:0]  band7,band4 */
		.xtal_cap20p = 0x02,	/* R16[1:0]  20pF (10)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 60,		/* Start freq, in MHz */
		.open_d = 0x08,		/* low */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x7b,		/* R27[7:0]  band8,band4 */
		.xtal_cap20p = 0x02,	/* R16[1:0]  20pF (10)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 65,		/* Start freq, in MHz */
		.open_d = 0x08,		/* low */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x69,		/* R27[7:0]  band9,band6 */
		.xtal_cap20p = 0x02,	/* R16[1:0]  20pF (10)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 70,		/* Start freq, in MHz */
		.open_d = 0x08,		/* low */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x58,		/* R27[7:0]  band10,band7 */
		.xtal_cap20p = 0x02,	/* R16[1:0]  20pF (10)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 75,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x44,		/* R27[7:0]  band11,band11 */
		.xtal_cap20p = 0x02,	/* R16[1:0]  20pF (10)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 80,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x44,		/* R27[7:0]  band11,band11 */
		.xtal_cap20p = 0x02,	/* R16[1:0]  20pF (10)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 90,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x34,		/* R27[7:0]  band12,band11 */
		.xtal_cap20p = 0x01,	/* R16[1:0]  10pF (01)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 100,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x34,		/* R27[7:0]  band12,band11 */
		.xtal_cap20p = 0x01,	/* R16[1:0]  10pF (01)    */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 0,
	}, {
		.freq = 110,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x24,		/* R27[7:0]  band13,band11 */
		.xtal_cap20p = 0x01,	/* R16[1:0]  10pF (01)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 1,
	}, {
		.freq = 120,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x24,		/* R27[7:0]  band13,band11 */
		.xtal_cap20p = 0x01,	/* R16[1:0]  10pF (01)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 1,
	}, {
		.freq = 140,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x14,		/* R27[7:0]  band14,band11 */
		.xtal_cap20p = 0x01,	/* R16[1:0]  10pF (01)   */
		.xtal_cap10p = 0x01,
		.xtal_cap0p = 0x00,
		.imr_mem = 1,
	}, {
		.freq = 180,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x13,		/* R27[7:0]  band14,band12 */
		.xtal_cap20p = 0x00,	/* R16[1:0]  0pF (00)   */
		.xtal_cap10p = 0x00,
		.xtal_cap0p = 0x00,
		.imr_mem = 1,
	}, {
		.freq = 220,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x13,		/* R27[7:0]  band14,band12 */
		.xtal_cap20p = 0x00,	/* R16[1:0]  0pF (00)   */
		.xtal_cap10p = 0x00,
		.xtal_cap0p = 0x00,
		.imr_mem = 2,
	}, {
		.freq = 250,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x11,		/* R27[7:0]  highest,highest */
		.xtal_cap20p = 0x00,	/* R16[1:0]  0pF (00)   */
		.xtal_cap10p = 0x00,
		.xtal_cap0p = 0x00,
		.imr_mem = 2,
	}, {
		.freq = 280,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x02,	/* R26[7:6]=0 (LPF)  R26[1:0]=2 (low) */
		.tf_c = 0x00,		/* R27[7:0]  highest,highest */
		.xtal_cap20p = 0x00,	/* R16[1:0]  0pF (00)   */
		.xtal_cap10p = 0x00,
		.xtal_cap0p = 0x00,
		.imr_mem = 2,
	}, {
		.freq = 310,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x41,	/* R26[7:6]=1 (bypass)  R26[1:0]=1 (middle) */
		.tf_c = 0x00,		/* R27[7:0]  highest,highest */
		.xtal_cap20p = 0x00,	/* R16[1:0]  0pF (00)   */
		.xtal_cap10p = 0x00,
		.xtal_cap0p = 0x00,
		.imr_mem = 2,
	}, {
		.freq = 450,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x41,	/* R26[7:6]=1 (bypass)  R26[1:0]=1 (middle) */
		.tf_c = 0x00,		/* R27[7:0]  highest,highest */
		.xtal_cap20p = 0x00,	/* R16[1:0]  0pF (00)   */
		.xtal_cap10p = 0x00,
		.xtal_cap0p = 0x00,
		.imr_mem = 3,
	}, {
		.freq = 588,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x40,	/* R26[7:6]=1 (bypass)  R26[1:0]=0 (highest) */
		.tf_c = 0x00,		/* R27[7:0]  highest,highest */
		.xtal_cap20p = 0x00,	/* R16[1:0]  0pF (00)   */
		.xtal_cap10p = 0x00,
		.xtal_cap0p = 0x00,
		.imr_mem = 3,
	}, {
		.freq = 650,		/* Start freq, in MHz */
		.open_d = 0x00,		/* high */
		.rf_mux_ploy = 0x40,	/* R26[7:6]=1 (bypass)  R26[1:0]=0 (highest) */
		.tf_c = 0x00,		/* R27[7:0]  highest,highest */
		.xtal_cap20p = 0x00,	/* R16[1:0]  0pF (00)   */
		.xtal_cap10p = 0x00,
		.xtal_cap0p = 0x00,
		.imr_mem = 4,
	}
};

static int r820t_xtal_capacitor[][2] = {
	{ 0x0b, XTAL_LOW_CAP_30P },
	{ 0x02, XTAL_LOW_CAP_20P },
	{ 0x01, XTAL_LOW_CAP_10P },
	{ 0x00, XTAL_LOW_CAP_0P  },
	{ 0x10, XTAL_HIGH_CAP_0P },
};

/*
 * measured with a Racal 6103E GSM test set at 928 MHz with -60 dBm
 * input power, for raw results see:
 *	http://steve-m.de/projects/rtl-sdr/gain_measurement/r820t/
 */

static const int r820t_lna_gain_steps[]  = {
	0, 9, 13, 40, 38, 13, 31, 22, 26, 31, 26, 14, 19, 5, 35, 13
};

static const int r820t_mixer_gain_steps[]  = {
	0, 5, 10, 10, 19, 9, 10, 25, 17, 10, 8, 16, 13, 6, 3, -8
};

/*
 * I2C read/write code and shadow registers logic
 */
static void shadow_store(struct r820t_priv *priv, u8 reg, const u8 *val,
			 int len)
{
	int r = reg - REG_SHADOW_START;

	if (r < 0) {
		len += r;
		r = 0;
	}
	if (len <= 0)
		return;
	if (len > NUM_REGS)
		len = NUM_REGS;

	tuner_dbg("%s: prev  reg=%02x len=%d: %*ph\n",
		  __func__, r + REG_SHADOW_START, len, len, val);

	memcpy(&priv->regs[r], val, len);
}

static int r820t_write(struct r820t_priv *priv, u8 reg, const u8 *val,
		       int len)
{
	int rc, size, pos = 0;

	/* Store the shadow registers */
	shadow_store(priv, reg, val, len);

	do {
		if (len > priv->cfg->max_i2c_msg_len - 1)
			size = priv->cfg->max_i2c_msg_len - 1;
		else
			size = len;

		/* Fill I2C buffer */
		priv->buf[0] = reg;
		memcpy(&priv->buf[1], &val[pos], size);

		rc = tuner_i2c_xfer_send(&priv->i2c_props, priv->buf, size + 1);
		if (rc != size + 1) {
			tuner_info("%s: i2c wr failed=%d reg=%02x len=%d: %*ph\n",
				   __func__, rc, reg, size, size, &priv->buf[1]);
			if (rc < 0)
				return rc;
			return -EREMOTEIO;
		}
		tuner_dbg("%s: i2c wr reg=%02x len=%d: %*ph\n",
			  __func__, reg, size, size, &priv->buf[1]);

		reg += size;
		len -= size;
		pos += size;
	} while (len > 0);

	return 0;
}

static int r820t_write_reg(struct r820t_priv *priv, u8 reg, u8 val)
{
	return r820t_write(priv, reg, &val, 1);
}

static int r820t_write_reg_mask(struct r820t_priv *priv, u8 reg, u8 val,
				u8 bit_mask)
{
	int r = reg - REG_SHADOW_START;

	if (r >= 0 && r < NUM_REGS)
		val = (priv->regs[r] & ~bit_mask) | (val & bit_mask);
	else
		return -EINVAL;

	return r820t_write(priv, reg, &val, 1);
}

static int r820_read(struct r820t_priv *priv, u8 reg, u8 *val, int len)
{
	int rc, i;
	u8 *p = &priv->buf[1];

	priv->buf[0] = reg;

	rc = tuner_i2c_xfer_send_recv(&priv->i2c_props, priv->buf, 1, p, len);
	if (rc != len) {
		tuner_info("%s: i2c rd failed=%d reg=%02x len=%d: %*ph\n",
			   __func__, rc, reg, len, len, p);
		if (rc < 0)
			return rc;
		return -EREMOTEIO;
	}
	tuner_dbg("%s: i2c rd reg=%02x len=%d: %*ph\n",
		  __func__, reg, len, len, p);

	/* Copy data to the output buffer */
	for (i = 0; i < len; i++)
		val[i] = bitrev8(p[i]);

	return 0;
}

/*
 * r820t tuning logic
 */

static int r820t_set_mux(struct r820t_priv *priv, u32 freq)
{
	const struct r820t_freq_range *range;
	int i, rc;
	u8 val;

	/* Get the proper frequency range */
	freq = freq / 1000000;
	for (i = 0; i < ARRAY_SIZE(freq_ranges) - 1; i++) {
		if (freq < freq_ranges[i + 1].freq)
			break;
	}
	range = &freq_ranges[i];

	tuner_dbg("set r820t range#%d for frequency %d MHz\n", i, freq);

	/* Open Drain */
	rc = r820t_write_reg_mask(priv, 0x17, range->open_d, 0x08);
	if (rc < 0)
		return rc;

	/* RF_MUX,Polymux */
	rc = r820t_write_reg_mask(priv, 0x1a, range->rf_mux_ploy, 0xc3);
	if (rc < 0)
		return rc;

	/* TF BAND */
	rc = r820t_write_reg(priv, 0x1b, range->tf_c);
	if (rc < 0)
		return rc;

	/* XTAL CAP & Drive */
	switch (priv->xtal_cap_sel) {
	case XTAL_LOW_CAP_30P:
	case XTAL_LOW_CAP_20P:
		val = range->xtal_cap20p | 0x08;
		break;
	case XTAL_LOW_CAP_10P:
		val = range->xtal_cap10p | 0x08;
		break;
	case XTAL_HIGH_CAP_0P:
		val = range->xtal_cap0p | 0x00;
		break;
	default:
	case XTAL_LOW_CAP_0P:
		val = range->xtal_cap0p | 0x08;
		break;
	}
	rc = r820t_write_reg_mask(priv, 0x10, val, 0x0b);
	if (rc < 0)
		return rc;

	/*
	 * FIXME: the original driver has a logic there with preserves
	 * gain/phase from registers 8 and 9 reading the data from the
	 * registers before writing, if "IMF done". That code was sort of
	 * commented there, as the flag is always false.
	 */
	rc = r820t_write_reg_mask(priv, 0x08, 0, 0x3f);
	if (rc < 0)
		return rc;

	rc = r820t_write_reg_mask(priv, 0x09, 0, 0x3f);

	return rc;
}

static int r820t_set_pll(struct r820t_priv *priv, u32 freq)
{
	u64 tmp64, vco_freq;
	int rc, i;
	u32 vco_fra;		/* VCO contribution by SDM (kHz) */
	u32 vco_min  = 1770000;
	u32 vco_max  = vco_min * 2;
	u32 pll_ref;
	u16 n_sdm = 2;
	u16 sdm = 0;
	u8 mix_div = 2;
	u8 div_buf = 0;
	u8 div_num = 0;
	u8 ni, si, nint, vco_fine_tune, val;
	u8 data[5];

	freq = freq / 1000;	/* Frequency in kHz */

	pll_ref = priv->cfg->xtal / 1000;

	tuner_dbg("set r820t pll for frequency %d kHz = %d\n", freq, pll_ref);

	/* FIXME: this seems to be a hack - probably it can be removed */
	rc = r820t_write_reg_mask(priv, 0x10, 0x00, 0x00);
	if (rc < 0)
		return rc;

	/* set pll autotune = 128kHz */
	rc = r820t_write_reg_mask(priv, 0x1a, 0x00, 0x0c);
	if (rc < 0)
		return rc;

	/* set VCO current = 100 */
	rc = r820t_write_reg_mask(priv, 0x12, 0x80, 0xe0);
	if (rc < 0)
		return rc;

	/* Calculate divider */
	while (mix_div <= 64) {
		if (((freq * mix_div) >= vco_min) &&
		   ((freq * mix_div) < vco_max)) {
			div_buf = mix_div;
			while (div_buf > 2) {
				div_buf = div_buf >> 1;
				div_num++;
			}
			break;
		}
		mix_div = mix_div << 1;
	}

	rc = r820_read(priv, 0x00, data, sizeof(data));
	if (rc < 0)
		return rc;

	vco_fine_tune = (data[4] & 0x30) >> 4;

	if (vco_fine_tune > VCO_POWER_REF)
		div_num = div_num - 1;
	else if (vco_fine_tune < VCO_POWER_REF)
		div_num = div_num + 1;

	rc = r820t_write_reg_mask(priv, 0x10, div_num << 5, 0xe0);
	if (rc < 0)
		return rc;

	vco_freq = (u64)(freq * (u64)mix_div);

	tmp64 = vco_freq;
	do_div(tmp64, 2 * pll_ref);
	nint = (u8)tmp64;

	tmp64 = vco_freq - ((u64)2) * pll_ref * nint;
	do_div(tmp64, 1000);
	vco_fra  = (u16)(tmp64);

	pll_ref /= 1000;

	/* boundary spur prevention */
	if (vco_fra < pll_ref / 64) {
		vco_fra = 0;
	} else if (vco_fra > pll_ref * 127 / 64) {
		vco_fra = 0;
		nint++;
	} else if ((vco_fra > pll_ref * 127 / 128) && (vco_fra < pll_ref)) {
		vco_fra = pll_ref * 127 / 128;
	} else if ((vco_fra > pll_ref) && (vco_fra < pll_ref * 129 / 128)) {
		vco_fra = pll_ref * 129 / 128;
	}

	if (nint > 63) {
		tuner_info("No valid PLL values for %u kHz!\n", freq);
		return -EINVAL;
	}

	ni = (nint - 13) / 4;
	si = nint - 4 * ni - 13;

	rc = r820t_write_reg(priv, 0x14, ni + (si << 6));
	if (rc < 0)
		return rc;

	/* pw_sdm */
	if (!vco_fra)
		val = 0x08;
	else
		val = 0x00;

	rc = r820t_write_reg_mask(priv, 0x12, val, 0x08);
	if (rc < 0)
		return rc;

	/* sdm calculator */
	while (vco_fra > 1) {
		if (vco_fra > (2 * pll_ref / n_sdm)) {
			sdm = sdm + 32768 / (n_sdm / 2);
			vco_fra = vco_fra - 2 * pll_ref / n_sdm;
			if (n_sdm >= 0x8000)
				break;
		}
		n_sdm = n_sdm << 1;
	}

	rc = r820t_write_reg_mask(priv, 0x16, sdm >> 8, 0x08);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg_mask(priv, 0x15, sdm & 0xff, 0x08);
	if (rc < 0)
		return rc;

	for (i = 0; i < 2; i++) {
		/*
		 * FIXME: Rafael chips R620D, R828D and R828 seems to
		 * need 20 ms for analog TV
		 */
		msleep(10);

		/* Check if PLL has locked */
		rc = r820_read(priv, 0x00, data, 3);
		if (rc < 0)
			return rc;
		if (data[2] & 0x40)
			break;

		if (!i) {
			/* Didn't lock. Increase VCO current */
			rc = r820t_write_reg_mask(priv, 0x12, 0x60, 0xe0);
			if (rc < 0)
				return rc;
		}
	}

	if (!(data[2] & 0x40)) {
		priv->has_lock = false;
		return 0;
	}

	priv->has_lock = true;
	tuner_dbg("tuner has lock at frequency %d kHz\n", freq);

	/* set pll autotune = 8kHz */
	rc = r820t_write_reg_mask(priv, 0x1a, 0x08, 0x08);

	return rc;
}

static int r820t_sysfreq_sel(struct r820t_priv *priv, u32 freq,
			     enum v4l2_tuner_type type,
			     v4l2_std_id std,
			     u32 delsys)
{
	int rc;
	u8 mixer_top, lna_top, cp_cur, div_buf_cur, lna_vth_l, mixer_vth_l;
	u8 air_cable1_in, cable2_in, pre_dect, lna_discharge, filter_cur;

	tuner_dbg("adjusting tuner parameters for the standard\n");

	switch (delsys) {
	case SYS_DVBT:
		if ((freq == 506000000) || (freq == 666000000) ||
		   (freq == 818000000)) {
			mixer_top = 0x14;	/* mixer top:14 , top-1, low-discharge */
			lna_top = 0xe5;		/* detect bw 3, lna top:4, predet top:2 */
			cp_cur = 0x28;		/* 101, 0.2 */
			div_buf_cur = 0x20;	/* 10, 200u */
		} else {
			mixer_top = 0x24;	/* mixer top:13 , top-1, low-discharge */
			lna_top = 0xe5;		/* detect bw 3, lna top:4, predet top:2 */
			cp_cur = 0x38;		/* 111, auto */
			div_buf_cur = 0x30;	/* 11, 150u */
		}
		lna_vth_l = 0x53;		/* lna vth 0.84	,  vtl 0.64 */
		mixer_vth_l = 0x75;		/* mixer vth 1.04, vtl 0.84 */
		air_cable1_in = 0x00;
		cable2_in = 0x00;
		pre_dect = 0x40;
		lna_discharge = 14;
		filter_cur = 0x40;		/* 10, low */
		break;
	case SYS_DVBT2:
		mixer_top = 0x24;	/* mixer top:13 , top-1, low-discharge */
		lna_top = 0xe5;		/* detect bw 3, lna top:4, predet top:2 */
		lna_vth_l = 0x53;	/* lna vth 0.84	,  vtl 0.64 */
		mixer_vth_l = 0x75;	/* mixer vth 1.04, vtl 0.84 */
		air_cable1_in = 0x00;
		cable2_in = 0x00;
		pre_dect = 0x40;
		lna_discharge = 14;
		cp_cur = 0x38;		/* 111, auto */
		div_buf_cur = 0x30;	/* 11, 150u */
		filter_cur = 0x40;	/* 10, low */
		break;
	case SYS_ISDBT:
		mixer_top = 0x24;	/* mixer top:13 , top-1, low-discharge */
		lna_top = 0xe5;		/* detect bw 3, lna top:4, predet top:2 */
		lna_vth_l = 0x75;	/* lna vth 1.04	,  vtl 0.84 */
		mixer_vth_l = 0x75;	/* mixer vth 1.04, vtl 0.84 */
		air_cable1_in = 0x00;
		cable2_in = 0x00;
		pre_dect = 0x40;
		lna_discharge = 14;
		cp_cur = 0x38;		/* 111, auto */
		div_buf_cur = 0x30;	/* 11, 150u */
		filter_cur = 0x40;	/* 10, low */
		break;
	default: /* DVB-T 8M */
		mixer_top = 0x24;	/* mixer top:13 , top-1, low-discharge */
		lna_top = 0xe5;		/* detect bw 3, lna top:4, predet top:2 */
		lna_vth_l = 0x53;	/* lna vth 0.84	,  vtl 0.64 */
		mixer_vth_l = 0x75;	/* mixer vth 1.04, vtl 0.84 */
		air_cable1_in = 0x00;
		cable2_in = 0x00;
		pre_dect = 0x40;
		lna_discharge = 14;
		cp_cur = 0x38;		/* 111, auto */
		div_buf_cur = 0x30;	/* 11, 150u */
		filter_cur = 0x40;	/* 10, low */
		break;
	}

	rc = r820t_write_reg_mask(priv, 0x1d, lna_top, 0xc7);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg_mask(priv, 0x1c, mixer_top, 0xf8);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x0d, lna_vth_l);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x0e, mixer_vth_l);
	if (rc < 0)
		return rc;

	/* Air-IN only for Astrometa */
	rc = r820t_write_reg_mask(priv, 0x05, air_cable1_in, 0x60);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg_mask(priv, 0x06, cable2_in, 0x08);
	if (rc < 0)
		return rc;

	rc = r820t_write_reg_mask(priv, 0x11, cp_cur, 0x38);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg_mask(priv, 0x17, div_buf_cur, 0x30);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg_mask(priv, 0x0a, filter_cur, 0x60);
	if (rc < 0)
		return rc;
	/*
	 * Original driver initializes regs 0x05 and 0x06 with the
	 * same value again on this point. Probably, it is just an
	 * error there
	 */

	/*
	 * Set LNA
	 */

	tuner_dbg("adjusting LNA parameters\n");
	if (type != V4L2_TUNER_ANALOG_TV) {
		/* LNA TOP: lowest */
		rc = r820t_write_reg_mask(priv, 0x1d, 0, 0x38);
		if (rc < 0)
			return rc;

		/* 0: normal mode */
		rc = r820t_write_reg_mask(priv, 0x1c, 0, 0x04);
		if (rc < 0)
			return rc;

		/* 0: PRE_DECT off */
		rc = r820t_write_reg_mask(priv, 0x06, 0, 0x40);
		if (rc < 0)
			return rc;

		/* agc clk 250hz */
		rc = r820t_write_reg_mask(priv, 0x1a, 0x30, 0x30);
		if (rc < 0)
			return rc;

		msleep(250);

		/* write LNA TOP = 3 */
		rc = r820t_write_reg_mask(priv, 0x1d, 0x18, 0x38);
		if (rc < 0)
			return rc;

		/*
		 * write discharge mode
		 * FIXME: IMHO, the mask here is wrong, but it matches
		 * what's there at the original driver
		 */
		rc = r820t_write_reg_mask(priv, 0x1c, mixer_top, 0x04);
		if (rc < 0)
			return rc;

		/* LNA discharge current */
		rc = r820t_write_reg_mask(priv, 0x1e, lna_discharge, 0x1f);
		if (rc < 0)
			return rc;

		/* agc clk 60hz */
		rc = r820t_write_reg_mask(priv, 0x1a, 0x20, 0x30);
		if (rc < 0)
			return rc;
	} else {
		/* PRE_DECT off */
		rc = r820t_write_reg_mask(priv, 0x06, 0, 0x40);
		if (rc < 0)
			return rc;

		/* write LNA TOP */
		rc = r820t_write_reg_mask(priv, 0x1d, lna_top, 0x38);
		if (rc < 0)
			return rc;

		/*
		 * write discharge mode
		 * FIXME: IMHO, the mask here is wrong, but it matches
		 * what's there at the original driver
		 */
		rc = r820t_write_reg_mask(priv, 0x1c, mixer_top, 0x04);
		if (rc < 0)
			return rc;

		/* LNA discharge current */
		rc = r820t_write_reg_mask(priv, 0x1e, lna_discharge, 0x1f);
		if (rc < 0)
			return rc;

		/* agc clk 1Khz, external det1 cap 1u */
		rc = r820t_write_reg_mask(priv, 0x1a, 0x00, 0x30);
		if (rc < 0)
			return rc;

		rc = r820t_write_reg_mask(priv, 0x10, 0x00, 0x04);
		if (rc < 0)
			return rc;
	 }
	 return 0;
}

static int r820t_set_tv_standard(struct r820t_priv *priv,
				 unsigned bw,
				 enum v4l2_tuner_type type,
				 v4l2_std_id std, u32 delsys)

{
	int rc, i;
	u32 if_khz, filt_cal_lo;
	u8 data[5], val;
	u8 filt_gain, img_r, filt_q, hp_cor, ext_enable, loop_through;
	u8 lt_att, flt_ext_widest, polyfil_cur;
	bool need_calibration;

	tuner_dbg("selecting the delivery system\n");

	if (delsys == SYS_ISDBT) {
		if_khz = 4063;
		filt_cal_lo = 59000;
		filt_gain = 0x10;	/* +3db, 6mhz on */
		img_r = 0x00;		/* image negative */
		filt_q = 0x10;		/* r10[4]:low q(1'b1) */
		hp_cor = 0x6a;		/* 1.7m disable, +2cap, 1.25mhz */
		ext_enable = 0x40;	/* r30[6], ext enable; r30[5]:0 ext at lna max */
		loop_through = 0x00;	/* r5[7], lt on */
		lt_att = 0x00;		/* r31[7], lt att enable */
		flt_ext_widest = 0x00;	/* r15[7]: flt_ext_wide off */
		polyfil_cur = 0x60;	/* r25[6:5]:min */
	} else {
		if (bw <= 6) {
			if_khz = 3570;
			filt_cal_lo = 56000;	/* 52000->56000 */
			filt_gain = 0x10;	/* +3db, 6mhz on */
			img_r = 0x00;		/* image negative */
			filt_q = 0x10;		/* r10[4]:low q(1'b1) */
			hp_cor = 0x6b;		/* 1.7m disable, +2cap, 1.0mhz */
			ext_enable = 0x60;	/* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */
			loop_through = 0x00;	/* r5[7], lt on */
			lt_att = 0x00;		/* r31[7], lt att enable */
			flt_ext_widest = 0x00;	/* r15[7]: flt_ext_wide off */
			polyfil_cur = 0x60;	/* r25[6:5]:min */
		} else if (bw == 7) {
			if_khz = 4070;
			filt_cal_lo = 60000;
			filt_gain = 0x10;	/* +3db, 6mhz on */
			img_r = 0x00;		/* image negative */
			filt_q = 0x10;		/* r10[4]:low q(1'b1) */
			hp_cor = 0x2b;		/* 1.7m disable, +1cap, 1.0mhz */
			ext_enable = 0x60;	/* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */
			loop_through = 0x00;	/* r5[7], lt on */
			lt_att = 0x00;		/* r31[7], lt att enable */
			flt_ext_widest = 0x00;	/* r15[7]: flt_ext_wide off */
			polyfil_cur = 0x60;	/* r25[6:5]:min */
#if 0 /* 7 MHz type 2 - nor sure why/where this is used - Perhaps Australia? */
			if_khz = 4570;
			filt_cal_lo = 63000;
			filt_gain = 0x10;	/* +3db, 6mhz on */
			img_r = 0x00;		/* image negative */
			filt_q = 0x10;		/* r10[4]:low q(1'b1) */
			hp_cor = 0x2a;		/* 1.7m disable, +1cap, 1.25mhz */
			ext_enable = 0x60;	/* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */
			loop_through = 0x00;	/* r5[7], lt on */
			lt_att = 0x00;		/* r31[7], lt att enable */
			flt_ext_widest = 0x00;	/* r15[7]: flt_ext_wide off */
			polyfil_cur = 0x60;	/* r25[6:5]:min */
#endif
		} else {
			if_khz = 4570;
			filt_cal_lo = 68500;
			filt_gain = 0x10;	/* +3db, 6mhz on */
			img_r = 0x00;		/* image negative */
			filt_q = 0x10;		/* r10[4]:low q(1'b1) */
			hp_cor = 0x0b;		/* 1.7m disable, +0cap, 1.0mhz */
			ext_enable = 0x60;	/* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */
			loop_through = 0x00;	/* r5[7], lt on */
			lt_att = 0x00;		/* r31[7], lt att enable */
			flt_ext_widest = 0x00;	/* r15[7]: flt_ext_wide off */
			polyfil_cur = 0x60;	/* r25[6:5]:min */
		}
	}

	/* Initialize the shadow registers */
	memcpy(priv->regs, r820t_init_array, sizeof(r820t_init_array));

	/* Init Flag & Xtal_check Result */
	if (priv->imr_done)
		val = 1 | priv->xtal_cap_sel << 1;
	else
		val = 0;
	rc = r820t_write_reg_mask(priv, 0x0c, val, 0x0f);
	if (rc < 0)
		return rc;

	/* version */
	rc = r820t_write_reg_mask(priv, 0x13, VER_NUM, 0x3f);
	if (rc < 0)
		return rc;

	/* for LT Gain test */
	if (type != V4L2_TUNER_ANALOG_TV) {
		rc = r820t_write_reg_mask(priv, 0x1d, 0x00, 0x38);
		if (rc < 0)
			return rc;
		msleep(1);
	}
	priv->int_freq = if_khz * 1000;

	/* Check if standard changed. If so, filter calibration is needed */
	if (type != priv->type)
		need_calibration = true;
	else if ((type == V4L2_TUNER_ANALOG_TV) && (std != priv->std))
		need_calibration = true;
	else if ((type == V4L2_TUNER_DIGITAL_TV) &&
		 ((delsys != priv->delsys) || bw != priv->bw))
		need_calibration = true;
	else
		need_calibration = false;

	if (need_calibration) {
		tuner_dbg("calibrating the tuner\n");
		for (i = 0; i < 2; i++) {
			/* Set filt_cap */
			rc = r820t_write_reg_mask(priv, 0x0b, hp_cor, 0x60);
			if (rc < 0)
				return rc;

			/* set cali clk =on */
			rc = r820t_write_reg_mask(priv, 0x0f, 0x04, 0x04);
			if (rc < 0)
				return rc;

			/* X'tal cap 0pF for PLL */
			rc = r820t_write_reg_mask(priv, 0x10, 0x00, 0x03);
			if (rc < 0)
				return rc;

			rc = r820t_set_pll(priv, filt_cal_lo);
			if (rc < 0 || !priv->has_lock)
				return rc;

			/* Start Trigger */
			rc = r820t_write_reg_mask(priv, 0x0b, 0x10, 0x10);
			if (rc < 0)
				return rc;

			msleep(1);

			/* Stop Trigger */
			rc = r820t_write_reg_mask(priv, 0x0b, 0x00, 0x10);
			if (rc < 0)
				return rc;

			/* set cali clk =off */
			rc = r820t_write_reg_mask(priv, 0x0f, 0x00, 0x04);
			if (rc < 0)
				return rc;

			/* Check if calibration worked */
			rc = r820_read(priv, 0x00, data, sizeof(data));
			if (rc < 0)
				return rc;

			priv->fil_cal_code = data[4] & 0x0f;
			if (priv->fil_cal_code && priv->fil_cal_code != 0x0f)
				break;
		}
		/* narrowest */
		if (priv->fil_cal_code == 0x0f)
			priv->fil_cal_code = 0;
	}

	rc = r820t_write_reg_mask(priv, 0x0a,
				  filt_q | priv->fil_cal_code, 0x1f);
	if (rc < 0)
		return rc;

	/* Set BW, Filter_gain, & HP corner */
	rc = r820t_write_reg_mask(priv, 0x0b, hp_cor, 0x10);
	if (rc < 0)
		return rc;


	/* Set Img_R */
	rc = r820t_write_reg_mask(priv, 0x07, img_r, 0x80);
	if (rc < 0)
		return rc;

	/* Set filt_3dB, V6MHz */
	rc = r820t_write_reg_mask(priv, 0x06, filt_gain, 0x30);
	if (rc < 0)
		return rc;

	/* channel filter extension */
	rc = r820t_write_reg_mask(priv, 0x1e, ext_enable, 0x60);
	if (rc < 0)
		return rc;

	/* Loop through */
	rc = r820t_write_reg_mask(priv, 0x05, loop_through, 0x80);
	if (rc < 0)
		return rc;

	/* Loop through attenuation */
	rc = r820t_write_reg_mask(priv, 0x1f, lt_att, 0x80);
	if (rc < 0)
		return rc;

	/* filter extension widest */
	rc = r820t_write_reg_mask(priv, 0x0f, flt_ext_widest, 0x80);
	if (rc < 0)
		return rc;

	/* RF poly filter current */
	rc = r820t_write_reg_mask(priv, 0x19, polyfil_cur, 0x60);
	if (rc < 0)
		return rc;

	/* Store current standard. If it changes, re-calibrate the tuner */
	priv->delsys = delsys;
	priv->type = type;
	priv->std = std;
	priv->bw = bw;

	return 0;
}

static int r820t_read_gain(struct r820t_priv *priv)
{
	u8 data[4];
	int rc;

	rc = r820_read(priv, 0x00, data, sizeof(data));
	if (rc < 0)
		return rc;

	return ((data[3] & 0x0f) << 1) + ((data[3] & 0xf0) >> 4);
}

static int r820t_set_gain_mode(struct r820t_priv *priv,
			       bool set_manual_gain,
			       int gain)
{
	int rc;

	if (set_manual_gain) {
		int i, total_gain = 0;
		uint8_t mix_index = 0, lna_index = 0;
		u8 data[4];

		/* LNA auto off */
		rc = r820t_write_reg_mask(priv, 0x05, 0x10, 0x10);
		if (rc < 0)
			return rc;

		 /* Mixer auto off */
		rc = r820t_write_reg_mask(priv, 0x07, 0, 0x10);
		if (rc < 0)
			return rc;

		rc = r820_read(priv, 0x00, data, sizeof(data));
		if (rc < 0)
			return rc;

		/* set fixed VGA gain for now (16.3 dB) */
		rc = r820t_write_reg_mask(priv, 0x0c, 0x08, 0x9f);
		if (rc < 0)
			return rc;

		for (i = 0; i < 15; i++) {
			if (total_gain >= gain)
				break;

			total_gain += r820t_lna_gain_steps[++lna_index];

			if (total_gain >= gain)
				break;

			total_gain += r820t_mixer_gain_steps[++mix_index];
		}

		/* set LNA gain */
		rc = r820t_write_reg_mask(priv, 0x05, lna_index, 0x0f);
		if (rc < 0)
			return rc;

		/* set Mixer gain */
		rc = r820t_write_reg_mask(priv, 0x07, mix_index, 0x0f);
		if (rc < 0)
			return rc;
	} else {
		/* LNA */
		rc = r820t_write_reg_mask(priv, 0x05, 0, 0xef);
		if (rc < 0)
			return rc;

		/* Mixer */
		rc = r820t_write_reg_mask(priv, 0x07, 0x10, 0xef);
		if (rc < 0)
			return rc;

		/* set fixed VGA gain for now (26.5 dB) */
		rc = r820t_write_reg_mask(priv, 0x0c, 0x0b, 0x9f);
		if (rc < 0)
			return rc;
	}

	return 0;
}


static int generic_set_freq(struct dvb_frontend *fe,
			    u32 freq /* in HZ */,
			    unsigned bw,
			    enum v4l2_tuner_type type,
			    v4l2_std_id std, u32 delsys)
{
	struct r820t_priv		*priv = fe->tuner_priv;
	int				rc = -EINVAL;
	u32				lo_freq;

	tuner_dbg("should set frequency to %d kHz, bw %d MHz\n",
		  freq / 1000, bw);

	rc = r820t_set_tv_standard(priv, bw, type, std, delsys);
	if (rc < 0)
		goto err;

	if ((type == V4L2_TUNER_ANALOG_TV) && (std == V4L2_STD_SECAM_LC))
		lo_freq = freq - priv->int_freq;
	 else
		lo_freq = freq + priv->int_freq;

	rc = r820t_set_mux(priv, lo_freq);
	if (rc < 0)
		goto err;

	rc = r820t_set_gain_mode(priv, true, 0);
	if (rc < 0)
		goto err;

	rc = r820t_set_pll(priv, lo_freq);
	if (rc < 0 || !priv->has_lock)
		goto err;

	rc = r820t_sysfreq_sel(priv, freq, type, std, delsys);
	if (rc < 0)
		goto err;

	tuner_dbg("%s: PLL locked on frequency %d Hz, gain=%d\n",
		  __func__, freq, r820t_read_gain(priv));

err:

	if (rc < 0)
		tuner_dbg("%s: failed=%d\n", __func__, rc);
	return rc;
}

/*
 * r820t standby logic
 */

static int r820t_standby(struct r820t_priv *priv)
{
	int rc;

	rc = r820t_write_reg(priv, 0x06, 0xb1);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x05, 0x03);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x07, 0x3a);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x08, 0x40);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x09, 0xc0);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x0a, 0x36);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x0c, 0x35);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x0f, 0x68);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x11, 0x03);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x17, 0xf4);
	if (rc < 0)
		return rc;
	rc = r820t_write_reg(priv, 0x19, 0x0c);

	/* Force initial calibration */
	priv->type = -1;

	return rc;
}

/*
 * r820t device init logic
 */

static int r820t_xtal_check(struct r820t_priv *priv)
{
	int rc, i;
	u8 data[3], val;

	/* Initialize the shadow registers */
	memcpy(priv->regs, r820t_init_array, sizeof(r820t_init_array));

	/* cap 30pF & Drive Low */
	rc = r820t_write_reg_mask(priv, 0x10, 0x0b, 0x0b);
	if (rc < 0)
		return rc;

	/* set pll autotune = 128kHz */
	rc = r820t_write_reg_mask(priv, 0x1a, 0x00, 0x0c);
	if (rc < 0)
		return rc;

	/* set manual initial reg = 111111;  */
	rc = r820t_write_reg_mask(priv, 0x13, 0x7f, 0x7f);
	if (rc < 0)
		return rc;

	/* set auto */
	rc = r820t_write_reg_mask(priv, 0x13, 0x00, 0x40);
	if (rc < 0)
		return rc;

	/* Try several xtal capacitor alternatives */
	for (i = 0; i < ARRAY_SIZE(r820t_xtal_capacitor); i++) {
		rc = r820t_write_reg_mask(priv, 0x10,
					  r820t_xtal_capacitor[i][0], 0x1b);
		if (rc < 0)
			return rc;

		msleep(5);

		rc = r820_read(priv, 0x00, data, sizeof(data));
		if (rc < 0)
			return rc;
		if ((!data[2]) & 0x40)
			continue;

		val = data[2] & 0x3f;

		if (priv->cfg->xtal == 16000000 && (val > 29 || val < 23))
			break;

		if (val != 0x3f)
			break;
	}

	if (i == ARRAY_SIZE(r820t_xtal_capacitor))
		return -EINVAL;

	return r820t_xtal_capacitor[i][1];
}

/*
 *  r820t frontend operations and tuner attach code
 *
 * All driver locks and i2c control are only in this part of the code
 */

static int r820t_init(struct dvb_frontend *fe)
{
	struct r820t_priv *priv = fe->tuner_priv;
	int rc, i;
	int xtal_cap = 0;

	tuner_dbg("%s:\n", __func__);

	mutex_lock(&priv->lock);
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);

	if ((priv->cfg->rafael_chip == CHIP_R820T) ||
	    (priv->cfg->rafael_chip == CHIP_R828S) ||
	    (priv->cfg->rafael_chip == CHIP_R820C)) {
		priv->xtal_cap_sel = XTAL_HIGH_CAP_0P;
	} else {
		for (i = 0; i < 3; i++) {
			rc = r820t_xtal_check(priv);
			if (rc < 0)
				goto err;
			if (!i || rc > xtal_cap)
				xtal_cap = rc;
		}
		priv->xtal_cap_sel = xtal_cap;
	}

	/* Initialize registers */
	rc = r820t_write(priv, 0x05,
			 r820t_init_array, sizeof(r820t_init_array));

err:
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 0);
	mutex_unlock(&priv->lock);

	if (rc < 0)
		tuner_dbg("%s: failed=%d\n", __func__, rc);
	return rc;
}

static int r820t_sleep(struct dvb_frontend *fe)
{
	struct r820t_priv *priv = fe->tuner_priv;
	int rc;

	tuner_dbg("%s:\n", __func__);

	mutex_lock(&priv->lock);
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);

	rc = r820t_standby(priv);

	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 0);
	mutex_unlock(&priv->lock);

	tuner_dbg("%s: failed=%d\n", __func__, rc);
	return rc;
}

static int r820t_set_analog_freq(struct dvb_frontend *fe,
				 struct analog_parameters *p)
{
	struct r820t_priv *priv = fe->tuner_priv;
	unsigned bw;
	int rc;

	tuner_dbg("%s called\n", __func__);

	/* if std is not defined, choose one */
	if (!p->std)
		p->std = V4L2_STD_MN;

	if ((p->std == V4L2_STD_PAL_M) || (p->std == V4L2_STD_NTSC))
		bw = 6;
	else
		bw = 8;

	mutex_lock(&priv->lock);
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);

	rc = generic_set_freq(fe, 62500l * p->frequency, bw,
			      V4L2_TUNER_ANALOG_TV, p->std, SYS_UNDEFINED);

	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 0);
	mutex_unlock(&priv->lock);

	return rc;
}

static int r820t_set_params(struct dvb_frontend *fe)
{
	struct r820t_priv *priv = fe->tuner_priv;
	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
	int rc;
	unsigned bw;

	tuner_dbg("%s: delivery_system=%d frequency=%d bandwidth_hz=%d\n",
		__func__, c->delivery_system, c->frequency, c->bandwidth_hz);

	mutex_lock(&priv->lock);
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);

	bw = (c->bandwidth_hz + 500000) / 1000000;
	if (!bw)
		bw = 8;

	rc = generic_set_freq(fe, c->frequency, bw,
			      V4L2_TUNER_DIGITAL_TV, 0, c->delivery_system);

	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 0);
	mutex_unlock(&priv->lock);

	if (rc)
		tuner_dbg("%s: failed=%d\n", __func__, rc);
	return rc;
}

static int r820t_signal(struct dvb_frontend *fe, u16 *strength)
{
	struct r820t_priv *priv = fe->tuner_priv;
	int rc = 0;

	mutex_lock(&priv->lock);
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);

	if (priv->has_lock) {
		rc = r820t_read_gain(priv);
		if (rc < 0)
			goto err;

		/* A higher gain at LNA means a lower signal strength */
		*strength = (45 - rc) << 4 | 0xff;
	} else {
		*strength = 0;
	}

err:
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 0);
	mutex_unlock(&priv->lock);

	tuner_dbg("%s: %s, gain=%d strength=%d\n",
		  __func__,
		  priv->has_lock ? "PLL locked" : "no signal",
		  rc, *strength);

	return 0;
}

static int r820t_get_if_frequency(struct dvb_frontend *fe, u32 *frequency)
{
	struct r820t_priv *priv = fe->tuner_priv;

	tuner_dbg("%s:\n", __func__);

	*frequency = priv->int_freq;

	return 0;
}

static int r820t_release(struct dvb_frontend *fe)
{
	struct r820t_priv *priv = fe->tuner_priv;

	tuner_dbg("%s:\n", __func__);

	mutex_lock(&r820t_list_mutex);

	if (priv)
		hybrid_tuner_release_state(priv);

	mutex_unlock(&r820t_list_mutex);

	fe->tuner_priv = NULL;

	kfree(fe->tuner_priv);

	return 0;
}

static const struct dvb_tuner_ops r820t_tuner_ops = {
	.info = {
		.name           = "Rafael Micro R820T",
		.frequency_min  =   42000000,
		.frequency_max  = 1002000000,
	},
	.init = r820t_init,
	.release = r820t_release,
	.sleep = r820t_sleep,
	.set_params = r820t_set_params,
	.set_analog_params = r820t_set_analog_freq,
	.get_if_frequency = r820t_get_if_frequency,
	.get_rf_strength = r820t_signal,
};

struct dvb_frontend *r820t_attach(struct dvb_frontend *fe,
				  struct i2c_adapter *i2c,
				  const struct r820t_config *cfg)
{
	struct r820t_priv *priv;
	int rc = -ENODEV;
	u8 data[5];
	int instance;

	mutex_lock(&r820t_list_mutex);

	instance = hybrid_tuner_request_state(struct r820t_priv, priv,
					      hybrid_tuner_instance_list,
					      i2c, cfg->i2c_addr,
					      "r820t");
	switch (instance) {
	case 0:
		/* memory allocation failure */
		goto err_no_gate;
		break;
	case 1:
		/* new tuner instance */
		priv->cfg = cfg;

		mutex_init(&priv->lock);

		fe->tuner_priv = priv;
		break;
	case 2:
		/* existing tuner instance */
		fe->tuner_priv = priv;
		break;
	}

	memcpy(&fe->ops.tuner_ops, &r820t_tuner_ops, sizeof(r820t_tuner_ops));

	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 1);

	/* check if the tuner is there */
	rc = r820_read(priv, 0x00, data, sizeof(data));
	if (rc < 0)
		goto err;

	rc = r820t_sleep(fe);
	if (rc < 0)
		goto err;

	tuner_info("Rafael Micro r820t successfully identified\n");

	fe->tuner_priv = priv;
	memcpy(&fe->ops.tuner_ops, &r820t_tuner_ops,
			sizeof(struct dvb_tuner_ops));

	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 0);

	mutex_unlock(&r820t_list_mutex);

	return fe;
err:
	if (fe->ops.i2c_gate_ctrl)
		fe->ops.i2c_gate_ctrl(fe, 0);

err_no_gate:
	mutex_unlock(&r820t_list_mutex);

	tuner_info("%s: failed=%d\n", __func__, rc);
	r820t_release(fe);
	return NULL;
}
EXPORT_SYMBOL_GPL(r820t_attach);

MODULE_DESCRIPTION("Rafael Micro r820t silicon tuner driver");
MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>");
MODULE_LICENSE("GPL");