415 lines
12 KiB
C
415 lines
12 KiB
C
|
/*
|
||
|
* Copyright 2016 Dominic Spill <dominicgs@gmail.com>
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#include "operacake.h"
|
||
|
#include "operacake_sctimer.h"
|
||
|
#include "hackrf_core.h"
|
||
|
#include "gpio.h"
|
||
|
#include "gpio_lpc.h"
|
||
|
#include "i2c_bus.h"
|
||
|
#include <libopencm3/lpc43xx/scu.h>
|
||
|
|
||
|
/*
|
||
|
* I2C Mode
|
||
|
*/
|
||
|
#define OPERACAKE_PIN_OE(x) (x<<7)
|
||
|
#define OPERACAKE_PIN_U2CTRL1(x) (x<<6)
|
||
|
#define OPERACAKE_PIN_U2CTRL0(x) (x<<5)
|
||
|
#define OPERACAKE_PIN_U3CTRL1(x) (x<<4)
|
||
|
#define OPERACAKE_PIN_U3CTRL0(x) (x<<3)
|
||
|
#define OPERACAKE_PIN_U1CTRL(x) (x<<2)
|
||
|
#define OPERACAKE_PIN_LEDEN2(x) (x<<1)
|
||
|
#define OPERACAKE_PIN_LEDEN(x) (x<<0)
|
||
|
|
||
|
#define OPERACAKE_PORT_A1 (OPERACAKE_PIN_U2CTRL0(0) | OPERACAKE_PIN_U2CTRL1(0))
|
||
|
#define OPERACAKE_PORT_A2 (OPERACAKE_PIN_U2CTRL0(1) | OPERACAKE_PIN_U2CTRL1(0))
|
||
|
#define OPERACAKE_PORT_A3 (OPERACAKE_PIN_U2CTRL0(0) | OPERACAKE_PIN_U2CTRL1(1))
|
||
|
#define OPERACAKE_PORT_A4 (OPERACAKE_PIN_U2CTRL0(1) | OPERACAKE_PIN_U2CTRL1(1))
|
||
|
|
||
|
#define OPERACAKE_PORT_B1 (OPERACAKE_PIN_U3CTRL0(0) | OPERACAKE_PIN_U3CTRL1(0))
|
||
|
#define OPERACAKE_PORT_B2 (OPERACAKE_PIN_U3CTRL0(1) | OPERACAKE_PIN_U3CTRL1(0))
|
||
|
#define OPERACAKE_PORT_B3 (OPERACAKE_PIN_U3CTRL0(0) | OPERACAKE_PIN_U3CTRL1(1))
|
||
|
#define OPERACAKE_PORT_B4 (OPERACAKE_PIN_U3CTRL0(1) | OPERACAKE_PIN_U3CTRL1(1))
|
||
|
|
||
|
#define OPERACAKE_SAMESIDE OPERACAKE_PIN_U1CTRL(1)
|
||
|
#define OPERACAKE_CROSSOVER OPERACAKE_PIN_U1CTRL(0)
|
||
|
#define OPERACAKE_EN_LEDS (OPERACAKE_PIN_LEDEN2(1) | OPERACAKE_PIN_LEDEN2(0))
|
||
|
#define OPERACAKE_GPIO_ENABLE OPERACAKE_PIN_OE(0)
|
||
|
#define OPERACAKE_GPIO_DISABLE OPERACAKE_PIN_OE(1)
|
||
|
|
||
|
#define OPERACAKE_REG_INPUT 0x00
|
||
|
#define OPERACAKE_REG_OUTPUT 0x01
|
||
|
#define OPERACAKE_REG_POLARITY 0x02
|
||
|
#define OPERACAKE_REG_CONFIG 0x03
|
||
|
|
||
|
#define OPERACAKE_DEFAULT_OUTPUT (OPERACAKE_GPIO_DISABLE | OPERACAKE_SAMESIDE \
|
||
|
| OPERACAKE_PORT_A1 | OPERACAKE_PORT_B1 \
|
||
|
| OPERACAKE_EN_LEDS)
|
||
|
#define OPERACAKE_CONFIG_ALL_OUTPUT (0x00)
|
||
|
// Leave LED bits as outputs
|
||
|
#define OPERACAKE_CONFIG_GPIO_INPUTS (0x7C)
|
||
|
|
||
|
#define OPERACAKE_POLARITY_NORMAL (0x00)
|
||
|
|
||
|
#define OPERACAKE_ADDRESS_DEFAULT 0x18
|
||
|
#define OPERACAKE_ADDRESS_INVALID 0xFF
|
||
|
|
||
|
#define OPERACAKE_MAX_BOARDS 8
|
||
|
|
||
|
#define INVALID_RANGE 0xFF;
|
||
|
static uint8_t current_range = INVALID_RANGE;
|
||
|
|
||
|
i2c_bus_t* const oc_bus = &i2c0;
|
||
|
|
||
|
enum operacake_switching_mode {
|
||
|
MODE_MANUAL = 0,
|
||
|
MODE_FREQUENCY = 1,
|
||
|
MODE_TIME = 2,
|
||
|
};
|
||
|
|
||
|
struct operacake_state {
|
||
|
bool present;
|
||
|
enum operacake_switching_mode mode;
|
||
|
uint8_t PA;
|
||
|
uint8_t PB;
|
||
|
};
|
||
|
|
||
|
struct operacake_state operacake_boards[OPERACAKE_MAX_BOARDS];
|
||
|
bool allow_gpio_mode = true;
|
||
|
|
||
|
/* read single register */
|
||
|
uint8_t operacake_read_reg(i2c_bus_t* const bus, uint8_t address, uint8_t reg) {
|
||
|
// Convert from Opera Cake address (0-7) to I2C address
|
||
|
address += OPERACAKE_ADDRESS_DEFAULT;
|
||
|
|
||
|
const uint8_t data_tx[] = { reg };
|
||
|
uint8_t data_rx[] = { 0x00 };
|
||
|
i2c_bus_transfer(bus, address, data_tx, 1, data_rx, 1);
|
||
|
return data_rx[0];
|
||
|
}
|
||
|
|
||
|
/* Write to one of the PCA9557 registers */
|
||
|
void operacake_write_reg(i2c_bus_t* const bus, uint8_t address, uint8_t reg, uint8_t value) {
|
||
|
// Convert from Opera Cake address (0-7) to I2C address
|
||
|
address += OPERACAKE_ADDRESS_DEFAULT;
|
||
|
|
||
|
const uint8_t data[] = {reg, value};
|
||
|
i2c_bus_transfer(bus, address, data, 2, NULL, 0);
|
||
|
}
|
||
|
|
||
|
uint8_t operacake_init(bool allow_gpio) {
|
||
|
/* Find connected operacakes */
|
||
|
for (int addr = 0; addr < 8; addr++) {
|
||
|
operacake_write_reg(oc_bus, addr, OPERACAKE_REG_OUTPUT,
|
||
|
OPERACAKE_DEFAULT_OUTPUT);
|
||
|
operacake_write_reg(oc_bus, addr, OPERACAKE_REG_CONFIG,
|
||
|
OPERACAKE_CONFIG_ALL_OUTPUT);
|
||
|
uint8_t reg = operacake_read_reg(oc_bus, addr, OPERACAKE_REG_CONFIG);
|
||
|
|
||
|
operacake_boards[addr].present = (reg == OPERACAKE_CONFIG_ALL_OUTPUT);
|
||
|
operacake_boards[addr].mode = MODE_MANUAL;
|
||
|
operacake_boards[addr].PA = OPERACAKE_PORT_A1;
|
||
|
operacake_boards[addr].PB = OPERACAKE_PORT_B1;
|
||
|
}
|
||
|
allow_gpio_mode = allow_gpio;
|
||
|
if (allow_gpio) {
|
||
|
operacake_sctimer_init();
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
bool operacake_is_board_present(uint8_t address) {
|
||
|
if (address >= OPERACAKE_MAX_BOARDS)
|
||
|
return false;
|
||
|
|
||
|
return operacake_boards[address].present;
|
||
|
}
|
||
|
|
||
|
void operacake_get_boards(uint8_t *addresses) {
|
||
|
int count = 0;
|
||
|
for (int i = 0; i < OPERACAKE_MAX_BOARDS; i++) {
|
||
|
addresses[i] = OPERACAKE_ADDRESS_INVALID;
|
||
|
}
|
||
|
for (int i = 0; i < OPERACAKE_MAX_BOARDS; i++) {
|
||
|
if (operacake_is_board_present(i))
|
||
|
addresses[count++] = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint8_t port_to_pins(uint8_t port) {
|
||
|
switch(port) {
|
||
|
case OPERACAKE_PA1:
|
||
|
return OPERACAKE_PORT_A1;
|
||
|
case OPERACAKE_PA2:
|
||
|
return OPERACAKE_PORT_A2;
|
||
|
case OPERACAKE_PA3:
|
||
|
return OPERACAKE_PORT_A3;
|
||
|
case OPERACAKE_PA4:
|
||
|
return OPERACAKE_PORT_A4;
|
||
|
|
||
|
case OPERACAKE_PB1:
|
||
|
return OPERACAKE_PORT_B1;
|
||
|
case OPERACAKE_PB2:
|
||
|
return OPERACAKE_PORT_B2;
|
||
|
case OPERACAKE_PB3:
|
||
|
return OPERACAKE_PORT_B3;
|
||
|
case OPERACAKE_PB4:
|
||
|
return OPERACAKE_PORT_B4;
|
||
|
}
|
||
|
return 0xFF;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Issue I2C command to activate ports.
|
||
|
*/
|
||
|
uint8_t operacake_activate_ports(uint8_t address, uint8_t PA, uint8_t PB)
|
||
|
{
|
||
|
uint8_t side, pa, pb, reg;
|
||
|
/* Ensure PA and PB are within the valid range. */
|
||
|
if ((PA > OPERACAKE_PB4) || (PB > OPERACAKE_PB4)) {
|
||
|
return 1;
|
||
|
}
|
||
|
/* Ensure PA and PB are on opposite sides. */
|
||
|
if (((PA <= OPERACAKE_PA4) && (PB <= OPERACAKE_PA4))
|
||
|
|| ((PA > OPERACAKE_PA4) && (PB > OPERACAKE_PA4))) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (PA > OPERACAKE_PA4) {
|
||
|
side = OPERACAKE_CROSSOVER;
|
||
|
} else {
|
||
|
side = OPERACAKE_SAMESIDE;
|
||
|
}
|
||
|
|
||
|
pa = port_to_pins(PA);
|
||
|
pb = port_to_pins(PB);
|
||
|
|
||
|
reg = (OPERACAKE_GPIO_DISABLE | side | pa | pb | OPERACAKE_EN_LEDS);
|
||
|
operacake_write_reg(oc_bus, address, OPERACAKE_REG_OUTPUT, reg);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void operacake_set_mode(uint8_t address, uint8_t mode) {
|
||
|
if (address >= OPERACAKE_MAX_BOARDS)
|
||
|
return;
|
||
|
|
||
|
operacake_boards[address].mode = mode;
|
||
|
current_range = INVALID_RANGE;
|
||
|
|
||
|
if (mode == MODE_TIME) {
|
||
|
// Switch Opera Cake to pin-control mode
|
||
|
uint8_t config_pins = (uint8_t)~(OPERACAKE_PIN_OE(1) | OPERACAKE_PIN_LEDEN(1) | OPERACAKE_PIN_LEDEN2(1));
|
||
|
operacake_write_reg(oc_bus, address, OPERACAKE_REG_CONFIG, config_pins);
|
||
|
operacake_write_reg(oc_bus, address, OPERACAKE_REG_OUTPUT, OPERACAKE_GPIO_ENABLE | OPERACAKE_EN_LEDS);
|
||
|
} else {
|
||
|
operacake_write_reg(oc_bus, address, OPERACAKE_REG_CONFIG, OPERACAKE_CONFIG_ALL_OUTPUT);
|
||
|
operacake_activate_ports(address, operacake_boards[address].PA, operacake_boards[address].PB);
|
||
|
}
|
||
|
|
||
|
// If any boards are in MODE_TIME, enable the sctimer events.
|
||
|
bool enable_sctimer = false;
|
||
|
for (int i = 0; i < OPERACAKE_MAX_BOARDS; i++) {
|
||
|
if (operacake_boards[i].mode == MODE_TIME)
|
||
|
enable_sctimer = true;
|
||
|
}
|
||
|
operacake_sctimer_enable(enable_sctimer);
|
||
|
}
|
||
|
|
||
|
uint8_t operacake_get_mode(uint8_t address) {
|
||
|
if (address >= OPERACAKE_MAX_BOARDS)
|
||
|
return 0;
|
||
|
|
||
|
return operacake_boards[address].mode;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set manual mode ports.
|
||
|
*/
|
||
|
uint8_t operacake_set_ports(uint8_t address, uint8_t PA, uint8_t PB)
|
||
|
{
|
||
|
/* Ensure PA and PB are within the valid range. */
|
||
|
if ((PA > OPERACAKE_PB4) || (PB > OPERACAKE_PB4)) {
|
||
|
return 1;
|
||
|
}
|
||
|
/* Ensure PA and PB are on opposite sides. */
|
||
|
if (((PA <= OPERACAKE_PA4) && (PB <= OPERACAKE_PA4))
|
||
|
|| ((PA > OPERACAKE_PA4) && (PB > OPERACAKE_PA4))) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
// Keep track of manual settings for when we switch in/out of time/frequency modes.
|
||
|
operacake_boards[address].PA = PA;
|
||
|
operacake_boards[address].PB = PB;
|
||
|
|
||
|
// Immediately apply register settings if the board is in manual mode.
|
||
|
if (operacake_boards[address].mode == MODE_MANUAL) {
|
||
|
return operacake_activate_ports(address, PA, PB);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* frequency mode aka "Opera Glasses"
|
||
|
*/
|
||
|
typedef struct {
|
||
|
uint16_t freq_min;
|
||
|
uint16_t freq_max;
|
||
|
uint8_t portA;
|
||
|
uint8_t portB;
|
||
|
} operacake_range;
|
||
|
|
||
|
static operacake_range ranges[MAX_OPERACAKE_RANGES * sizeof(operacake_range)];
|
||
|
static uint8_t range_idx = 0;
|
||
|
|
||
|
uint8_t operacake_add_range(uint16_t freq_min, uint16_t freq_max, uint8_t port) {
|
||
|
if(range_idx >= MAX_OPERACAKE_RANGES) {
|
||
|
return 1;
|
||
|
}
|
||
|
ranges[range_idx].freq_min = freq_min;
|
||
|
ranges[range_idx].freq_max = freq_max;
|
||
|
ranges[range_idx].portA = port;
|
||
|
/* Make the B port mirror the A port. */
|
||
|
ranges[range_idx].portB = (port + 4) % 8;
|
||
|
range_idx++;
|
||
|
current_range = INVALID_RANGE;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void operacake_clear_ranges(void)
|
||
|
{
|
||
|
range_idx = 0;
|
||
|
}
|
||
|
|
||
|
#define FREQ_ONE_MHZ (1000000ull)
|
||
|
|
||
|
uint8_t operacake_set_range(uint32_t freq_mhz) {
|
||
|
if(range_idx == 0) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int range;
|
||
|
for(range=0; range<range_idx; range++) {
|
||
|
if((freq_mhz >= ranges[range].freq_min)
|
||
|
&& (freq_mhz <= ranges[range].freq_max)) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
/* Use the last range if there was no match. */
|
||
|
if (range == range_idx) {
|
||
|
range--;
|
||
|
}
|
||
|
if(range == current_range) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < OPERACAKE_MAX_BOARDS; i++) {
|
||
|
if (operacake_is_board_present(i) && operacake_get_mode(i) == MODE_FREQUENCY) {
|
||
|
operacake_activate_ports(i, ranges[range].portA, ranges[range].portB);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
current_range = range;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* GPIO
|
||
|
*/
|
||
|
uint16_t gpio_test(uint8_t address)
|
||
|
{
|
||
|
uint8_t i, reg, bit_mask, gpio_mask = 0x1F;
|
||
|
uint16_t result = 0;
|
||
|
if(!allow_gpio_mode)
|
||
|
return 0xFFFF;
|
||
|
|
||
|
scu_pinmux(SCU_PINMUX_GPIO3_8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||
|
scu_pinmux(SCU_PINMUX_GPIO3_12, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||
|
scu_pinmux(SCU_PINMUX_GPIO3_13, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||
|
scu_pinmux(SCU_PINMUX_GPIO3_14, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||
|
scu_pinmux(SCU_PINMUX_GPIO3_15, SCU_GPIO_FAST | SCU_CONF_FUNCTION0);
|
||
|
|
||
|
static struct gpio_t gpio_pins[] = {
|
||
|
GPIO(3, 8), // u1ctrl IO2
|
||
|
GPIO(3, 14), // u3ctrl0 IO3
|
||
|
GPIO(3, 15), // u3ctrl1 IO4
|
||
|
GPIO(3, 12), // u2ctrl0 IO5
|
||
|
GPIO(3, 13) // u2ctrl1 IO6
|
||
|
};
|
||
|
// Setup I2C to put it in GPIO mode
|
||
|
reg = (OPERACAKE_GPIO_ENABLE | OPERACAKE_EN_LEDS);
|
||
|
operacake_write_reg(oc_bus, address, OPERACAKE_REG_OUTPUT, reg);
|
||
|
operacake_write_reg(oc_bus, address, OPERACAKE_REG_CONFIG,
|
||
|
OPERACAKE_CONFIG_GPIO_INPUTS);
|
||
|
operacake_write_reg(oc_bus, address, OPERACAKE_REG_POLARITY,
|
||
|
OPERACAKE_POLARITY_NORMAL);
|
||
|
// clear state
|
||
|
for(i=0; i<5; i++) {
|
||
|
gpio_output(&gpio_pins[i]);
|
||
|
gpio_write(&gpio_pins[i], 0);
|
||
|
}
|
||
|
// Test each pin separately
|
||
|
for(i=0; i<5; i++) {
|
||
|
// Set pin high
|
||
|
gpio_write(&gpio_pins[i], 1);
|
||
|
// check input
|
||
|
reg = operacake_read_reg(oc_bus, address, OPERACAKE_REG_INPUT);
|
||
|
reg >>= 2;
|
||
|
reg &= gpio_mask;
|
||
|
bit_mask = 1 << i;
|
||
|
result <<= 1;
|
||
|
if(!(reg & bit_mask)) {
|
||
|
// Is the correct bit set?
|
||
|
result |= 1;
|
||
|
}
|
||
|
result <<= 1;
|
||
|
if(reg & ~bit_mask) {
|
||
|
// Are any other bits set?
|
||
|
result |= 1;
|
||
|
}
|
||
|
result <<= 1;
|
||
|
// set pin low
|
||
|
gpio_write(&gpio_pins[i], 0);
|
||
|
// check input
|
||
|
reg = operacake_read_reg(oc_bus, address, OPERACAKE_REG_INPUT);
|
||
|
reg >>= 2;
|
||
|
reg &= gpio_mask;
|
||
|
bit_mask = 1 << i;
|
||
|
if(reg & bit_mask) {
|
||
|
// Is the correct bit clear?
|
||
|
result |= 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// clean up
|
||
|
for(i=0; i<5; i++) {
|
||
|
gpio_input(&gpio_pins[i]);
|
||
|
}
|
||
|
|
||
|
// Put it back in to I2C mode and set default pins
|
||
|
operacake_write_reg(oc_bus, address, OPERACAKE_REG_CONFIG,
|
||
|
OPERACAKE_CONFIG_ALL_OUTPUT);
|
||
|
operacake_write_reg(oc_bus, address, OPERACAKE_REG_OUTPUT,
|
||
|
OPERACAKE_DEFAULT_OUTPUT);
|
||
|
return result;
|
||
|
}
|