552 lines
13 KiB
C
552 lines
13 KiB
C
/*
|
|
* Copyright 2012 Michael Ossmann
|
|
*
|
|
* This file is part of HackRF.
|
|
*
|
|
* 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, 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; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/*
|
|
* 'gcc -DTEST -DDEBUG -O2 -o test rffc5071.c' prints out what test
|
|
* program would do if it had a real spi library
|
|
*/
|
|
|
|
/*
|
|
* The actual part on Jawbreaker is the RFFC5072, not the RFFC5071, but the
|
|
* RFFC5071 may be installed instead. The only difference between the parts is
|
|
* that the RFFC5071 includes a second mixer.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include "rffc5071.h"
|
|
#include "rffc5071_regs.def" // private register def macros
|
|
|
|
#if (defined DEBUG)
|
|
#include <stdio.h>
|
|
#define LOG printf
|
|
#else
|
|
#define LOG(x,...)
|
|
#include <libopencm3/lpc43xx/ssp.h>
|
|
#include <libopencm3/lpc43xx/scu.h>
|
|
#include <libopencm3/lpc43xx/gpio.h>
|
|
#include "hackrf_core.h"
|
|
#endif
|
|
|
|
/* Default register values. */
|
|
static uint16_t rffc5071_regs_default[RFFC5071_NUM_REGS] = {
|
|
0xbefa, /* 00 */
|
|
0x4064, /* 01 */
|
|
0x9055, /* 02 */
|
|
0x2d02, /* 03 */
|
|
0xacbf, /* 04 */
|
|
0xacbf, /* 05 */
|
|
0x0028, /* 06 */
|
|
0x0028, /* 07 */
|
|
0xff00, /* 08 */
|
|
0x8220, /* 09 */
|
|
0x0202, /* 0A */
|
|
0x4800, /* 0B */
|
|
0x1a94, /* 0C */
|
|
0xd89d, /* 0D */
|
|
0x8900, /* 0E */
|
|
0x1e84, /* 0F */
|
|
0x89d8, /* 10 */
|
|
0x9d00, /* 11 */
|
|
0x2a20, /* 12 */
|
|
0x0000, /* 13 */
|
|
0x0000, /* 14 */
|
|
0x0000, /* 15 */
|
|
0x0000, /* 16 */
|
|
0x4900, /* 17 */
|
|
0x0281, /* 18 */
|
|
0xf00f, /* 19 */
|
|
0x0000, /* 1A */
|
|
0x0000, /* 1B */
|
|
0xc840, /* 1C */
|
|
0x1000, /* 1D */
|
|
0x0005, /* 1E */ };
|
|
|
|
uint16_t rffc5071_regs[RFFC5071_NUM_REGS];
|
|
|
|
/* Mark all regsisters dirty so all will be written at init. */
|
|
uint32_t rffc5071_regs_dirty = 0x7fffffff;
|
|
|
|
/* Set up all registers according to defaults specified in docs. */
|
|
void rffc5071_init(void)
|
|
{
|
|
LOG("# rffc5071_init\n");
|
|
memcpy(rffc5071_regs, rffc5071_regs_default, sizeof(rffc5071_regs));
|
|
rffc5071_regs_dirty = 0x7fffffff;
|
|
|
|
/* Write default register values to chip. */
|
|
rffc5071_regs_commit();
|
|
}
|
|
|
|
/*
|
|
* Set up pins for GPIO and SPI control, configure SSP peripheral for SPI, and
|
|
* set our own default register configuration.
|
|
*/
|
|
void rffc5071_setup(void)
|
|
{
|
|
rffc5071_init();
|
|
LOG("# rffc5071_setup\n");
|
|
#if !defined TEST
|
|
/* Configure GPIO pins. */
|
|
scu_pinmux(SCU_MIXER_ENX, SCU_GPIO_FAST);
|
|
scu_pinmux(SCU_MIXER_SCLK, SCU_GPIO_FAST | SCU_CONF_FUNCTION4);
|
|
scu_pinmux(SCU_MIXER_SDATA, SCU_GPIO_FAST);
|
|
scu_pinmux(SCU_MIXER_RESETX, SCU_GPIO_FAST);
|
|
|
|
/* Set GPIO pins as outputs. */
|
|
GPIO_DIR(PORT_MIXER_ENX) |= PIN_MIXER_ENX;
|
|
GPIO_DIR(PORT_MIXER_SCLK) |= PIN_MIXER_SCLK;
|
|
GPIO_DIR(PORT_MIXER_SDATA) |= PIN_MIXER_SDATA;
|
|
GPIO_DIR(PORT_MIXER_RESETX) |= PIN_MIXER_RESETX;
|
|
|
|
/* set to known state */
|
|
gpio_set(PORT_MIXER_ENX, PIN_MIXER_ENX); /* active low */
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
gpio_clear(PORT_MIXER_SDATA, PIN_MIXER_SDATA);
|
|
gpio_set(PORT_MIXER_RESETX, PIN_MIXER_RESETX); /* active low */
|
|
#endif
|
|
|
|
/* initial setup */
|
|
/* put zeros in freq contol registers */
|
|
set_RFFC5071_P2N(0);
|
|
set_RFFC5071_P2LODIV(0);
|
|
set_RFFC5071_P2PRESC(0);
|
|
set_RFFC5071_P2VCOSEL(0);
|
|
|
|
set_RFFC5071_P2N(0);
|
|
set_RFFC5071_P2LODIV(0);
|
|
set_RFFC5071_P2PRESC(0);
|
|
set_RFFC5071_P2VCOSEL(0);
|
|
|
|
set_RFFC5071_P2N(0);
|
|
set_RFFC5071_P2LODIV(0);
|
|
set_RFFC5071_P2PRESC(0);
|
|
set_RFFC5071_P2VCOSEL(0);
|
|
|
|
/* set ENBL and MODE to be configured via 3-wire interface,
|
|
* not control pins. */
|
|
set_RFFC5071_SIPIN(1);
|
|
|
|
#ifdef JAWBREAKER
|
|
/* initial safe switch control settings */
|
|
rffc5071_set_gpo(SWITCHCTRL_SAFE);
|
|
#endif
|
|
|
|
/* GPOs are active at all times */
|
|
set_RFFC5071_GATE(1);
|
|
|
|
rffc5071_regs_commit();
|
|
}
|
|
|
|
void serial_delay(void)
|
|
{
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < 2; i++)
|
|
__asm__("nop");
|
|
}
|
|
|
|
/* SPI register read.
|
|
*
|
|
* Send 9 bits:
|
|
* first bit is ignored,
|
|
* second bit is one for read operation,
|
|
* next 7 bits are register address.
|
|
* Then receive 16 bits (register value).
|
|
*/
|
|
uint16_t rffc5071_spi_read(uint8_t r) {
|
|
|
|
int bits = 9;
|
|
int msb = 1 << (bits -1);
|
|
uint32_t data = 0x80 | (r & 0x7f);
|
|
|
|
#if DEBUG
|
|
LOG("reg%d = 0\n", r);
|
|
return 0;
|
|
#else
|
|
/* make sure everything is starting in the correct state */
|
|
gpio_set(PORT_MIXER_ENX, PIN_MIXER_ENX);
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
gpio_clear(PORT_MIXER_SDATA, PIN_MIXER_SDATA);
|
|
|
|
/*
|
|
* The device requires two clocks while ENX is high before a serial
|
|
* transaction. This is not clearly documented.
|
|
*/
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
/* start transaction by bringing ENX low */
|
|
gpio_clear(PORT_MIXER_ENX, PIN_MIXER_ENX);
|
|
|
|
while (bits--) {
|
|
if (data & msb)
|
|
gpio_set(PORT_MIXER_SDATA, PIN_MIXER_SDATA);
|
|
else
|
|
gpio_clear(PORT_MIXER_SDATA, PIN_MIXER_SDATA);
|
|
data <<= 1;
|
|
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
}
|
|
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
bits = 16;
|
|
data = 0;
|
|
/* set SDATA line as input */
|
|
GPIO_DIR(PORT_MIXER_SDATA) &= ~PIN_MIXER_SDATA;
|
|
|
|
while (bits--) {
|
|
data <<= 1;
|
|
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
if (MIXER_SDATA_STATE)
|
|
data |= 1;
|
|
}
|
|
/* set SDATA line as output */
|
|
GPIO_DIR(PORT_MIXER_SDATA) |= PIN_MIXER_SDATA;
|
|
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_ENX, PIN_MIXER_ENX);
|
|
|
|
/*
|
|
* The device requires a clock while ENX is high after a serial
|
|
* transaction. This is not clearly documented.
|
|
*/
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
return data;
|
|
#endif /* DEBUG */
|
|
}
|
|
|
|
/* SPI register write
|
|
*
|
|
* Send 25 bits:
|
|
* first bit is ignored,
|
|
* second bit is zero for write operation,
|
|
* next 7 bits are register address,
|
|
* next 16 bits are register value.
|
|
*/
|
|
void rffc5071_spi_write(uint8_t r, uint16_t v) {
|
|
|
|
#if DEBUG
|
|
LOG("0x%04x -> reg%d\n", v, r);
|
|
#else
|
|
|
|
int bits = 25;
|
|
int msb = 1 << (bits -1);
|
|
uint32_t data = ((r & 0x7f) << 16) | v;
|
|
|
|
/* make sure everything is starting in the correct state */
|
|
gpio_set(PORT_MIXER_ENX, PIN_MIXER_ENX);
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
gpio_clear(PORT_MIXER_SDATA, PIN_MIXER_SDATA);
|
|
|
|
/*
|
|
* The device requires two clocks while ENX is high before a serial
|
|
* transaction. This is not clearly documented.
|
|
*/
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
/* start transaction by bringing ENX low */
|
|
gpio_clear(PORT_MIXER_ENX, PIN_MIXER_ENX);
|
|
|
|
while (bits--) {
|
|
if (data & msb)
|
|
gpio_set(PORT_MIXER_SDATA, PIN_MIXER_SDATA);
|
|
else
|
|
gpio_clear(PORT_MIXER_SDATA, PIN_MIXER_SDATA);
|
|
data <<= 1;
|
|
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
}
|
|
|
|
gpio_set(PORT_MIXER_ENX, PIN_MIXER_ENX);
|
|
|
|
/*
|
|
* The device requires a clock while ENX is high after a serial
|
|
* transaction. This is not clearly documented.
|
|
*/
|
|
serial_delay();
|
|
gpio_set(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
|
|
serial_delay();
|
|
gpio_clear(PORT_MIXER_SCLK, PIN_MIXER_SCLK);
|
|
#endif
|
|
}
|
|
|
|
uint16_t rffc5071_reg_read(uint8_t r)
|
|
{
|
|
/* Readback register is not cached. */
|
|
if (r == RFFC5071_READBACK_REG)
|
|
return rffc5071_spi_read(r);
|
|
|
|
/* Discard uncommited write when reading. This shouldn't
|
|
* happen, and probably has not been tested. */
|
|
if ((rffc5071_regs_dirty >> r) & 0x1) {
|
|
rffc5071_spi_read(r);
|
|
};
|
|
return rffc5071_regs[r];
|
|
}
|
|
|
|
void rffc5071_reg_write(uint8_t r, uint16_t v)
|
|
{
|
|
rffc5071_regs[r] = v;
|
|
rffc5071_spi_write(r, v);
|
|
RFFC5071_REG_SET_CLEAN(r);
|
|
}
|
|
|
|
static inline void rffc5071_reg_commit(uint8_t r)
|
|
{
|
|
rffc5071_reg_write(r,rffc5071_regs[r]);
|
|
}
|
|
|
|
void rffc5071_regs_commit(void)
|
|
{
|
|
int r;
|
|
for(r = 0; r < RFFC5071_NUM_REGS; r++) {
|
|
if ((rffc5071_regs_dirty >> r) & 0x1) {
|
|
rffc5071_reg_commit(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
void rffc5071_tx(uint8_t gpo) {
|
|
LOG("# rffc5071_tx\n");
|
|
set_RFFC5071_ENBL(0);
|
|
set_RFFC5071_FULLD(0);
|
|
set_RFFC5071_MODE(1); /* mixer 2 used for both RX and TX */
|
|
#ifdef JAWBREAKER
|
|
/* honor SWITCHCTRL_AMP_BYPASS and SWITCHCTRL_HP settings from caller */
|
|
gpo &= (SWITCHCTRL_AMP_BYPASS | SWITCHCTRL_HP | SWITCHCTRL_MIX_BYPASS);
|
|
if ((gpo & SWITCHCTRL_AMP_BYPASS) == SWITCHCTRL_AMP_BYPASS)
|
|
gpo |= SWITCHCTRL_NO_TX_AMP_PWR;
|
|
gpo |= (SWITCHCTRL_TX | SWITCHCTRL_NO_RX_AMP_PWR);
|
|
rffc5071_set_gpo(gpo);
|
|
#else
|
|
(void)gpo;
|
|
#endif
|
|
rffc5071_regs_commit();
|
|
|
|
#ifdef JAWBREAKER
|
|
/* honor SWITCHCTRL_MIX_BYPASS setting from caller */
|
|
if ((gpo & SWITCHCTRL_MIX_BYPASS) == SWITCHCTRL_MIX_BYPASS)
|
|
rffc5071_disable();
|
|
else
|
|
#endif
|
|
rffc5071_enable();
|
|
}
|
|
|
|
void rffc5071_rx(uint8_t gpo) {
|
|
LOG("# rfc5071_rx\n");
|
|
set_RFFC5071_ENBL(0);
|
|
set_RFFC5071_FULLD(0);
|
|
set_RFFC5071_MODE(1); /* mixer 2 used for both RX and TX */
|
|
#ifdef JAWBREAKER
|
|
/* honor SWITCHCTRL_AMP_BYPASS and SWITCHCTRL_HP settings from caller */
|
|
gpo &= (SWITCHCTRL_AMP_BYPASS | SWITCHCTRL_HP | SWITCHCTRL_MIX_BYPASS);
|
|
if ((gpo & SWITCHCTRL_AMP_BYPASS) == SWITCHCTRL_AMP_BYPASS)
|
|
gpo |= SWITCHCTRL_NO_RX_AMP_PWR;
|
|
gpo |= SWITCHCTRL_NO_TX_AMP_PWR;
|
|
rffc5071_set_gpo(gpo);
|
|
#else
|
|
(void)gpo;
|
|
#endif
|
|
rffc5071_regs_commit();
|
|
|
|
#ifdef JAWBREAKER
|
|
/* honor SWITCHCTRL_MIX_BYPASS setting from caller */
|
|
if ((gpo & SWITCHCTRL_MIX_BYPASS) == SWITCHCTRL_MIX_BYPASS)
|
|
rffc5071_disable();
|
|
else
|
|
#endif
|
|
rffc5071_enable();
|
|
}
|
|
|
|
/*
|
|
* This function turns on both mixer (full-duplex) on the RFFC5071, but our
|
|
* current hardware designs do not support full-duplex operation.
|
|
*/
|
|
void rffc5071_rxtx(void) {
|
|
LOG("# rfc5071_rxtx\n");
|
|
set_RFFC5071_ENBL(0);
|
|
set_RFFC5071_FULLD(1); /* mixer 1 and mixer 2 (RXTX) */
|
|
rffc5071_regs_commit();
|
|
|
|
rffc5071_enable();
|
|
}
|
|
|
|
void rffc5071_disable(void) {
|
|
LOG("# rfc5071_disable\n");
|
|
set_RFFC5071_ENBL(0);
|
|
rffc5071_regs_commit();
|
|
}
|
|
|
|
void rffc5071_enable(void) {
|
|
LOG("# rfc5071_enable\n");
|
|
set_RFFC5071_ENBL(1);
|
|
rffc5071_regs_commit();
|
|
}
|
|
|
|
#define LO_MAX 5400
|
|
#define REF_FREQ 50
|
|
|
|
/* configure frequency synthesizer in integer mode (lo in MHz) */
|
|
uint16_t rffc5071_config_synth_int(uint16_t lo) {
|
|
uint8_t lodiv;
|
|
uint16_t fvco;
|
|
uint8_t fbkdiv;
|
|
uint16_t n;
|
|
uint16_t tune_freq;
|
|
|
|
LOG("# config_synth_int\n");
|
|
|
|
/* Calculate n_lo */
|
|
uint8_t n_lo = 0;
|
|
uint16_t x = LO_MAX / lo;
|
|
while (x > 1) {
|
|
n_lo++;
|
|
x >>= 1;
|
|
}
|
|
|
|
lodiv = 1 << n_lo;
|
|
fvco = lodiv * lo;
|
|
|
|
/* higher divider and charge pump current required above
|
|
* 3.2GHz. Programming guide says these values (fbkdiv, n,
|
|
* maybe pump?) can be changed back after enable in order to
|
|
* improve phase noise, since the VCO will already be stable
|
|
* and will be unaffected. */
|
|
if (fvco > 3200) {
|
|
fbkdiv = 4;
|
|
set_RFFC5071_PLLCPL(3);
|
|
} else {
|
|
fbkdiv = 2;
|
|
set_RFFC5071_PLLCPL(2);
|
|
}
|
|
|
|
n = (fvco / fbkdiv) / REF_FREQ;
|
|
tune_freq = 50*n*fbkdiv/lodiv;
|
|
LOG("# lo=%d n_lo=%d lodiv=%d fvco=%d fbkdiv=%d n=%d tune_freq=%d\n",
|
|
lo, n_lo, lodiv, fvco, fbkdiv, n, tune_freq);
|
|
|
|
/* Path 1 */
|
|
set_RFFC5071_P1LODIV(n_lo);
|
|
set_RFFC5071_P1N(n);
|
|
set_RFFC5071_P1PRESC(fbkdiv >> 1);
|
|
set_RFFC5071_P1NMSB(0);
|
|
set_RFFC5071_P1NLSB(0);
|
|
|
|
/* Path 2 */
|
|
set_RFFC5071_P2LODIV(n_lo);
|
|
set_RFFC5071_P2N(n);
|
|
set_RFFC5071_P2PRESC(fbkdiv >> 1);
|
|
set_RFFC5071_P2NMSB(0);
|
|
set_RFFC5071_P2NLSB(0);
|
|
|
|
rffc5071_regs_commit();
|
|
|
|
return tune_freq;
|
|
}
|
|
|
|
/* !!!!!!!!!!! hz is currently ignored !!!!!!!!!!!
|
|
*
|
|
* Tuning is rounded down to the nearest 25MHz or 50MHz depending on
|
|
* frequency requsted. Actual tuned value in MHz is returned. */
|
|
uint16_t rffc5071_set_frequency(uint16_t mhz, uint32_t hz) {
|
|
uint16_t tune_freq;
|
|
|
|
// Fractional tuning unimplemented, 'hz' ignored
|
|
hz=hz;
|
|
|
|
rffc5071_disable();
|
|
tune_freq = rffc5071_config_synth_int(mhz);
|
|
rffc5071_enable();
|
|
|
|
return tune_freq;
|
|
}
|
|
|
|
void rffc5071_set_gpo(uint8_t gpo)
|
|
{
|
|
/* We set GPO for both paths just in case. */
|
|
set_RFFC5071_P1GPO(gpo);
|
|
set_RFFC5071_P2GPO(gpo);
|
|
|
|
rffc5071_regs_commit();
|
|
}
|
|
|
|
#ifdef TEST
|
|
int main(int ac, char **av)
|
|
{
|
|
rffc5071_setup();
|
|
rffc5071_tx(0);
|
|
rffc5071_set_frequency(500, 0);
|
|
rffc5071_set_frequency(525, 0);
|
|
rffc5071_set_frequency(550, 0);
|
|
rffc5071_set_frequency(1500, 0);
|
|
rffc5071_set_frequency(1525, 0);
|
|
rffc5071_set_frequency(1550, 0);
|
|
rffc5071_disable();
|
|
rffc5071_rx(0);
|
|
rffc5071_disable();
|
|
rffc5071_rxtx();
|
|
rffc5071_disable();
|
|
}
|
|
#endif //TEST
|