diff options
author | FreeArtMan <=> | 2015-09-19 14:36:47 +0100 |
---|---|---|
committer | FreeArtMan <=> | 2015-09-19 14:36:47 +0100 |
commit | f8b84cf48099fb020b1174c8cc6195dde42308c3 (patch) | |
tree | 56a2ecbb7456c047d404c2b88c57e3d3be057ec7 /src | |
download | r820t-f8b84cf48099fb020b1174c8cc6195dde42308c3.tar.gz r820t-f8b84cf48099fb020b1174c8cc6195dde42308c3.zip |
Initial
Diffstat (limited to 'src')
-rw-r--r-- | src/librtlsdr.c | 1938 | ||||
-rw-r--r-- | src/make.mk | 13 | ||||
-rw-r--r-- | src/tuner_e4k.c | 1000 | ||||
-rw-r--r-- | src/tuner_fc0012.c | 345 | ||||
-rw-r--r-- | src/tuner_fc0013.c | 500 | ||||
-rw-r--r-- | src/tuner_fc2580.c | 494 | ||||
-rw-r--r-- | src/tuner_r82xx.c | 1328 |
7 files changed, 5618 insertions, 0 deletions
diff --git a/src/librtlsdr.c b/src/librtlsdr.c new file mode 100644 index 0000000..cdf1ca9 --- /dev/null +++ b/src/librtlsdr.c @@ -0,0 +1,1938 @@ +/* + * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver + * Copyright (C) 2012-2014 by Steve Markgraf <steve@steve-m.de> + * Copyright (C) 2012 by Dimitri Stolnikov <horiz0n@gmx.net> + * + * 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. + * + * 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 <errno.h> +#include <signal.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#ifndef _WIN32 +#include <unistd.h> +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#include <libusb.h> + +/* + * All libusb callback functions should be marked with the LIBUSB_CALL macro + * to ensure that they are compiled with the same calling convention as libusb. + * + * If the macro isn't available in older libusb versions, we simply define it. + */ +#ifndef LIBUSB_CALL +#define LIBUSB_CALL +#endif + +/* libusb < 1.0.9 doesn't have libusb_handle_events_timeout_completed */ +#ifndef HAVE_LIBUSB_HANDLE_EVENTS_TIMEOUT_COMPLETED +#define libusb_handle_events_timeout_completed(ctx, tv, c) \ + libusb_handle_events_timeout(ctx, tv) +#endif + +/* two raised to the power of n */ +#define TWO_POW(n) ((double)(1ULL<<(n))) + +#include "rtl-sdr.h" +#include "tuner_e4k.h" +#include "tuner_fc0012.h" +#include "tuner_fc0013.h" +#include "tuner_fc2580.h" +#include "tuner_r82xx.h" + +typedef struct rtlsdr_tuner_iface { + /* tuner interface */ + int (*init)(void *); + int (*exit)(void *); + int (*set_freq)(void *, uint32_t freq /* Hz */); + int (*set_bw)(void *, int bw /* Hz */); + int (*set_gain)(void *, int gain /* tenth dB */); + int (*set_if_gain)(void *, int stage, int gain /* tenth dB */); + int (*set_gain_mode)(void *, int manual); +} rtlsdr_tuner_iface_t; + +enum rtlsdr_async_status { + RTLSDR_INACTIVE = 0, + RTLSDR_CANCELING, + RTLSDR_RUNNING +}; + +#define FIR_LEN 16 + +/* + * FIR coefficients. + * + * The filter is running at XTal frequency. It is symmetric filter with 32 + * coefficients. Only first 16 coefficients are specified, the other 16 + * use the same values but in reversed order. The first coefficient in + * the array is the outer one, the last, the last is the inner one. + * First 8 coefficients are 8 bit signed integers, the next 8 coefficients + * are 12 bit signed integers. All coefficients have the same weight. + * + * Default FIR coefficients used for DAB/FM by the Windows driver, + * the DVB driver uses different ones + */ +static const int fir_default[FIR_LEN] = { + -54, -36, -41, -40, -32, -14, 14, 53, /* 8 bit signed */ + 101, 156, 215, 273, 327, 372, 404, 421 /* 12 bit signed */ +}; + +struct rtlsdr_dev { + libusb_context *ctx; + struct libusb_device_handle *devh; + uint32_t xfer_buf_num; + uint32_t xfer_buf_len; + struct libusb_transfer **xfer; + unsigned char **xfer_buf; + rtlsdr_read_async_cb_t cb; + void *cb_ctx; + enum rtlsdr_async_status async_status; + int async_cancel; + /* rtl demod context */ + uint32_t rate; /* Hz */ + uint32_t rtl_xtal; /* Hz */ + int fir[FIR_LEN]; + int direct_sampling; + /* tuner context */ + enum rtlsdr_tuner tuner_type; + rtlsdr_tuner_iface_t *tuner; + uint32_t tun_xtal; /* Hz */ + uint32_t freq; /* Hz */ + uint32_t bw; + uint32_t offs_freq; /* Hz */ + int corr; /* ppm */ + int gain; /* tenth dB */ + struct e4k_state e4k_s; + struct r82xx_config r82xx_c; + struct r82xx_priv r82xx_p; + /* status */ + int dev_lost; + int driver_active; + unsigned int xfer_errors; +}; + +void rtlsdr_set_gpio_bit(rtlsdr_dev_t *dev, uint8_t gpio, int val); +static int rtlsdr_set_if_freq(rtlsdr_dev_t *dev, uint32_t freq); + +/* generic tuner interface functions, shall be moved to the tuner implementations */ +int e4000_init(void *dev) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + devt->e4k_s.i2c_addr = E4K_I2C_ADDR; + rtlsdr_get_xtal_freq(devt, NULL, &devt->e4k_s.vco.fosc); + devt->e4k_s.rtl_dev = dev; + return e4k_init(&devt->e4k_s); +} +int e4000_exit(void *dev) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return e4k_standby(&devt->e4k_s, 1); +} +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) { + int r = 0; + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + + r |= e4k_if_filter_bw_set(&devt->e4k_s, E4K_IF_FILTER_MIX, bw); + r |= e4k_if_filter_bw_set(&devt->e4k_s, E4K_IF_FILTER_RC, bw); + r |= e4k_if_filter_bw_set(&devt->e4k_s, E4K_IF_FILTER_CHAN, bw); + + return r; +} + +int e4000_set_gain(void *dev, int gain) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + int mixgain = (gain > 340) ? 12 : 4; +#if 0 + int enhgain = (gain - 420); +#endif + if(e4k_set_lna_gain(&devt->e4k_s, min(300, gain - mixgain * 10)) == -EINVAL) + return -1; + if(e4k_mixer_gain_set(&devt->e4k_s, mixgain) == -EINVAL) + return -1; +#if 0 /* enhanced mixer gain seems to have no effect */ + if(enhgain >= 0) + if(e4k_set_enh_gain(&devt->e4k_s, enhgain) == -EINVAL) + return -1; +#endif + return 0; +} +int e4000_set_if_gain(void *dev, int stage, int gain) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return e4k_if_gain_set(&devt->e4k_s, (uint8_t)stage, (int8_t)(gain / 10)); +} +int e4000_set_gain_mode(void *dev, int manual) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return e4k_enable_manual_gain(&devt->e4k_s, manual); +} + +int _fc0012_init(void *dev) { return fc0012_init(dev); } +int fc0012_exit(void *dev) { return 0; } +int fc0012_set_freq(void *dev, uint32_t freq) { + /* select V-band/U-band filter */ + rtlsdr_set_gpio_bit(dev, 6, (freq > 300000000) ? 1 : 0); + return fc0012_set_params(dev, freq, 6000000); +} +int fc0012_set_bw(void *dev, int bw) { return 0; } +int _fc0012_set_gain(void *dev, int gain) { return fc0012_set_gain(dev, gain); } +int fc0012_set_gain_mode(void *dev, int manual) { return 0; } + +int _fc0013_init(void *dev) { return fc0013_init(dev); } +int fc0013_exit(void *dev) { return 0; } +int fc0013_set_freq(void *dev, uint32_t freq) { + return fc0013_set_params(dev, freq, 6000000); +} +int fc0013_set_bw(void *dev, int bw) { return 0; } +int _fc0013_set_gain(void *dev, int gain) { return fc0013_set_lna_gain(dev, gain); } + +int fc2580_init(void *dev) { return fc2580_Initialize(dev); } +int fc2580_exit(void *dev) { return 0; } +int _fc2580_set_freq(void *dev, uint32_t freq) { + return fc2580_SetRfFreqHz(dev, freq); +} +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; } + +int r820t_init(void *dev) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + devt->r82xx_p.rtl_dev = dev; + + if (devt->tuner_type == RTLSDR_TUNER_R828D) { + devt->r82xx_c.i2c_addr = R828D_I2C_ADDR; + devt->r82xx_c.rafael_chip = CHIP_R828D; + } else { + devt->r82xx_c.i2c_addr = R820T_I2C_ADDR; + devt->r82xx_c.rafael_chip = CHIP_R820T; + } + + rtlsdr_get_xtal_freq(devt, NULL, &devt->r82xx_c.xtal); + + devt->r82xx_c.max_i2c_msg_len = 8; + devt->r82xx_c.use_predetect = 0; + devt->r82xx_p.cfg = &devt->r82xx_c; + + return r82xx_init(&devt->r82xx_p); +} +int r820t_exit(void *dev) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_standby(&devt->r82xx_p); +} + +int r820t_set_freq(void *dev, uint32_t freq) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_set_freq(&devt->r82xx_p, freq); +} + +int r820t_set_bw(void *dev, int bw) { + int r; + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + + r = r82xx_set_bandwidth(&devt->r82xx_p, bw, devt->rate); + if(r < 0) + return r; + r = rtlsdr_set_if_freq(devt, r); + if (r) + return r; + return rtlsdr_set_center_freq(devt, devt->freq); +} + +int r820t_set_gain(void *dev, int gain) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_set_gain(&devt->r82xx_p, 1, gain); +} +int r820t_set_gain_mode(void *dev, int manual) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_set_gain(&devt->r82xx_p, manual, 0); +} + +/* definition order must match enum rtlsdr_tuner */ +static rtlsdr_tuner_iface_t tuners[] = { + { + NULL, NULL, NULL, NULL, NULL, NULL, NULL /* dummy for unknown tuners */ + }, + { + e4000_init, e4000_exit, + e4000_set_freq, e4000_set_bw, e4000_set_gain, e4000_set_if_gain, + e4000_set_gain_mode + }, + { + _fc0012_init, fc0012_exit, + fc0012_set_freq, fc0012_set_bw, _fc0012_set_gain, NULL, + fc0012_set_gain_mode + }, + { + _fc0013_init, fc0013_exit, + fc0013_set_freq, fc0013_set_bw, _fc0013_set_gain, NULL, + fc0013_set_gain_mode + }, + { + fc2580_init, fc2580_exit, + _fc2580_set_freq, fc2580_set_bw, fc2580_set_gain, NULL, + fc2580_set_gain_mode + }, + { + r820t_init, r820t_exit, + r820t_set_freq, r820t_set_bw, r820t_set_gain, NULL, + r820t_set_gain_mode + }, + { + r820t_init, r820t_exit, + r820t_set_freq, r820t_set_bw, r820t_set_gain, NULL, + r820t_set_gain_mode + }, +}; + +typedef struct rtlsdr_dongle { + uint16_t vid; + uint16_t pid; + const char *name; +} rtlsdr_dongle_t; + +/* + * Please add your device here and send a patch to osmocom-sdr@lists.osmocom.org + */ +static rtlsdr_dongle_t known_devices[] = { + { 0x0bda, 0x2832, "Generic RTL2832U" }, + { 0x0bda, 0x2838, "Generic RTL2832U OEM" }, + { 0x0413, 0x6680, "DigitalNow Quad DVB-T PCI-E card" }, + { 0x0413, 0x6f0f, "Leadtek WinFast DTV Dongle mini D" }, + { 0x0458, 0x707f, "Genius TVGo DVB-T03 USB dongle (Ver. B)" }, + { 0x0ccd, 0x00a9, "Terratec Cinergy T Stick Black (rev 1)" }, + { 0x0ccd, 0x00b3, "Terratec NOXON DAB/DAB+ USB dongle (rev 1)" }, + { 0x0ccd, 0x00b4, "Terratec Deutschlandradio DAB Stick" }, + { 0x0ccd, 0x00b5, "Terratec NOXON DAB Stick - Radio Energy" }, + { 0x0ccd, 0x00b7, "Terratec Media Broadcast DAB Stick" }, + { 0x0ccd, 0x00b8, "Terratec BR DAB Stick" }, + { 0x0ccd, 0x00b9, "Terratec WDR DAB Stick" }, + { 0x0ccd, 0x00c0, "Terratec MuellerVerlag DAB Stick" }, + { 0x0ccd, 0x00c6, "Terratec Fraunhofer DAB Stick" }, + { 0x0ccd, 0x00d3, "Terratec Cinergy T Stick RC (Rev.3)" }, + { 0x0ccd, 0x00d7, "Terratec T Stick PLUS" }, + { 0x0ccd, 0x00e0, "Terratec NOXON DAB/DAB+ USB dongle (rev 2)" }, + { 0x1554, 0x5020, "PixelView PV-DT235U(RN)" }, + { 0x15f4, 0x0131, "Astrometa DVB-T/DVB-T2" }, + { 0x185b, 0x0620, "Compro Videomate U620F"}, + { 0x185b, 0x0650, "Compro Videomate U650F"}, + { 0x185b, 0x0680, "Compro Videomate U680F"}, + { 0x1b80, 0xd393, "GIGABYTE GT-U7300" }, + { 0x1b80, 0xd394, "DIKOM USB-DVBT HD" }, + { 0x1b80, 0xd395, "Peak 102569AGPK" }, + { 0x1b80, 0xd397, "KWorld KW-UB450-T USB DVB-T Pico TV" }, + { 0x1b80, 0xd398, "Zaapa ZT-MINDVBZP" }, + { 0x1b80, 0xd39d, "SVEON STV20 DVB-T USB & FM" }, + { 0x1b80, 0xd3a4, "Twintech UT-40" }, + { 0x1b80, 0xd3a8, "ASUS U3100MINI_PLUS_V2" }, + { 0x1b80, 0xd3af, "SVEON STV27 DVB-T USB & FM" }, + { 0x1b80, 0xd3b0, "SVEON STV21 DVB-T USB & FM" }, + { 0x1d19, 0x1101, "Dexatek DK DVB-T Dongle (Logilink VG0002A)" }, + { 0x1d19, 0x1102, "Dexatek DK DVB-T Dongle (MSI DigiVox mini II V3.0)" }, + { 0x1d19, 0x1103, "Dexatek Technology Ltd. DK 5217 DVB-T Dongle" }, + { 0x1d19, 0x1104, "MSI DigiVox Micro HD" }, + { 0x1f4d, 0xa803, "Sweex DVB-T USB" }, + { 0x1f4d, 0xb803, "GTek T803" }, + { 0x1f4d, 0xc803, "Lifeview LV5TDeluxe" }, + { 0x1f4d, 0xd286, "MyGica TD312" }, + { 0x1f4d, 0xd803, "PROlectrix DV107669" }, +}; + +#define DEFAULT_BUF_NUMBER 15 +#define DEFAULT_BUF_LENGTH (16 * 32 * 512) + +#define DEF_RTL_XTAL_FREQ 28800000 +#define MIN_RTL_XTAL_FREQ (DEF_RTL_XTAL_FREQ - 1000) +#define MAX_RTL_XTAL_FREQ (DEF_RTL_XTAL_FREQ + 1000) + +#define CTRL_IN (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN) +#define CTRL_OUT (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT) +#define CTRL_TIMEOUT 300 +#define BULK_TIMEOUT 0 + +#define EEPROM_ADDR 0xa0 + +enum usb_reg { + USB_SYSCTL = 0x2000, + USB_CTRL = 0x2010, + USB_STAT = 0x2014, + USB_EPA_CFG = 0x2144, + USB_EPA_CTL = 0x2148, + USB_EPA_MAXPKT = 0x2158, + USB_EPA_MAXPKT_2 = 0x215a, + USB_EPA_FIFO_CFG = 0x2160, +}; + +enum sys_reg { + DEMOD_CTL = 0x3000, + GPO = 0x3001, + GPI = 0x3002, + GPOE = 0x3003, + GPD = 0x3004, + SYSINTE = 0x3005, + SYSINTS = 0x3006, + GP_CFG0 = 0x3007, + GP_CFG1 = 0x3008, + SYSINTE_1 = 0x3009, + SYSINTS_1 = 0x300a, + DEMOD_CTL_1 = 0x300b, + IR_SUSPEND = 0x300c, +}; + +enum blocks { + DEMODB = 0, + USBB = 1, + SYSB = 2, + TUNB = 3, + ROMB = 4, + IRB = 5, + IICB = 6, +}; + +int rtlsdr_read_array(rtlsdr_dev_t *dev, uint8_t block, uint16_t addr, uint8_t *array, uint8_t len) +{ + int r; + uint16_t index = (block << 8); + + r = libusb_control_transfer(dev->devh, CTRL_IN, 0, addr, index, array, len, CTRL_TIMEOUT); +#if 0 + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); +#endif + return r; +} + +int rtlsdr_write_array(rtlsdr_dev_t *dev, uint8_t block, uint16_t addr, uint8_t *array, uint8_t len) +{ + int r; + uint16_t index = (block << 8) | 0x10; + + r = libusb_control_transfer(dev->devh, CTRL_OUT, 0, addr, index, array, len, CTRL_TIMEOUT); +#if 0 + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); +#endif + return r; +} + +int rtlsdr_i2c_write_reg(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t reg, uint8_t val) +{ + uint16_t addr = i2c_addr; + uint8_t data[2]; + + data[0] = reg; + data[1] = val; + return rtlsdr_write_array(dev, IICB, addr, (uint8_t *)&data, 2); +} + +uint8_t rtlsdr_i2c_read_reg(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t reg) +{ + uint16_t addr = i2c_addr; + uint8_t data = 0; + + rtlsdr_write_array(dev, IICB, addr, ®, 1); + rtlsdr_read_array(dev, IICB, addr, &data, 1); + + return data; +} + +int rtlsdr_i2c_write(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t *buffer, int len) +{ + uint16_t addr = i2c_addr; + + if (!dev) + return -1; + + return rtlsdr_write_array(dev, IICB, addr, buffer, len); +} + +int rtlsdr_i2c_read(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t *buffer, int len) +{ + uint16_t addr = i2c_addr; + + if (!dev) + return -1; + + return rtlsdr_read_array(dev, IICB, addr, buffer, len); +} + +uint16_t rtlsdr_read_reg(rtlsdr_dev_t *dev, uint8_t block, uint16_t addr, uint8_t len) +{ + int r; + unsigned char data[2]; + uint16_t index = (block << 8); + uint16_t reg; + + r = libusb_control_transfer(dev->devh, CTRL_IN, 0, addr, index, data, len, CTRL_TIMEOUT); + + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); + + reg = (data[1] << 8) | data[0]; + + return reg; +} + +int rtlsdr_write_reg(rtlsdr_dev_t *dev, uint8_t block, uint16_t addr, uint16_t val, uint8_t len) +{ + int r; + unsigned char data[2]; + + uint16_t index = (block << 8) | 0x10; + + if (len == 1) + data[0] = val & 0xff; + else + data[0] = val >> 8; + + data[1] = val & 0xff; + + r = libusb_control_transfer(dev->devh, CTRL_OUT, 0, addr, index, data, len, CTRL_TIMEOUT); + + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); + + return r; +} + +uint16_t rtlsdr_demod_read_reg(rtlsdr_dev_t *dev, uint8_t page, uint16_t addr, uint8_t len) +{ + int r; + unsigned char data[2]; + + uint16_t index = page; + uint16_t reg; + addr = (addr << 8) | 0x20; + + r = libusb_control_transfer(dev->devh, CTRL_IN, 0, addr, index, data, len, CTRL_TIMEOUT); + + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); + + reg = (data[1] << 8) | data[0]; + + return reg; +} + +int rtlsdr_demod_write_reg(rtlsdr_dev_t *dev, uint8_t page, uint16_t addr, uint16_t val, uint8_t len) +{ + int r; + unsigned char data[2]; + uint16_t index = 0x10 | page; + addr = (addr << 8) | 0x20; + + if (len == 1) + data[0] = val & 0xff; + else + data[0] = val >> 8; + + data[1] = val & 0xff; + + r = libusb_control_transfer(dev->devh, CTRL_OUT, 0, addr, index, data, len, CTRL_TIMEOUT); + + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); + + rtlsdr_demod_read_reg(dev, 0x0a, 0x01, 1); + + return (r == len) ? 0 : -1; +} + +void rtlsdr_set_gpio_bit(rtlsdr_dev_t *dev, uint8_t gpio, int val) +{ + uint16_t r; + + gpio = 1 << gpio; + r = rtlsdr_read_reg(dev, SYSB, GPO, 1); + r = val ? (r | gpio) : (r & ~gpio); + rtlsdr_write_reg(dev, SYSB, GPO, r, 1); +} + +void rtlsdr_set_gpio_output(rtlsdr_dev_t *dev, uint8_t gpio) +{ + int r; + gpio = 1 << gpio; + + r = rtlsdr_read_reg(dev, SYSB, GPD, 1); + rtlsdr_write_reg(dev, SYSB, GPO, r & ~gpio, 1); + r = rtlsdr_read_reg(dev, SYSB, GPOE, 1); + rtlsdr_write_reg(dev, SYSB, GPOE, r | gpio, 1); +} + +void rtlsdr_set_i2c_repeater(rtlsdr_dev_t *dev, int on) +{ + rtlsdr_demod_write_reg(dev, 1, 0x01, on ? 0x18 : 0x10, 1); +} + +int rtlsdr_set_fir(rtlsdr_dev_t *dev) +{ + uint8_t fir[20]; + + int i; + /* format: int8_t[8] */ + for (i = 0; i < 8; ++i) { + const int val = dev->fir[i]; + if (val < -128 || val > 127) { + return -1; + } + fir[i] = val; + } + /* format: int12_t[8] */ + for (i = 0; i < 8; i += 2) { + const int val0 = dev->fir[8+i]; + const int val1 = dev->fir[8+i+1]; + if (val0 < -2048 || val0 > 2047 || val1 < -2048 || val1 > 2047) { + return -1; + } + fir[8+i*3/2] = val0 >> 4; + fir[8+i*3/2+1] = (val0 << 4) | ((val1 >> 8) & 0x0f); + fir[8+i*3/2+2] = val1; + } + + for (i = 0; i < (int)sizeof(fir); i++) { + if (rtlsdr_demod_write_reg(dev, 1, 0x1c + i, fir[i], 1)) + return -1; + } + + return 0; +} + +void rtlsdr_init_baseband(rtlsdr_dev_t *dev) +{ + unsigned int i; + + /* initialize USB */ + rtlsdr_write_reg(dev, USBB, USB_SYSCTL, 0x09, 1); + rtlsdr_write_reg(dev, USBB, USB_EPA_MAXPKT, 0x0002, 2); + rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x1002, 2); + + /* poweron demod */ + rtlsdr_write_reg(dev, SYSB, DEMOD_CTL_1, 0x22, 1); + rtlsdr_write_reg(dev, SYSB, DEMOD_CTL, 0xe8, 1); + + /* reset demod (bit 3, soft_rst) */ + rtlsdr_demod_write_reg(dev, 1, 0x01, 0x14, 1); + rtlsdr_demod_write_reg(dev, 1, 0x01, 0x10, 1); + + /* disable spectrum inversion and adjacent channel rejection */ + rtlsdr_demod_write_reg(dev, 1, 0x15, 0x00, 1); + rtlsdr_demod_write_reg(dev, 1, 0x16, 0x0000, 2); + + /* clear both DDC shift and IF frequency registers */ + for (i = 0; i < 6; i++) + rtlsdr_demod_write_reg(dev, 1, 0x16 + i, 0x00, 1); + + rtlsdr_set_fir(dev); + + /* enable SDR mode, disable DAGC (bit 5) */ + rtlsdr_demod_write_reg(dev, 0, 0x19, 0x05, 1); + + /* init FSM state-holding register */ + rtlsdr_demod_write_reg(dev, 1, 0x93, 0xf0, 1); + rtlsdr_demod_write_reg(dev, 1, 0x94, 0x0f, 1); + + /* disable AGC (en_dagc, bit 0) (this seems to have no effect) */ + rtlsdr_demod_write_reg(dev, 1, 0x11, 0x00, 1); + + /* disable RF and IF AGC loop */ + rtlsdr_demod_write_reg(dev, 1, 0x04, 0x00, 1); + + /* disable PID filter (enable_PID = 0) */ + rtlsdr_demod_write_reg(dev, 0, 0x61, 0x60, 1); + + /* opt_adc_iq = 0, default ADC_I/ADC_Q datapath */ + rtlsdr_demod_write_reg(dev, 0, 0x06, 0x80, 1); + + /* Enable Zero-IF mode (en_bbin bit), DC cancellation (en_dc_est), + * IQ estimation/compensation (en_iq_comp, en_iq_est) */ + rtlsdr_demod_write_reg(dev, 1, 0xb1, 0x1b, 1); + + /* disable 4.096 MHz clock output on pin TP_CK0 */ + rtlsdr_demod_write_reg(dev, 0, 0x0d, 0x83, 1); +} + +int rtlsdr_deinit_baseband(rtlsdr_dev_t *dev) +{ + int r = 0; + + if (!dev) + return -1; + + if (dev->tuner && dev->tuner->exit) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->exit(dev); /* deinitialize tuner */ + rtlsdr_set_i2c_repeater(dev, 0); + } + + /* poweroff demodulator and ADCs */ + rtlsdr_write_reg(dev, SYSB, DEMOD_CTL, 0x20, 1); + + return r; +} + +static int rtlsdr_set_if_freq(rtlsdr_dev_t *dev, uint32_t freq) +{ + uint32_t rtl_xtal; + int32_t if_freq; + uint8_t tmp; + int r; + + if (!dev) + return -1; + + /* read corrected clock value */ + if (rtlsdr_get_xtal_freq(dev, &rtl_xtal, NULL)) + return -2; + + if_freq = ((freq * TWO_POW(22)) / rtl_xtal) * (-1); + + tmp = (if_freq >> 16) & 0x3f; + r = rtlsdr_demod_write_reg(dev, 1, 0x19, tmp, 1); + tmp = (if_freq >> 8) & 0xff; + r |= rtlsdr_demod_write_reg(dev, 1, 0x1a, tmp, 1); + tmp = if_freq & 0xff; + r |= rtlsdr_demod_write_reg(dev, 1, 0x1b, tmp, 1); + + return r; +} + +int rtlsdr_set_sample_freq_correction(rtlsdr_dev_t *dev, int ppm) +{ + int r = 0; + uint8_t tmp; + int16_t offs = ppm * (-1) * TWO_POW(24) / 1000000; + + tmp = offs & 0xff; + r |= rtlsdr_demod_write_reg(dev, 1, 0x3f, tmp, 1); + tmp = (offs >> 8) & 0x3f; + r |= rtlsdr_demod_write_reg(dev, 1, 0x3e, tmp, 1); + + return r; +} + +int rtlsdr_set_xtal_freq(rtlsdr_dev_t *dev, uint32_t rtl_freq, uint32_t tuner_freq) +{ + int r = 0; + + if (!dev) + return -1; + + if (rtl_freq > 0 && + (rtl_freq < MIN_RTL_XTAL_FREQ || rtl_freq > MAX_RTL_XTAL_FREQ)) + return -2; + + if (rtl_freq > 0 && dev->rtl_xtal != rtl_freq) { + dev->rtl_xtal = rtl_freq; + + /* update xtal-dependent settings */ + if (dev->rate) + r = rtlsdr_set_sample_rate(dev, dev->rate); + } + + if (dev->tun_xtal != tuner_freq) { + if (0 == tuner_freq) + dev->tun_xtal = dev->rtl_xtal; + else + dev->tun_xtal = tuner_freq; + + /* read corrected clock value into e4k and r82xx structure */ + if (rtlsdr_get_xtal_freq(dev, NULL, &dev->e4k_s.vco.fosc) || + rtlsdr_get_xtal_freq(dev, NULL, &dev->r82xx_c.xtal)) + return -3; + + /* update xtal-dependent settings */ + if (dev->freq) + r = rtlsdr_set_center_freq(dev, dev->freq); + } + + return r; +} + +int rtlsdr_get_xtal_freq(rtlsdr_dev_t *dev, uint32_t *rtl_freq, uint32_t *tuner_freq) +{ + if (!dev) + return -1; + + #define APPLY_PPM_CORR(val,ppm) (((val) * (1.0 + (ppm) / 1e6))) + + if (rtl_freq) + *rtl_freq = (uint32_t) APPLY_PPM_CORR(dev->rtl_xtal, dev->corr); + + if (tuner_freq) + *tuner_freq = (uint32_t) APPLY_PPM_CORR(dev->tun_xtal, dev->corr); + + return 0; +} + +int rtlsdr_get_usb_strings(rtlsdr_dev_t *dev, char *manufact, char *product, + char *serial) +{ + struct libusb_device_descriptor dd; + libusb_device *device = NULL; + const int buf_max = 256; + int r = 0; + + if (!dev || !dev->devh) + return -1; + + device = libusb_get_device(dev->devh); + + r = libusb_get_device_descriptor(device, &dd); + if (r < 0) + return -1; + + if (manufact) { + memset(manufact, 0, buf_max); + libusb_get_string_descriptor_ascii(dev->devh, dd.iManufacturer, + (unsigned char *)manufact, + buf_max); + } + + if (product) { + memset(product, 0, buf_max); + libusb_get_string_descriptor_ascii(dev->devh, dd.iProduct, + (unsigned char *)product, + buf_max); + } + + if (serial) { + memset(serial, 0, buf_max); + libusb_get_string_descriptor_ascii(dev->devh, dd.iSerialNumber, + (unsigned char *)serial, + buf_max); + } + + return 0; +} + +int rtlsdr_write_eeprom(rtlsdr_dev_t *dev, uint8_t *data, uint8_t offset, uint16_t len) +{ + int r = 0; + int i; + uint8_t cmd[2]; + + if (!dev) + return -1; + + if ((len + offset) > 256) + return -2; + + for (i = 0; i < len; i++) { + cmd[0] = i + offset; + r = rtlsdr_write_array(dev, IICB, EEPROM_ADDR, cmd, 1); + r = rtlsdr_read_array(dev, IICB, EEPROM_ADDR, &cmd[1], 1); + + /* only write the byte if it differs */ + if (cmd[1] == data[i]) + continue; + + cmd[1] = data[i]; + r = rtlsdr_write_array(dev, IICB, EEPROM_ADDR, cmd, 2); + if (r != sizeof(cmd)) + return -3; + + /* for some EEPROMs (e.g. ATC 240LC02) we need a delay + * between write operations, otherwise they will fail */ +#ifdef _WIN32 + Sleep(5); +#else + usleep(5000); +#endif + } + + return 0; +} + +int rtlsdr_read_eeprom(rtlsdr_dev_t *dev, uint8_t *data, uint8_t offset, uint16_t len) +{ + int r = 0; + int i; + + if (!dev) + return -1; + + if ((len + offset) > 256) + return -2; + + r = rtlsdr_write_array(dev, IICB, EEPROM_ADDR, &offset, 1); + if (r < 0) + return -3; + + for (i = 0; i < len; i++) { + r = rtlsdr_read_array(dev, IICB, EEPROM_ADDR, data + i, 1); + + if (r < 0) + return -3; + } + + return r; +} + +int rtlsdr_set_center_freq(rtlsdr_dev_t *dev, uint32_t freq) +{ + int r = -1; + + if (!dev || !dev->tuner) + return -1; + + if (dev->direct_sampling) { + r = rtlsdr_set_if_freq(dev, freq); + } else if (dev->tuner && dev->tuner->set_freq) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->set_freq(dev, freq - dev->offs_freq); + rtlsdr_set_i2c_repeater(dev, 0); + } + + if (!r) + dev->freq = freq; + else + dev->freq = 0; + + return r; +} + +uint32_t rtlsdr_get_center_freq(rtlsdr_dev_t *dev) +{ + if (!dev) + return 0; + + return dev->freq; +} + +int rtlsdr_set_freq_correction(rtlsdr_dev_t *dev, int ppm) +{ + int r = 0; + + if (!dev) + return -1; + + if (dev->corr == ppm) + return -2; + + dev->corr = ppm; + + r |= rtlsdr_set_sample_freq_correction(dev, ppm); + + /* read corrected clock value into e4k and r82xx structure */ + if (rtlsdr_get_xtal_freq(dev, NULL, &dev->e4k_s.vco.fosc) || + rtlsdr_get_xtal_freq(dev, NULL, &dev->r82xx_c.xtal)) + return -3; + + if (dev->freq) /* retune to apply new correction value */ + r |= rtlsdr_set_center_freq(dev, dev->freq); + + return r; +} + +int rtlsdr_get_freq_correction(rtlsdr_dev_t *dev) +{ + if (!dev) + return 0; + + return dev->corr; +} + +enum rtlsdr_tuner rtlsdr_get_tuner_type(rtlsdr_dev_t *dev) +{ + if (!dev) + return RTLSDR_TUNER_UNKNOWN; + + return dev->tuner_type; +} + +int rtlsdr_get_tuner_gains(rtlsdr_dev_t *dev, int *gains) +{ + /* all gain values are expressed in tenths of a dB */ + const int e4k_gains[] = { -10, 15, 40, 65, 90, 115, 140, 165, 190, 215, + 240, 290, 340, 420 }; + const int fc0012_gains[] = { -99, -40, 71, 179, 192 }; + const int fc0013_gains[] = { -99, -73, -65, -63, -60, -58, -54, 58, 61, + 63, 65, 67, 68, 70, 71, 179, 181, 182, + 184, 186, 188, 191, 197 }; + const int fc2580_gains[] = { 0 /* no gain values */ }; + const int r82xx_gains[] = { 0, 9, 14, 27, 37, 77, 87, 125, 144, 157, + 166, 197, 207, 229, 254, 280, 297, 328, + 338, 364, 372, 386, 402, 421, 434, 439, + 445, 480, 496 }; + const int unknown_gains[] = { 0 /* no gain values */ }; + + const int *ptr = NULL; + int len = 0; + + if (!dev) + return -1; + + switch (dev->tuner_type) { + case RTLSDR_TUNER_E4000: + ptr = e4k_gains; len = sizeof(e4k_gains); + break; + case RTLSDR_TUNER_FC0012: + ptr = fc0012_gains; len = sizeof(fc0012_gains); + break; + case RTLSDR_TUNER_FC0013: + ptr = fc0013_gains; len = sizeof(fc0013_gains); + break; + case RTLSDR_TUNER_FC2580: + ptr = fc2580_gains; len = sizeof(fc2580_gains); + break; + case RTLSDR_TUNER_R820T: + case RTLSDR_TUNER_R828D: + ptr = r82xx_gains; len = sizeof(r82xx_gains); + break; + default: + ptr = unknown_gains; len = sizeof(unknown_gains); + break; + } + + if (!gains) { /* no buffer provided, just return the count */ + return len / sizeof(int); + } else { + if (len) + memcpy(gains, ptr, len); + + return len / sizeof(int); + } +} + +int rtlsdr_set_tuner_bandwidth(rtlsdr_dev_t *dev, uint32_t bw) +{ + int r = 0; + + if (!dev || !dev->tuner) + return -1; + + if (dev->tuner->set_bw) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->set_bw(dev, bw > 0 ? bw : dev->rate); + rtlsdr_set_i2c_repeater(dev, 0); + if (r) + return r; + dev->bw = bw; + } + return r; +} + +int rtlsdr_set_tuner_gain(rtlsdr_dev_t *dev, int gain) +{ + int r = 0; + + if (!dev || !dev->tuner) + return -1; + + 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; + else + dev->gain = 0; + + return r; +} + +int rtlsdr_get_tuner_gain(rtlsdr_dev_t *dev) +{ + if (!dev) + return 0; + + return dev->gain; +} + +int rtlsdr_set_tuner_if_gain(rtlsdr_dev_t *dev, int stage, int gain) +{ + int r = 0; + + if (!dev || !dev->tuner) + return -1; + + if (dev->tuner->set_if_gain) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->set_if_gain(dev, stage, gain); + rtlsdr_set_i2c_repeater(dev, 0); + } + + return r; +} + +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) +{ + int r = 0; + uint16_t tmp; + uint32_t rsamp_ratio, real_rsamp_ratio; + double real_rate; + + if (!dev) + return -1; + + /* check if the rate is supported by the resampler */ + if ((samp_rate <= 225000) || (samp_rate > 3200000) || + ((samp_rate > 300000) && (samp_rate <= 900000))) { + fprintf(stderr, "Invalid sample rate: %u Hz\n", samp_rate); + return -EINVAL; + } + + rsamp_ratio = (dev->rtl_xtal * TWO_POW(22)) / samp_rate; + rsamp_ratio &= 0x0ffffffc; + + real_rsamp_ratio = rsamp_ratio | ((rsamp_ratio & 0x08000000) << 1); + real_rate = (dev->rtl_xtal * TWO_POW(22)) / real_rsamp_ratio; + + if ( ((double)samp_rate) != real_rate ) + fprintf(stderr, "Exact sample rate is: %f Hz\n", real_rate); + + dev->rate = (uint32_t)real_rate; + + if (dev->tuner && dev->tuner->set_bw) { + rtlsdr_set_i2c_repeater(dev, 1); + dev->tuner->set_bw(dev, dev->bw > 0 ? dev->bw : dev->rate); + rtlsdr_set_i2c_repeater(dev, 0); + } + + tmp = (rsamp_ratio >> 16); + r |= rtlsdr_demod_write_reg(dev, 1, 0x9f, tmp, 2); + tmp = rsamp_ratio & 0xffff; + r |= rtlsdr_demod_write_reg(dev, 1, 0xa1, tmp, 2); + + r |= rtlsdr_set_sample_freq_correction(dev, dev->corr); + + /* reset demod (bit 3, soft_rst) */ + r |= rtlsdr_demod_write_reg(dev, 1, 0x01, 0x14, 1); + r |= rtlsdr_demod_write_reg(dev, 1, 0x01, 0x10, 1); + + /* recalculate offset frequency if offset tuning is enabled */ + if (dev->offs_freq) + rtlsdr_set_offset_tuning(dev, 1); + + return r; +} + +uint32_t rtlsdr_get_sample_rate(rtlsdr_dev_t *dev) +{ + if (!dev) + return 0; + + return dev->rate; +} + +int rtlsdr_set_testmode(rtlsdr_dev_t *dev, int on) +{ + if (!dev) + return -1; + + return rtlsdr_demod_write_reg(dev, 0, 0x19, on ? 0x03 : 0x05, 1); +} + +int rtlsdr_set_agc_mode(rtlsdr_dev_t *dev, int on) +{ + if (!dev) + return -1; + + return rtlsdr_demod_write_reg(dev, 0, 0x19, on ? 0x25 : 0x05, 1); +} + +int rtlsdr_set_direct_sampling(rtlsdr_dev_t *dev, int on) +{ + int r = 0; + + if (!dev) + return -1; + + if (on) { + if (dev->tuner && dev->tuner->exit) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->exit(dev); + rtlsdr_set_i2c_repeater(dev, 0); + } + + /* disable Zero-IF mode */ + r |= rtlsdr_demod_write_reg(dev, 1, 0xb1, 0x1a, 1); + + /* disable spectrum inversion */ + r |= rtlsdr_demod_write_reg(dev, 1, 0x15, 0x00, 1); + + /* only enable In-phase ADC input */ + r |= rtlsdr_demod_write_reg(dev, 0, 0x08, 0x4d, 1); + + /* swap I and Q ADC, this allows to select between two inputs */ + r |= rtlsdr_demod_write_reg(dev, 0, 0x06, (on > 1) ? 0x90 : 0x80, 1); + + fprintf(stderr, "Enabled direct sampling mode, input %i\n", on); + dev->direct_sampling = on; + } else { + if (dev->tuner && dev->tuner->init) { + rtlsdr_set_i2c_repeater(dev, 1); + r |= dev->tuner->init(dev); + rtlsdr_set_i2c_repeater(dev, 0); + } + + if ((dev->tuner_type == RTLSDR_TUNER_R820T) || + (dev->tuner_type == RTLSDR_TUNER_R828D)) { + r |= rtlsdr_set_if_freq(dev, R82XX_IF_FREQ); + + /* enable spectrum inversion */ + r |= rtlsdr_demod_write_reg(dev, 1, 0x15, 0x01, 1); + } else { + r |= rtlsdr_set_if_freq(dev, 0); + + /* enable In-phase + Quadrature ADC input */ + r |= rtlsdr_demod_write_reg(dev, 0, 0x08, 0xcd, 1); + + /* Enable Zero-IF mode */ + r |= rtlsdr_demod_write_reg(dev, 1, 0xb1, 0x1b, 1); + } + + /* opt_adc_iq = 0, default ADC_I/ADC_Q datapath */ + r |= rtlsdr_demod_write_reg(dev, 0, 0x06, 0x80, 1); + + fprintf(stderr, "Disabled direct sampling mode\n"); + dev->direct_sampling = 0; + } + + r |= rtlsdr_set_center_freq(dev, dev->freq); + + return r; +} + +int rtlsdr_get_direct_sampling(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + return dev->direct_sampling; +} + +int rtlsdr_set_offset_tuning(rtlsdr_dev_t *dev, int on) +{ + int r = 0; + int bw; + + if (!dev) + return -1; + + if ((dev->tuner_type == RTLSDR_TUNER_R820T) || + (dev->tuner_type == RTLSDR_TUNER_R828D)) + return -2; + + if (dev->direct_sampling) + return -3; + + /* based on keenerds 1/f noise measurements */ + dev->offs_freq = on ? ((dev->rate / 2) * 170 / 100) : 0; + r |= rtlsdr_set_if_freq(dev, dev->offs_freq); + + if (dev->tuner && dev->tuner->set_bw) { + rtlsdr_set_i2c_repeater(dev, 1); + if (on) { + bw = 2 * dev->offs_freq; + } else if (dev->bw > 0) { + bw = dev->bw; + } else { + bw = dev->rate; + } + dev->tuner->set_bw(dev, bw); + rtlsdr_set_i2c_repeater(dev, 0); + } + + if (dev->freq > dev->offs_freq) + r |= rtlsdr_set_center_freq(dev, dev->freq); + + return r; +} + +int rtlsdr_get_offset_tuning(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + return (dev->offs_freq) ? 1 : 0; +} + +static rtlsdr_dongle_t *find_known_device(uint16_t vid, uint16_t pid) +{ + unsigned int i; + rtlsdr_dongle_t *device = NULL; + + for (i = 0; i < sizeof(known_devices)/sizeof(rtlsdr_dongle_t); i++ ) { + if (known_devices[i].vid == vid && known_devices[i].pid == pid) { + device = &known_devices[i]; + break; + } + } + + return device; +} + +uint32_t rtlsdr_get_device_count(void) +{ + int i,r; + libusb_context *ctx; + libusb_device **list; + uint32_t device_count = 0; + struct libusb_device_descriptor dd; + ssize_t cnt; + + r = libusb_init(&ctx); + if(r < 0) + return 0; + + cnt = libusb_get_device_list(ctx, &list); + + for (i = 0; i < cnt; i++) { + libusb_get_device_descriptor(list[i], &dd); + + if (find_known_device(dd.idVendor, dd.idProduct)) + device_count++; + } + + libusb_free_device_list(list, 1); + + libusb_exit(ctx); + + return device_count; +} + +const char *rtlsdr_get_device_name(uint32_t index) +{ + int i,r; + libusb_context *ctx; + libusb_device **list; + struct libusb_device_descriptor dd; + rtlsdr_dongle_t *device = NULL; + uint32_t device_count = 0; + ssize_t cnt; + + r = libusb_init(&ctx); + if(r < 0) + return ""; + + cnt = libusb_get_device_list(ctx, &list); + + for (i = 0; i < cnt; i++) { + libusb_get_device_descriptor(list[i], &dd); + + device = find_known_device(dd.idVendor, dd.idProduct); + + if (device) { + device_count++; + + if (index == device_count - 1) + break; + } + } + + libusb_free_device_list(list, 1); + + libusb_exit(ctx); + + if (device) + return device->name; + else + return ""; +} + +int rtlsdr_get_device_usb_strings(uint32_t index, char *manufact, + char *product, char *serial) +{ + int r = -2; + int i; + libusb_context *ctx; + libusb_device **list; + struct libusb_device_descriptor dd; + rtlsdr_dongle_t *device = NULL; + rtlsdr_dev_t devt; + uint32_t device_count = 0; + ssize_t cnt; + + r = libusb_init(&ctx); + if(r < 0) + return r; + + cnt = libusb_get_device_list(ctx, &list); + + for (i = 0; i < cnt; i++) { + libusb_get_device_descriptor(list[i], &dd); + + device = find_known_device(dd.idVendor, dd.idProduct); + + if (device) { + device_count++; + + if (index == device_count - 1) { + r = libusb_open(list[i], &devt.devh); + if (!r) { + r = rtlsdr_get_usb_strings(&devt, + manufact, + product, + serial); + libusb_close(devt.devh); + } + break; + } + } + } + + libusb_free_device_list(list, 1); + + libusb_exit(ctx); + + return r; +} + +int rtlsdr_get_index_by_serial(const char *serial) +{ + int i, cnt, r; + char str[256]; + + if (!serial) + return -1; + + cnt = rtlsdr_get_device_count(); + + if (!cnt) + return -2; + + for (i = 0; i < cnt; i++) { + r = rtlsdr_get_device_usb_strings(i, NULL, NULL, str); + if (!r && !strcmp(serial, str)) + return i; + } + + return -3; +} + +int rtlsdr_open(rtlsdr_dev_t **out_dev, uint32_t index) +{ + int r; + int i; + libusb_device **list; + rtlsdr_dev_t *dev = NULL; + libusb_device *device = NULL; + uint32_t device_count = 0; + struct libusb_device_descriptor dd; + uint8_t reg; + ssize_t cnt; + + dev = malloc(sizeof(rtlsdr_dev_t)); + if (NULL == dev) + return -ENOMEM; + + memset(dev, 0, sizeof(rtlsdr_dev_t)); + memcpy(dev->fir, fir_default, sizeof(fir_default)); + + r = libusb_init(&dev->ctx); + if(r < 0){ + free(dev); + return -1; + } + + dev->dev_lost = 1; + + cnt = libusb_get_device_list(dev->ctx, &list); + + for (i = 0; i < cnt; i++) { + device = list[i]; + + libusb_get_device_descriptor(list[i], &dd); + + if (find_known_device(dd.idVendor, dd.idProduct)) { + device_count++; + } + + if (index == device_count - 1) + break; + + device = NULL; + } + + if (!device) { + r = -1; + goto err; + } + + r = libusb_open(device, &dev->devh); + if (r < 0) { + libusb_free_device_list(list, 1); + fprintf(stderr, "usb_open error %d\n", r); + if(r == LIBUSB_ERROR_ACCESS) + fprintf(stderr, "Please fix the device permissions, e.g. " + "by installing the udev rules file rtl-sdr.rules\n"); + goto err; + } + + libusb_free_device_list(list, 1); + + if (libusb_kernel_driver_active(dev->devh, 0) == 1) { + dev->driver_active = 1; + +#ifdef DETACH_KERNEL_DRIVER + if (!libusb_detach_kernel_driver(dev->devh, 0)) { + fprintf(stderr, "Detached kernel driver\n"); + } else { + fprintf(stderr, "Detaching kernel driver failed!"); + goto err; + } +#else + fprintf(stderr, "\nKernel driver is active, or device is " + "claimed by second instance of librtlsdr." + "\nIn the first case, please either detach" + " or blacklist the kernel module\n" + "(dvb_usb_rtl28xxu), or enable automatic" + " detaching at compile time.\n\n"); +#endif + } + + r = libusb_claim_interface(dev->devh, 0); + if (r < 0) { + fprintf(stderr, "usb_claim_interface error %d\n", r); + goto err; + } + + dev->rtl_xtal = DEF_RTL_XTAL_FREQ; + + /* perform a dummy write, if it fails, reset the device */ + if (rtlsdr_write_reg(dev, USBB, USB_SYSCTL, 0x09, 1) < 0) { + fprintf(stderr, "Resetting device...\n"); + libusb_reset_device(dev->devh); + } + + rtlsdr_init_baseband(dev); + dev->dev_lost = 0; + + /* Probe tuners */ + rtlsdr_set_i2c_repeater(dev, 1); + + reg = rtlsdr_i2c_read_reg(dev, E4K_I2C_ADDR, E4K_CHECK_ADDR); + if (reg == E4K_CHECK_VAL) { + fprintf(stderr, "Found Elonics E4000 tuner\n"); + dev->tuner_type = RTLSDR_TUNER_E4000; + goto found; + } + + reg = rtlsdr_i2c_read_reg(dev, FC0013_I2C_ADDR, FC0013_CHECK_ADDR); + if (reg == FC0013_CHECK_VAL) { + fprintf(stderr, "Found Fitipower FC0013 tuner\n"); + dev->tuner_type = RTLSDR_TUNER_FC0013; + goto found; + } + + reg = rtlsdr_i2c_read_reg(dev, R820T_I2C_ADDR, R82XX_CHECK_ADDR); + if (reg == R82XX_CHECK_VAL) { + fprintf(stderr, "Found Rafael Micro R820T tuner\n"); + dev->tuner_type = RTLSDR_TUNER_R820T; + goto found; + } + + reg = rtlsdr_i2c_read_reg(dev, R828D_I2C_ADDR, R82XX_CHECK_ADDR); + if (reg == R82XX_CHECK_VAL) { + fprintf(stderr, "Found Rafael Micro R828D tuner\n"); + dev->tuner_type = RTLSDR_TUNER_R828D; + goto found; + } + + /* initialise GPIOs */ + rtlsdr_set_gpio_output(dev, 5); + + /* reset tuner before probing */ + rtlsdr_set_gpio_bit(dev, 5, 1); + rtlsdr_set_gpio_bit(dev, 5, 0); + + reg = rtlsdr_i2c_read_reg(dev, FC2580_I2C_ADDR, FC2580_CHECK_ADDR); + if ((reg & 0x7f) == FC2580_CHECK_VAL) { + fprintf(stderr, "Found FCI 2580 tuner\n"); + dev->tuner_type = RTLSDR_TUNER_FC2580; + goto found; + } + + reg = rtlsdr_i2c_read_reg(dev, FC0012_I2C_ADDR, FC0012_CHECK_ADDR); + if (reg == FC0012_CHECK_VAL) { + fprintf(stderr, "Found Fitipower FC0012 tuner\n"); + rtlsdr_set_gpio_output(dev, 6); + dev->tuner_type = RTLSDR_TUNER_FC0012; + goto found; + } + +found: + /* use the rtl clock value by default */ + dev->tun_xtal = dev->rtl_xtal; + dev->tuner = &tuners[dev->tuner_type]; + + switch (dev->tuner_type) { + case RTLSDR_TUNER_R828D: + dev->tun_xtal = R828D_XTAL_FREQ; + case RTLSDR_TUNER_R820T: + /* disable Zero-IF mode */ + rtlsdr_demod_write_reg(dev, 1, 0xb1, 0x1a, 1); + + /* only enable In-phase ADC input */ + rtlsdr_demod_write_reg(dev, 0, 0x08, 0x4d, 1); + + /* the R82XX use 3.57 MHz IF for the DVB-T 6 MHz mode, and + * 4.57 MHz for the 8 MHz mode */ + rtlsdr_set_if_freq(dev, R82XX_IF_FREQ); + + /* enable spectrum inversion */ + rtlsdr_demod_write_reg(dev, 1, 0x15, 0x01, 1); + break; + case RTLSDR_TUNER_UNKNOWN: + fprintf(stderr, "No supported tuner found\n"); + rtlsdr_set_direct_sampling(dev, 1); + break; + default: + break; + } + + if (dev->tuner->init) + r = dev->tuner->init(dev); + + rtlsdr_set_i2c_repeater(dev, 0); + + *out_dev = dev; + + return 0; +err: + if (dev) { + if (dev->ctx) + libusb_exit(dev->ctx); + + free(dev); + } + + return r; +} + +int rtlsdr_close(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + if(!dev->dev_lost) { + /* block until all async operations have been completed (if any) */ + while (RTLSDR_INACTIVE != dev->async_status) { +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif + } + + rtlsdr_deinit_baseband(dev); + } + + libusb_release_interface(dev->devh, 0); + +#ifdef DETACH_KERNEL_DRIVER + if (dev->driver_active) { + if (!libusb_attach_kernel_driver(dev->devh, 0)) + fprintf(stderr, "Reattached kernel driver\n"); + else + fprintf(stderr, "Reattaching kernel driver failed!\n"); + } +#endif + + libusb_close(dev->devh); + + libusb_exit(dev->ctx); + + free(dev); + + return 0; +} + +int rtlsdr_reset_buffer(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x1002, 2); + rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x0000, 2); + + return 0; +} + +int rtlsdr_read_sync(rtlsdr_dev_t *dev, void *buf, int len, int *n_read) +{ + if (!dev) + return -1; + + return libusb_bulk_transfer(dev->devh, 0x81, buf, len, n_read, BULK_TIMEOUT); +} + +static void LIBUSB_CALL _libusb_callback(struct libusb_transfer *xfer) +{ + rtlsdr_dev_t *dev = (rtlsdr_dev_t *)xfer->user_data; + + if (LIBUSB_TRANSFER_COMPLETED == xfer->status) { + if (dev->cb) + dev->cb(xfer->buffer, xfer->actual_length, dev->cb_ctx); + + libusb_submit_transfer(xfer); /* resubmit transfer */ + dev->xfer_errors = 0; + } else if (LIBUSB_TRANSFER_CANCELLED != xfer->status) { +#ifndef _WIN32 + if (LIBUSB_TRANSFER_ERROR == xfer->status) + dev->xfer_errors++; + + if (dev->xfer_errors >= dev->xfer_buf_num || + LIBUSB_TRANSFER_NO_DEVICE == xfer->status) { +#endif + dev->dev_lost = 1; + rtlsdr_cancel_async(dev); + fprintf(stderr, "cb transfer status: %d, " + "canceling...\n", xfer->status); +#ifndef _WIN32 + } +#endif + } +} + +int rtlsdr_wait_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx) +{ + return rtlsdr_read_async(dev, cb, ctx, 0, 0); +} + +static int _rtlsdr_alloc_async_buffers(rtlsdr_dev_t *dev) +{ + unsigned int i; + + if (!dev) + return -1; + + if (!dev->xfer) { + dev->xfer = malloc(dev->xfer_buf_num * + sizeof(struct libusb_transfer *)); + + for(i = 0; i < dev->xfer_buf_num; ++i) + dev->xfer[i] = libusb_alloc_transfer(0); + } + + if (!dev->xfer_buf) { + dev->xfer_buf = malloc(dev->xfer_buf_num * + sizeof(unsigned char *)); + + for(i = 0; i < dev->xfer_buf_num; ++i) + dev->xfer_buf[i] = malloc(dev->xfer_buf_len); + } + + return 0; +} + +static int _rtlsdr_free_async_buffers(rtlsdr_dev_t *dev) +{ + unsigned int i; + + if (!dev) + return -1; + + if (dev->xfer) { + for(i = 0; i < dev->xfer_buf_num; ++i) { + if (dev->xfer[i]) { + libusb_free_transfer(dev->xfer[i]); + } + } + + free(dev->xfer); + dev->xfer = NULL; + } + + if (dev->xfer_buf) { + for(i = 0; i < dev->xfer_buf_num; ++i) { + if (dev->xfer_buf[i]) + free(dev->xfer_buf[i]); + } + + free(dev->xfer_buf); + dev->xfer_buf = NULL; + } + + return 0; +} + +int rtlsdr_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx, + uint32_t buf_num, uint32_t buf_len) +{ + unsigned int i; + int r = 0; + struct timeval tv = { 1, 0 }; + struct timeval zerotv = { 0, 0 }; + enum rtlsdr_async_status next_status = RTLSDR_INACTIVE; + + if (!dev) + return -1; + + if (RTLSDR_INACTIVE != dev->async_status) + return -2; + + dev->async_status = RTLSDR_RUNNING; + dev->async_cancel = 0; + + dev->cb = cb; + dev->cb_ctx = ctx; + + if (buf_num > 0) + dev->xfer_buf_num = buf_num; + else + dev->xfer_buf_num = DEFAULT_BUF_NUMBER; + + if (buf_len > 0 && buf_len % 512 == 0) /* len must be multiple of 512 */ + dev->xfer_buf_len = buf_len; + else + dev->xfer_buf_len = DEFAULT_BUF_LENGTH; + + _rtlsdr_alloc_async_buffers(dev); + + for(i = 0; i < dev->xfer_buf_num; ++i) { + libusb_fill_bulk_transfer(dev->xfer[i], + dev->devh, + 0x81, + dev->xfer_buf[i], + dev->xfer_buf_len, + _libusb_callback, + (void *)dev, + BULK_TIMEOUT); + + r = libusb_submit_transfer(dev->xfer[i]); + if (r < 0) { + fprintf(stderr, "Failed to submit transfer %i!\n", i); + dev->async_status = RTLSDR_CANCELING; + break; + } + } + + while (RTLSDR_INACTIVE != dev->async_status) { + r = libusb_handle_events_timeout_completed(dev->ctx, &tv, + &dev->async_cancel); + if (r < 0) { + /*fprintf(stderr, "handle_events returned: %d\n", r);*/ + if (r == LIBUSB_ERROR_INTERRUPTED) /* stray signal */ + continue; + break; + } + + if (RTLSDR_CANCELING == dev->async_status) { + next_status = RTLSDR_INACTIVE; + + if (!dev->xfer) + break; + + for(i = 0; i < dev->xfer_buf_num; ++i) { + if (!dev->xfer[i]) + continue; + + if (LIBUSB_TRANSFER_CANCELLED != + dev->xfer[i]->status) { + r = libusb_cancel_transfer(dev->xfer[i]); + /* handle events after canceling + * to allow transfer status to + * propagate */ + libusb_handle_events_timeout_completed(dev->ctx, + &zerotv, NULL); + if (r < 0) + continue; + + next_status = RTLSDR_CANCELING; + } + } + + if (dev->dev_lost || RTLSDR_INACTIVE == next_status) { + /* handle any events that still need to + * be handled before exiting after we + * just cancelled all transfers */ + libusb_handle_events_timeout_completed(dev->ctx, + &zerotv, NULL); + break; + } + } + } + + _rtlsdr_free_async_buffers(dev); + + dev->async_status = next_status; + + return r; +} + +int rtlsdr_cancel_async(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + /* if streaming, try to cancel gracefully */ + if (RTLSDR_RUNNING == dev->async_status) { + dev->async_status = RTLSDR_CANCELING; + dev->async_cancel = 1; + return 0; + } + + /* if called while in pending state, change the state forcefully */ +#if 0 + if (RTLSDR_INACTIVE != dev->async_status) { + dev->async_status = RTLSDR_INACTIVE; + return 0; + } +#endif + return -2; +} + +uint32_t rtlsdr_get_tuner_clock(void *dev) +{ + uint32_t tuner_freq; + + if (!dev) + return 0; + + /* read corrected clock value */ + if (rtlsdr_get_xtal_freq((rtlsdr_dev_t *)dev, NULL, &tuner_freq)) + return 0; + + return tuner_freq; +} + +int rtlsdr_i2c_write_fn(void *dev, uint8_t addr, uint8_t *buf, int len) +{ + if (dev) + return rtlsdr_i2c_write(((rtlsdr_dev_t *)dev), addr, buf, len); + + return -1; +} + +int rtlsdr_i2c_read_fn(void *dev, uint8_t addr, uint8_t *buf, int len) +{ + if (dev) + return rtlsdr_i2c_read(((rtlsdr_dev_t *)dev), addr, buf, len); + + return -1; +} diff --git a/src/make.mk b/src/make.mk new file mode 100644 index 0000000..57e1dbf --- /dev/null +++ b/src/make.mk @@ -0,0 +1,13 @@ +DIR_SRC=src +SRC_LIB += $(wildcard $(DIR_SRC)/*.c) +OBJ_LIB += $(SRC_LIB:.c=.o) +LDFLAGS_LIB=`pkg-config --libs libusb` -lc + +$(DIR_SRC)-lib-o: $(OBJ_LIB) + +$(DIR_SRC)-lib: $(DIR_SRC)-lib-o + gcc -shared -Wl,-soname,lib$(PROJECT).so.1 -o lib$(PROJECT).so.1 $(subst $(DIR_SRC)/,$(BUILD_DIR)$(DIR_SRC)/,$(OBJ_LIB)) $(LDFLAGS_LIB) + ar rcv lib$(PROJECT).a $(subst $(DIR_SRC)/,$(BUILD_DIR)$(DIR_SRC)/,$(OBJ_LIB)) + +$(DIR_SRC)/%.o: $(DIR_SRC)/%.c + $(CC) $(CFLAGS) -fPIC $(INCLUDE) -c $< -o $(BUILD_DIR)$@ diff --git a/src/tuner_e4k.c b/src/tuner_e4k.c new file mode 100644 index 0000000..400e745 --- /dev/null +++ b/src/tuner_e4k.c @@ -0,0 +1,1000 @@ +/* + * Elonics E4000 tuner driver + * + * (C) 2011-2012 by Harald Welte <laforge@gnumonks.org> + * (C) 2012 by Sylvain Munaut <tnt@246tNt.com> + * (C) 2012 by Hoernchen <la@tfc-server.de> + * + * 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 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. + * + * 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> +#include <rtlsdr_i2c.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 */ + +/*! \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 + */ +static int e4k_reg_write(struct e4k_state *e4k, uint8_t reg, uint8_t val) +{ + int r; + uint8_t data[2]; + data[0] = reg; + data[1] = val; + + r = rtlsdr_i2c_write_fn(e4k->rtl_dev, e4k->i2c_addr, data, 2); + return r == 2 ? 0 : -1; +} + +/*! \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 + */ +static int e4k_reg_read(struct e4k_state *e4k, uint8_t reg) +{ + uint8_t data = reg; + + if (rtlsdr_i2c_write_fn(e4k->rtl_dev, e4k->i2c_addr, &data, 1) < 1) + return -1; + + if (rtlsdr_i2c_read_fn(e4k->rtl_dev, e4k->i2c_addr, &data, 1) < 1) + return -1; + + return data; +} + +/*! \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(enum e4k_band band, uint32_t freq) +{ + int rc; + + switch (band) { + case E4K_BAND_VHF2: + case E4K_BAND_VHF3: + rc = 0; + break; + case E4K_BAND_UHF: + rc = closest_arr_idx(rf_filt_center_uhf, + ARRAY_SIZE(rf_filt_center_uhf), + freq); + break; + case E4K_BAND_L: + rc = closest_arr_idx(rf_filt_center_l, + ARRAY_SIZE(rf_filt_center_l), + freq); + break; + default: + rc = -EINVAL; + break; + } + + 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->band, 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, "[E4K] 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, "[E4K] Fosc %u invalid\n", fosc); + return 0; + } + + return 1; +} + +static int is_z_valid(uint32_t z) +{ + if (z > 255) { + fprintf(stderr, "[E4K] 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 uint32_t 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; + } + + /* workaround: if we don't reset this register before writing to it, + * we get a gap between 325-350 MHz */ + rc = e4k_reg_set_mask(e4k, E4K_REG_SYNTH1, 0x06, 0); + 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, + * 0 in case of error + */ +uint32_t 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 0; + + 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, "[E4K] 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) +{ + /* 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(1135)) + 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) +{ + uint32_t rc; + struct e4k_pll_params p; + + /* determine PLL parameters */ + rc = e4k_compute_pll_params(&p, e4k->vco.fosc, freq); + if (!rc) + return -EINVAL; + + /* 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)) { + fprintf(stderr, "[E4K] PLL not locked for %u Hz!\n", freq); + 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[] = { + { 0, 0, 0 }, + { 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); + + /* special case: 0 = off*/ + if(0 == gain) + return 0; + else + return -EINVAL; +} + +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] number of the stage (1..6) + * \param [value] gain value in dB + * \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; + + fprintf(stderr, "[E4K] 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; +} + +/*********************************************************************** + * Standby */ + +/*! \brief Enable/disable standby mode + */ +int e4k_standby(struct e4k_state *e4k, int enable) +{ + e4k_reg_set_mask(e4k, E4K_REG_MASTER1, E4K_MASTER1_NORM_STBY, + enable ? 0 : E4K_MASTER1_NORM_STBY); + + 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; +} diff --git a/src/tuner_fc0012.c b/src/tuner_fc0012.c new file mode 100644 index 0000000..768cf1c --- /dev/null +++ b/src/tuner_fc0012.c @@ -0,0 +1,345 @@ +/* + * Fitipower FC0012 tuner driver + * + * Copyright (C) 2012 Hans-Frieder Vogt <hfvogt@gmx.net> + * + * modified for use in librtlsdr + * Copyright (C) 2012 Steve Markgraf <steve@steve-m.de> + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <stdio.h> + +#include "rtlsdr_i2c.h" +#include "tuner_fc0012.h" + +static int fc0012_writereg(void *dev, uint8_t reg, uint8_t val) +{ + uint8_t data[2]; + data[0] = reg; + data[1] = val; + + if (rtlsdr_i2c_write_fn(dev, FC0012_I2C_ADDR, data, 2) < 0) + return -1; + + return 0; +} + +static int fc0012_readreg(void *dev, uint8_t reg, uint8_t *val) +{ + uint8_t data = reg; + + if (rtlsdr_i2c_write_fn(dev, FC0012_I2C_ADDR, &data, 1) < 0) + return -1; + + if (rtlsdr_i2c_read_fn(dev, FC0012_I2C_ADDR, &data, 1) < 0) + return -1; + + *val = data; + + return 0; +} + +/* Incomplete list of register settings: + * + * Name Reg Bits Desc + * CHIP_ID 0x00 0-7 Chip ID (constant 0xA1) + * RF_A 0x01 0-3 Number of count-to-9 cycles in RF + * divider (suggested: 2..9) + * RF_M 0x02 0-7 Total number of cycles (to-8 and to-9) + * in RF divider + * RF_K_HIGH 0x03 0-6 Bits 8..14 of fractional divider + * RF_K_LOW 0x04 0-7 Bits 0..7 of fractional RF divider + * RF_OUTDIV_A 0x05 3-7 Power of two required? + * LNA_POWER_DOWN 0x06 0 Set to 1 to switch off low noise amp + * RF_OUTDIV_B 0x06 1 Set to select 3 instead of 2 for the + * RF output divider + * VCO_SPEED 0x06 3 Select tuning range of VCO: + * 0 = Low range, (ca. 1.1 - 1.5GHz) + * 1 = High range (ca. 1.4 - 1.8GHz) + * BANDWIDTH 0x06 6-7 Set bandwidth. 6MHz = 0x80, 7MHz=0x40 + * 8MHz=0x00 + * XTAL_SPEED 0x07 5 Set to 1 for 28.8MHz Crystal input + * or 0 for 36MHz + * <agc params> 0x08 0-7 + * EN_CAL_RSSI 0x09 4 Enable calibrate RSSI + * (Receive Signal Strength Indicator) + * LNA_FORCE 0x0d 0 + * AGC_FORCE 0x0d ? + * LNA_GAIN 0x13 3-4 Low noise amp gain + * LNA_COMPS 0x15 3 ? + * VCO_CALIB 0x0e 7 Set high then low to calibrate VCO + * (fast lock?) + * VCO_VOLTAGE 0x0e 0-6 Read Control voltage of VCO + * (big value -> low freq) + */ + +int fc0012_init(void *dev) +{ + int ret = 0; + unsigned int i; + uint8_t reg[] = { + 0x00, /* dummy reg. 0 */ + 0x05, /* reg. 0x01 */ + 0x10, /* reg. 0x02 */ + 0x00, /* reg. 0x03 */ + 0x00, /* reg. 0x04 */ + 0x0f, /* reg. 0x05: may also be 0x0a */ + 0x00, /* reg. 0x06: divider 2, VCO slow */ + 0x00, /* reg. 0x07: may also be 0x0f */ + 0xff, /* reg. 0x08: AGC Clock divide by 256, AGC gain 1/256, + Loop Bw 1/8 */ + 0x6e, /* reg. 0x09: Disable LoopThrough, Enable LoopThrough: 0x6f */ + 0xb8, /* reg. 0x0a: Disable LO Test Buffer */ + 0x82, /* reg. 0x0b: Output Clock is same as clock frequency, + may also be 0x83 */ + 0xfc, /* reg. 0x0c: depending on AGC Up-Down mode, may need 0xf8 */ + 0x02, /* reg. 0x0d: AGC Not Forcing & LNA Forcing, 0x02 for DVB-T */ + 0x00, /* reg. 0x0e */ + 0x00, /* reg. 0x0f */ + 0x00, /* reg. 0x10: may also be 0x0d */ + 0x00, /* reg. 0x11 */ + 0x1f, /* reg. 0x12: Set to maximum gain */ + 0x08, /* reg. 0x13: Set to Middle Gain: 0x08, + Low Gain: 0x00, High Gain: 0x10, enable IX2: 0x80 */ + 0x00, /* reg. 0x14 */ + 0x04, /* reg. 0x15: Enable LNA COMPS */ + }; + +#if 0 + switch (rtlsdr_get_tuner_clock(dev)) { + case FC_XTAL_27_MHZ: + case FC_XTAL_28_8_MHZ: + reg[0x07] |= 0x20; + break; + case FC_XTAL_36_MHZ: + default: + break; + } +#endif + reg[0x07] |= 0x20; + +// if (priv->dual_master) + reg[0x0c] |= 0x02; + + for (i = 1; i < sizeof(reg); i++) { + ret = fc0012_writereg(dev, i, reg[i]); + if (ret) + break; + } + + return ret; +} + +int fc0012_set_params(void *dev, uint32_t freq, uint32_t bandwidth) +{ + int i, ret = 0; + uint8_t reg[7], am, pm, multi, tmp; + uint64_t f_vco; + uint32_t xtal_freq_div_2; + uint16_t xin, xdiv; + int vco_select = 0; + + xtal_freq_div_2 = rtlsdr_get_tuner_clock(dev) / 2; + + /* select frequency divider and the frequency of VCO */ + if (freq < 37084000) { /* freq * 96 < 3560000000 */ + multi = 96; + reg[5] = 0x82; + reg[6] = 0x00; + } else if (freq < 55625000) { /* freq * 64 < 3560000000 */ + multi = 64; + reg[5] = 0x82; + reg[6] = 0x02; + } else if (freq < 74167000) { /* freq * 48 < 3560000000 */ + multi = 48; + reg[5] = 0x42; + reg[6] = 0x00; + } else if (freq < 111250000) { /* freq * 32 < 3560000000 */ + multi = 32; + reg[5] = 0x42; + reg[6] = 0x02; + } else if (freq < 148334000) { /* freq * 24 < 3560000000 */ + multi = 24; + reg[5] = 0x22; + reg[6] = 0x00; + } else if (freq < 222500000) { /* freq * 16 < 3560000000 */ + multi = 16; + reg[5] = 0x22; + reg[6] = 0x02; + } else if (freq < 296667000) { /* freq * 12 < 3560000000 */ + multi = 12; + reg[5] = 0x12; + reg[6] = 0x00; + } else if (freq < 445000000) { /* freq * 8 < 3560000000 */ + multi = 8; + reg[5] = 0x12; + reg[6] = 0x02; + } else if (freq < 593334000) { /* freq * 6 < 3560000000 */ + multi = 6; + reg[5] = 0x0a; + reg[6] = 0x00; + } else { + multi = 4; + reg[5] = 0x0a; + reg[6] = 0x02; + } + + f_vco = freq * multi; + + if (f_vco >= 3060000000U) { + reg[6] |= 0x08; + vco_select = 1; + } + + /* From divided value (XDIV) determined the FA and FP value */ + xdiv = (uint16_t)(f_vco / xtal_freq_div_2); + if ((f_vco - xdiv * xtal_freq_div_2) >= (xtal_freq_div_2 / 2)) + xdiv++; + + pm = (uint8_t)(xdiv / 8); + am = (uint8_t)(xdiv - (8 * pm)); + + if (am < 2) { + am += 8; + pm--; + } + + if (pm > 31) { + reg[1] = am + (8 * (pm - 31)); + reg[2] = 31; + } else { + reg[1] = am; + reg[2] = pm; + } + + if ((reg[1] > 15) || (reg[2] < 0x0b)) { + fprintf(stderr, "[FC0012] no valid PLL combination " + "found for %u Hz!\n", freq); + return -1; + } + + /* fix clock out */ + reg[6] |= 0x20; + + /* From VCO frequency determines the XIN ( fractional part of Delta + Sigma PLL) and divided value (XDIV) */ + xin = (uint16_t)((f_vco - (f_vco / xtal_freq_div_2) * xtal_freq_div_2) / 1000); + xin = (xin << 15) / (xtal_freq_div_2 / 1000); + if (xin >= 16384) + xin += 32768; + + reg[3] = xin >> 8; /* xin with 9 bit resolution */ + reg[4] = xin & 0xff; + + reg[6] &= 0x3f; /* bits 6 and 7 describe the bandwidth */ + switch (bandwidth) { + case 6000000: + reg[6] |= 0x80; + break; + case 7000000: + reg[6] |= 0x40; + break; + case 8000000: + default: + break; + } + + /* modified for Realtek demod */ + reg[5] |= 0x07; + + for (i = 1; i <= 6; i++) { + ret = fc0012_writereg(dev, i, reg[i]); + if (ret) + goto exit; + } + + /* VCO Calibration */ + ret = fc0012_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x00); + + /* VCO Re-Calibration if needed */ + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x00); + + if (!ret) { +// msleep(10); + ret = fc0012_readreg(dev, 0x0e, &tmp); + } + if (ret) + goto exit; + + /* vco selection */ + tmp &= 0x3f; + + if (vco_select) { + if (tmp > 0x3c) { + reg[6] &= ~0x08; + ret = fc0012_writereg(dev, 0x06, reg[6]); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x00); + } + } else { + if (tmp < 0x02) { + reg[6] |= 0x08; + ret = fc0012_writereg(dev, 0x06, reg[6]); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x00); + } + } + +exit: + return ret; +} + +int fc0012_set_gain(void *dev, int gain) +{ + int ret; + uint8_t tmp = 0; + + ret = fc0012_readreg(dev, 0x13, &tmp); + + /* mask bits off */ + tmp &= 0xe0; + + switch (gain) { + case -99: /* -9.9 dB */ + tmp |= 0x02; + break; + case -40: /* -4 dB */ + break; + case 71: + tmp |= 0x08; /* 7.1 dB */ + break; + case 179: + tmp |= 0x17; /* 17.9 dB */ + break; + case 192: + default: + tmp |= 0x10; /* 19.2 dB */ + break; + } + + ret = fc0012_writereg(dev, 0x13, tmp); + + return ret; +} diff --git a/src/tuner_fc0013.c b/src/tuner_fc0013.c new file mode 100644 index 0000000..78b696e --- /dev/null +++ b/src/tuner_fc0013.c @@ -0,0 +1,500 @@ +/* + * Fitipower FC0013 tuner driver + * + * Copyright (C) 2012 Hans-Frieder Vogt <hfvogt@gmx.net> + * partially based on driver code from Fitipower + * Copyright (C) 2010 Fitipower Integrated Technology Inc + * + * modified for use in librtlsdr + * Copyright (C) 2012 Steve Markgraf <steve@steve-m.de> + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdint.h> +#include <stdio.h> + +#include "rtlsdr_i2c.h" +#include "tuner_fc0013.h" + +static int fc0013_writereg(void *dev, uint8_t reg, uint8_t val) +{ + uint8_t data[2]; + data[0] = reg; + data[1] = val; + + if (rtlsdr_i2c_write_fn(dev, FC0013_I2C_ADDR, data, 2) < 0) + return -1; + + return 0; +} + +static int fc0013_readreg(void *dev, uint8_t reg, uint8_t *val) +{ + uint8_t data = reg; + + if (rtlsdr_i2c_write_fn(dev, FC0013_I2C_ADDR, &data, 1) < 0) + return -1; + + if (rtlsdr_i2c_read_fn(dev, FC0013_I2C_ADDR, &data, 1) < 0) + return -1; + + *val = data; + + return 0; +} + +int fc0013_init(void *dev) +{ + int ret = 0; + unsigned int i; + uint8_t reg[] = { + 0x00, /* reg. 0x00: dummy */ + 0x09, /* reg. 0x01 */ + 0x16, /* reg. 0x02 */ + 0x00, /* reg. 0x03 */ + 0x00, /* reg. 0x04 */ + 0x17, /* reg. 0x05 */ + 0x02, /* reg. 0x06: LPF bandwidth */ + 0x0a, /* reg. 0x07: CHECK */ + 0xff, /* reg. 0x08: AGC Clock divide by 256, AGC gain 1/256, + Loop Bw 1/8 */ + 0x6e, /* reg. 0x09: Disable LoopThrough, Enable LoopThrough: 0x6f */ + 0xb8, /* reg. 0x0a: Disable LO Test Buffer */ + 0x82, /* reg. 0x0b: CHECK */ + 0xfc, /* reg. 0x0c: depending on AGC Up-Down mode, may need 0xf8 */ + 0x01, /* reg. 0x0d: AGC Not Forcing & LNA Forcing, may need 0x02 */ + 0x00, /* reg. 0x0e */ + 0x00, /* reg. 0x0f */ + 0x00, /* reg. 0x10 */ + 0x00, /* reg. 0x11 */ + 0x00, /* reg. 0x12 */ + 0x00, /* reg. 0x13 */ + 0x50, /* reg. 0x14: DVB-t High Gain, UHF. + Middle Gain: 0x48, Low Gain: 0x40 */ + 0x01, /* reg. 0x15 */ + }; +#if 0 + switch (rtlsdr_get_tuner_clock(dev)) { + case FC_XTAL_27_MHZ: + case FC_XTAL_28_8_MHZ: + reg[0x07] |= 0x20; + break; + case FC_XTAL_36_MHZ: + default: + break; + } +#endif + reg[0x07] |= 0x20; + +// if (dev->dual_master) + reg[0x0c] |= 0x02; + + for (i = 1; i < sizeof(reg); i++) { + ret = fc0013_writereg(dev, i, reg[i]); + if (ret < 0) + break; + } + + return ret; +} + +int fc0013_rc_cal_add(void *dev, int rc_val) +{ + int ret; + uint8_t rc_cal; + int val; + + /* push rc_cal value, get rc_cal value */ + ret = fc0013_writereg(dev, 0x10, 0x00); + if (ret) + goto error_out; + + /* get rc_cal value */ + ret = fc0013_readreg(dev, 0x10, &rc_cal); + if (ret) + goto error_out; + + rc_cal &= 0x0f; + + val = (int)rc_cal + rc_val; + + /* forcing rc_cal */ + ret = fc0013_writereg(dev, 0x0d, 0x11); + if (ret) + goto error_out; + + /* modify rc_cal value */ + if (val > 15) + ret = fc0013_writereg(dev, 0x10, 0x0f); + else if (val < 0) + ret = fc0013_writereg(dev, 0x10, 0x00); + else + ret = fc0013_writereg(dev, 0x10, (uint8_t)val); + +error_out: + return ret; +} + +int fc0013_rc_cal_reset(void *dev) +{ + int ret; + + ret = fc0013_writereg(dev, 0x0d, 0x01); + if (!ret) + ret = fc0013_writereg(dev, 0x10, 0x00); + + return ret; +} + +static int fc0013_set_vhf_track(void *dev, uint32_t freq) +{ + int ret; + uint8_t tmp; + + ret = fc0013_readreg(dev, 0x1d, &tmp); + if (ret) + goto error_out; + tmp &= 0xe3; + if (freq <= 177500000) { /* VHF Track: 7 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x1c); + } else if (freq <= 184500000) { /* VHF Track: 6 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x18); + } else if (freq <= 191500000) { /* VHF Track: 5 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x14); + } else if (freq <= 198500000) { /* VHF Track: 4 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x10); + } else if (freq <= 205500000) { /* VHF Track: 3 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x0c); + } else if (freq <= 219500000) { /* VHF Track: 2 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x08); + } else if (freq < 300000000) { /* VHF Track: 1 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x04); + } else { /* UHF and GPS */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x1c); + } + +error_out: + return ret; +} + +int fc0013_set_params(void *dev, uint32_t freq, uint32_t bandwidth) +{ + int i, ret = 0; + uint8_t reg[7], am, pm, multi, tmp; + uint64_t f_vco; + uint32_t xtal_freq_div_2; + uint16_t xin, xdiv; + int vco_select = 0; + + xtal_freq_div_2 = rtlsdr_get_tuner_clock(dev) / 2; + + /* set VHF track */ + ret = fc0013_set_vhf_track(dev, freq); + if (ret) + goto exit; + + if (freq < 300000000) { + /* enable VHF filter */ + ret = fc0013_readreg(dev, 0x07, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x07, tmp | 0x10); + if (ret) + goto exit; + + /* disable UHF & disable GPS */ + ret = fc0013_readreg(dev, 0x14, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x14, tmp & 0x1f); + if (ret) + goto exit; + } else if (freq <= 862000000) { + /* disable VHF filter */ + ret = fc0013_readreg(dev, 0x07, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x07, tmp & 0xef); + if (ret) + goto exit; + + /* enable UHF & disable GPS */ + ret = fc0013_readreg(dev, 0x14, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x14, (tmp & 0x1f) | 0x40); + if (ret) + goto exit; + } else { + /* disable VHF filter */ + ret = fc0013_readreg(dev, 0x07, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x07, tmp & 0xef); + if (ret) + goto exit; + + /* disable UHF & enable GPS */ + ret = fc0013_readreg(dev, 0x14, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x14, (tmp & 0x1f) | 0x20); + if (ret) + goto exit; + } + + /* select frequency divider and the frequency of VCO */ + if (freq < 37084000) { /* freq * 96 < 3560000000 */ + multi = 96; + reg[5] = 0x82; + reg[6] = 0x00; + } else if (freq < 55625000) { /* freq * 64 < 3560000000 */ + multi = 64; + reg[5] = 0x02; + reg[6] = 0x02; + } else if (freq < 74167000) { /* freq * 48 < 3560000000 */ + multi = 48; + reg[5] = 0x42; + reg[6] = 0x00; + } else if (freq < 111250000) { /* freq * 32 < 3560000000 */ + multi = 32; + reg[5] = 0x82; + reg[6] = 0x02; + } else if (freq < 148334000) { /* freq * 24 < 3560000000 */ + multi = 24; + reg[5] = 0x22; + reg[6] = 0x00; + } else if (freq < 222500000) { /* freq * 16 < 3560000000 */ + multi = 16; + reg[5] = 0x42; + reg[6] = 0x02; + } else if (freq < 296667000) { /* freq * 12 < 3560000000 */ + multi = 12; + reg[5] = 0x12; + reg[6] = 0x00; + } else if (freq < 445000000) { /* freq * 8 < 3560000000 */ + multi = 8; + reg[5] = 0x22; + reg[6] = 0x02; + } else if (freq < 593334000) { /* freq * 6 < 3560000000 */ + multi = 6; + reg[5] = 0x0a; + reg[6] = 0x00; + } else if (freq < 950000000) { /* freq * 4 < 3800000000 */ + multi = 4; + reg[5] = 0x12; + reg[6] = 0x02; + } else { + multi = 2; + reg[5] = 0x0a; + reg[6] = 0x02; + } + + f_vco = freq * multi; + + if (f_vco >= 3060000000U) { + reg[6] |= 0x08; + vco_select = 1; + } + + /* From divided value (XDIV) determined the FA and FP value */ + xdiv = (uint16_t)(f_vco / xtal_freq_div_2); + if ((f_vco - xdiv * xtal_freq_div_2) >= (xtal_freq_div_2 / 2)) + xdiv++; + + pm = (uint8_t)(xdiv / 8); + am = (uint8_t)(xdiv - (8 * pm)); + + if (am < 2) { + am += 8; + pm--; + } + + if (pm > 31) { + reg[1] = am + (8 * (pm - 31)); + reg[2] = 31; + } else { + reg[1] = am; + reg[2] = pm; + } + + if ((reg[1] > 15) || (reg[2] < 0x0b)) { + fprintf(stderr, "[FC0013] no valid PLL combination " + "found for %u Hz!\n", freq); + return -1; + } + + /* fix clock out */ + reg[6] |= 0x20; + + /* From VCO frequency determines the XIN ( fractional part of Delta + Sigma PLL) and divided value (XDIV) */ + xin = (uint16_t)((f_vco - (f_vco / xtal_freq_div_2) * xtal_freq_div_2) / 1000); + xin = (xin << 15) / (xtal_freq_div_2 / 1000); + if (xin >= 16384) + xin += 32768; + + reg[3] = xin >> 8; + reg[4] = xin & 0xff; + + reg[6] &= 0x3f; /* bits 6 and 7 describe the bandwidth */ + switch (bandwidth) { + case 6000000: + reg[6] |= 0x80; + break; + case 7000000: + reg[6] |= 0x40; + break; + case 8000000: + default: + break; + } + + /* modified for Realtek demod */ + reg[5] |= 0x07; + + for (i = 1; i <= 6; i++) { + ret = fc0013_writereg(dev, i, reg[i]); + if (ret) + goto exit; + } + + ret = fc0013_readreg(dev, 0x11, &tmp); + if (ret) + goto exit; + if (multi == 64) + ret = fc0013_writereg(dev, 0x11, tmp | 0x04); + else + ret = fc0013_writereg(dev, 0x11, tmp & 0xfb); + if (ret) + goto exit; + + /* VCO Calibration */ + ret = fc0013_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x00); + + /* VCO Re-Calibration if needed */ + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x00); + + if (!ret) { +// msleep(10); + ret = fc0013_readreg(dev, 0x0e, &tmp); + } + if (ret) + goto exit; + + /* vco selection */ + tmp &= 0x3f; + + if (vco_select) { + if (tmp > 0x3c) { + reg[6] &= ~0x08; + ret = fc0013_writereg(dev, 0x06, reg[6]); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x00); + } + } else { + if (tmp < 0x02) { + reg[6] |= 0x08; + ret = fc0013_writereg(dev, 0x06, reg[6]); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x00); + } + } + +exit: + return ret; +} + +int fc0013_set_gain_mode(void *dev, int manual) +{ + int ret = 0; + uint8_t tmp = 0; + + ret |= fc0013_readreg(dev, 0x0d, &tmp); + + if (manual) + tmp |= (1 << 3); + else + tmp &= ~(1 << 3); + + ret |= fc0013_writereg(dev, 0x0d, tmp); + + /* set a fixed IF-gain for now */ + ret |= fc0013_writereg(dev, 0x13, 0x0a); + + return ret; +} + +int fc0013_lna_gains[] ={ + -99, 0x02, + -73, 0x03, + -65, 0x05, + -63, 0x04, + -63, 0x00, + -60, 0x07, + -58, 0x01, + -54, 0x06, + 58, 0x0f, + 61, 0x0e, + 63, 0x0d, + 65, 0x0c, + 67, 0x0b, + 68, 0x0a, + 70, 0x09, + 71, 0x08, + 179, 0x17, + 181, 0x16, + 182, 0x15, + 184, 0x14, + 186, 0x13, + 188, 0x12, + 191, 0x11, + 197, 0x10 +}; + +#define GAIN_CNT (sizeof(fc0013_lna_gains) / sizeof(int) / 2) + +int fc0013_set_lna_gain(void *dev, int gain) +{ + int ret = 0; + unsigned int i; + uint8_t tmp = 0; + + ret |= fc0013_readreg(dev, 0x14, &tmp); + + /* mask bits off */ + tmp &= 0xe0; + + for (i = 0; i < GAIN_CNT; i++) { + if ((fc0013_lna_gains[i*2] >= gain) || (i+1 == GAIN_CNT)) { + tmp |= fc0013_lna_gains[i*2 + 1]; + break; + } + } + + /* set gain */ + ret |= fc0013_writereg(dev, 0x14, tmp); + + return ret; +} diff --git a/src/tuner_fc2580.c b/src/tuner_fc2580.c new file mode 100644 index 0000000..d2eeba5 --- /dev/null +++ b/src/tuner_fc2580.c @@ -0,0 +1,494 @@ +/* + * FCI FC2580 tuner driver, taken from the kernel driver that can be found + * on http://linux.terratec.de/tv_en.html + * + * This driver is a mess, and should be cleaned up/rewritten. + * + */ + +#include <stdint.h> + +#include "rtlsdr_i2c.h" +#include "tuner_fc2580.h" + +/* 16.384 MHz (at least on the Logilink VG0002A) */ +#define CRYSTAL_FREQ 16384000 + +/* glue functions to rtl-sdr code */ + +fc2580_fci_result_type fc2580_i2c_write(void *pTuner, unsigned char reg, unsigned char val) +{ + uint8_t data[2]; + + data[0] = reg; + data[1] = val; + + if (rtlsdr_i2c_write_fn(pTuner, FC2580_I2C_ADDR, data, 2) < 0) + return FC2580_FCI_FAIL; + + return FC2580_FCI_SUCCESS; +} + +fc2580_fci_result_type fc2580_i2c_read(void *pTuner, unsigned char reg, unsigned char *read_data) +{ + uint8_t data = reg; + + if (rtlsdr_i2c_write_fn(pTuner, FC2580_I2C_ADDR, &data, 1) < 0) + return FC2580_FCI_FAIL; + + if (rtlsdr_i2c_read_fn(pTuner, FC2580_I2C_ADDR, &data, 1) < 0) + return FC2580_FCI_FAIL; + + *read_data = data; + + return FC2580_FCI_SUCCESS; +} + +int +fc2580_Initialize( + void *pTuner + ) +{ + int AgcMode; + unsigned int CrystalFreqKhz; + + //TODO set AGC mode + AgcMode = FC2580_AGC_EXTERNAL; + + // Initialize tuner with AGC mode. + // Note: CrystalFreqKhz = round(CrystalFreqHz / 1000) + CrystalFreqKhz = (unsigned int)((CRYSTAL_FREQ + 500) / 1000); + + if(fc2580_set_init(pTuner, AgcMode, CrystalFreqKhz) != FC2580_FCI_SUCCESS) + goto error_status_initialize_tuner; + + + return FUNCTION_SUCCESS; + + +error_status_initialize_tuner: + return FUNCTION_ERROR; +} + +int +fc2580_SetRfFreqHz( + void *pTuner, + unsigned long RfFreqHz + ) +{ + unsigned int RfFreqKhz; + unsigned int CrystalFreqKhz; + + // Set tuner RF frequency in KHz. + // Note: RfFreqKhz = round(RfFreqHz / 1000) + // CrystalFreqKhz = round(CrystalFreqHz / 1000) + RfFreqKhz = (unsigned int)((RfFreqHz + 500) / 1000); + CrystalFreqKhz = (unsigned int)((CRYSTAL_FREQ + 500) / 1000); + + if(fc2580_set_freq(pTuner, RfFreqKhz, CrystalFreqKhz) != FC2580_FCI_SUCCESS) + goto error_status_set_tuner_rf_frequency; + + return FUNCTION_SUCCESS; + +error_status_set_tuner_rf_frequency: + return FUNCTION_ERROR; +} + +/** + +@brief Set FC2580 tuner bandwidth mode. + +*/ +int +fc2580_SetBandwidthMode( + void *pTuner, + int BandwidthMode + ) +{ + unsigned int CrystalFreqKhz; + + // Set tuner bandwidth mode. + // Note: CrystalFreqKhz = round(CrystalFreqHz / 1000) + CrystalFreqKhz = (unsigned int)((CRYSTAL_FREQ + 500) / 1000); + + if(fc2580_set_filter(pTuner, (unsigned char)BandwidthMode, CrystalFreqKhz) != FC2580_FCI_SUCCESS) + goto error_status_set_tuner_bandwidth_mode; + + return FUNCTION_SUCCESS; + + +error_status_set_tuner_bandwidth_mode: + return FUNCTION_ERROR; +} + +void fc2580_wait_msec(void *pTuner, int a) +{ + /* USB latency is enough for now ;) */ +// usleep(a * 1000); + return; +} + +/*============================================================================== + fc2580 initial setting + + This function is a generic function which gets called to initialize + + fc2580 in DVB-H mode or L-Band TDMB mode + + <input parameter> + + ifagc_mode + type : integer + 1 : Internal AGC + 2 : Voltage Control Mode + +==============================================================================*/ +fc2580_fci_result_type fc2580_set_init( void *pTuner, int ifagc_mode, unsigned int freq_xtal ) +{ + fc2580_fci_result_type result = FC2580_FCI_SUCCESS; + + result &= fc2580_i2c_write(pTuner, 0x00, 0x00); /*** Confidential ***/ + result &= fc2580_i2c_write(pTuner, 0x12, 0x86); + result &= fc2580_i2c_write(pTuner, 0x14, 0x5C); + result &= fc2580_i2c_write(pTuner, 0x16, 0x3C); + result &= fc2580_i2c_write(pTuner, 0x1F, 0xD2); + result &= fc2580_i2c_write(pTuner, 0x09, 0xD7); + result &= fc2580_i2c_write(pTuner, 0x0B, 0xD5); + result &= fc2580_i2c_write(pTuner, 0x0C, 0x32); + result &= fc2580_i2c_write(pTuner, 0x0E, 0x43); + result &= fc2580_i2c_write(pTuner, 0x21, 0x0A); + result &= fc2580_i2c_write(pTuner, 0x22, 0x82); + if( ifagc_mode == 1 ) + { + result &= fc2580_i2c_write(pTuner, 0x45, 0x10); //internal AGC + result &= fc2580_i2c_write(pTuner, 0x4C, 0x00); //HOLD_AGC polarity + } + else if( ifagc_mode == 2 ) + { + result &= fc2580_i2c_write(pTuner, 0x45, 0x20); //Voltage Control Mode + result &= fc2580_i2c_write(pTuner, 0x4C, 0x02); //HOLD_AGC polarity + } + result &= fc2580_i2c_write(pTuner, 0x3F, 0x88); + result &= fc2580_i2c_write(pTuner, 0x02, 0x0E); + result &= fc2580_i2c_write(pTuner, 0x58, 0x14); + result &= fc2580_set_filter(pTuner, 8, freq_xtal); //BW = 7.8MHz + + return result; +} + + +/*============================================================================== + fc2580 frequency setting + + This function is a generic function which gets called to change LO Frequency + + of fc2580 in DVB-H mode or L-Band TDMB mode + + <input parameter> + freq_xtal: kHz + + f_lo + Value of target LO Frequency in 'kHz' unit + ex) 2.6GHz = 2600000 + +==============================================================================*/ +fc2580_fci_result_type fc2580_set_freq( void *pTuner, unsigned int f_lo, unsigned int freq_xtal ) +{ + unsigned int f_diff, f_diff_shifted, n_val, k_val; + unsigned int f_vco, r_val, f_comp; + unsigned char pre_shift_bits = 4;// number of preshift to prevent overflow in shifting f_diff to f_diff_shifted + unsigned char data_0x18; + unsigned char data_0x02 = (USE_EXT_CLK<<5)|0x0E; + + fc2580_band_type band = ( f_lo > 1000000 )? FC2580_L_BAND : ( f_lo > 400000 )? FC2580_UHF_BAND : FC2580_VHF_BAND; + + fc2580_fci_result_type result = FC2580_FCI_SUCCESS; + + f_vco = ( band == FC2580_UHF_BAND )? f_lo * 4 : (( band == FC2580_L_BAND )? f_lo * 2 : f_lo * 12); + r_val = ( f_vco >= 2*76*freq_xtal )? 1 : ( f_vco >= 76*freq_xtal )? 2 : 4; + f_comp = freq_xtal/r_val; + n_val = ( f_vco / 2 ) / f_comp; + + f_diff = f_vco - 2* f_comp * n_val; + f_diff_shifted = f_diff << ( 20 - pre_shift_bits ); + k_val = f_diff_shifted / ( ( 2* f_comp ) >> pre_shift_bits ); + + if( f_diff_shifted - k_val * ( ( 2* f_comp ) >> pre_shift_bits ) >= ( f_comp >> pre_shift_bits ) ) + k_val = k_val + 1; + + if( f_vco >= BORDER_FREQ ) //Select VCO Band + data_0x02 = data_0x02 | 0x08; //0x02[3] = 1; + else + data_0x02 = data_0x02 & 0xF7; //0x02[3] = 0; + +// if( band != curr_band ) { + switch(band) + { + case FC2580_UHF_BAND: + data_0x02 = (data_0x02 & 0x3F); + + result &= fc2580_i2c_write(pTuner, 0x25, 0xF0); + result &= fc2580_i2c_write(pTuner, 0x27, 0x77); + result &= fc2580_i2c_write(pTuner, 0x28, 0x53); + result &= fc2580_i2c_write(pTuner, 0x29, 0x60); + result &= fc2580_i2c_write(pTuner, 0x30, 0x09); + result &= fc2580_i2c_write(pTuner, 0x50, 0x8C); + result &= fc2580_i2c_write(pTuner, 0x53, 0x50); + + if( f_lo < 538000 ) + result &= fc2580_i2c_write(pTuner, 0x5F, 0x13); + else + result &= fc2580_i2c_write(pTuner, 0x5F, 0x15); + + if( f_lo < 538000 ) + { + result &= fc2580_i2c_write(pTuner, 0x61, 0x07); + result &= fc2580_i2c_write(pTuner, 0x62, 0x06); + result &= fc2580_i2c_write(pTuner, 0x67, 0x06); + result &= fc2580_i2c_write(pTuner, 0x68, 0x08); + result &= fc2580_i2c_write(pTuner, 0x69, 0x10); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x12); + } + else if( f_lo < 794000 ) + { + result &= fc2580_i2c_write(pTuner, 0x61, 0x03); + result &= fc2580_i2c_write(pTuner, 0x62, 0x03); + result &= fc2580_i2c_write(pTuner, 0x67, 0x03); //ACI improve + result &= fc2580_i2c_write(pTuner, 0x68, 0x05); //ACI improve + result &= fc2580_i2c_write(pTuner, 0x69, 0x0C); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x0E); + } + else + { + result &= fc2580_i2c_write(pTuner, 0x61, 0x07); + result &= fc2580_i2c_write(pTuner, 0x62, 0x06); + result &= fc2580_i2c_write(pTuner, 0x67, 0x07); + result &= fc2580_i2c_write(pTuner, 0x68, 0x09); + result &= fc2580_i2c_write(pTuner, 0x69, 0x10); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x12); + } + + result &= fc2580_i2c_write(pTuner, 0x63, 0x15); + + result &= fc2580_i2c_write(pTuner, 0x6B, 0x0B); + result &= fc2580_i2c_write(pTuner, 0x6C, 0x0C); + result &= fc2580_i2c_write(pTuner, 0x6D, 0x78); + result &= fc2580_i2c_write(pTuner, 0x6E, 0x32); + result &= fc2580_i2c_write(pTuner, 0x6F, 0x14); + result &= fc2580_set_filter(pTuner, 8, freq_xtal); //BW = 7.8MHz + break; + case FC2580_VHF_BAND: + data_0x02 = (data_0x02 & 0x3F) | 0x80; + result &= fc2580_i2c_write(pTuner, 0x27, 0x77); + result &= fc2580_i2c_write(pTuner, 0x28, 0x33); + result &= fc2580_i2c_write(pTuner, 0x29, 0x40); + result &= fc2580_i2c_write(pTuner, 0x30, 0x09); + result &= fc2580_i2c_write(pTuner, 0x50, 0x8C); + result &= fc2580_i2c_write(pTuner, 0x53, 0x50); + result &= fc2580_i2c_write(pTuner, 0x5F, 0x0F); + result &= fc2580_i2c_write(pTuner, 0x61, 0x07); + result &= fc2580_i2c_write(pTuner, 0x62, 0x00); + result &= fc2580_i2c_write(pTuner, 0x63, 0x15); + result &= fc2580_i2c_write(pTuner, 0x67, 0x03); + result &= fc2580_i2c_write(pTuner, 0x68, 0x05); + result &= fc2580_i2c_write(pTuner, 0x69, 0x10); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x12); + result &= fc2580_i2c_write(pTuner, 0x6B, 0x08); + result &= fc2580_i2c_write(pTuner, 0x6C, 0x0A); + result &= fc2580_i2c_write(pTuner, 0x6D, 0x78); + result &= fc2580_i2c_write(pTuner, 0x6E, 0x32); + result &= fc2580_i2c_write(pTuner, 0x6F, 0x54); + result &= fc2580_set_filter(pTuner, 7, freq_xtal); //BW = 6.8MHz + break; + case FC2580_L_BAND: + data_0x02 = (data_0x02 & 0x3F) | 0x40; + result &= fc2580_i2c_write(pTuner, 0x2B, 0x70); + result &= fc2580_i2c_write(pTuner, 0x2C, 0x37); + result &= fc2580_i2c_write(pTuner, 0x2D, 0xE7); + result &= fc2580_i2c_write(pTuner, 0x30, 0x09); + result &= fc2580_i2c_write(pTuner, 0x44, 0x20); + result &= fc2580_i2c_write(pTuner, 0x50, 0x8C); + result &= fc2580_i2c_write(pTuner, 0x53, 0x50); + result &= fc2580_i2c_write(pTuner, 0x5F, 0x0F); + result &= fc2580_i2c_write(pTuner, 0x61, 0x0F); + result &= fc2580_i2c_write(pTuner, 0x62, 0x00); + result &= fc2580_i2c_write(pTuner, 0x63, 0x13); + result &= fc2580_i2c_write(pTuner, 0x67, 0x00); + result &= fc2580_i2c_write(pTuner, 0x68, 0x02); + result &= fc2580_i2c_write(pTuner, 0x69, 0x0C); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x0E); + result &= fc2580_i2c_write(pTuner, 0x6B, 0x08); + result &= fc2580_i2c_write(pTuner, 0x6C, 0x0A); + result &= fc2580_i2c_write(pTuner, 0x6D, 0xA0); + result &= fc2580_i2c_write(pTuner, 0x6E, 0x50); + result &= fc2580_i2c_write(pTuner, 0x6F, 0x14); + result &= fc2580_set_filter(pTuner, 1, freq_xtal); //BW = 1.53MHz + break; + default: + break; + } +// curr_band = band; +// } + + //A command about AGC clock's pre-divide ratio + if( freq_xtal >= 28000 ) + result &= fc2580_i2c_write(pTuner, 0x4B, 0x22 ); + + //Commands about VCO Band and PLL setting. + result &= fc2580_i2c_write(pTuner, 0x02, data_0x02); + data_0x18 = ( ( r_val == 1 )? 0x00 : ( ( r_val == 2 )? 0x10 : 0x20 ) ) + (unsigned char)(k_val >> 16); + result &= fc2580_i2c_write(pTuner, 0x18, data_0x18); //Load 'R' value and high part of 'K' values + result &= fc2580_i2c_write(pTuner, 0x1A, (unsigned char)( k_val >> 8 ) ); //Load middle part of 'K' value + result &= fc2580_i2c_write(pTuner, 0x1B, (unsigned char)( k_val ) ); //Load lower part of 'K' value + result &= fc2580_i2c_write(pTuner, 0x1C, (unsigned char)( n_val ) ); //Load 'N' value + + //A command about UHF LNA Load Cap + if( band == FC2580_UHF_BAND ) + result &= fc2580_i2c_write(pTuner, 0x2D, ( f_lo <= (unsigned int)794000 )? 0x9F : 0x8F ); //LNA_OUT_CAP + + + return result; +} + + +/*============================================================================== + fc2580 filter BW setting + + This function is a generic function which gets called to change Bandwidth + + frequency of fc2580's channel selection filter + + <input parameter> + freq_xtal: kHz + + filter_bw + 1 : 1.53MHz(TDMB) + 6 : 6MHz (Bandwidth 6MHz) + 7 : 6.8MHz (Bandwidth 7MHz) + 8 : 7.8MHz (Bandwidth 8MHz) + + +==============================================================================*/ +fc2580_fci_result_type fc2580_set_filter( void *pTuner, unsigned char filter_bw, unsigned int freq_xtal ) +{ + unsigned char cal_mon = 0, i; + fc2580_fci_result_type result = FC2580_FCI_SUCCESS; + + if(filter_bw == 1) + { + result &= fc2580_i2c_write(pTuner, 0x36, 0x1C); + result &= fc2580_i2c_write(pTuner, 0x37, (unsigned char)(4151*freq_xtal/1000000) ); + result &= fc2580_i2c_write(pTuner, 0x39, 0x00); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + if(filter_bw == 6) + { + result &= fc2580_i2c_write(pTuner, 0x36, 0x18); + result &= fc2580_i2c_write(pTuner, 0x37, (unsigned char)(4400*freq_xtal/1000000) ); + result &= fc2580_i2c_write(pTuner, 0x39, 0x00); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + else if(filter_bw == 7) + { + result &= fc2580_i2c_write(pTuner, 0x36, 0x18); + result &= fc2580_i2c_write(pTuner, 0x37, (unsigned char)(3910*freq_xtal/1000000) ); + result &= fc2580_i2c_write(pTuner, 0x39, 0x80); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + else if(filter_bw == 8) + { + result &= fc2580_i2c_write(pTuner, 0x36, 0x18); + result &= fc2580_i2c_write(pTuner, 0x37, (unsigned char)(3300*freq_xtal/1000000) ); + result &= fc2580_i2c_write(pTuner, 0x39, 0x80); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + + + for(i=0; i<5; i++) + { + fc2580_wait_msec(pTuner, 5);//wait 5ms + result &= fc2580_i2c_read(pTuner, 0x2F, &cal_mon); + if( (cal_mon & 0xC0) != 0xC0) + { + result &= fc2580_i2c_write(pTuner, 0x2E, 0x01); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + else + break; + } + + result &= fc2580_i2c_write(pTuner, 0x2E, 0x01); + + return result; +} + +/*============================================================================== + fc2580 RSSI function + + This function is a generic function which returns fc2580's + + current RSSI value. + + <input parameter> + none + + <return value> + int + rssi : estimated input power. + +==============================================================================*/ +//int fc2580_get_rssi(void) { +// +// unsigned char s_lna, s_rfvga, s_cfs, s_ifvga; +// int ofs_lna, ofs_rfvga, ofs_csf, ofs_ifvga, rssi; +// +// fc2580_i2c_read(0x71, &s_lna ); +// fc2580_i2c_read(0x72, &s_rfvga ); +// fc2580_i2c_read(0x73, &s_cfs ); +// fc2580_i2c_read(0x74, &s_ifvga ); +// +// +// ofs_lna = +// (curr_band==FC2580_UHF_BAND)? +// (s_lna==0)? 0 : +// (s_lna==1)? -6 : +// (s_lna==2)? -17 : +// (s_lna==3)? -22 : -30 : +// (curr_band==FC2580_VHF_BAND)? +// (s_lna==0)? 0 : +// (s_lna==1)? -6 : +// (s_lna==2)? -19 : +// (s_lna==3)? -24 : -32 : +// (curr_band==FC2580_L_BAND)? +// (s_lna==0)? 0 : +// (s_lna==1)? -6 : +// (s_lna==2)? -11 : +// (s_lna==3)? -16 : -34 : +// 0;//FC2580_NO_BAND +// ofs_rfvga = -s_rfvga+((s_rfvga>=11)? 1 : 0) + ((s_rfvga>=18)? 1 : 0); +// ofs_csf = -6*s_cfs; +// ofs_ifvga = s_ifvga/4; +// +// return rssi = ofs_lna+ofs_rfvga+ofs_csf+ofs_ifvga+OFS_RSSI; +// +//} + +/*============================================================================== + fc2580 Xtal frequency Setting + + This function is a generic function which sets + + the frequency of xtal. + + <input parameter> + + frequency + frequency value of internal(external) Xtal(clock) in kHz unit. + +==============================================================================*/ +//void fc2580_set_freq_xtal(unsigned int frequency) { +// +// freq_xtal = frequency; +// +//} + diff --git a/src/tuner_r82xx.c b/src/tuner_r82xx.c new file mode 100644 index 0000000..f620238 --- /dev/null +++ b/src/tuner_r82xx.c @@ -0,0 +1,1328 @@ +/* + * Rafael Micro R820T/R828D driver + * + * Copyright (C) 2013 Mauro Carvalho Chehab <mchehab@redhat.com> + * Copyright (C) 2013 Steve Markgraf <steve@steve-m.de> + * + * This driver is a heavily modified version of the driver found in the + * Linux kernel: + * http://git.linuxtv.org/linux-2.6.git/history/HEAD:/drivers/media/tuners/r820t.c + * + * 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. + * + * 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 <stdio.h> +#include <stdint.h> +#include <string.h> + +#include "rtlsdr_i2c.h" +#include "tuner_r82xx.h" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#define MHZ(x) ((x)*1000*1000) +#define KHZ(x) ((x)*1000) + +/* + * Static constants + */ + +/* Those initial values start from REG_SHADOW_START */ +static const uint8_t r82xx_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 r82xx_freq_range freq_ranges[] = { + { + /* .freq = */ 0, /* Start freq, in MHz */ + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + }, { + /* .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, + } +}; + +static int r82xx_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 }, +}; + +/* + * I2C read/write code and shadow registers logic + */ +static void shadow_store(struct r82xx_priv *priv, uint8_t reg, const uint8_t *val, + int len) +{ + int r = reg - REG_SHADOW_START; + + if (r < 0) { + len += r; + r = 0; + } + if (len <= 0) + return; + if (len > NUM_REGS - r) + len = NUM_REGS - r; + + memcpy(&priv->regs[r], val, len); +} + +static int r82xx_write(struct r82xx_priv *priv, uint8_t reg, const uint8_t *val, + unsigned 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 = rtlsdr_i2c_write_fn(priv->rtl_dev, priv->cfg->i2c_addr, + priv->buf, size + 1); + + if (rc != size + 1) { + fprintf(stderr, "%s: i2c wr failed=%d reg=%02x len=%d\n", + __FUNCTION__, rc, reg, size); + if (rc < 0) + return rc; + return -1; + } + + reg += size; + len -= size; + pos += size; + } while (len > 0); + + return 0; +} + +static int r82xx_write_reg(struct r82xx_priv *priv, uint8_t reg, uint8_t val) +{ + return r82xx_write(priv, reg, &val, 1); +} + +static int r82xx_read_cache_reg(struct r82xx_priv *priv, int reg) +{ + reg -= REG_SHADOW_START; + + if (reg >= 0 && reg < NUM_REGS) + return priv->regs[reg]; + else + return -1; +} + +static int r82xx_write_reg_mask(struct r82xx_priv *priv, uint8_t reg, uint8_t val, + uint8_t bit_mask) +{ + int rc = r82xx_read_cache_reg(priv, reg); + + if (rc < 0) + return rc; + + val = (rc & ~bit_mask) | (val & bit_mask); + + return r82xx_write(priv, reg, &val, 1); +} + +static uint8_t r82xx_bitrev(uint8_t byte) +{ + const uint8_t lut[16] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, + 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf }; + + return (lut[byte & 0xf] << 4) | lut[byte >> 4]; +} + +static int r82xx_read(struct r82xx_priv *priv, uint8_t reg, uint8_t *val, int len) +{ + int rc, i; + uint8_t *p = &priv->buf[1]; + + priv->buf[0] = reg; + + rc = rtlsdr_i2c_write_fn(priv->rtl_dev, priv->cfg->i2c_addr, priv->buf, 1); + if (rc < 1) + return rc; + + rc = rtlsdr_i2c_read_fn(priv->rtl_dev, priv->cfg->i2c_addr, p, len); + + if (rc != len) { + fprintf(stderr, "%s: i2c rd failed=%d reg=%02x len=%d\n", + __FUNCTION__, rc, reg, len); + if (rc < 0) + return rc; + return -1; + } + + /* Copy data to the output buffer */ + for (i = 0; i < len; i++) + val[i] = r82xx_bitrev(p[i]); + + return 0; +} + +/* + * r82xx tuning logic + */ + +static int r82xx_set_mux(struct r82xx_priv *priv, uint32_t freq) +{ + const struct r82xx_freq_range *range; + int rc; + unsigned int i; + uint8_t 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]; + + /* Open Drain */ + rc = r82xx_write_reg_mask(priv, 0x17, range->open_d, 0x08); + if (rc < 0) + return rc; + + /* RF_MUX,Polymux */ + rc = r82xx_write_reg_mask(priv, 0x1a, range->rf_mux_ploy, 0xc3); + if (rc < 0) + return rc; + + /* TF BAND */ + rc = r82xx_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 = r82xx_write_reg_mask(priv, 0x10, val, 0x0b); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x08, 0x00, 0x3f); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x09, 0x00, 0x3f); + + return rc; +} + +static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) +{ + int rc, i; + unsigned sleep_time = 10000; + uint64_t vco_freq; + uint32_t vco_fra; /* VCO contribution by SDM (kHz) */ + uint32_t vco_min = 1770000; + uint32_t vco_max = vco_min * 2; + uint32_t freq_khz, pll_ref, pll_ref_khz; + uint16_t n_sdm = 2; + uint16_t sdm = 0; + uint8_t mix_div = 2; + uint8_t div_buf = 0; + uint8_t div_num = 0; + uint8_t vco_power_ref = 2; + uint8_t refdiv2 = 0; + uint8_t ni, si, nint, vco_fine_tune, val; + uint8_t data[5]; + + /* Frequency in kHz */ + freq_khz = (freq + 500) / 1000; + pll_ref = priv->cfg->xtal; + pll_ref_khz = (priv->cfg->xtal + 500) / 1000; + + rc = r82xx_write_reg_mask(priv, 0x10, refdiv2, 0x10); + if (rc < 0) + return rc; + + /* set pll autotune = 128kHz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x00, 0x0c); + if (rc < 0) + return rc; + + /* set VCO current = 100 */ + rc = r82xx_write_reg_mask(priv, 0x12, 0x80, 0xe0); + if (rc < 0) + return rc; + + /* Calculate divider */ + while (mix_div <= 64) { + if (((freq_khz * mix_div) >= vco_min) && + ((freq_khz * 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 = r82xx_read(priv, 0x00, data, sizeof(data)); + if (rc < 0) + return rc; + + if (priv->cfg->rafael_chip == CHIP_R828D) + vco_power_ref = 1; + + 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 = r82xx_write_reg_mask(priv, 0x10, div_num << 5, 0xe0); + if (rc < 0) + return rc; + + vco_freq = (uint64_t)freq * (uint64_t)mix_div; + nint = vco_freq / (2 * pll_ref); + vco_fra = (vco_freq - 2 * pll_ref * nint) / 1000; + + if (nint > ((128 / vco_power_ref) - 1)) { + fprintf(stderr, "[R82XX] No valid PLL values for %u Hz!\n", freq); + return -1; + } + + ni = (nint - 13) / 4; + si = nint - 4 * ni - 13; + + rc = r82xx_write_reg(priv, 0x14, ni + (si << 6)); + if (rc < 0) + return rc; + + /* pw_sdm */ + if (!vco_fra) + val = 0x08; + else + val = 0x00; + + rc = r82xx_write_reg_mask(priv, 0x12, val, 0x08); + if (rc < 0) + return rc; + + /* sdm calculator */ + while (vco_fra > 1) { + if (vco_fra > (2 * pll_ref_khz / n_sdm)) { + sdm = sdm + 32768 / (n_sdm / 2); + vco_fra = vco_fra - 2 * pll_ref_khz / n_sdm; + if (n_sdm >= 0x8000) + break; + } + n_sdm <<= 1; + } + + rc = r82xx_write_reg(priv, 0x16, sdm >> 8); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x15, sdm & 0xff); + if (rc < 0) + return rc; + + for (i = 0; i < 2; i++) { +// usleep_range(sleep_time, sleep_time + 1000); + + /* Check if PLL has locked */ + rc = r82xx_read(priv, 0x00, data, 3); + if (rc < 0) + return rc; + if (data[2] & 0x40) + break; + + if (!i) { + /* Didn't lock. Increase VCO current */ + rc = r82xx_write_reg_mask(priv, 0x12, 0x60, 0xe0); + if (rc < 0) + return rc; + } + } + + if (!(data[2] & 0x40)) { + fprintf(stderr, "[R82XX] PLL not locked!\n"); + priv->has_lock = 0; + return 0; + } + + priv->has_lock = 1; + + /* set pll autotune = 8kHz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x08, 0x08); + + return rc; +} + +static int r82xx_sysfreq_sel(struct r82xx_priv *priv, uint32_t freq, + enum r82xx_tuner_type type, + uint32_t delsys) +{ + int rc; + uint8_t mixer_top, lna_top, cp_cur, div_buf_cur, lna_vth_l, mixer_vth_l; + uint8_t air_cable1_in, cable2_in, pre_dect, lna_discharge, filter_cur; + + 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; + } + + if (priv->cfg->use_predetect) { + rc = r82xx_write_reg_mask(priv, 0x06, pre_dect, 0x40); + if (rc < 0) + return rc; + } + + rc = r82xx_write_reg_mask(priv, 0x1d, lna_top, 0xc7); + if (rc < 0) + return rc; + rc = r82xx_write_reg_mask(priv, 0x1c, mixer_top, 0xf8); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0d, lna_vth_l); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0e, mixer_vth_l); + if (rc < 0) + return rc; + + priv->input = air_cable1_in; + + /* Air-IN only for Astrometa */ + rc = r82xx_write_reg_mask(priv, 0x05, air_cable1_in, 0x60); + if (rc < 0) + return rc; + rc = r82xx_write_reg_mask(priv, 0x06, cable2_in, 0x08); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x11, cp_cur, 0x38); + if (rc < 0) + return rc; + rc = r82xx_write_reg_mask(priv, 0x17, div_buf_cur, 0x30); + if (rc < 0) + return rc; + rc = r82xx_write_reg_mask(priv, 0x0a, filter_cur, 0x60); + if (rc < 0) + return rc; + + /* + * Set LNA + */ + + if (type != TUNER_ANALOG_TV) { + /* LNA TOP: lowest */ + rc = r82xx_write_reg_mask(priv, 0x1d, 0, 0x38); + if (rc < 0) + return rc; + + /* 0: normal mode */ + rc = r82xx_write_reg_mask(priv, 0x1c, 0, 0x04); + if (rc < 0) + return rc; + + /* 0: PRE_DECT off */ + rc = r82xx_write_reg_mask(priv, 0x06, 0, 0x40); + if (rc < 0) + return rc; + + /* agc clk 250hz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x30, 0x30); + if (rc < 0) + return rc; + +// msleep(250); + + /* write LNA TOP = 3 */ + rc = r82xx_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 = r82xx_write_reg_mask(priv, 0x1c, mixer_top, 0x04); + if (rc < 0) + return rc; + + /* LNA discharge current */ + rc = r82xx_write_reg_mask(priv, 0x1e, lna_discharge, 0x1f); + if (rc < 0) + return rc; + + /* agc clk 60hz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x20, 0x30); + if (rc < 0) + return rc; + } else { + /* PRE_DECT off */ + rc = r82xx_write_reg_mask(priv, 0x06, 0, 0x40); + if (rc < 0) + return rc; + + /* write LNA TOP */ + rc = r82xx_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 = r82xx_write_reg_mask(priv, 0x1c, mixer_top, 0x04); + if (rc < 0) + return rc; + + /* LNA discharge current */ + rc = r82xx_write_reg_mask(priv, 0x1e, lna_discharge, 0x1f); + if (rc < 0) + return rc; + + /* agc clk 1Khz, external det1 cap 1u */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x00, 0x30); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x10, 0x00, 0x04); + if (rc < 0) + return rc; + } + return 0; +} + +static int r82xx_set_tv_standard(struct r82xx_priv *priv, + unsigned bw, + enum r82xx_tuner_type type, + uint32_t delsys) + +{ + int rc, i; + uint32_t if_khz, filt_cal_lo; + uint8_t data[5]; + uint8_t filt_gain, img_r, filt_q, hp_cor, ext_enable, loop_through; + uint8_t lt_att, flt_ext_widest, polyfil_cur; + int need_calibration; + + 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 0 + /* + * There are two 7 MHz tables defined on the original + * driver, but just the second one seems to be visible + * by rtl2832. Keep this one here commented, as it + * might be needed in the future + */ + + 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 */ +#endif + /* 7 MHz, second table */ + 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 */ + } 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, r82xx_init_array, sizeof(r82xx_init_array)); + + /* Init Flag & Xtal_check Result (inits VGA gain, needed?)*/ + rc = r82xx_write_reg_mask(priv, 0x0c, 0x00, 0x0f); + if (rc < 0) + return rc; + + /* version */ + rc = r82xx_write_reg_mask(priv, 0x13, VER_NUM, 0x3f); + if (rc < 0) + return rc; + + /* for LT Gain test */ + if (type != TUNER_ANALOG_TV) { + rc = r82xx_write_reg_mask(priv, 0x1d, 0x00, 0x38); + if (rc < 0) + return rc; +// usleep_range(1000, 2000); + } + priv->int_freq = if_khz * 1000; + + /* Check if standard changed. If so, filter calibration is needed */ + /* as we call this function only once in rtlsdr, force calibration */ + need_calibration = 1; + + if (need_calibration) { + for (i = 0; i < 2; i++) { + /* Set filt_cap */ + rc = r82xx_write_reg_mask(priv, 0x0b, hp_cor, 0x60); + if (rc < 0) + return rc; + + /* set cali clk =on */ + rc = r82xx_write_reg_mask(priv, 0x0f, 0x04, 0x04); + if (rc < 0) + return rc; + + /* X'tal cap 0pF for PLL */ + rc = r82xx_write_reg_mask(priv, 0x10, 0x00, 0x03); + if (rc < 0) + return rc; + + rc = r82xx_set_pll(priv, filt_cal_lo * 1000); + if (rc < 0 || !priv->has_lock) + return rc; + + /* Start Trigger */ + rc = r82xx_write_reg_mask(priv, 0x0b, 0x10, 0x10); + if (rc < 0) + return rc; + +// usleep_range(1000, 2000); + + /* Stop Trigger */ + rc = r82xx_write_reg_mask(priv, 0x0b, 0x00, 0x10); + if (rc < 0) + return rc; + + /* set cali clk =off */ + rc = r82xx_write_reg_mask(priv, 0x0f, 0x00, 0x04); + if (rc < 0) + return rc; + + /* Check if calibration worked */ + rc = r82xx_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 = r82xx_write_reg_mask(priv, 0x0a, + filt_q | priv->fil_cal_code, 0x1f); + if (rc < 0) + return rc; + + /* Set BW, Filter_gain, & HP corner */ + rc = r82xx_write_reg_mask(priv, 0x0b, hp_cor, 0xef); + if (rc < 0) + return rc; + + /* Set Img_R */ + rc = r82xx_write_reg_mask(priv, 0x07, img_r, 0x80); + if (rc < 0) + return rc; + + /* Set filt_3dB, V6MHz */ + rc = r82xx_write_reg_mask(priv, 0x06, filt_gain, 0x30); + if (rc < 0) + return rc; + + /* channel filter extension */ + rc = r82xx_write_reg_mask(priv, 0x1e, ext_enable, 0x60); + if (rc < 0) + return rc; + + /* Loop through */ + rc = r82xx_write_reg_mask(priv, 0x05, loop_through, 0x80); + if (rc < 0) + return rc; + + /* Loop through attenuation */ + rc = r82xx_write_reg_mask(priv, 0x1f, lt_att, 0x80); + if (rc < 0) + return rc; + + /* filter extension widest */ + rc = r82xx_write_reg_mask(priv, 0x0f, flt_ext_widest, 0x80); + if (rc < 0) + return rc; + + /* RF poly filter current */ + rc = r82xx_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->bw = bw; + + return 0; +} + +static int r82xx_read_gain(struct r82xx_priv *priv) +{ + uint8_t data[4]; + int rc; + + rc = r82xx_read(priv, 0x00, data, sizeof(data)); + if (rc < 0) + return rc; + + return ((data[3] & 0x0f) << 1) + ((data[3] & 0xf0) >> 4); +} + +/* 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/ + */ + +#define VGA_BASE_GAIN -47 +static const int r82xx_vga_gain_steps[] = { + 0, 26, 26, 30, 42, 35, 24, 13, 14, 32, 36, 34, 35, 37, 35, 36 +}; + +static const int r82xx_lna_gain_steps[] = { + 0, 9, 13, 40, 38, 13, 31, 22, 26, 31, 26, 14, 19, 5, 35, 13 +}; + +static const int r82xx_mixer_gain_steps[] = { + 0, 5, 10, 10, 19, 9, 10, 25, 17, 10, 8, 16, 13, 6, 3, -8 +}; + +int r82xx_set_gain(struct r82xx_priv *priv, int set_manual_gain, int gain) +{ + int rc; + + if (set_manual_gain) { + int i, total_gain = 0; + uint8_t mix_index = 0, lna_index = 0; + uint8_t data[4]; + + /* LNA auto off */ + rc = r82xx_write_reg_mask(priv, 0x05, 0x10, 0x10); + if (rc < 0) + return rc; + + /* Mixer auto off */ + rc = r82xx_write_reg_mask(priv, 0x07, 0, 0x10); + if (rc < 0) + return rc; + + rc = r82xx_read(priv, 0x00, data, sizeof(data)); + if (rc < 0) + return rc; + + /* set fixed VGA gain for now (16.3 dB) */ + rc = r82xx_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 += r82xx_lna_gain_steps[++lna_index]; + + if (total_gain >= gain) + break; + + total_gain += r82xx_mixer_gain_steps[++mix_index]; + } + + /* set LNA gain */ + rc = r82xx_write_reg_mask(priv, 0x05, lna_index, 0x0f); + if (rc < 0) + return rc; + + /* set Mixer gain */ + rc = r82xx_write_reg_mask(priv, 0x07, mix_index, 0x0f); + if (rc < 0) + return rc; + } else { + /* LNA */ + rc = r82xx_write_reg_mask(priv, 0x05, 0, 0x10); + if (rc < 0) + return rc; + + /* Mixer */ + rc = r82xx_write_reg_mask(priv, 0x07, 0x10, 0x10); + if (rc < 0) + return rc; + + /* set fixed VGA gain for now (26.5 dB) */ + rc = r82xx_write_reg_mask(priv, 0x0c, 0x0b, 0x9f); + if (rc < 0) + return rc; + } + + return 0; +} + +/* Bandwidth contribution by low-pass filter. */ +static const int r82xx_if_low_pass_bw_table[] = { + 1700000, 1600000, 1550000, 1450000, 1200000, 900000, 700000, 550000, 450000, 350000 +}; + +#define FILT_HP_BW1 350000 +#define FILT_HP_BW2 380000 +int r82xx_set_bandwidth(struct r82xx_priv *priv, int bw, uint32_t rate) +{ + int rc; + unsigned int i; + int real_bw = 0; + uint8_t reg_0a; + uint8_t reg_0b; + + if (bw > 7000000) { + // BW: 8 MHz + reg_0a = 0x10; + reg_0b = 0x0b; + priv->int_freq = 4570000; + } else if (bw > 6000000) { + // BW: 7 MHz + reg_0a = 0x10; + reg_0b = 0x2a; + priv->int_freq = 4570000; + } else if (bw > r82xx_if_low_pass_bw_table[0] + FILT_HP_BW1 + FILT_HP_BW2) { + // BW: 6 MHz + reg_0a = 0x10; + reg_0b = 0x6b; + priv->int_freq = 3570000; + } else { + reg_0a = 0x00; + reg_0b = 0x80; + priv->int_freq = 2300000; + + if (bw > r82xx_if_low_pass_bw_table[0] + FILT_HP_BW1) { + bw -= FILT_HP_BW2; + priv->int_freq += FILT_HP_BW2; + real_bw += FILT_HP_BW2; + } else { + reg_0b |= 0x20; + } + + if (bw > r82xx_if_low_pass_bw_table[0]) { + bw -= FILT_HP_BW1; + priv->int_freq += FILT_HP_BW1; + real_bw += FILT_HP_BW1; + } else { + reg_0b |= 0x40; + } + + // find low-pass filter + for(i = 0; i < ARRAY_SIZE(r82xx_if_low_pass_bw_table); ++i) { + if (bw > r82xx_if_low_pass_bw_table[i]) + break; + } + --i; + reg_0b |= 15 - i; + real_bw += r82xx_if_low_pass_bw_table[i]; + + priv->int_freq -= real_bw / 2; + } + + rc = r82xx_write_reg_mask(priv, 0x0a, reg_0a, 0x10); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x0b, reg_0b, 0xef); + if (rc < 0) + return rc; + + return priv->int_freq; +} +#undef FILT_HP_BW1 +#undef FILT_HP_BW2 + +int r82xx_set_freq(struct r82xx_priv *priv, uint32_t freq) +{ + int rc = -1; + uint32_t lo_freq = freq + priv->int_freq; + uint8_t air_cable1_in; + + rc = r82xx_set_mux(priv, lo_freq); + if (rc < 0) + goto err; + + rc = r82xx_set_pll(priv, lo_freq); + if (rc < 0 || !priv->has_lock) + goto err; + + /* switch between 'Cable1' and 'Air-In' inputs on sticks with + * R828D tuner. We switch at 345 MHz, because that's where the + * noise-floor has about the same level with identical LNA + * settings. The original driver used 320 MHz. */ + air_cable1_in = (freq > MHZ(345)) ? 0x00 : 0x60; + + if ((priv->cfg->rafael_chip == CHIP_R828D) && + (air_cable1_in != priv->input)) { + priv->input = air_cable1_in; + rc = r82xx_write_reg_mask(priv, 0x05, air_cable1_in, 0x60); + } + +err: + if (rc < 0) + fprintf(stderr, "%s: failed=%d\n", __FUNCTION__, rc); + return rc; +} + +/* + * r82xx standby logic + */ + +int r82xx_standby(struct r82xx_priv *priv) +{ + int rc; + + /* If device was not initialized yet, don't need to standby */ + if (!priv->init_done) + return 0; + + rc = r82xx_write_reg(priv, 0x06, 0xb1); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x05, 0x03); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x07, 0x3a); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x08, 0x40); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x09, 0xc0); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0a, 0x36); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0c, 0x35); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0f, 0x68); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x11, 0x03); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x17, 0xf4); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x19, 0x0c); + + /* Force initial calibration */ + priv->type = -1; + + return rc; +} + +/* + * r82xx device init logic + */ + +static int r82xx_xtal_check(struct r82xx_priv *priv) +{ + int rc; + unsigned int i; + uint8_t data[3], val; + + /* Initialize the shadow registers */ + memcpy(priv->regs, r82xx_init_array, sizeof(r82xx_init_array)); + + /* cap 30pF & Drive Low */ + rc = r82xx_write_reg_mask(priv, 0x10, 0x0b, 0x0b); + if (rc < 0) + return rc; + + /* set pll autotune = 128kHz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x00, 0x0c); + if (rc < 0) + return rc; + + /* set manual initial reg = 111111; */ + rc = r82xx_write_reg_mask(priv, 0x13, 0x7f, 0x7f); + if (rc < 0) + return rc; + + /* set auto */ + rc = r82xx_write_reg_mask(priv, 0x13, 0x00, 0x40); + if (rc < 0) + return rc; + + /* Try several xtal capacitor alternatives */ + for (i = 0; i < ARRAY_SIZE(r82xx_xtal_capacitor); i++) { + rc = r82xx_write_reg_mask(priv, 0x10, + r82xx_xtal_capacitor[i][0], 0x1b); + if (rc < 0) + return rc; + +// usleep_range(5000, 6000); + + rc = r82xx_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(r82xx_xtal_capacitor)) + return -1; + + return r82xx_xtal_capacitor[i][1]; +} + +int r82xx_init(struct r82xx_priv *priv) +{ + int rc; + + /* TODO: R828D might need r82xx_xtal_check() */ + priv->xtal_cap_sel = XTAL_HIGH_CAP_0P; + + /* Initialize registers */ + rc = r82xx_write(priv, 0x05, + r82xx_init_array, sizeof(r82xx_init_array)); + + rc = r82xx_set_tv_standard(priv, 3, TUNER_DIGITAL_TV, 0); + if (rc < 0) + goto err; + + rc = r82xx_sysfreq_sel(priv, 0, TUNER_DIGITAL_TV, SYS_DVBT); + + priv->init_done = 1; + +err: + if (rc < 0) + fprintf(stderr, "%s: failed=%d\n", __FUNCTION__, rc); + return rc; +} + +#if 0 +/* Not used, for now */ +static int r82xx_gpio(struct r82xx_priv *priv, int enable) +{ + return r82xx_write_reg_mask(priv, 0x0f, enable ? 1 : 0, 0x01); +} +#endif |