/* * Copyright 2016 Dominic Spill * * 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 /* * 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= 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; }