initial checkin of first driver for OsmoSDR tuner
This commit is contained in:
commit
d5be5374c5
|
@ -0,0 +1,6 @@
|
|||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.la
|
||||
|
||||
firmware/tests/tuner-test
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef _COMMON_H
|
||||
#define _COMMON_H
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
#endif
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef _LOGGING_H
|
||||
#define _LOGGING_H
|
||||
|
||||
#define DTUN 1
|
||||
|
||||
/*! \brief different log levels */
|
||||
#define LOGL_DEBUG 1 /*!< \brief debugging information */
|
||||
#define LOGL_INFO 3
|
||||
#define LOGL_NOTICE 5 /*!< \brief abnormal/unexpected condition */
|
||||
#define LOGL_ERROR 7 /*!< \brief error condition, requires user action */
|
||||
#define LOGL_FATAL 8 /*!< \brief fatal, program aborted */
|
||||
|
||||
#define LOGP(ss, level, fmt, args...) \
|
||||
logp2(ss, level, __FILE__, __LINE__, 0, fmt, ##args)
|
||||
|
||||
#define LOGPC(ss, level, fmt, args...) \
|
||||
logp2(ss, level, __FILE__, __LINE__, 1, fmt, ##args)
|
||||
|
||||
void logp2(int subsys, unsigned int level, char *file,
|
||||
int line, int cont, const char *format, ...)
|
||||
__attribute__ ((format (printf, 6, 7)));
|
||||
|
||||
#endif
|
|
@ -0,0 +1,182 @@
|
|||
#ifndef _E4K_TUNER_H
|
||||
#define _E4K_TUNER_H
|
||||
|
||||
enum e4k_reg {
|
||||
E4K_REG_MASTER1 = 0x00,
|
||||
E4K_REG_MASTER2 = 0x01,
|
||||
E4K_REG_MASTER3 = 0x02,
|
||||
E4K_REG_MASTER4 = 0x03,
|
||||
E4K_REG_MASTER5 = 0x04,
|
||||
E4K_REG_CLK_INP = 0x05,
|
||||
E4K_REG_REF_CLK = 0x06,
|
||||
E4K_REG_SYNTH1 = 0x07,
|
||||
E4K_REG_SYNTH2 = 0x08,
|
||||
E4K_REG_SYNTH3 = 0x09,
|
||||
E4K_REG_SYNTH4 = 0x0a,
|
||||
E4K_REG_SYNTH5 = 0x0b,
|
||||
E4K_REG_SYNTH6 = 0x0c,
|
||||
E4K_REG_SYNTH7 = 0x0d,
|
||||
E4K_REG_SYNTH8 = 0x0e,
|
||||
E4K_REG_SYNTH9 = 0x0f,
|
||||
E4K_REG_FILT1 = 0x10,
|
||||
E4K_REG_FILT2 = 0x11,
|
||||
E4K_REG_FILT3 = 0x12,
|
||||
// gap
|
||||
E4K_REG_GAIN1 = 0x14,
|
||||
E4K_REG_GAIN2 = 0x15,
|
||||
E4K_REG_GAIN3 = 0x16,
|
||||
E4K_REG_GAIN4 = 0x17,
|
||||
// gap
|
||||
E4K_REG_AGC1 = 0x1a,
|
||||
E4K_REG_AGC2 = 0x1b,
|
||||
E4K_REG_AGC3 = 0x1c,
|
||||
E4K_REG_AGC4 = 0x1d,
|
||||
E4K_REG_AGC5 = 0x1e,
|
||||
E4K_REG_AGC6 = 0x1f,
|
||||
E4K_REG_AGC7 = 0x20,
|
||||
E4K_REG_AGC8 = 0x21,
|
||||
// gap
|
||||
E4K_REG_AGC11 = 0x24,
|
||||
E4K_REG_AGC12 = 0x25,
|
||||
// gap
|
||||
E4K_REG_DC1 = 0x29,
|
||||
E4K_REG_DC2 = 0x2a,
|
||||
E4K_REG_DC3 = 0x2b,
|
||||
E4K_REG_DC4 = 0x2c,
|
||||
E4K_REG_DC5 = 0x2d,
|
||||
E4K_REG_DC6 = 0x2e,
|
||||
E4K_REG_DC7 = 0x2f,
|
||||
E4K_REG_DC8 = 0x30,
|
||||
// gap
|
||||
E4K_REG_QLUT0 = 0x50,
|
||||
E4K_REG_QLUT1 = 0x51,
|
||||
E4K_REG_QLUT2 = 0x52,
|
||||
E4K_REG_QLUT3 = 0x53,
|
||||
// gap
|
||||
E4K_REG_ILUT0 = 0x60,
|
||||
E4K_REG_ILUT1 = 0x61,
|
||||
E4K_REG_ILUT2 = 0x62,
|
||||
E4K_REG_ILUT3 = 0x63,
|
||||
// gap
|
||||
E4K_REG_DCTIME1 = 0x70,
|
||||
E4K_REG_DCTIME2 = 0x71,
|
||||
E4K_REG_DCTIME3 = 0x72,
|
||||
E4K_REG_DCTIME4 = 0x73,
|
||||
E4K_REG_PWM1 = 0x74,
|
||||
E4K_REG_PWM2 = 0x75,
|
||||
E4K_REG_PWM3 = 0x76,
|
||||
E4K_REG_PWM4 = 0x77,
|
||||
E4K_REG_BIAS = 0x78,
|
||||
E4K_REG_CLKOUT_PWDN = 0x7a,
|
||||
E4K_REG_CHFILT_CALIB = 0x7b,
|
||||
E4K_REG_I2C_REG_ADDR = 0x7d,
|
||||
// FIXME
|
||||
};
|
||||
|
||||
#define E4K_MASTER1_RESET (1 << 0)
|
||||
#define E4K_MASTER1_NORM_STBY (1 << 1)
|
||||
#define E4K_MASTER1_POR_DET (1 << 2)
|
||||
|
||||
#define E4K_SYNTH1_PLL_LOCK (1 << 0)
|
||||
#define E4K_SYNTH1_BAND_SHIF 1
|
||||
|
||||
#define E4K_SYNTH7_3PHASE_EN (1 << 3)
|
||||
|
||||
#define E4K_SYNTH8_VCOCAL_UPD (1 << 2)
|
||||
|
||||
#define E4K_FILT3_DISABLE (1 << 5)
|
||||
|
||||
#define E4K_AGC1_LIN_MODE (1 << 4)
|
||||
#define E4K_AGC1_LNA_UPDATE (1 << 5)
|
||||
#define E4K_AGC1_LNA_G_LOW (1 << 6)
|
||||
#define E4K_AGC1_LNA_G_HIGH (1 << 7)
|
||||
|
||||
#define E4K_AGC6_LNA_CAL_REQ (1 << 4)
|
||||
|
||||
#define E4K_AGC7_MIX_GAIN_AUTO (1 << 0)
|
||||
#define E4K_AGC7_GAIN_STEP_5dB (1 << 5)
|
||||
|
||||
#define E4K_AGC8_SENS_LIN_AUTO (1 << 0)
|
||||
|
||||
#define E4K_AGC11_LNA_GAIN_ENH (1 << 0)
|
||||
|
||||
#define E4K_DC1_CAL_REQ (1 << 0)
|
||||
|
||||
#define E4K_DC5_I_LUT_EN (1 << 0)
|
||||
#define E4K_DC5_Q_LUT_EN (1 << 1)
|
||||
#define E4K_DC5_RANGE_DET_EN (1 << 2)
|
||||
#define E4K_DC5_RANGE_EN (1 << 3)
|
||||
#define E4K_DC5_TIMEVAR_EN (1 << 4)
|
||||
|
||||
#define E4K_CLKOUT_DISABLE 0x96
|
||||
|
||||
#define E4K_CHFCALIB_CMD (1 << 0)
|
||||
|
||||
#define E4K_AGC1_MOD_MASK 0xF
|
||||
|
||||
enum e4k_agc_mode {
|
||||
E4K_AGC_MOD_SERIAL = 0x0,
|
||||
E4K_AGC_MOD_IF_PWM_LNA_SERIAL = 0x1,
|
||||
E4K_AGC_MOD_IF_PWM_LNA_AUTONL = 0x2,
|
||||
E4K_AGC_MOD_IF_PWM_LNA_SUPERV = 0x3,
|
||||
E4K_AGC_MOD_IF_SERIAL_LNA_PWM = 0x4,
|
||||
E4K_AGC_MOD_IF_PWM_LNA_PWM = 0x5,
|
||||
E4K_AGC_MOD_IF_DIG_LNA_SERIAL = 0x6,
|
||||
E4K_AGC_MOD_IF_DIG_LNA_AUTON = 0x7,
|
||||
E4K_AGC_MOD_IF_DIG_LNA_SUPERV = 0x8,
|
||||
E4K_AGC_MOD_IF_SERIAL_LNA_AUTON = 0x9,
|
||||
E4K_AGC_MOD_IF_SERIAL_LNA_SUPERV = 0xa,
|
||||
};
|
||||
|
||||
enum e4k_band {
|
||||
E4K_BAND_VHF2 = 0,
|
||||
E4K_BAND_VHF3 = 1,
|
||||
E4K_BAND_UHF = 2,
|
||||
E4K_BAND_L = 3,
|
||||
};
|
||||
|
||||
enum e4k_mixer_filter_bw {
|
||||
E4K_F_MIX_BW_27M = 0,
|
||||
E4K_F_MIX_BW_4M6 = 8,
|
||||
E4K_F_MIX_BW_4M2 = 9,
|
||||
E4K_F_MIX_BW_3M8 = 10,
|
||||
E4K_F_MIX_BW_3M4 = 11,
|
||||
E4K_F_MIX_BW_3M = 12,
|
||||
E4K_F_MIX_BW_2M7 = 13,
|
||||
E4K_F_MIX_BW_2M3 = 14,
|
||||
E4K_F_MIX_BW_1M9 = 15,
|
||||
};
|
||||
|
||||
enum e4k_if_filter {
|
||||
E4K_IF_FILTER_MIX,
|
||||
E4K_IF_FILTER_CHAN,
|
||||
E4K_IF_FILTER_RC
|
||||
};
|
||||
struct e4k_pll_params {
|
||||
uint32_t fosc;
|
||||
uint32_t intended_flo;
|
||||
uint32_t flo;
|
||||
uint16_t x;
|
||||
uint8_t z;
|
||||
uint8_t r;
|
||||
uint8_t r_idx;
|
||||
uint8_t threephase;
|
||||
};
|
||||
|
||||
struct e4k_state {
|
||||
uint8_t i2c_addr;
|
||||
enum e4k_band band;
|
||||
struct e4k_pll_params vco;
|
||||
};
|
||||
|
||||
int e4k_init(struct e4k_state *e4k);
|
||||
int e4k_if_gain_set(struct e4k_state *e4k, uint8_t stage, int8_t value);
|
||||
int e4k_tune_freq(struct e4k_state *e4k, uint32_t freq);
|
||||
int e4k_tune_params(struct e4k_state *e4k, struct e4k_pll_params *p);
|
||||
int e4k_compute_pll_params(struct e4k_pll_params *oscp, uint32_t fosc, uint32_t intended_flo);
|
||||
int e4k_if_filter_bw_get(struct e4k_state *e4k, enum e4k_if_filter filter);
|
||||
int e4k_if_filter_bw_set(struct e4k_state *e4k, enum e4k_if_filter filter,
|
||||
uint32_t bandwidth);
|
||||
int e4k_rf_filter_set(struct e4k_state *e4k);
|
||||
|
||||
#endif /* _E4K_TUNER_H */
|
|
@ -0,0 +1,726 @@
|
|||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <common.h>
|
||||
#include <logging.h>
|
||||
|
||||
#include <tuner_e4k.h>
|
||||
|
||||
#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
|
||||
};
|
||||
|
||||
/* structure describing a field in a register */
|
||||
struct reg_field {
|
||||
uint8_t reg;
|
||||
uint8_t shift;
|
||||
uint8_t width;
|
||||
};
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* Register Access */
|
||||
|
||||
#if 0
|
||||
/*! \brief Write a register of the tuner chip
|
||||
* \param[in] e4k reference to the tuner
|
||||
* \param[in] reg number of the register
|
||||
* \param[in] val value to be written
|
||||
* \returns 0 on success, negative in case of error
|
||||
*/
|
||||
int e4k_reg_write(struct e4k_state *e4k, uint8_t reg, uint8_t val)
|
||||
{
|
||||
/* FIXME */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief Read a register of the tuner chip
|
||||
* \param[in] e4k reference to the tuner
|
||||
* \param[in] reg number of the register
|
||||
* \returns positive 8bit register contents on success, negative in case of error
|
||||
*/
|
||||
int e4k_reg_read(struct e4k_state *e4k, uint8_t reg)
|
||||
{
|
||||
/* FIXME */
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*! \brief Set or clear some (masked) bits inside a register
|
||||
* \param[in] e4k reference to the tuner
|
||||
* \param[in] reg number of the register
|
||||
* \param[in] mask bit-mask of the value
|
||||
* \param[in] val data value to be written to register
|
||||
* \returns 0 on success, negative in case of error
|
||||
*/
|
||||
static int e4k_reg_set_mask(struct e4k_state *e4k, uint8_t reg,
|
||||
uint8_t mask, uint8_t val)
|
||||
{
|
||||
uint8_t tmp = e4k_reg_read(e4k, reg);
|
||||
|
||||
if ((tmp & mask) == val)
|
||||
return 0;
|
||||
|
||||
return e4k_reg_write(e4k, reg, (tmp & ~mask) | (val & mask));
|
||||
}
|
||||
|
||||
/*! \brief Write a given field inside a register
|
||||
* \param[in] e4k reference to the tuner
|
||||
* \param[in] field structure describing the field
|
||||
* \param[in] val value to be written
|
||||
* \returns 0 on success, negative in case of error
|
||||
*/
|
||||
static int e4k_field_write(struct e4k_state *e4k, const struct reg_field *field, uint8_t val)
|
||||
{
|
||||
int rc;
|
||||
uint8_t mask;
|
||||
|
||||
rc = e4k_reg_read(e4k, field->reg);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
mask = width2mask[field->width] << field->shift;
|
||||
|
||||
return e4k_reg_set_mask(e4k, field->reg, mask, val << field->shift);
|
||||
}
|
||||
|
||||
/*! \brief Read a given field inside a register
|
||||
* \param[in] e4k reference to the tuner
|
||||
* \param[in] field structure describing the field
|
||||
* \returns positive value of the field, negative in case of error
|
||||
*/
|
||||
static int e4k_field_read(struct e4k_state *e4k, const struct reg_field *field)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = e4k_reg_read(e4k, field->reg);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
rc = (rc >> field->shift) & width2mask[field->width];
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* Filter Control */
|
||||
|
||||
static const uint32_t rf_filt_center_uhf[] = {
|
||||
MHZ(360), MHZ(380), MHZ(405), MHZ(425),
|
||||
MHZ(450), MHZ(475), MHZ(505), MHZ(540),
|
||||
MHZ(575), MHZ(615), MHZ(670), MHZ(720),
|
||||
MHZ(760), MHZ(840), MHZ(890), MHZ(970)
|
||||
};
|
||||
|
||||
static const uint32_t rf_filt_center_l[] = {
|
||||
MHZ(1300), MHZ(1320), MHZ(1360), MHZ(1410),
|
||||
MHZ(1445), MHZ(1460), MHZ(1490), MHZ(1530),
|
||||
MHZ(1560), MHZ(1590), MHZ(1640), MHZ(1660),
|
||||
MHZ(1680), MHZ(1700), MHZ(1720), MHZ(1750)
|
||||
};
|
||||
|
||||
static int closest_arr_idx(const uint32_t *arr, unsigned int arr_size, uint32_t freq)
|
||||
{
|
||||
unsigned int i;
|
||||
uint32_t last_delta = 0xffffffff;
|
||||
|
||||
/* iterate over the array containing an ordered 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 (last_delta < delta)
|
||||
return i-1;
|
||||
last_delta = delta;
|
||||
}
|
||||
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
/* 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:
|
||||
if (freq < MHZ(268))
|
||||
rc = 0;
|
||||
else
|
||||
rc = 8;
|
||||
break;
|
||||
case E4K_BAND_VHF3:
|
||||
if (freq < MHZ(509))
|
||||
rc = 0;
|
||||
else
|
||||
rc = 8;
|
||||
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[] = {
|
||||
[E4K_IF_FILTER_MIX] = mix_filter_bw,
|
||||
[E4K_IF_FILTER_CHAN] = ifch_filter_bw,
|
||||
[E4K_IF_FILTER_RC] = ifrc_filter_bw,
|
||||
};
|
||||
|
||||
static const uint32_t if_filter_bw_len[] = {
|
||||
[E4K_IF_FILTER_MIX] = ARRAY_SIZE(&mix_filter_bw),
|
||||
[E4K_IF_FILTER_CHAN] = ARRAY_SIZE(&ifch_filter_bw),
|
||||
[E4K_IF_FILTER_RC] = ARRAY_SIZE(&ifrc_filter_bw),
|
||||
};
|
||||
|
||||
static const struct reg_field if_filter_fields[] = {
|
||||
[E4K_IF_FILTER_MIX] = {
|
||||
.reg = E4K_REG_FILT2, .shift = 4, .width = 4,
|
||||
},
|
||||
[E4K_IF_FILTER_CHAN] = {
|
||||
.reg = E4K_REG_FILT3, .shift = 0, .width = 5,
|
||||
},
|
||||
[E4K_IF_FILTER_RC] = {
|
||||
.reg = E4K_REG_FILT2, .shift = 0, .width = 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;
|
||||
uint8_t mask;
|
||||
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);
|
||||
}
|
||||
|
||||
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 3600000 /* 3.6 GHz */
|
||||
#define E4K_PLL_Y 65535
|
||||
|
||||
/* \brief table of R dividers in case 3phase mixing is enabled,
|
||||
* the values have to be halved if it's 2phase */
|
||||
static const uint8_t vco_r_table_3ph[] = {
|
||||
4, 8, 12, 16, 24, 32, 40, 48
|
||||
};
|
||||
|
||||
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) {
|
||||
LOGP(DTUN, LOGL_ERROR, "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)) {
|
||||
LOGP(DTUN, LOGL_ERROR, "Fosc %u invalid\n", fosc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int is_flo_valid(uint32_t flo)
|
||||
{
|
||||
if (flo < MHZ(64) || flo > MHZ(1700)) {
|
||||
LOGP(DTUN, LOGL_ERROR, "Flo %u invalid\n", flo);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int is_z_valid(uint32_t z)
|
||||
{
|
||||
if (z > 255) {
|
||||
LOGP(DTUN, LOGL_ERROR, "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(300))
|
||||
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 unsigned int 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 * 65535 either.
|
||||
*/
|
||||
fvco_z = (uint64_t)f_osc * z;
|
||||
|
||||
if (!is_fvco_valid(fvco_z))
|
||||
return 0;
|
||||
|
||||
fvco_x = ((uint64_t)f_osc * x) / E4K_PLL_Y;
|
||||
|
||||
fvco = fvco_z + fvco_x;
|
||||
|
||||
/* this shouldn't happen, but better to check explicitly for integer
|
||||
* overflows before converting uint64_t to "int" */
|
||||
if (fvco > UINT_MAX) {
|
||||
LOGP(DTUN, LOGL_ERROR, "Fvco %llu > INT_MAX\n", fvco);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return fvco;
|
||||
}
|
||||
|
||||
static int compute_flo(uint32_t f_osc, uint8_t z, uint16_t x, uint8_t r)
|
||||
{
|
||||
unsigned int fvco = compute_fvco(f_osc, z, x);
|
||||
if (fvco == 0)
|
||||
return -EINVAL;
|
||||
|
||||
return fvco / r;
|
||||
}
|
||||
|
||||
static int e4k_band_set(struct e4k_state *e4k, enum e4k_band band)
|
||||
{
|
||||
int rc;
|
||||
|
||||
switch (band) {
|
||||
case E4K_BAND_VHF2:
|
||||
case E4K_BAND_VHF3:
|
||||
case E4K_BAND_UHF:
|
||||
e4k_reg_write(e4k, E4K_REG_BIAS, 3);
|
||||
break;
|
||||
case E4K_BAND_L:
|
||||
e4k_reg_write(e4k, E4K_REG_BIAS, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
rc = e4k_reg_set_mask(e4k, E4K_REG_SYNTH1, 0x03, band);
|
||||
if (rc >= 0)
|
||||
e4k->band = band;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int compute_lowest_r_idx(uint32_t flo, uint32_t fosc)
|
||||
{
|
||||
int three_phase_mixing = use_3ph_mixing(intended_flo);
|
||||
uint32_t r_ideal;
|
||||
|
||||
/* determine what would be the idael R divider, taking into account
|
||||
* fractional remainder of the division */
|
||||
r_ideal = flo / fosc;
|
||||
if (flo % fosc)
|
||||
r_ideal++;
|
||||
|
||||
/* find the next best (bigger) possible R value */
|
||||
for (i = 0; i < ARRAY_SIZE(vco_r_table_3ph); i++) {
|
||||
uint32_t r = vco_r_table_3ph[i];
|
||||
|
||||
if (!three_phase_mixing)
|
||||
r = r / 2;
|
||||
|
||||
if (r < r_ideal)
|
||||
continue;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/* this shouldn't happen!!! */
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*! \brief Compute PLL parameters for givent target frequency
|
||||
* \param[out] oscp Oscillator parameters, if computation successful
|
||||
* \param[in] fosc Clock input frequency applied to the chip (Hz)
|
||||
* \param[in] intended_flo target tuning frequency (Hz)
|
||||
* \returns actual PLL frequency, as close as possible to intended_flo,
|
||||
* negative in case of error
|
||||
*/
|
||||
int e4k_compute_pll_params(struct e4k_pll_params *oscp, uint32_t fosc, uint32_t intended_flo)
|
||||
{
|
||||
int i;
|
||||
int three_phase_mixing = use_3ph_mixing(intended_flo);
|
||||
|
||||
if (!is_fosc_valid(fosc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!is_flo_valid(intended_flo))
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(vco_r_table_3ph); i++) {
|
||||
uint8_t r = vco_r_table_3ph[i];
|
||||
uint64_t intended_fvco, z, remainder;
|
||||
uint32_t x;
|
||||
int flo;
|
||||
|
||||
if (!three_phase_mixing)
|
||||
r = r / 2;
|
||||
|
||||
LOGP(DTUN, LOGL_DEBUG, "Fint=%u, R=%u\n", intended_flo, r);
|
||||
|
||||
/* flo(max) = 1700MHz, R(max) = 48, we need 64bit! */
|
||||
intended_fvco = (uint64_t)intended_flo * r;
|
||||
/* check if fvco is in range, if not continue */
|
||||
if (intended_fvco > UINT_MAX) {
|
||||
LOGP(DTUN, LOGL_DEBUG, "intended_fvco > UINT_MAX\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_fvco_valid(intended_fvco))
|
||||
continue;
|
||||
|
||||
/* compute integral component of multiplier */
|
||||
z = intended_fvco / fosc;
|
||||
if (!is_z_valid(z))
|
||||
continue;
|
||||
|
||||
/* 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 = 65535 -> 64bit! */
|
||||
x = (remainder * E4K_PLL_Y) / fosc;
|
||||
/* x(max) as result of this computation is 65535 */
|
||||
|
||||
flo = compute_flo(fosc, z, x, r);
|
||||
if (flo < 0)
|
||||
continue;
|
||||
|
||||
oscp->fosc = fosc;
|
||||
oscp->flo = flo;
|
||||
oscp->intended_flo = intended_flo;
|
||||
oscp->r = r;
|
||||
oscp->r_idx = i;
|
||||
oscp->threephase = three_phase_mixing;
|
||||
oscp->x = x;
|
||||
oscp->z = z;
|
||||
|
||||
return flo;
|
||||
}
|
||||
|
||||
LOGP(DTUN, LOGL_ERROR, "No valid set of PLL params found for %u\n",
|
||||
intended_flo);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int e4k_tune_params(struct e4k_state *e4k, struct e4k_pll_params *p)
|
||||
{
|
||||
uint8_t val;
|
||||
|
||||
/* program R index + 3phase/2phase */
|
||||
val = (p->r_idx & 0x7) | ((p->threephase & 0x1) << 3);
|
||||
e4k_reg_write(e4k, E4K_REG_SYNTH7, val);
|
||||
/* 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));
|
||||
|
||||
/* FIXME: determine the band */
|
||||
|
||||
/* set the band */
|
||||
//e4k_band_set(e4k, band);
|
||||
|
||||
/* select and set proper RF filter */
|
||||
e4k_rf_filter_set(e4k);
|
||||
|
||||
return e4k->vco.flo;
|
||||
}
|
||||
|
||||
/*! \brief High-level tuning API, just specify frquency
|
||||
*
|
||||
* This function will compute matching PLL parameters, program them into the
|
||||
* hardware and set the band as well as RF filter.
|
||||
*
|
||||
* \param[in] e4k reference to tuner
|
||||
* \param[in] freq frequency in Hz
|
||||
* \returns actual tuned frequency, negative in case of error
|
||||
*/
|
||||
int e4k_tune_freq(struct e4k_state *e4k, uint32_t freq)
|
||||
{
|
||||
int rc;
|
||||
struct e4k_pll_params p;
|
||||
|
||||
/* determine PLL parameters */
|
||||
rc = e4k_compute_pll_params(&p, e4k->vco.fosc, freq);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* actually tune to those parameters */
|
||||
return e4k_tune_params(e4k, &p);
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* 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[] = {
|
||||
[1] = if_stage1_gain,
|
||||
[2] = if_stage23_gain,
|
||||
[3] = if_stage23_gain,
|
||||
[4] = if_stage4_gain,
|
||||
[5] = if_stage56_gain,
|
||||
[6] = if_stage56_gain
|
||||
};
|
||||
|
||||
static const uint8_t if_stage_gain_len[] = {
|
||||
[0] = 0,
|
||||
[1] = ARRAY_SIZE(if_stage1_gain),
|
||||
[2] = ARRAY_SIZE(if_stage23_gain),
|
||||
[3] = ARRAY_SIZE(if_stage23_gain),
|
||||
[4] = ARRAY_SIZE(if_stage4_gain),
|
||||
[5] = ARRAY_SIZE(if_stage56_gain),
|
||||
[6] = ARRAY_SIZE(if_stage56_gain)
|
||||
};
|
||||
|
||||
static const struct reg_field if_stage_gain_regs[] = {
|
||||
[1] = { .reg = E4K_REG_GAIN3, .shift = 0, .width = 1 },
|
||||
[2] = { .reg = E4K_REG_GAIN3, .shift = 1, .width = 2 },
|
||||
[3] = { .reg = E4K_REG_GAIN3, .shift = 3, .width = 2 },
|
||||
[4] = { .reg = E4K_REG_GAIN3, .shift = 5, .width = 2 },
|
||||
[5] = { .reg = E4K_REG_GAIN4, .shift = 0, .width = 3 },
|
||||
[6] = { .reg = E4K_REG_GAIN4, .shift = 3, .width = 3 }
|
||||
};
|
||||
|
||||
static int find_stage_gain(uint8_t stage, int8_t val)
|
||||
{
|
||||
const int8_t *arr;
|
||||
int i;
|
||||
|
||||
if (stage >= ARRAY_SIZE(if_stage_gain))
|
||||
return -EINVAL;
|
||||
|
||||
arr = if_stage_gain[stage];
|
||||
|
||||
for (i = 0; i < if_stage_gain_len[stage]; i++) {
|
||||
if (arr[i] == val)
|
||||
return i;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*! \brief Set the gain of one of the IF gain stages
|
||||
* \param[e4k] handle to the tuner chip
|
||||
* \param [stage] numbere of the stage (1..6)
|
||||
* \param [value] gain value in dBm
|
||||
* \returns 0 on success, negative in case of error
|
||||
*/
|
||||
int e4k_if_gain_set(struct e4k_state *e4k, uint8_t stage, int8_t value)
|
||||
{
|
||||
int rc;
|
||||
uint8_t mask;
|
||||
const struct reg_field *field;
|
||||
|
||||
rc = find_stage_gain(stage, value);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* compute the bit-mask for the given gain field */
|
||||
field = &if_stage_gain_regs[stage];
|
||||
mask = width2mask[field->width] << field->shift;
|
||||
|
||||
return e4k_reg_set_mask(e4k, field->reg, mask, rc << field->shift);
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* 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, 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);
|
||||
}
|
||||
|
||||
/*! \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);
|
||||
|
||||
/* write some magic values into registers */
|
||||
magic_init(e4k);
|
||||
|
||||
/* Set LNA mode to autnonmous */
|
||||
e4k_reg_set_mask(e4k, E4K_REG_AGC1, E4K_AGC1_MOD_MASK,
|
||||
E4K_AGC_MOD_IF_SERIAL_LNA_AUTON);
|
||||
|
||||
/* Set Miser Gain Control to autonomous */
|
||||
e4k_reg_set_mask(e4k, E4K_REG_AGC7, E4K_AGC7_MIX_GAIN_AUTO,
|
||||
E4K_AGC7_MIX_GAIN_AUTO);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* FIXME: do we need to program Output Common Mode voltage ? */
|
||||
|
||||
/* FIXME: initialize DC offset lookup tables */
|
||||
|
||||
/* Disable Clock output, write 0x96 into 0x7A */
|
||||
e4k_reg_write(e4k, E4K_REG_CLKOUT_PWDN, E4K_CLKOUT_DISABLE);
|
||||
|
||||
/* Clear the reset-detect register */
|
||||
e4k_reg_set_mask(e4k, E4K_REG_MASTER1, E4K_MASTER1_POR_DET, E4K_MASTER1_POR_DET);
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
CFLAGS=-I../include/ -O2
|
||||
|
||||
all: tuner-test
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -o $@ -c $^
|
||||
|
||||
tuner-test: tuner-test.o ../src/tuner_e4k.o
|
||||
$(CC) $(LDFLAGS) -o $@ $^
|
|
@ -0,0 +1,94 @@
|
|||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <common.h>
|
||||
#include <tuner_e4k.h>
|
||||
|
||||
|
||||
void logp2(int subsys, unsigned int level, char *file,
|
||||
int line, int cont, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
fprintf(stderr, "%u/%u/%s:%u: ", subsys, level, file, line);
|
||||
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
|
||||
static uint8_t regs[0x7f];
|
||||
|
||||
/* stub functions for register read/write */
|
||||
int e4k_reg_write(struct e4k_state *e4k, uint8_t reg, uint8_t val)
|
||||
{
|
||||
printf("REG WRITE: [0x%02x] = 0x%02x\n", reg, val);
|
||||
|
||||
if (reg > ARRAY_SIZE(regs))
|
||||
return -ERANGE;
|
||||
|
||||
regs[reg] = val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int e4k_reg_read(struct e4k_state *e4k, uint8_t reg)
|
||||
{
|
||||
if (reg > ARRAY_SIZE(regs))
|
||||
return -ERANGE;
|
||||
|
||||
printf("REG READ: [0x%02x] = 0x%02x\n", reg, regs[reg]);
|
||||
|
||||
return regs[reg];
|
||||
}
|
||||
|
||||
static struct e4k_state g_e4k;
|
||||
|
||||
#define FOSC 26000000
|
||||
|
||||
static void dump_params(struct e4k_pll_params *p)
|
||||
{
|
||||
int32_t delta = p->intended_flo - p->flo;
|
||||
|
||||
printf("Flo_int = %u: R=%u, X=%u, Z=%u, Flo = %u, delta=%d\n",
|
||||
p->intended_flo, p->r, p->x, p->z, p->flo, delta);
|
||||
}
|
||||
|
||||
static void compute_and_dump(uint32_t flo)
|
||||
{
|
||||
struct e4k_pll_params params;
|
||||
int rc;
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
rc = e4k_compute_pll_params(¶ms, FOSC, flo);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "something went wrong!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dump_params(¶ms);
|
||||
e4k_tune_params(&g_e4k, ¶ms);
|
||||
}
|
||||
|
||||
static const uint32_t test_freqs[] = {
|
||||
888000000, 66666666, 425000000
|
||||
};
|
||||
//1234567890
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf("Initializing....\n");
|
||||
e4k_init(&g_e4k);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(test_freqs); i++) {
|
||||
compute_and_dump(test_freqs[i]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
|
||||
my $min_distance = 0xffffff;
|
||||
my $max_distance = 0;
|
||||
|
||||
my $freq_old = 0;
|
||||
|
||||
while (my $line = <STDIN>) {
|
||||
my ($freq) = $line =~ /^(\d+) /;
|
||||
|
||||
if ($freq_old != 0) {
|
||||
my $diff = $freq - $freq_old;
|
||||
if ($diff < $min_distance) {
|
||||
$min_distance = $diff;
|
||||
}
|
||||
if ($diff > $max_distance) {
|
||||
printf("New max distance at %u vs %u Hz: %u
|
||||
Hz\n", $freq_old, $freq, $diff);
|
||||
$max_distance = $diff;
|
||||
}
|
||||
}
|
||||
$freq_old = $freq;
|
||||
}
|
||||
|
||||
printf("Min distance = %u Hz\n", $min_distance);
|
||||
printf("Max distance = %u Hz\n", $max_distance);
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
|
||||
sub pll_fvco($$$$) {
|
||||
my ($fosc, $z, $x, $y) = @_;
|
||||
|
||||
return $fosc * ($z + ($x/$y));
|
||||
}
|
||||
|
||||
sub pll_flo($$) {
|
||||
my ($fvco, $r_num) = @_;
|
||||
|
||||
return $fvco / $r_num;
|
||||
}
|
||||
|
||||
sub is_flo_valid($) {
|
||||
my $flo = shift;
|
||||
|
||||
if ($flo < 64000000 || $flo > 1700000000) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub min_vco_mult($) {
|
||||
my ($fosc) = @_;
|
||||
return (2600000000 / $fosc);
|
||||
}
|
||||
|
||||
sub max_vco_mult($) {
|
||||
my ($fosc) = @_;
|
||||
return (3900000000 / $fosc);
|
||||
}
|
||||
|
||||
sub test_setting($$$$$) {
|
||||
my ($fosc, $z, $x, $y, $r) = @_;
|
||||
my $flo;
|
||||
my $fvco = pll_fvco($fosc, $z, $x, $y);
|
||||
|
||||
if ($fvco < 2600000000 || $fvco > 3900000000) {
|
||||
return;
|
||||
}
|
||||
|
||||
$flo = pll_flo($fvco, $r);
|
||||
if (is_flo_valid($flo)) {
|
||||
printf("%010u Hz (Z=%u, X=%u, R=%u)\n", $flo, $z, $x, $r);
|
||||
}
|
||||
|
||||
$r = $r * 2;
|
||||
$flo = pll_flo($fvco, $r);
|
||||
if (is_flo_valid($flo) && $flo < 300000000) {
|
||||
printf("%010u Hz (Z=%u, X=%u, R=%u, TPM)\n", $flo, $z, $x, $r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub hr() {
|
||||
printf("======================================================================\n");
|
||||
}
|
||||
|
||||
my $fosc = 26000000;
|
||||
my $y = 65535;
|
||||
my @r_int_vals = (2,4,6,8,12,16,20,24);
|
||||
|
||||
|
||||
my $min_vco_mult = min_vco_mult($fosc);
|
||||
my $max_vco_mult = max_vco_mult($fosc);
|
||||
|
||||
printf("Fosc = %u, min_vco_mult=%u, max_vco_mult=%u\n", $fosc,
|
||||
$min_vco_mult, $max_vco_mult);
|
||||
|
||||
for (my $z = $min_vco_mult; $z <= $max_vco_mult; $z++) {
|
||||
for (my $x = 0; $x <= 0xffff; $x+= 1) {
|
||||
foreach my $r (@r_int_vals) {
|
||||
test_setting($fosc, $z, $x, $y, $r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue