Quellcode durchsuchen

use new E4000 tuner driver, allow manual gain

Many thanks to Hoernchen for making the driver work properly
and adding manual gain!

Signed-off-by: Steve Markgraf <steve@steve-m.de>
Steve Markgraf vor 13 Jahren
Ursprung
Commit
86c34428aa
11 geänderte Dateien mit 1328 neuen und 2217 gelöschten Zeilen
  1. 1 1
      include/Makefile.am
  2. 60 0
      include/reg_field.h
  3. 2 0
      include/rtl-sdr.h
  4. 0 129
      include/tuner_e4000.h
  5. 219 0
      include/tuner_e4k.h
  6. 2 2
      src/CMakeLists.txt
  7. 1 1
      src/Makefile.am
  8. 82 16
      src/rtl-sdr.c
  9. 6 1
      src/rtl_tcp.c
  10. 0 2067
      src/tuner_e4000.c
  11. 955 0
      src/tuner_e4k.c

+ 1 - 1
include/Makefile.am

@@ -1,5 +1,5 @@
 rtlsdr_HEADERS = rtl-sdr.h rtl-sdr_export.h
 
-noinst_HEADERS = rtlsdr_i2c.h tuner_e4000.h tuner_fc0012.h tuner_fc0013.h tuner_fc2580.h
+noinst_HEADERS = rtlsdr_i2c.h tuner_e4k.h tuner_fc0012.h tuner_fc0013.h tuner_fc2580.h
 
 rtlsdrdir = $(includedir)

+ 60 - 0
include/reg_field.h

@@ -0,0 +1,60 @@
+#ifndef _REG_FIELD_H
+#define _REG_FIELD_H
+
+#include <stdint.h>
+#include <stdarg.h>
+
+enum cmd_op {
+	CMD_OP_GET	= (1 << 0),
+	CMD_OP_SET	= (1 << 1),
+	CMD_OP_EXEC	= (1 << 2),
+};
+
+enum pstate {
+	ST_IN_CMD,
+	ST_IN_ARG,
+};
+
+struct strbuf {
+	uint8_t idx;
+	char buf[32];
+};
+
+struct cmd_state {
+	struct strbuf cmd;
+	struct strbuf arg;
+	enum pstate state;
+	void (*out)(const char *format, va_list ap);
+};
+
+struct cmd {
+	const char *cmd;
+	uint32_t ops;
+	int (*cb)(struct cmd_state *cs, enum cmd_op op, const char *cmd,
+		  int argc, char **argv);
+	const char *help;
+};
+
+/* structure describing a field in a register */
+struct reg_field {
+	uint8_t reg;
+	uint8_t shift;
+	uint8_t width;
+};
+
+struct reg_field_ops {
+	const struct reg_field *fields;
+	const char **field_names;
+	uint32_t num_fields;
+	void *data;
+	int (*write_cb)(void *data, uint32_t reg, uint32_t val);
+	uint32_t (*read_cb)(void *data, uint32_t reg);
+};
+
+uint32_t reg_field_read(struct reg_field_ops *ops, struct reg_field *field);
+int reg_field_write(struct reg_field_ops *ops, struct reg_field *field, uint32_t val);
+int reg_field_cmd(struct cmd_state *cs, enum cmd_op op,
+		  const char *cmd, int argc, char **argv,
+		  struct reg_field_ops *ops);
+
+#endif

+ 2 - 0
include/rtl-sdr.h

@@ -87,6 +87,8 @@ RTLSDR_API int rtlsdr_set_tuner_gain(rtlsdr_dev_t *dev, int gain);
 
 RTLSDR_API int rtlsdr_get_tuner_gain(rtlsdr_dev_t *dev);
 
+RTLSDR_API int rtlsdr_set_tuner_gain_mode(rtlsdr_dev_t *dev, int manual);
+
 /* this will select the baseband filters according to the requested sample rate */
 RTLSDR_API int rtlsdr_set_sample_rate(rtlsdr_dev_t *dev, uint32_t rate);
 

+ 0 - 129
include/tuner_e4000.h

@@ -1,129 +0,0 @@
-#ifndef __TUNER_E4000_H
-#define __TUNER_E4000_H
-
-// Definition (implemeted for E4000)
-#define E4000_1_SUCCESS			1
-#define E4000_1_FAIL			0
-#define E4000_I2C_SUCCESS		1
-#define E4000_I2C_FAIL			0
-
-#define E4K_I2C_ADDR		0xc8
-#define E4K_CHECK_ADDR		0x02
-#define E4K_CHECK_VAL		0x40
-
-// Function (implemeted for E4000)
-int
-I2CReadByte(void *pTuner,
-    unsigned char NoUse,
-    unsigned char RegAddr,
-    unsigned char *pReadingByte
-    );
-
-int
-I2CWriteByte(
-    void *pTuner,
-	unsigned char NoUse,
-	unsigned char RegAddr,
-	unsigned char WritingByte
-	);
-
-int
-I2CWriteArray(void *pTuner,
-    unsigned char NoUse,
-    unsigned char RegStartAddr,
-    unsigned char ByteNum,
-    unsigned char *pWritingBytes
-    );
-
-
-
-// Functions (from E4000 source code)
-int tunerreset (void *pTuner);
-int Tunerclock(void *pTuner);
-int Qpeak(void *pTuner);
-int DCoffloop(void *pTuner);
-int GainControlinit(void *pTuner);
-
-int Gainmanual(void *pTuner);
-int E4000_gain_freq(void *pTuner, int frequency);
-int PLL(void *pTuner, int Ref_clk, int Freq);
-int LNAfilter(void *pTuner, int Freq);
-int IFfilter(void *pTuner, int bandwidth, int Ref_clk);
-int freqband(void *pTuner, int Freq);
-int DCoffLUT(void *pTuner);
-int GainControlauto(void *pTuner);
-
-int E4000_sensitivity(void *pTuner, int Freq, int bandwidth);
-int E4000_linearity(void *pTuner, int Freq, int bandwidth);
-int E4000_high_linearity(void *pTuner);
-int E4000_nominal(void *pTuner, int Freq, int bandwidth);
-
-
-// The following context is E4000 tuner API source code
-
-// Definitions
-
-// Bandwidth in Hz
-enum E4000_BANDWIDTH_HZ
-{
-	E4000_BANDWIDTH_6000000HZ = 6000000,
-	E4000_BANDWIDTH_7000000HZ = 7000000,
-	E4000_BANDWIDTH_8000000HZ = 8000000,
-};
-
-
-// Manipulaing functions
-void
-e4000_GetTunerType(
-    void *pTuner,
-	int *pTunerType
-	);
-
-void
-e4000_GetDeviceAddr(
-    void *pTuner,
-	unsigned char *pDeviceAddr
-	);
-
-int
-e4000_Initialize(
-    void *pTuner
-	);
-
-int
-e4000_SetRfFreqHz(
-    void *pTuner,
-	unsigned long RfFreqHz
-	);
-
-int
-e4000_GetRfFreqHz(
-    void *pTuner,
-	unsigned long *pRfFreqHz
-	);
-
-
-
-
-
-// Extra manipulaing functions
-int
-e4000_GetRegByte(
-    void *pTuner,
-	unsigned char RegAddr,
-	unsigned char *pReadingByte
-	);
-
-int
-e4000_SetBandwidthHz(
-    void *pTuner,
-	unsigned long BandwidthHz
-	);
-
-int
-e4000_GetBandwidthHz(
-    void *pTuner,
-	unsigned long *pBandwidthHz
-	);
-
-#endif

+ 219 - 0
include/tuner_e4k.h

@@ -0,0 +1,219 @@
+#ifndef _E4K_TUNER_H
+#define _E4K_TUNER_H
+
+/* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define E4K_I2C_ADDR	0xc8
+#define E4K_CHECK_ADDR	0x02
+#define E4K_CHECK_VAL	0x40
+
+enum e4k_reg {
+	E4K_REG_MASTER1		= 0x00,
+	E4K_REG_MASTER2		= 0x01,
+	E4K_REG_MASTER3		= 0x02,
+	E4K_REG_MASTER4		= 0x03,
+	E4K_REG_MASTER5		= 0x04,
+	E4K_REG_CLK_INP		= 0x05,
+	E4K_REG_REF_CLK		= 0x06,
+	E4K_REG_SYNTH1		= 0x07,
+	E4K_REG_SYNTH2		= 0x08,
+	E4K_REG_SYNTH3		= 0x09,
+	E4K_REG_SYNTH4		= 0x0a,
+	E4K_REG_SYNTH5		= 0x0b,
+	E4K_REG_SYNTH6		= 0x0c,
+	E4K_REG_SYNTH7		= 0x0d,
+	E4K_REG_SYNTH8		= 0x0e,
+	E4K_REG_SYNTH9		= 0x0f,
+	E4K_REG_FILT1		= 0x10,
+	E4K_REG_FILT2		= 0x11,
+	E4K_REG_FILT3		= 0x12,
+	// gap
+	E4K_REG_GAIN1		= 0x14,
+	E4K_REG_GAIN2		= 0x15,
+	E4K_REG_GAIN3		= 0x16,
+	E4K_REG_GAIN4		= 0x17,
+	// gap
+	E4K_REG_AGC1		= 0x1a,
+	E4K_REG_AGC2		= 0x1b,
+	E4K_REG_AGC3		= 0x1c,
+	E4K_REG_AGC4		= 0x1d,
+	E4K_REG_AGC5		= 0x1e,
+	E4K_REG_AGC6		= 0x1f,
+	E4K_REG_AGC7		= 0x20,
+	E4K_REG_AGC8		= 0x21,
+	// gap
+	E4K_REG_AGC11		= 0x24,
+	E4K_REG_AGC12		= 0x25,
+	// gap
+	E4K_REG_DC1		= 0x29,
+	E4K_REG_DC2		= 0x2a,
+	E4K_REG_DC3		= 0x2b,
+	E4K_REG_DC4		= 0x2c,
+	E4K_REG_DC5		= 0x2d,
+	E4K_REG_DC6		= 0x2e,
+	E4K_REG_DC7		= 0x2f,
+	E4K_REG_DC8		= 0x30,
+	// gap
+	E4K_REG_QLUT0		= 0x50,
+	E4K_REG_QLUT1		= 0x51,
+	E4K_REG_QLUT2		= 0x52,
+	E4K_REG_QLUT3		= 0x53,
+	// gap
+	E4K_REG_ILUT0		= 0x60,
+	E4K_REG_ILUT1		= 0x61,
+	E4K_REG_ILUT2		= 0x62,
+	E4K_REG_ILUT3		= 0x63,
+	// gap
+	E4K_REG_DCTIME1		= 0x70,
+	E4K_REG_DCTIME2		= 0x71,
+	E4K_REG_DCTIME3		= 0x72,
+	E4K_REG_DCTIME4		= 0x73,
+	E4K_REG_PWM1		= 0x74,
+	E4K_REG_PWM2		= 0x75,
+	E4K_REG_PWM3		= 0x76,
+	E4K_REG_PWM4		= 0x77,
+	E4K_REG_BIAS		= 0x78,
+	E4K_REG_CLKOUT_PWDN	= 0x7a,
+	E4K_REG_CHFILT_CALIB	= 0x7b,
+	E4K_REG_I2C_REG_ADDR	= 0x7d,
+	// FIXME
+};
+
+#define E4K_MASTER1_RESET	(1 << 0)
+#define E4K_MASTER1_NORM_STBY	(1 << 1)
+#define E4K_MASTER1_POR_DET	(1 << 2)
+
+#define E4K_SYNTH1_PLL_LOCK	(1 << 0)
+#define E4K_SYNTH1_BAND_SHIF	1
+
+#define E4K_SYNTH7_3PHASE_EN	(1 << 3)
+
+#define E4K_SYNTH8_VCOCAL_UPD	(1 << 2)
+
+#define E4K_FILT3_DISABLE	(1 << 5)
+
+#define E4K_AGC1_LIN_MODE	(1 << 4)
+#define E4K_AGC1_LNA_UPDATE	(1 << 5)
+#define E4K_AGC1_LNA_G_LOW	(1 << 6)
+#define E4K_AGC1_LNA_G_HIGH	(1 << 7)
+
+#define E4K_AGC6_LNA_CAL_REQ	(1 << 4)
+
+#define E4K_AGC7_MIX_GAIN_AUTO	(1 << 0)
+#define E4K_AGC7_GAIN_STEP_5dB	(1 << 5)
+
+#define E4K_AGC8_SENS_LIN_AUTO	(1 << 0)
+
+#define E4K_AGC11_LNA_GAIN_ENH	(1 << 0)
+
+#define E4K_DC1_CAL_REQ		(1 << 0)
+
+#define E4K_DC5_I_LUT_EN	(1 << 0)
+#define E4K_DC5_Q_LUT_EN	(1 << 1)
+#define E4K_DC5_RANGE_DET_EN	(1 << 2)
+#define E4K_DC5_RANGE_EN	(1 << 3)
+#define E4K_DC5_TIMEVAR_EN	(1 << 4)
+
+#define E4K_CLKOUT_DISABLE	0x96
+
+#define E4K_CHFCALIB_CMD	(1 << 0)
+
+#define E4K_AGC1_MOD_MASK	0xF
+
+enum e4k_agc_mode {
+	E4K_AGC_MOD_SERIAL		= 0x0,
+	E4K_AGC_MOD_IF_PWM_LNA_SERIAL	= 0x1,
+	E4K_AGC_MOD_IF_PWM_LNA_AUTONL	= 0x2,
+	E4K_AGC_MOD_IF_PWM_LNA_SUPERV	= 0x3,
+	E4K_AGC_MOD_IF_SERIAL_LNA_PWM	= 0x4,
+	E4K_AGC_MOD_IF_PWM_LNA_PWM	= 0x5,
+	E4K_AGC_MOD_IF_DIG_LNA_SERIAL	= 0x6,
+	E4K_AGC_MOD_IF_DIG_LNA_AUTON	= 0x7,
+	E4K_AGC_MOD_IF_DIG_LNA_SUPERV	= 0x8,
+	E4K_AGC_MOD_IF_SERIAL_LNA_AUTON	= 0x9,
+	E4K_AGC_MOD_IF_SERIAL_LNA_SUPERV = 0xa,
+};
+
+enum e4k_band {
+	E4K_BAND_VHF2	= 0,
+	E4K_BAND_VHF3	= 1,
+	E4K_BAND_UHF	= 2,
+	E4K_BAND_L	= 3,
+};
+
+enum e4k_mixer_filter_bw {
+	E4K_F_MIX_BW_27M	= 0,
+	E4K_F_MIX_BW_4M6	= 8,
+	E4K_F_MIX_BW_4M2	= 9,
+	E4K_F_MIX_BW_3M8	= 10,
+	E4K_F_MIX_BW_3M4	= 11,
+	E4K_F_MIX_BW_3M		= 12,
+	E4K_F_MIX_BW_2M7	= 13,
+	E4K_F_MIX_BW_2M3	= 14,
+	E4K_F_MIX_BW_1M9	= 15,
+};
+
+enum e4k_if_filter {
+	E4K_IF_FILTER_MIX,
+	E4K_IF_FILTER_CHAN,
+	E4K_IF_FILTER_RC
+};
+struct e4k_pll_params {
+	uint32_t fosc;
+	uint32_t intended_flo;
+	uint32_t flo;
+	uint16_t x;
+	uint8_t z;
+	uint8_t r;
+	uint8_t r_idx;
+	uint8_t threephase;
+};
+
+struct e4k_state {
+	void *i2c_dev;
+	uint8_t i2c_addr;
+	enum e4k_band band;
+	struct e4k_pll_params vco;
+	void *rtl_dev;
+};
+
+int e4k_init(struct e4k_state *e4k);
+int e4k_if_gain_set(struct e4k_state *e4k, uint8_t stage, int8_t value);
+int e4k_mixer_gain_set(struct e4k_state *e4k, int8_t value);
+int e4k_commonmode_set(struct e4k_state *e4k, int8_t value);
+int e4k_tune_freq(struct e4k_state *e4k, uint32_t freq);
+int e4k_tune_params(struct e4k_state *e4k, struct e4k_pll_params *p);
+int e4k_compute_pll_params(struct e4k_pll_params *oscp, uint32_t fosc, uint32_t intended_flo);
+int e4k_if_filter_bw_get(struct e4k_state *e4k, enum e4k_if_filter filter);
+int e4k_if_filter_bw_set(struct e4k_state *e4k, enum e4k_if_filter filter,
+		         uint32_t bandwidth);
+int e4k_if_filter_chan_enable(struct e4k_state *e4k, int on);
+int e4k_rf_filter_set(struct e4k_state *e4k);
+
+int e4k_reg_write(struct e4k_state *e4k, uint8_t reg, uint8_t val);
+uint8_t e4k_reg_read(struct e4k_state *e4k, uint8_t reg);
+
+int e4k_manual_dc_offset(struct e4k_state *e4k, int8_t iofs, int8_t irange, int8_t qofs, int8_t qrange);
+int e4k_dc_offset_calibrate(struct e4k_state *e4k);
+int e4k_dc_offset_gen_table(struct e4k_state *e4k);
+
+int e4k_set_lna_gain(struct e4k_state *e4k, int32_t gain);
+int e4k_enable_manual_gain(struct e4k_state *e4k, uint8_t manual);
+int e4k_set_enh_gain(struct e4k_state *e4k, int32_t gain);
+#endif /* _E4K_TUNER_H */

+ 2 - 2
src/CMakeLists.txt

@@ -22,7 +22,7 @@
 ########################################################################
 add_library(rtlsdr_shared SHARED
     rtl-sdr.c
-    tuner_e4000.c
+    tuner_e4k.c
     tuner_fc0012.c
     tuner_fc0013.c
     tuner_fc2580.c
@@ -37,7 +37,7 @@ set_target_properties(rtlsdr_shared PROPERTIES OUTPUT_NAME rtlsdr)
 
 add_library(rtlsdr_static STATIC
     rtl-sdr.c
-    tuner_e4000.c
+    tuner_e4k.c
     tuner_fc0012.c
     tuner_fc0013.c
     tuner_fc2580.c

+ 1 - 1
src/Makefile.am

@@ -7,7 +7,7 @@ AM_CFLAGS = -fPIC -Wall
 
 lib_LTLIBRARIES = librtlsdr.la
 
-librtlsdr_la_SOURCES = rtl-sdr.c tuner_e4000.c tuner_fc0012.c tuner_fc0013.c tuner_fc2580.c
+librtlsdr_la_SOURCES = rtl-sdr.c tuner_e4k.c tuner_fc0012.c tuner_fc0013.c tuner_fc2580.c
 librtlsdr_la_LDFALGS = -version-info $(LIBVERSION)
 
 bin_PROGRAMS         = rtl_sdr rtl_tcp

+ 82 - 16
src/rtl-sdr.c

@@ -25,6 +25,7 @@
 #include <math.h>
 #ifndef _WIN32
 #include <unistd.h>
+#define min(a, b) (((a) < (b)) ? (a) : (b))
 #endif
 
 #include <libusb.h>
@@ -40,7 +41,7 @@
 #endif
 
 #include "rtl-sdr.h"
-#include "tuner_e4000.h"
+#include "tuner_e4k.h"
 #include "tuner_fc0012.h"
 #include "tuner_fc0013.h"
 #include "tuner_fc2580.h"
@@ -52,6 +53,7 @@ typedef struct rtlsdr_tuner {
 	int (*set_freq)(void *, uint32_t freq /* Hz */);
 	int (*set_bw)(void *, int bw /* Hz */);
 	int (*set_gain)(void *, int gain /* dB */);
+	int (*set_gain_mode)(void *, int manual);
 } rtlsdr_tuner_t;
 
 enum rtlsdr_async_status {
@@ -79,20 +81,48 @@ struct rtlsdr_dev {
 	uint32_t freq; /* Hz */
 	int corr; /* ppm */
 	int gain; /* dB */
+	struct e4k_state e4k_s;
 };
 
 void rtlsdr_set_gpio_bit(rtlsdr_dev_t *dev, uint8_t gpio, int val);
 
 /* generic tuner interface functions, shall be moved to the tuner implementations */
-int e4k_init(void *dev) { return e4000_Initialize(dev); }
-int e4k_exit(void *dev) { return 0; }
-int e4k_set_freq(void *dev, uint32_t freq) {
-	return e4000_SetRfFreqHz(dev, freq);
+int e4000_init(void *dev) {
+	rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev;
+	devt->e4k_s.i2c_addr = E4K_I2C_ADDR;
+	devt->e4k_s.vco.fosc = devt->tun_xtal;
+	devt->e4k_s.rtl_dev = dev;
+	return e4k_init(&devt->e4k_s);
 }
-int e4k_set_bw(void *dev, int bw) {
-	return e4000_SetBandwidthHz(dev, 4000000);
+int e4000_exit(void *dev) { return 0; }
+int e4000_set_freq(void *dev, uint32_t freq) {
+	rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev;
+	return e4k_tune_freq(&devt->e4k_s, freq);
+}
+int e4000_set_bw(void *dev, int bw) {
+	return 0;
+}
+int e4000_set_gain(void *dev, int gain) {
+	int rc;
+	rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev;
+	int mixgain = (gain > 340) ? 12 : 4;
+	int enhgain = (gain - 420);
+	if(e4k_set_lna_gain(&devt->e4k_s, min(300, gain - 40)) == -EINVAL)
+		return -1;
+	if(e4k_mixer_gain_set(&devt->e4k_s, mixgain) == -EINVAL)
+		return -1;
+	if(enhgain >= 0)
+		if(e4k_set_enh_gain(&devt->e4k_s, enhgain) == -EINVAL)
+			return -1;
+
+	return 0;
+}
+
+int e4000_set_gain_mode(void *dev, int manual) {
+	rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev;
+	e4k_enable_manual_gain(&devt->e4k_s, manual);
+	return 0;
 }
-int e4k_set_gain(void *dev, int gain) { return 0; }
 
 int fc0012_init(void *dev) { return FC0012_Open(dev); }
 int fc0012_exit(void *dev) { return 0; }
@@ -105,6 +135,7 @@ int fc0012_set_bw(void *dev, int bw) {
 	return FC0012_SetFrequency(dev, ((rtlsdr_dev_t *) dev)->freq/1000, 6);
 }
 int fc0012_set_gain(void *dev, int gain) { return 0; }
+int fc0012_set_gain_mode(void *dev, int manual) { return 0; }
 
 int fc0013_init(void *dev) { return FC0013_Open(dev); }
 int fc0013_exit(void *dev) { return 0; }
@@ -115,6 +146,7 @@ int fc0013_set_bw(void *dev, int bw) {
 	return FC0013_SetFrequency(dev, ((rtlsdr_dev_t *) dev)->freq/1000, 6);
 }
 int fc0013_set_gain(void *dev, int gain) { return 0; }
+int fc0013_set_gain_mode(void *dev, int manual) { return 0; }
 
 int fc2580_init(void *dev) { return fc2580_Initialize(dev); }
 int fc2580_exit(void *dev) { return 0; }
@@ -125,6 +157,7 @@ int fc2580_set_bw(void *dev, int bw) {
 	return fc2580_SetBandwidthMode(dev, 1);
 }
 int fc2580_set_gain(void *dev, int gain) { return 0; }
+int fc2580_set_gain_mode(void *dev, int manual) { return 0; }
 
 enum rtlsdr_tuners {
 	RTLSDR_TUNER_E4000,
@@ -135,20 +168,24 @@ enum rtlsdr_tuners {
 
 static rtlsdr_tuner_t tuners[] = {
 	{
-		e4k_init, e4k_exit,
-		e4k_set_freq, e4k_set_bw, e4k_set_gain
+		e4000_init, e4000_exit,
+		e4000_set_freq, e4000_set_bw, e4000_set_gain,
+		e4000_set_gain_mode
 	},
 	{
 		fc0012_init, fc0012_exit,
-		fc0012_set_freq, fc0012_set_bw, fc0012_set_gain
+		fc0012_set_freq, fc0012_set_bw, fc0012_set_gain,
+		fc0012_set_gain_mode
 	},
 	{
 		fc0013_init, fc0013_exit,
-		fc0013_set_freq, fc0013_set_bw, fc0013_set_gain
+		fc0013_set_freq, fc0013_set_bw, fc0013_set_gain,
+		fc0013_set_gain_mode
 	},
 	{
 		fc2580_init, fc2580_exit,
-		_fc2580_set_freq, fc2580_set_bw, fc2580_set_gain
+		_fc2580_set_freq, fc2580_set_bw, fc2580_set_gain,
+		fc2580_set_gain_mode
 	},
 };
 
@@ -277,6 +314,16 @@ uint8_t rtlsdr_i2c_read_reg(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t reg)
 	return data;
 }
 
+/* TODO clean this up again */
+int e4k_reg_write(struct e4k_state *e4k, uint8_t reg, uint8_t val)
+{
+	return rtlsdr_i2c_write_reg((rtlsdr_dev_t*)e4k->rtl_dev, e4k->i2c_addr, reg, val);}
+
+uint8_t e4k_reg_read(struct e4k_state *e4k, uint8_t reg)
+{
+	return rtlsdr_i2c_read_reg((rtlsdr_dev_t*)e4k->rtl_dev, e4k->i2c_addr, reg);
+}
+
 int rtlsdr_i2c_write(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t *buffer, int len)
 {
 	uint16_t addr = i2c_addr;
@@ -594,8 +641,11 @@ int rtlsdr_set_tuner_gain(rtlsdr_dev_t *dev, int gain)
 	if (!dev || !dev->tuner)
 		return -1;
 
-	if (dev->tuner->set_gain)
+	if (dev->tuner->set_gain) {
+		rtlsdr_set_i2c_repeater(dev, 1);
 		r = dev->tuner->set_gain((void *)dev, gain);
+		rtlsdr_set_i2c_repeater(dev, 0);
+	}
 
 	if (!r)
 		dev->gain = gain;
@@ -611,6 +661,24 @@ int rtlsdr_get_tuner_gain(rtlsdr_dev_t *dev)
 	return dev->gain;
 }
 
+int rtlsdr_set_tuner_gain_mode(rtlsdr_dev_t *dev, int mode)
+{
+	int r = 0;
+
+	if (!dev || !dev->tuner)
+		return -1;
+
+	if (dev->tuner->set_gain_mode) {
+		rtlsdr_set_i2c_repeater(dev, 1);
+		r = dev->tuner->set_gain_mode((void *)dev, mode);
+		rtlsdr_set_i2c_repeater(dev, 0);
+	}
+
+	return r;
+}
+
+
+
 int rtlsdr_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate)
 {
 	uint16_t tmp;
@@ -862,8 +930,6 @@ err:
 
 int rtlsdr_close(rtlsdr_dev_t *dev)
 {
-	int i;
-
 	if (!dev)
 		return -1;
 

+ 6 - 1
src/rtl_tcp.c

@@ -32,6 +32,8 @@
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <fcntl.h>
+#else
+#include <WinSock2.h>
 #endif
 
 #include <pthread.h>
@@ -278,6 +280,9 @@ static void *command_worker(void *arg)
 			printf("set freq %d\n", cmd.param);
 			rtlsdr_set_center_freq(dev, cmd.param);
 			break;
+		case 0x04:
+			 rtlsdr_set_tuner_gain(dev, cmd.param);
+			 break;
 		default:
 			break;
 		}
@@ -293,7 +298,7 @@ int main(int argc, char **argv)
 	uint32_t frequency = 0, samp_rate = 2048000;
 	struct sockaddr_in local, remote;
 	int device_count;
-	uint32_t dev_index = 0, gain = 5;
+	uint32_t dev_index = 0, gain = -10;
 	struct llist *curelem,*prev;
 	pthread_attr_t attr;
 	void *status;

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 2067
src/tuner_e4000.c


+ 955 - 0
src/tuner_e4k.c

@@ -0,0 +1,955 @@
+/* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <limits.h>
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <reg_field.h>
+#include <tuner_e4k.h>
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+/* If this is defined, the limits are somewhat relaxed compared to what the
+ * vendor claims is possible */
+#define OUT_OF_SPEC
+
+#define MHZ(x)	((x)*1000*1000)
+#define KHZ(x)	((x)*1000)
+
+uint32_t unsigned_delta(uint32_t a, uint32_t b)
+{
+	if (a > b)
+		return a - b;
+	else
+		return b - a;
+}
+
+/* look-up table bit-width -> mask */
+static const uint8_t width2mask[] = {
+	0, 1, 3, 7, 0xf, 0x1f, 0x3f, 0x7f, 0xff
+};
+
+/***********************************************************************
+ * Register Access */
+
+#if 0
+/*! \brief Write a register of the tuner chip
+ *  \param[in] e4k reference to the tuner
+ *  \param[in] reg number of the register
+ *  \param[in] val value to be written
+ *  \returns 0 on success, negative in case of error
+ */
+int e4k_reg_write(struct e4k_state *e4k, uint8_t reg, uint8_t val)
+{
+	/* FIXME */
+	return 0;
+}
+
+/*! \brief Read a register of the tuner chip
+ *  \param[in] e4k reference to the tuner
+ *  \param[in] reg number of the register
+ *  \returns positive 8bit register contents on success, negative in case of error
+ */
+int e4k_reg_read(struct e4k_state *e4k, uint8_t reg)
+{
+	/* FIXME */
+	return 0;
+}
+#endif
+
+/*! \brief Set or clear some (masked) bits inside a register
+ *  \param[in] e4k reference to the tuner
+ *  \param[in] reg number of the register
+ *  \param[in] mask bit-mask of the value
+ *  \param[in] val data value to be written to register
+ *  \returns 0 on success, negative in case of error
+ */
+static int e4k_reg_set_mask(struct e4k_state *e4k, uint8_t reg,
+		     uint8_t mask, uint8_t val)
+{
+	uint8_t tmp = e4k_reg_read(e4k, reg);
+
+	if ((tmp & mask) == val)
+		return 0;
+
+	return e4k_reg_write(e4k, reg, (tmp & ~mask) | (val & mask));
+}
+
+/*! \brief Write a given field inside a register
+ *  \param[in] e4k reference to the tuner
+ *  \param[in] field structure describing the field
+ *  \param[in] val value to be written
+ *  \returns 0 on success, negative in case of error
+ */
+static int e4k_field_write(struct e4k_state *e4k, const struct reg_field *field, uint8_t val)
+{
+	int rc;
+	uint8_t mask;
+
+	rc = e4k_reg_read(e4k, field->reg);
+	if (rc < 0)
+		return rc;
+
+	mask = width2mask[field->width] << field->shift;
+
+	return e4k_reg_set_mask(e4k, field->reg, mask, val << field->shift);
+}
+
+/*! \brief Read a given field inside a register
+ *  \param[in] e4k reference to the tuner
+ *  \param[in] field structure describing the field
+ *  \returns positive value of the field, negative in case of error
+ */
+static int e4k_field_read(struct e4k_state *e4k, const struct reg_field *field)
+{
+	int rc;
+
+	rc = e4k_reg_read(e4k, field->reg);
+	if (rc < 0)
+		return rc;
+
+	rc = (rc >> field->shift) & width2mask[field->width];
+
+	return rc;
+}
+
+/***********************************************************************
+ * Filter Control */
+
+static const uint32_t rf_filt_center_uhf[] = {
+	MHZ(360), MHZ(380), MHZ(405), MHZ(425),
+	MHZ(450), MHZ(475), MHZ(505), MHZ(540),
+	MHZ(575), MHZ(615), MHZ(670), MHZ(720),
+	MHZ(760), MHZ(840), MHZ(890), MHZ(970)
+};
+
+static const uint32_t rf_filt_center_l[] = {
+	MHZ(1300), MHZ(1320), MHZ(1360), MHZ(1410),
+	MHZ(1445), MHZ(1460), MHZ(1490), MHZ(1530),
+	MHZ(1560), MHZ(1590), MHZ(1640), MHZ(1660),
+	MHZ(1680), MHZ(1700), MHZ(1720), MHZ(1750)
+};
+
+static int closest_arr_idx(const uint32_t *arr, unsigned int arr_size, uint32_t freq)
+{
+	unsigned int i, bi = 0;
+	uint32_t best_delta = 0xffffffff;
+
+	/* iterate over the array containing a list of the center
+	 * frequencies, selecting the closest one */
+	for (i = 0; i < arr_size; i++) {
+		uint32_t delta = unsigned_delta(freq, arr[i]);
+		if (delta < best_delta) {
+			best_delta = delta;
+			bi = i;
+		}
+	}
+
+	return bi;
+}
+
+/* return 4-bit index as to which RF filter to select */
+static int choose_rf_filter(uint32_t freq)
+{
+	int rc;
+
+	if (freq < MHZ(350))
+		rc = 0;
+	else if (freq < MHZ(1000))
+		rc = closest_arr_idx(rf_filt_center_uhf,
+				     ARRAY_SIZE(rf_filt_center_uhf),
+				     freq);
+	else
+		rc = closest_arr_idx(rf_filt_center_l,
+				     ARRAY_SIZE(rf_filt_center_l),
+				     freq);
+
+	return rc;
+}
+
+/* \brief Automatically select apropriate RF filter based on e4k state */
+int e4k_rf_filter_set(struct e4k_state *e4k)
+{
+	int rc;
+
+	rc = choose_rf_filter(e4k->vco.flo);
+	if (rc < 0)
+		return rc;
+
+	return e4k_reg_set_mask(e4k, E4K_REG_FILT1, 0xF, rc);
+}
+
+/* Mixer Filter */
+static const uint32_t mix_filter_bw[] = {
+	KHZ(27000), KHZ(27000), KHZ(27000), KHZ(27000),
+	KHZ(27000), KHZ(27000), KHZ(27000), KHZ(27000),
+	KHZ(4600), KHZ(4200), KHZ(3800), KHZ(3400),
+	KHZ(3300), KHZ(2700), KHZ(2300), KHZ(1900)
+};
+
+/* IF RC Filter */
+static const uint32_t ifrc_filter_bw[] = {
+	KHZ(21400), KHZ(21000), KHZ(17600), KHZ(14700),
+	KHZ(12400), KHZ(10600), KHZ(9000), KHZ(7700),
+	KHZ(6400), KHZ(5300), KHZ(4400), KHZ(3400),
+	KHZ(2600), KHZ(1800), KHZ(1200), KHZ(1000)
+};
+
+/* IF Channel Filter */
+static const uint32_t ifch_filter_bw[] = {
+	KHZ(5500), KHZ(5300), KHZ(5000), KHZ(4800),
+	KHZ(4600), KHZ(4400), KHZ(4300), KHZ(4100),
+	KHZ(3900), KHZ(3800), KHZ(3700), KHZ(3600),
+	KHZ(3400), KHZ(3300), KHZ(3200), KHZ(3100),
+	KHZ(3000), KHZ(2950), KHZ(2900), KHZ(2800),
+	KHZ(2750), KHZ(2700), KHZ(2600), KHZ(2550),
+	KHZ(2500), KHZ(2450), KHZ(2400), KHZ(2300),
+	KHZ(2280), KHZ(2240), KHZ(2200), KHZ(2150)
+};
+
+static const uint32_t *if_filter_bw[] = {
+	mix_filter_bw,
+	ifch_filter_bw,
+	ifrc_filter_bw,
+};
+
+static const uint32_t if_filter_bw_len[] = {
+	ARRAY_SIZE(mix_filter_bw),
+	ARRAY_SIZE(ifch_filter_bw),
+	ARRAY_SIZE(ifrc_filter_bw),
+};
+
+static const struct reg_field if_filter_fields[] = {
+	{
+		E4K_REG_FILT2, 4, 4,
+	},
+	{
+		E4K_REG_FILT3, 0, 5,
+	},
+	{
+		E4K_REG_FILT2, 0, 4,
+	}
+};
+
+static int find_if_bw(enum e4k_if_filter filter, uint32_t bw)
+{
+	if (filter >= ARRAY_SIZE(if_filter_bw))
+		return -EINVAL;
+
+	return closest_arr_idx(if_filter_bw[filter],
+			       if_filter_bw_len[filter], bw);
+}
+
+/*! \brief Set the filter band-width of any of the IF filters
+ *  \param[in] e4k reference to the tuner chip
+ *  \param[in] filter filter to be configured
+ *  \param[in] bandwidth bandwidth to be configured
+ *  \returns positive actual filter band-width, negative in case of error
+ */
+int e4k_if_filter_bw_set(struct e4k_state *e4k, enum e4k_if_filter filter,
+		         uint32_t bandwidth)
+{
+	int bw_idx;
+	const struct reg_field *field;
+
+	if (filter >= ARRAY_SIZE(if_filter_bw))
+		return -EINVAL;
+
+	bw_idx = find_if_bw(filter, bandwidth);
+
+	field = &if_filter_fields[filter];
+
+	return e4k_field_write(e4k, field, bw_idx);
+}
+
+/*! \brief Enables / Disables the channel filter
+ *  \param[in] e4k reference to the tuner chip
+ *  \param[in] on 1=filter enabled, 0=filter disabled
+ *  \returns 0 success, negative errors
+ */
+int e4k_if_filter_chan_enable(struct e4k_state *e4k, int on)
+{
+	return e4k_reg_set_mask(e4k, E4K_REG_FILT3, E4K_FILT3_DISABLE,
+	                        on ? 0 : E4K_FILT3_DISABLE);
+}
+
+int e4k_if_filter_bw_get(struct e4k_state *e4k, enum e4k_if_filter filter)
+{
+	const uint32_t *arr;
+	int rc;
+	const struct reg_field *field;
+
+	if (filter >= ARRAY_SIZE(if_filter_bw))
+		return -EINVAL;
+
+	field = &if_filter_fields[filter];
+
+	rc = e4k_field_read(e4k, field);
+	if (rc < 0)
+		return rc;
+
+	arr = if_filter_bw[filter];
+
+	return arr[rc];
+}
+
+
+/***********************************************************************
+ * Frequency Control */
+
+#define E4K_FVCO_MIN_KHZ	2600000	/* 2.6 GHz */
+#define E4K_FVCO_MAX_KHZ	3900000	/* 3.9 GHz */
+#define E4K_PLL_Y		65536
+
+#ifdef OUT_OF_SPEC
+#define E4K_FLO_MIN_MHZ		50
+#define E4K_FLO_MAX_MHZ		2200UL
+#else
+#define E4K_FLO_MIN_MHZ		64
+#define E4K_FLO_MAX_MHZ		1700
+#endif
+
+struct pll_settings {
+	uint32_t freq;
+	uint8_t reg_synth7;
+	uint8_t mult;
+};
+
+static const struct pll_settings pll_vars[] = {
+	{KHZ(72400),	(1 << 3) | 7,	48},
+	{KHZ(81200),	(1 << 3) | 6,	40},
+	{KHZ(108300),	(1 << 3) | 5,	32},
+	{KHZ(162500),	(1 << 3) | 4,	24},
+	{KHZ(216600),	(1 << 3) | 3,	16},
+	{KHZ(325000),	(1 << 3) | 2,	12},
+	{KHZ(350000),	(1 << 3) | 1,	8},
+	{KHZ(432000),	(0 << 3) | 3,	8},
+	{KHZ(667000),	(0 << 3) | 2,	6},
+	{KHZ(1200000),	(0 << 3) | 1,	4}
+};
+
+static int is_fvco_valid(uint32_t fvco_z)
+{
+	/* check if the resulting fosc is valid */
+	if (fvco_z/1000 < E4K_FVCO_MIN_KHZ ||
+	    fvco_z/1000 > E4K_FVCO_MAX_KHZ) {
+		fprintf(stderr, "Fvco %u invalid\n", fvco_z);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int is_fosc_valid(uint32_t fosc)
+{
+	if (fosc < MHZ(16) || fosc > MHZ(30)) {
+		fprintf(stderr, "Fosc %u invalid\n", fosc);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int is_z_valid(uint32_t z)
+{
+	if (z > 255) {
+		fprintf(stderr, "Z %u invalid\n", z);
+		return 0;
+	}
+
+	return 1;
+}
+
+/*! \brief Determine if 3-phase mixing shall be used or not */
+static int use_3ph_mixing(uint32_t flo)
+{
+	/* this is a magic number somewhre between VHF and UHF */
+	if (flo < MHZ(350))
+		return 1;
+
+	return 0;
+}
+
+/* \brief compute Fvco based on Fosc, Z and X
+ * \returns positive value (Fvco in Hz), 0 in case of error */
+static uint64_t compute_fvco(uint32_t f_osc, uint8_t z, uint16_t x)
+{
+	uint64_t fvco_z, fvco_x, fvco;
+
+	/* We use the following transformation in order to
+	 * handle the fractional part with integer arithmetic:
+	 *  Fvco = Fosc * (Z + X/Y) <=> Fvco = Fosc * Z + (Fosc * X)/Y
+	 * This avoids X/Y = 0.  However, then we would overflow a 32bit
+	 * integer, as we cannot hold e.g. 26 MHz * 65536 either.
+	 */
+	fvco_z = (uint64_t)f_osc * z;
+
+#if 0
+	if (!is_fvco_valid(fvco_z))
+		return 0;
+#endif
+
+	fvco_x = ((uint64_t)f_osc * x) / E4K_PLL_Y;
+
+	fvco = fvco_z + fvco_x;
+
+	return fvco;
+}
+
+static int compute_flo(uint32_t f_osc, uint8_t z, uint16_t x, uint8_t r)
+{
+	uint64_t fvco = compute_fvco(f_osc, z, x);
+	if (fvco == 0)
+		return -EINVAL;
+
+	return fvco / r;
+}
+
+static int e4k_band_set(struct e4k_state *e4k, enum e4k_band band)
+{
+	int rc;
+
+	switch (band) {
+	case E4K_BAND_VHF2:
+	case E4K_BAND_VHF3:
+	case E4K_BAND_UHF:
+		e4k_reg_write(e4k, E4K_REG_BIAS, 3);
+		break;
+	case E4K_BAND_L:
+		e4k_reg_write(e4k, E4K_REG_BIAS, 0);
+		break;
+	}
+
+	rc = e4k_reg_set_mask(e4k, E4K_REG_SYNTH1, 0x06, band << 1);
+	if (rc >= 0)
+		e4k->band = band;
+
+	return rc;
+}
+
+/*! \brief Compute PLL parameters for givent target frequency
+ *  \param[out] oscp Oscillator parameters, if computation successful
+ *  \param[in] fosc Clock input frequency applied to the chip (Hz)
+ *  \param[in] intended_flo target tuning frequency (Hz)
+ *  \returns actual PLL frequency, as close as possible to intended_flo,
+ *	     negative in case of error
+ */
+int e4k_compute_pll_params(struct e4k_pll_params *oscp, uint32_t fosc, uint32_t intended_flo)
+{
+	uint32_t i;
+	uint8_t r = 2;
+	uint64_t intended_fvco, remainder;
+	uint64_t z = 0;
+	uint32_t x;
+	int flo;
+	int three_phase_mixing = 0;
+	oscp->r_idx = 0;
+
+	if (!is_fosc_valid(fosc))
+		return -EINVAL;
+
+	for(i = 0; i < ARRAY_SIZE(pll_vars); ++i) {
+		if(intended_flo < pll_vars[i].freq) {
+			three_phase_mixing = (pll_vars[i].reg_synth7 & 0x08) ? 1 : 0;
+			oscp->r_idx = pll_vars[i].reg_synth7;
+			r = pll_vars[i].mult;
+			break;
+		}
+	}
+
+	//fprintf(stderr, "Fint=%u, R=%u\n", intended_flo, r);
+
+	/* flo(max) = 1700MHz, R(max) = 48, we need 64bit! */
+	intended_fvco = (uint64_t)intended_flo * r;
+
+	/* compute integral component of multiplier */
+	z = intended_fvco / fosc;
+
+	/* compute fractional part.  this will not overflow,
+	* as fosc(max) = 30MHz and z(max) = 255 */
+	remainder = intended_fvco - (fosc * z);
+	/* remainder(max) = 30MHz, E4K_PLL_Y = 65536 -> 64bit! */
+	x = (remainder * E4K_PLL_Y) / fosc;
+	/* x(max) as result of this computation is 65536 */
+
+	flo = compute_flo(fosc, z, x, r);
+
+	oscp->fosc = fosc;
+	oscp->flo = flo;
+	oscp->intended_flo = intended_flo;
+	oscp->r = r;
+//	oscp->r_idx = pll_vars[i].reg_synth7 & 0x0;
+	oscp->threephase = three_phase_mixing;
+	oscp->x = x;
+	oscp->z = z;
+
+	return flo;
+}
+
+int e4k_tune_params(struct e4k_state *e4k, struct e4k_pll_params *p)
+{
+	uint8_t val;
+
+	/* program R + 3phase/2phase */
+	e4k_reg_write(e4k, E4K_REG_SYNTH7, p->r_idx);
+	/* program Z */
+	e4k_reg_write(e4k, E4K_REG_SYNTH3, p->z);
+	/* program X */
+	e4k_reg_write(e4k, E4K_REG_SYNTH4, p->x & 0xff);
+	e4k_reg_write(e4k, E4K_REG_SYNTH5, p->x >> 8);
+
+	/* we're in auto calibration mode, so there's no need to trigger it */
+
+	memcpy(&e4k->vco, p, sizeof(e4k->vco));
+
+	/* set the band */
+	if (e4k->vco.flo < MHZ(140))
+		e4k_band_set(e4k, E4K_BAND_VHF2);
+	else if (e4k->vco.flo < MHZ(350))
+		e4k_band_set(e4k, E4K_BAND_VHF3);
+	else if (e4k->vco.flo < MHZ(1000))
+		e4k_band_set(e4k, E4K_BAND_UHF);
+	else
+		e4k_band_set(e4k, E4K_BAND_L);
+
+	/* select and set proper RF filter */
+	e4k_rf_filter_set(e4k);
+
+	return e4k->vco.flo;
+}
+
+/*! \brief High-level tuning API, just specify frquency
+ *
+ *  This function will compute matching PLL parameters, program them into the
+ *  hardware and set the band as well as RF filter.
+ *
+ *  \param[in] e4k reference to tuner
+ *  \param[in] freq frequency in Hz
+ *  \returns actual tuned frequency, negative in case of error
+ */
+int e4k_tune_freq(struct e4k_state *e4k, uint32_t freq)
+{
+	int rc, i;
+	struct e4k_pll_params p;
+
+	/* determine PLL parameters */
+	rc = e4k_compute_pll_params(&p, e4k->vco.fosc, freq);
+	if (rc < 0)
+		return rc;
+
+	/* actually tune to those parameters */
+	rc = e4k_tune_params(e4k, &p);
+
+	/* check PLL lock */
+	rc = e4k_reg_read(e4k, E4K_REG_SYNTH1);
+	if (!(rc & 0x01)) {
+		printf("[E4K] PLL not locked!\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/***********************************************************************
+ * Gain Control */
+
+static const int8_t if_stage1_gain[] = {
+	-3, 6
+};
+
+static const int8_t if_stage23_gain[] = {
+	0, 3, 6, 9
+};
+
+static const int8_t if_stage4_gain[] = {
+	0, 1, 2, 2
+};
+
+static const int8_t if_stage56_gain[] = {
+	3, 6, 9, 12, 15, 15, 15, 15
+};
+
+static const int8_t *if_stage_gain[] = {
+	0,
+	if_stage1_gain,
+	if_stage23_gain,
+	if_stage23_gain,
+	if_stage4_gain,
+	if_stage56_gain,
+	if_stage56_gain
+};
+
+static const uint8_t if_stage_gain_len[] = {
+	0,
+	ARRAY_SIZE(if_stage1_gain),
+	ARRAY_SIZE(if_stage23_gain),
+	ARRAY_SIZE(if_stage23_gain),
+	ARRAY_SIZE(if_stage4_gain),
+	ARRAY_SIZE(if_stage56_gain),
+	ARRAY_SIZE(if_stage56_gain)
+};
+
+static const struct reg_field if_stage_gain_regs[] = {
+	{ E4K_REG_GAIN3, 0, 1 },
+	{ E4K_REG_GAIN3, 1, 2 },
+	{ E4K_REG_GAIN3, 3, 2 },
+	{ E4K_REG_GAIN3, 5, 2 },
+	{ E4K_REG_GAIN4, 0, 3 },
+	{ E4K_REG_GAIN4, 3, 3 }
+};
+
+static const int32_t lnagain[] = {
+	-50,	0,
+	-25,	1,
+	0,	4,
+	25,	5,
+	50,	6,
+	75,	7,
+	100,	8,
+	125,	9,
+	150,	10,
+	175,	11,
+	200,	12,
+	250,	13,
+	300,	14,
+};
+
+static const int32_t enhgain[] = {
+	10, 30, 50, 70
+};
+
+int e4k_set_lna_gain(struct e4k_state *e4k, int32_t gain)
+{
+	uint32_t i;
+	for(i = 0; i < ARRAY_SIZE(lnagain)/2; ++i) {
+		if(lnagain[i*2] == gain) {
+			e4k_reg_set_mask(e4k, E4K_REG_GAIN1, 0xf, lnagain[i*2+1]);
+			return gain;
+		}
+	}
+	return -EINVAL;
+}
+
+int e4k_set_enh_gain(struct e4k_state *e4k, int32_t gain)
+{
+	uint32_t i;
+	for(i = 0; i < ARRAY_SIZE(enhgain); ++i) {
+		if(enhgain[i] == gain) {
+			e4k_reg_set_mask(e4k, E4K_REG_AGC11, 0x7, E4K_AGC11_LNA_GAIN_ENH | (i << 1));
+			return gain;
+		}
+	}
+	e4k_reg_set_mask(e4k, E4K_REG_AGC11, 0x7, 0);
+	return 0;
+}
+
+int e4k_enable_manual_gain(struct e4k_state *e4k, uint8_t manual)
+{
+	if (manual) {
+		/* Set LNA mode to manual */
+		e4k_reg_set_mask(e4k, E4K_REG_AGC1, E4K_AGC1_MOD_MASK, E4K_AGC_MOD_SERIAL);
+
+		/* Set Mixer Gain Control to manual */
+		e4k_reg_set_mask(e4k, E4K_REG_AGC7, E4K_AGC7_MIX_GAIN_AUTO, 0);
+	} else {
+		/* Set LNA mode to auto */
+		e4k_reg_set_mask(e4k, E4K_REG_AGC1, E4K_AGC1_MOD_MASK, E4K_AGC_MOD_IF_SERIAL_LNA_AUTON);
+		/* Set Mixer Gain Control to auto */
+		e4k_reg_set_mask(e4k, E4K_REG_AGC7, E4K_AGC7_MIX_GAIN_AUTO, 1);
+
+		e4k_reg_set_mask(e4k, E4K_REG_AGC11, 0x7, 0);
+	}
+
+	return 0;
+}
+
+static int find_stage_gain(uint8_t stage, int8_t val)
+{
+	const int8_t *arr;
+	int i;
+
+	if (stage >= ARRAY_SIZE(if_stage_gain))
+		return -EINVAL;
+
+	arr = if_stage_gain[stage];
+
+	for (i = 0; i < if_stage_gain_len[stage]; i++) {
+		if (arr[i] == val)
+			return i;
+	}
+	return -EINVAL;
+}
+
+/*! \brief Set the gain of one of the IF gain stages
+ *  \param[e4k] handle to the tuner chip
+ *  \param [stage] numbere of the stage (1..6)
+ *  \param [value] gain value in dBm
+ *  \returns 0 on success, negative in case of error
+ */
+int e4k_if_gain_set(struct e4k_state *e4k, uint8_t stage, int8_t value)
+{
+	int rc;
+	uint8_t mask;
+	const struct reg_field *field;
+
+	rc = find_stage_gain(stage, value);
+	if (rc < 0)
+		return rc;
+
+	/* compute the bit-mask for the given gain field */
+	field = &if_stage_gain_regs[stage];
+	mask = width2mask[field->width] << field->shift;
+
+	return e4k_reg_set_mask(e4k, field->reg, mask, rc << field->shift);
+}
+
+int e4k_mixer_gain_set(struct e4k_state *e4k, int8_t value)
+{
+	uint8_t bit;
+
+	switch (value) {
+	case 4:
+		bit = 0;
+		break;
+	case 12:
+		bit = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return e4k_reg_set_mask(e4k, E4K_REG_GAIN2, 1, bit);
+}
+
+int e4k_commonmode_set(struct e4k_state *e4k, int8_t value)
+{
+	if(value < 0)
+		return -EINVAL;
+	else if(value > 7)
+		return -EINVAL;
+
+	return e4k_reg_set_mask(e4k, E4K_REG_DC7, 7, value);
+}
+
+/***********************************************************************
+ * DC Offset */
+
+int e4k_manual_dc_offset(struct e4k_state *e4k, int8_t iofs, int8_t irange, int8_t qofs, int8_t qrange)
+{
+	int res;
+
+	if((iofs < 0x00) || (iofs > 0x3f))
+		return -EINVAL;
+	if((irange < 0x00) || (irange > 0x03))
+		return -EINVAL;
+	if((qofs < 0x00) || (qofs > 0x3f))
+		return -EINVAL;
+	if((qrange < 0x00) || (qrange > 0x03))
+		return -EINVAL;
+
+	res = e4k_reg_set_mask(e4k, E4K_REG_DC2, 0x3f, iofs);
+	if(res < 0)
+		return res;
+
+	res = e4k_reg_set_mask(e4k, E4K_REG_DC3, 0x3f, qofs);
+	if(res < 0)
+		return res;
+
+	res = e4k_reg_set_mask(e4k, E4K_REG_DC4, 0x33, (qrange << 4) | irange);
+	return res;
+}
+
+/*! \brief Perform a DC offset calibration right now
+ *  \param[e4k] handle to the tuner chip
+ */
+int e4k_dc_offset_calibrate(struct e4k_state *e4k)
+{
+	/* make sure the DC range detector is enabled */
+	e4k_reg_set_mask(e4k, E4K_REG_DC5, E4K_DC5_RANGE_DET_EN, E4K_DC5_RANGE_DET_EN);
+
+	return e4k_reg_write(e4k, E4K_REG_DC1, 0x01);
+}
+
+
+static const int8_t if_gains_max[] = {
+	0, 6, 9, 9, 2, 15, 15
+};
+
+struct gain_comb {
+	int8_t mixer_gain;
+	int8_t if1_gain;
+	uint8_t reg;
+};
+
+static const struct gain_comb dc_gain_comb[] = {
+	{ 4,  -3, 0x50 },
+	{ 4,   6, 0x51 },
+	{ 12, -3, 0x52 },
+	{ 12,  6, 0x53 },
+};
+
+#define TO_LUT(offset, range)	(offset | (range << 6))
+
+int e4k_dc_offset_gen_table(struct e4k_state *e4k)
+{
+	uint32_t i;
+
+	/* FIXME: read ont current gain values and write them back
+	 * before returning to the caller */
+
+	/* disable auto mixer gain */
+	e4k_reg_set_mask(e4k, E4K_REG_AGC7, E4K_AGC7_MIX_GAIN_AUTO, 0);
+
+	/* set LNA/IF gain to full manual */
+	e4k_reg_set_mask(e4k, E4K_REG_AGC1, E4K_AGC1_MOD_MASK,
+			 E4K_AGC_MOD_SERIAL);
+
+	/* set all 'other' gains to maximum */
+	for (i = 2; i <= 6; i++)
+		e4k_if_gain_set(e4k, i, if_gains_max[i]);
+
+	/* iterate over all mixer + if_stage_1 gain combinations */
+	for (i = 0; i < ARRAY_SIZE(dc_gain_comb); i++) {
+		uint8_t offs_i, offs_q, range, range_i, range_q;
+
+		/* set the combination of mixer / if1 gain */
+		e4k_mixer_gain_set(e4k, dc_gain_comb[i].mixer_gain);
+		e4k_if_gain_set(e4k, 1, dc_gain_comb[i].if1_gain);
+
+		/* perform actual calibration */
+		e4k_dc_offset_calibrate(e4k);
+
+		/* extract I/Q offset and range values */
+		offs_i = e4k_reg_read(e4k, E4K_REG_DC2) & 0x3f;
+		offs_q = e4k_reg_read(e4k, E4K_REG_DC3) & 0x3f;
+		range  = e4k_reg_read(e4k, E4K_REG_DC4);
+		range_i = range & 0x3;
+		range_q = (range >> 4) & 0x3;
+
+		printf("Table %u I=%u/%u, Q=%u/%u\n",
+			i, range_i, offs_i, range_q, offs_q);
+
+		/* write into the table */
+		e4k_reg_write(e4k, dc_gain_comb[i].reg,
+			      TO_LUT(offs_q, range_q));
+		e4k_reg_write(e4k, dc_gain_comb[i].reg + 0x10,
+			      TO_LUT(offs_i, range_i));
+	}
+
+	return 0;
+}
+
+/***********************************************************************
+ * Initialization */
+
+static int magic_init(struct e4k_state *e4k)
+{
+	e4k_reg_write(e4k, 0x7e, 0x01);
+	e4k_reg_write(e4k, 0x7f, 0xfe);
+	e4k_reg_write(e4k, 0x82, 0x00);
+	e4k_reg_write(e4k, 0x86, 0x50);	/* polarity A */
+	e4k_reg_write(e4k, 0x87, 0x20);
+	e4k_reg_write(e4k, 0x88, 0x01);
+	e4k_reg_write(e4k, 0x9f, 0x7f);
+	e4k_reg_write(e4k, 0xa0, 0x07);
+
+	return 0;
+}
+
+/*! \brief Initialize the E4K tuner
+ */
+int e4k_init(struct e4k_state *e4k)
+{
+	/* make a dummy i2c read or write command, will not be ACKed! */
+	e4k_reg_read(e4k, 0);
+
+	/* Make sure we reset everything and clear POR indicator */
+	e4k_reg_write(e4k, E4K_REG_MASTER1,
+		E4K_MASTER1_RESET |
+		E4K_MASTER1_NORM_STBY |
+		E4K_MASTER1_POR_DET
+	);
+
+	/* Configure clock input */
+	e4k_reg_write(e4k, E4K_REG_CLK_INP, 0x00);
+
+	/* Disable clock output */
+	e4k_reg_write(e4k, E4K_REG_REF_CLK, 0x00);
+	e4k_reg_write(e4k, E4K_REG_CLKOUT_PWDN, 0x96);
+
+	/* Write some magic values into registers */
+	magic_init(e4k);
+#if 0
+	/* Set common mode voltage a bit higher for more margin 850 mv */
+	e4k_commonmode_set(e4k, 4);
+
+	/* Initialize DC offset lookup tables */
+	e4k_dc_offset_gen_table(e4k);
+
+	/* Enable time variant DC correction */
+	e4k_reg_write(e4k, E4K_REG_DCTIME1, 0x01);
+	e4k_reg_write(e4k, E4K_REG_DCTIME2, 0x01);
+#endif
+
+	/* Set LNA mode to manual */
+	e4k_reg_write(e4k, E4K_REG_AGC4, 0x10); /* High threshold */
+	e4k_reg_write(e4k, E4K_REG_AGC5, 0x04);	/* Low threshold */
+	e4k_reg_write(e4k, E4K_REG_AGC6, 0x1a);	/* LNA calib + loop rate */
+
+	e4k_reg_set_mask(e4k, E4K_REG_AGC1, E4K_AGC1_MOD_MASK,
+		E4K_AGC_MOD_SERIAL);
+
+	/* Set Mixer Gain Control to manual */
+	e4k_reg_set_mask(e4k, E4K_REG_AGC7, E4K_AGC7_MIX_GAIN_AUTO, 0);
+
+#if 0
+	/* Enable LNA Gain enhancement */
+	e4k_reg_set_mask(e4k, E4K_REG_AGC11, 0x7,
+			 E4K_AGC11_LNA_GAIN_ENH | (2 << 1));
+
+	/* Enable automatic IF gain mode switching */
+	e4k_reg_set_mask(e4k, E4K_REG_AGC8, 0x1, E4K_AGC8_SENS_LIN_AUTO);
+#endif
+
+	/* Use auto-gain as default */
+	e4k_enable_manual_gain(e4k, 0);
+
+	/* Select moderate gain levels */
+	e4k_if_gain_set(e4k, 1, 6);
+	e4k_if_gain_set(e4k, 2, 0);
+	e4k_if_gain_set(e4k, 3, 0);
+	e4k_if_gain_set(e4k, 4, 0);
+	e4k_if_gain_set(e4k, 5, 9);
+	e4k_if_gain_set(e4k, 6, 9);
+
+	/* Set the most narrow filter we can possibly use */
+	e4k_if_filter_bw_set(e4k, E4K_IF_FILTER_MIX, KHZ(1900));
+	e4k_if_filter_bw_set(e4k, E4K_IF_FILTER_RC, KHZ(1000));
+	e4k_if_filter_bw_set(e4k, E4K_IF_FILTER_CHAN, KHZ(2150));
+	e4k_if_filter_chan_enable(e4k, 1);
+
+	/* Disable time variant DC correction and LUT */
+	e4k_reg_set_mask(e4k, E4K_REG_DC5, 0x03, 0);
+	e4k_reg_set_mask(e4k, E4K_REG_DCTIME1, 0x03, 0);
+	e4k_reg_set_mask(e4k, E4K_REG_DCTIME2, 0x03, 0);
+
+	return 0;
+}