mirror of https://github.com/ARMmbed/mbed-os.git
2350 lines
67 KiB
C++
2350 lines
67 KiB
C++
/*
|
|
* Copyright (c) 2019, Arm Limited and affiliates.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "MbedTester.h"
|
|
#include "fpga_config.h"
|
|
#include "BlockDevice.h"
|
|
#include "rtos/ThisThread.h"
|
|
#include "platform/mbed_wait_api.h"
|
|
#include "platform/mbed_error.h"
|
|
#include "drivers/MbedCRC.h"
|
|
|
|
#define mbed_tester_printf(...)
|
|
|
|
#define PHYSICAL_PINS 128
|
|
#define LOGICAL_PINS 8
|
|
#define FIRMWARE_SIZE 2192012
|
|
#define FIRMWARE_REGION_SIZE 0x220000
|
|
#define FIRMWARE_HEADER_SIZE 0x10000
|
|
#define FLASH_SECTOR_SIZE 0x1000
|
|
#define LENGTH_SIZE 0x4
|
|
#define CRC_SIZE 0x4
|
|
#define FLASH_SPI_FREQ_HZ 2000000
|
|
#define ANALOG_COUNT 4
|
|
|
|
#define PHYSICAL_NC ((MbedTester::PhysicalIndex)0xFF)
|
|
|
|
static const uint8_t KEY[8] = {
|
|
0x92, 0x9d, 0x9a, 0x9b,
|
|
0x29, 0x35, 0xa2, 0x65
|
|
};
|
|
|
|
template<size_t width>
|
|
class MbedTesterBitMap {
|
|
public:
|
|
|
|
MbedTesterBitMap()
|
|
{
|
|
for (size_t i = 0; i < _count; i++) {
|
|
_bitmap[i] = 0;
|
|
}
|
|
}
|
|
|
|
bool get(size_t index)
|
|
{
|
|
if (index >= width) {
|
|
return false;
|
|
}
|
|
return _bitmap[index / 32] & (1 << (index % 32)) ? true : false;
|
|
}
|
|
|
|
void set(size_t index)
|
|
{
|
|
if (index >= width) {
|
|
return;
|
|
}
|
|
_bitmap[index / 32] |= 1 << (index % 32);
|
|
}
|
|
|
|
void clear(size_t index)
|
|
{
|
|
if (index >= width) {
|
|
return;
|
|
}
|
|
_bitmap[index / 32] &= ~(1 << (index % 32));
|
|
}
|
|
|
|
private:
|
|
|
|
static const size_t _count = (width + 31) / 32;
|
|
uint32_t _bitmap[(width + 31) / 32];
|
|
};
|
|
|
|
static uint8_t spi_transfer(mbed::DigitalInOut *clk, mbed::DigitalInOut *mosi, mbed::DigitalInOut *miso, uint8_t data)
|
|
{
|
|
uint8_t ret = 0;
|
|
for (int i = 0; i < 8; i++) {
|
|
*clk = 0;
|
|
*mosi = (data >> (7 - i)) & 1;
|
|
wait_ns(100);
|
|
*clk = 1;
|
|
ret |= *miso ? 1 << (7 - i) : 0;
|
|
wait_ns(100);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void mbed_tester_command(mbed::DigitalInOut *clk, mbed::DigitalInOut *mosi, mbed::DigitalInOut *miso, uint8_t miso_index, uint32_t addr, bool write_n_read, uint8_t *data, uint8_t size)
|
|
{
|
|
// 8 - Start Key
|
|
for (uint32_t i = 0; i < sizeof(KEY); i++) {
|
|
spi_transfer(clk, mosi, miso, KEY[i]);
|
|
}
|
|
|
|
// 1 - Physical pin index for MISO
|
|
spi_transfer(clk, mosi, miso, miso_index);
|
|
|
|
// 1 - Number of SPI transfers which follow (= N + 5)
|
|
spi_transfer(clk, mosi, miso, size + 5);
|
|
|
|
// 4 - Little endian address for transfer
|
|
spi_transfer(clk, mosi, miso, (addr >> (8 * 0)) & 0xFF);
|
|
spi_transfer(clk, mosi, miso, (addr >> (8 * 1)) & 0xFF);
|
|
spi_transfer(clk, mosi, miso, (addr >> (8 * 2)) & 0xFF);
|
|
spi_transfer(clk, mosi, miso, (addr >> (8 * 3)) & 0xFF);
|
|
|
|
// 1 - direction
|
|
spi_transfer(clk, mosi, miso, write_n_read ? 1 : 0);
|
|
|
|
// N - Data to read or write
|
|
if (write_n_read) {//read: false, write: true
|
|
for (int i = 0; i < size; i++) {
|
|
spi_transfer(clk, mosi, miso, data[i]);
|
|
}
|
|
} else {
|
|
for (int i = 0; i < size; i++) {
|
|
data[i] = spi_transfer(clk, mosi, miso, 0);
|
|
}
|
|
}
|
|
*clk = 0;
|
|
|
|
}
|
|
|
|
static bool mbed_tester_test(mbed::DigitalInOut *clk, mbed::DigitalInOut *mosi, mbed::DigitalInOut *miso, uint8_t miso_index)
|
|
{
|
|
uint8_t buf[4];
|
|
memset(buf, 0, sizeof(buf));
|
|
mbed_tester_command(clk, mosi, miso, miso_index, TESTER_CONTROL, false, buf, sizeof(buf));
|
|
return memcmp(buf, "mbed", sizeof(buf)) == 0;
|
|
}
|
|
|
|
|
|
class MbedTesterBlockDevice : public BlockDevice {
|
|
public:
|
|
|
|
MbedTesterBlockDevice(mbed::DigitalInOut &mosi, mbed::DigitalInOut &miso, mbed::DigitalInOut &clk, mbed::DigitalInOut &cs, uint32_t frequency)
|
|
: _mosi(mosi), _miso(miso), _clk(clk), _cs(cs), _wait_ns(1000000000 / frequency / 2), _init(false)
|
|
{
|
|
|
|
// Set initial values
|
|
_cs.write(1);
|
|
_clk.write(0);
|
|
|
|
// Set direction
|
|
_mosi.output();
|
|
_miso.input();
|
|
_clk.output();
|
|
_cs.output();
|
|
}
|
|
|
|
|
|
virtual int init()
|
|
{
|
|
if (_check_id()) {
|
|
_init = true;
|
|
}
|
|
return _init ? BD_ERROR_OK : BD_ERROR_DEVICE_ERROR;
|
|
}
|
|
|
|
virtual int deinit()
|
|
{
|
|
_init = false;
|
|
return BD_ERROR_OK;
|
|
}
|
|
|
|
virtual int read(void *buffer, bd_addr_t addr, bd_size_t size)
|
|
{
|
|
if (!is_valid_read(addr, size) || !_init) {
|
|
return BD_ERROR_DEVICE_ERROR;
|
|
}
|
|
|
|
_assert_cs(true);
|
|
|
|
uint8_t cmd[] = {
|
|
0x0B, // Fast read
|
|
(uint8_t)(addr >> (2 * 8)), // Address
|
|
(uint8_t)(addr >> (1 * 8)),
|
|
(uint8_t)(addr >> (0 * 8)),
|
|
0x00 // Dummy
|
|
};
|
|
_write((char *)cmd, sizeof(cmd), NULL, 0);
|
|
_write(NULL, 0, (char *)buffer, size);
|
|
|
|
_assert_cs(false);
|
|
|
|
return BD_ERROR_OK;
|
|
}
|
|
|
|
virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size)
|
|
{
|
|
if (!is_valid_program(addr, size) || !_init) {
|
|
return BD_ERROR_DEVICE_ERROR;
|
|
}
|
|
|
|
const bd_size_t max_program_size = 256;
|
|
bd_size_t programmed = 0;
|
|
while (programmed < size) {
|
|
const bd_size_t size_left = size - programmed;
|
|
const bd_size_t program_size = size_left < max_program_size ? size_left : max_program_size;
|
|
|
|
_write_enable();
|
|
_page_program(addr + programmed, (const uint8_t *)buffer, program_size);
|
|
_wait_ready();
|
|
programmed += program_size;
|
|
}
|
|
|
|
return BD_ERROR_OK;
|
|
}
|
|
|
|
virtual int erase(bd_addr_t addr, bd_size_t size)
|
|
{
|
|
if (!is_valid_erase(addr, size) || !_init) {
|
|
return BD_ERROR_DEVICE_ERROR;
|
|
}
|
|
|
|
if ((addr == 0) && (size == FLASH_SECTOR_SIZE)) {
|
|
// Allow 4K erase only on the first sector. The flash on the basys3 does
|
|
// not allow sector erases at the higher addresses.
|
|
_write_enable();
|
|
_sector_erase(addr);
|
|
_wait_ready();
|
|
return BD_ERROR_OK;
|
|
}
|
|
|
|
if (!is_valid_erase(addr, size)) {
|
|
return BD_ERROR_DEVICE_ERROR;
|
|
}
|
|
|
|
const uint32_t erase_size = get_erase_size();
|
|
bd_size_t erased = 0;
|
|
while (erased < erase_size) {
|
|
_write_enable();
|
|
_block_erase(addr + erased);
|
|
_wait_ready();
|
|
erased += erase_size;
|
|
}
|
|
return BD_ERROR_OK;
|
|
}
|
|
|
|
virtual bd_size_t get_read_size() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
virtual bd_size_t get_program_size() const
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
virtual bd_size_t get_erase_size() const
|
|
{
|
|
return 0x10000;
|
|
}
|
|
|
|
virtual bd_size_t get_erase_size(bd_addr_t addr) const
|
|
{
|
|
return get_erase_size();
|
|
}
|
|
|
|
virtual bd_size_t size() const
|
|
{
|
|
return 8 * 1024 * 1024;
|
|
}
|
|
|
|
virtual const char *get_type() const
|
|
{
|
|
return "MbedTesterBlockDevice";
|
|
}
|
|
|
|
protected:
|
|
|
|
void _write_enable()
|
|
{
|
|
uint8_t command[1];
|
|
|
|
_assert_cs(true);
|
|
|
|
command[0] = 0x06;
|
|
_write((char *)command, 1, NULL, 0);
|
|
|
|
_assert_cs(false);
|
|
}
|
|
|
|
void _sector_erase(uint32_t addr)
|
|
{
|
|
uint8_t command[4];
|
|
|
|
_assert_cs(true);
|
|
|
|
command[0] = 0x20;
|
|
command[1] = (addr >> (2 * 8)) & 0xFF;
|
|
command[2] = (addr >> (1 * 8)) & 0xFF;
|
|
command[3] = (addr >> (0 * 8)) & 0xFF;
|
|
_write((char *)command, 4, NULL, 0);
|
|
|
|
_assert_cs(false);
|
|
}
|
|
|
|
void _block_erase(uint32_t addr)
|
|
{
|
|
uint8_t command[4];
|
|
|
|
_assert_cs(true);
|
|
|
|
command[0] = 0xD8;
|
|
command[1] = (addr >> (2 * 8)) & 0xFF;
|
|
command[2] = (addr >> (1 * 8)) & 0xFF;
|
|
command[3] = (addr >> (0 * 8)) & 0xFF;
|
|
_write((char *)command, 4, NULL, 0);
|
|
|
|
_assert_cs(false);
|
|
}
|
|
|
|
void _page_program(uint32_t addr, const uint8_t *data, uint32_t size)
|
|
{
|
|
uint8_t command[4];
|
|
|
|
_assert_cs(true);
|
|
|
|
command[0] = 0x02;
|
|
command[1] = (addr >> (2 * 8)) & 0xFF;
|
|
command[2] = (addr >> (1 * 8)) & 0xFF;
|
|
command[3] = (addr >> (0 * 8)) & 0xFF;
|
|
_write((char *)command, 4, NULL, 0);
|
|
_write((char *)data, size, NULL, 0);
|
|
|
|
_assert_cs(false);
|
|
}
|
|
|
|
void _wait_ready()
|
|
{
|
|
uint8_t command[2];
|
|
uint8_t response[2];
|
|
|
|
// Wait for ready
|
|
response[1] = 0xFF;
|
|
do {
|
|
_assert_cs(true);
|
|
|
|
command[0] = 0x05;
|
|
command[1] = 0;
|
|
_write((char *)command, 2, (char *)response, 2);
|
|
|
|
_assert_cs(false);
|
|
|
|
} while (response[1] & (1 << 0));
|
|
}
|
|
|
|
bool _check_id()
|
|
{
|
|
uint8_t command[1];
|
|
char id0[3];
|
|
char id1[3];
|
|
|
|
// Read ID twice and verify it is the same
|
|
|
|
_assert_cs(true);
|
|
|
|
command[0] = 0x9F;
|
|
_write((char *)command, 1, NULL, 0);
|
|
_write(NULL, 0, id0, sizeof(id0));
|
|
|
|
_assert_cs(false);
|
|
|
|
_assert_cs(true);
|
|
|
|
command[0] = 0x9F;
|
|
_write((char *)command, 1, NULL, 0);
|
|
_write(NULL, 0, id1, sizeof(id1));
|
|
|
|
_assert_cs(false);
|
|
|
|
// Return failure if IDs are not the same
|
|
for (size_t i = 0; i < sizeof(id0); i++) {
|
|
if (id0[i] != id1[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If all 0xFF return failure
|
|
if ((id0[0] == 0xFF) && (id0[1] == 0xFF) && (id0[2] == 0xFF)) {
|
|
return false;
|
|
}
|
|
|
|
// If all 0x00 return failure
|
|
if ((id0[0] == 0x00) && (id0[1] == 0x00) && (id0[2] == 0x00)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void _write(const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length)
|
|
{
|
|
int transfers = 0;
|
|
if (tx_length > transfers) {
|
|
transfers = tx_length;
|
|
}
|
|
if (rx_length > transfers) {
|
|
transfers = rx_length;
|
|
}
|
|
|
|
for (int i = 0; i < transfers; i++) {
|
|
uint8_t out = i < tx_length ? tx_buffer[i] : 0;
|
|
uint8_t in = 0;
|
|
for (int j = 0; j < 8; j++) {
|
|
_mosi.write((out >> 7) & 1);
|
|
out = out << 1;
|
|
wait_ns(_wait_ns);
|
|
|
|
_clk.write(1);
|
|
in = (in << 1) | (_miso.read() ? 1 : 0);
|
|
wait_ns(_wait_ns);
|
|
|
|
_clk.write(0);
|
|
}
|
|
if (i < rx_length) {
|
|
rx_buffer[i] = in;
|
|
}
|
|
}
|
|
}
|
|
|
|
void _assert_cs(bool asserted)
|
|
{
|
|
_clk = 0;
|
|
wait_ns(_wait_ns);
|
|
_cs = asserted ? 0 : 1;
|
|
wait_ns(_wait_ns);
|
|
}
|
|
|
|
mbed::DigitalInOut &_mosi;
|
|
mbed::DigitalInOut &_miso;
|
|
mbed::DigitalInOut &_clk;
|
|
mbed::DigitalInOut &_cs;
|
|
uint32_t _wait_ns;
|
|
bool _init;
|
|
};
|
|
|
|
static void dummy_progress(uint8_t)
|
|
{
|
|
// Stub progress handler for firmware update/dump
|
|
}
|
|
|
|
// Header taken from app note XAPP1081. Information on the commands
|
|
// can be found in the 7 Series FPGA configuration user guide - UG470
|
|
static const uint8_t BANK_B_SELECT[] = {
|
|
0x20, 0x00, 0x00, 0x00, // 0x20000000 NOP
|
|
0x30, 0x02, 0x00, 0x01, // 0x30020001 WRITE to WBSTAR (Warm boot start address register)
|
|
0x00, 0x23, 0x00, 0x00, // 0x00230000 0x230000 = Second bank start address
|
|
0x30, 0x00, 0x80, 0x01, // 0x30008001 WRITE to CMD register
|
|
0x00, 0x00, 0x00, 0x0F, // 0x0000000F 0x0F = IPROG command (starts warm boot)
|
|
0x20, 0x00, 0x00, 0x00, // 0x20000000 NOP
|
|
0x20, 0x00, 0x00, 0x00, // 0x20000000 NOP
|
|
0x20, 0x00, 0x00, 0x00 // 0x20000000 NOP
|
|
};
|
|
|
|
static const uint8_t SYNC_WORD[] = {
|
|
0xAA, 0x99, 0x55, 0x66 // 0xAA995566 Sync word
|
|
};
|
|
|
|
static bool _firmware_header_valid(BlockDevice &flash, bool &valid)
|
|
{
|
|
uint8_t buf[64];
|
|
size_t pos = 0;
|
|
size_t read_size;
|
|
|
|
// Default to invalid
|
|
valid = false;
|
|
|
|
// Check that first portion is erased
|
|
while (pos < FLASH_SECTOR_SIZE - sizeof(SYNC_WORD)) {
|
|
read_size = FLASH_SECTOR_SIZE - pos;
|
|
if (read_size > sizeof(buf)) {
|
|
read_size = sizeof(buf);
|
|
}
|
|
if (flash.read(buf, pos, read_size) != BD_ERROR_OK) {
|
|
return false;
|
|
}
|
|
pos += read_size;
|
|
for (size_t i = 0; i < read_size; i++) {
|
|
if (buf[i] != 0xFF) {
|
|
valid = false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip the sync word
|
|
pos += sizeof(SYNC_WORD);
|
|
|
|
// Check that BANK_B_SELECT is valid
|
|
read_size = sizeof(BANK_B_SELECT);
|
|
if (flash.read(buf, pos, read_size) != BD_ERROR_OK) {
|
|
return false;
|
|
}
|
|
pos += read_size;
|
|
if (memcmp(buf, BANK_B_SELECT, sizeof(BANK_B_SELECT)) != 0) {
|
|
valid = false;
|
|
return true;
|
|
}
|
|
|
|
// Check if the rest is 0xFF
|
|
while (pos < FIRMWARE_HEADER_SIZE) {
|
|
read_size = FIRMWARE_HEADER_SIZE - pos;
|
|
if (read_size > sizeof(buf)) {
|
|
read_size = sizeof(buf);
|
|
}
|
|
if (flash.read(buf, pos, read_size) != BD_ERROR_OK) {
|
|
return false;
|
|
}
|
|
pos += read_size;
|
|
for (size_t i = 0; i < read_size; i++) {
|
|
if (buf[i] != 0xFF) {
|
|
valid = false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
valid = true;
|
|
return true;
|
|
}
|
|
|
|
static bool _firmware_get_active_bank(BlockDevice &flash, bool &second_bank_active)
|
|
{
|
|
uint8_t buf[sizeof(SYNC_WORD)];
|
|
|
|
if (flash.read(buf, FLASH_SECTOR_SIZE - sizeof(SYNC_WORD), sizeof(SYNC_WORD)) != BD_ERROR_OK) {
|
|
return false;
|
|
}
|
|
|
|
second_bank_active = memcmp(buf, SYNC_WORD, sizeof(SYNC_WORD)) == 0 ? true : false;
|
|
return true;
|
|
}
|
|
|
|
static bool _firmware_set_active_bank(BlockDevice &flash, bool second_bank)
|
|
{
|
|
bool valid = false;
|
|
if (!_firmware_header_valid(flash, valid)) {
|
|
return false;
|
|
}
|
|
if (!valid) {
|
|
if (flash.erase(0, FIRMWARE_HEADER_SIZE) != BD_ERROR_OK) {
|
|
return false;
|
|
}
|
|
if (flash.program(BANK_B_SELECT, FLASH_SECTOR_SIZE, sizeof(BANK_B_SELECT)) != BD_ERROR_OK) {
|
|
return false;
|
|
}
|
|
}
|
|
if (!flash.erase(0, FLASH_SECTOR_SIZE)) {
|
|
return false;
|
|
}
|
|
|
|
|
|
if (second_bank) {
|
|
// Write the sync word. Before the sync word is written the FPGA will boot from the first bank.
|
|
// After the sync word is written the FPGA will boot from the second bank.
|
|
if (flash.program(SYNC_WORD, FLASH_SECTOR_SIZE - sizeof(SYNC_WORD), sizeof(SYNC_WORD)) != BD_ERROR_OK) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
MbedTester::MbedTester(const PinList *form_factor, const PinList *exclude_pins)
|
|
: _form_factor(form_factor), _exclude_pins(exclude_pins), _control_auto(true), _control_valid(false),
|
|
_clk_index(PHYSICAL_NC), _mosi_index(PHYSICAL_NC), _miso_index(PHYSICAL_NC), _aux_index(PHYSICAL_NC),
|
|
_clk(NULL), _mosi(NULL), _miso(NULL), _aux(NULL)
|
|
{
|
|
_reset();
|
|
_init_io_exp_rst_flag = 0;
|
|
}
|
|
|
|
MbedTester::~MbedTester()
|
|
{
|
|
_free_control_pins();
|
|
}
|
|
|
|
|
|
void MbedTester::set_control_pins_auto()
|
|
{
|
|
_control_auto = true;
|
|
}
|
|
|
|
void MbedTester::set_control_pins_manual(PinName clk, PinName mosi, PinName miso, PinName aux)
|
|
{
|
|
int index;
|
|
index = _form_factor.index(clk);
|
|
if (index < 0) {
|
|
error("Invalid CLK index");
|
|
}
|
|
PhysicalIndex clk_index = index;
|
|
|
|
index = _form_factor.index(mosi);
|
|
if (index < 0) {
|
|
error("Invalid MOSI index");
|
|
}
|
|
PhysicalIndex mosi_index = index;
|
|
|
|
index = _form_factor.index(miso);
|
|
if (index < 0) {
|
|
error("Invalid MISO index");
|
|
}
|
|
PhysicalIndex miso_index = index;
|
|
|
|
index = _form_factor.index(aux);
|
|
if (index < 0) {
|
|
error("Invalid AUX index");
|
|
}
|
|
PhysicalIndex aux_index = index;
|
|
|
|
if (clk_index + 1 != mosi_index) {
|
|
error("MOSI pin index does not follow CLK as required");
|
|
}
|
|
|
|
if ((miso_index == clk_index) || (miso_index == mosi_index)) {
|
|
error("MISO conflicts with a control channel");
|
|
}
|
|
if ((aux_index == clk_index) || (aux_index == mosi_index) || (aux_index == miso_index)) {
|
|
error("AUX conflicts with a control channel");
|
|
}
|
|
|
|
// All criteria have been met so set the pins
|
|
_control_auto = false;
|
|
_free_control_pins();
|
|
_clk_index = clk_index;
|
|
_mosi_index = mosi_index;
|
|
_miso_index = miso_index;
|
|
_aux_index = aux_index;
|
|
_setup_control_pins();
|
|
_control_valid = true;
|
|
}
|
|
|
|
bool MbedTester::firmware_dump(mbed::FileHandle *dest, mbed::Callback<void(uint8_t)> progress)
|
|
{
|
|
_update_control_pins();
|
|
|
|
if (!progress) {
|
|
progress = mbed::callback(dummy_progress);
|
|
}
|
|
|
|
// Mapping intentionally different from control channel to prevent
|
|
// unintentional activation (clk and mosi flipped)
|
|
MbedTesterBlockDevice flash(*_clk, *_miso, *_mosi, *_aux, FLASH_SPI_FREQ_HZ);
|
|
sys_pin_mode_spi_serial_flash(_clk_index, _miso_index, _mosi_index, _aux_index);
|
|
|
|
progress(0);
|
|
|
|
if (flash.init() != BD_ERROR_OK) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
|
|
// Set the start of dump to the active bank
|
|
bool second_bank_active;
|
|
if (!_firmware_get_active_bank(flash, second_bank_active)) {
|
|
// Error determining active bank
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
const uint32_t start = FIRMWARE_HEADER_SIZE + (second_bank_active ? FIRMWARE_REGION_SIZE : 0);
|
|
|
|
// Get the firmware size
|
|
uint32_t offset = 0;
|
|
uint8_t buf[256];
|
|
uint32_t prev_percent_done = 0;
|
|
if (flash.read(buf, start + offset, LENGTH_SIZE) != BD_ERROR_OK) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
if (dest->write(buf, LENGTH_SIZE) != LENGTH_SIZE) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
offset += LENGTH_SIZE;
|
|
uint32_t data_size = (buf[0] << (0 * 8)) |
|
|
(buf[1] << (1 * 8)) |
|
|
(buf[2] << (2 * 8)) |
|
|
(buf[3] << (3 * 8));
|
|
if (data_size > FIRMWARE_REGION_SIZE - LENGTH_SIZE - CRC_SIZE) {
|
|
data_size = FIRMWARE_REGION_SIZE - LENGTH_SIZE - CRC_SIZE;
|
|
}
|
|
const uint32_t firmware_size = data_size + LENGTH_SIZE + CRC_SIZE;
|
|
|
|
// Dump firmware
|
|
while (offset < firmware_size) {
|
|
uint32_t read_size = firmware_size - offset;
|
|
if (read_size > sizeof(buf)) {
|
|
read_size = sizeof(buf);
|
|
}
|
|
if (flash.read(buf, start + offset, read_size) != BD_ERROR_OK) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
ssize_t write_size = dest->write(buf, read_size);
|
|
if ((uint32_t)write_size != read_size) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
offset += read_size;
|
|
|
|
const uint8_t percent_done = (offset * 100) / firmware_size;
|
|
if (percent_done != prev_percent_done) {
|
|
progress(percent_done);
|
|
prev_percent_done = percent_done;
|
|
}
|
|
}
|
|
|
|
progress(100);
|
|
|
|
sys_pin_mode_disabled();
|
|
return true;
|
|
}
|
|
|
|
bool MbedTester::firmware_dump_all(mbed::FileHandle *dest, mbed::Callback<void(uint8_t)> progress)
|
|
{
|
|
_update_control_pins();
|
|
|
|
if (!progress) {
|
|
progress = mbed::callback(dummy_progress);
|
|
}
|
|
|
|
// Mapping intentionally different from control channel to prevent
|
|
// unintentional activation (clk and mosi flipped)
|
|
MbedTesterBlockDevice flash(*_clk, *_miso, *_mosi, *_aux, FLASH_SPI_FREQ_HZ);
|
|
sys_pin_mode_spi_serial_flash(_clk_index, _miso_index, _mosi_index, _aux_index);
|
|
|
|
progress(0);
|
|
|
|
if (flash.init() != BD_ERROR_OK) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
|
|
uint32_t pos = 0;
|
|
uint8_t buf[256];
|
|
uint32_t prev_percent_done = 0;
|
|
const uint32_t total_size = flash.size();
|
|
while (pos < total_size) {
|
|
uint32_t read_size = total_size - pos;
|
|
if (read_size > sizeof(buf)) {
|
|
read_size = sizeof(buf);
|
|
}
|
|
if (flash.read(buf, pos, read_size) != BD_ERROR_OK) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
ssize_t write_size = dest->write(buf, read_size);
|
|
if ((uint32_t)write_size != read_size) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
pos += read_size;
|
|
|
|
const uint8_t percent_done = (pos * 100) / total_size;
|
|
if (percent_done != prev_percent_done) {
|
|
progress(percent_done);
|
|
prev_percent_done = percent_done;
|
|
}
|
|
}
|
|
|
|
progress(100);
|
|
|
|
sys_pin_mode_disabled();
|
|
return true;
|
|
}
|
|
|
|
bool MbedTester::firmware_update(mbed::FileHandle *src, mbed::Callback<void(uint8_t)> progress)
|
|
{
|
|
_update_control_pins();
|
|
|
|
if (!progress) {
|
|
progress = mbed::callback(dummy_progress);
|
|
}
|
|
|
|
// Mapping intentionally different from control channel to prevent
|
|
// unintentional activation (clk and mosi flipped)
|
|
MbedTesterBlockDevice flash(*_clk, *_miso, *_mosi, *_aux, FLASH_SPI_FREQ_HZ);
|
|
sys_pin_mode_spi_serial_flash(_clk_index, _miso_index, _mosi_index, _aux_index);
|
|
|
|
progress(0);
|
|
|
|
if (flash.init() != BD_ERROR_OK) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
|
|
// Validate file size
|
|
const uint32_t file_size = src->size();
|
|
if (file_size > FIRMWARE_REGION_SIZE) {
|
|
// Firmware image too big
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
if (file_size < LENGTH_SIZE + CRC_SIZE) {
|
|
// Firmware image too small
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
|
|
// Set the start of programming to the inactive bank
|
|
bool second_bank_active;
|
|
if (!_firmware_get_active_bank(flash, second_bank_active)) {
|
|
// Error determining active bank
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
const uint32_t start = FIRMWARE_HEADER_SIZE + (second_bank_active ? 0 : FIRMWARE_REGION_SIZE);
|
|
|
|
// Setup CRC calculation
|
|
uint32_t crc;
|
|
mbed::MbedCRC<POLY_32BIT_ANSI, 32> ct;
|
|
if (ct.compute_partial_start(&crc) != 0) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
|
|
uint8_t buf[256];
|
|
const bd_size_t erase_size = flash.get_erase_size();
|
|
uint32_t offset = 0;
|
|
uint32_t prev_percent_done = 0;
|
|
uint32_t stored_crc = 0;
|
|
bool size_valid = false;
|
|
while (offset < file_size) {
|
|
|
|
// Prepare data
|
|
uint32_t program_size = file_size - offset;
|
|
if (program_size > sizeof(buf)) {
|
|
program_size = sizeof(buf);
|
|
}
|
|
ssize_t read_size = src->read(buf, program_size);
|
|
if (read_size < 0) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
} else if (read_size == 0) {
|
|
break;
|
|
}
|
|
program_size = read_size;
|
|
|
|
// Record values and calculate checksum
|
|
uint32_t crc_offset = 0;
|
|
uint32_t crc_size = program_size;
|
|
if (offset == 0) {
|
|
// Overlap with the size field
|
|
|
|
// Check that the data length is correct
|
|
const size_t data_size = (buf[0] << (0 * 8)) |
|
|
(buf[1] << (1 * 8)) |
|
|
(buf[2] << (2 * 8)) |
|
|
(buf[3] << (3 * 8));
|
|
if (data_size != file_size - LENGTH_SIZE - CRC_SIZE) {
|
|
// Invalid data length
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
size_valid = true;
|
|
|
|
// Don't include the length in the checksum
|
|
crc_offset += LENGTH_SIZE;
|
|
crc_size -= LENGTH_SIZE;
|
|
}
|
|
if (offset + program_size > file_size - CRC_SIZE) {
|
|
// Overlap with the CRC field
|
|
for (uint32_t i = 0; i < CRC_SIZE; i++) {
|
|
uint32_t byte_offset = file_size - CRC_SIZE + i;
|
|
if ((byte_offset >= offset) && (byte_offset < offset + program_size)) {
|
|
uint32_t buf_pos = byte_offset - offset;
|
|
stored_crc |= buf[buf_pos] << (i * 8);
|
|
|
|
// Don't include the stored CRC in the CRC
|
|
crc_size--;
|
|
}
|
|
}
|
|
}
|
|
if (ct.compute_partial(buf + crc_offset, crc_size, &crc) != 0) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
|
|
// Write data to file
|
|
const uint32_t addr = start + offset;
|
|
if (addr % erase_size == 0) {
|
|
if (flash.erase(addr, erase_size) != BD_ERROR_OK) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
}
|
|
if (flash.program(buf, addr, read_size) != BD_ERROR_OK) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
|
|
offset += program_size;
|
|
|
|
const uint8_t percent_done = (offset * 100) / file_size;
|
|
if (percent_done != prev_percent_done) {
|
|
progress(percent_done);
|
|
prev_percent_done = percent_done;
|
|
}
|
|
}
|
|
|
|
// Check that everything was good and if so switch active bank
|
|
if (!size_valid) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
if (ct.compute_partial_stop(&crc) != 0) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
if (crc != stored_crc) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
if (!_firmware_set_active_bank(flash, !second_bank_active)) {
|
|
sys_pin_mode_disabled();
|
|
return false;
|
|
}
|
|
|
|
progress(100);
|
|
|
|
sys_pin_mode_disabled();
|
|
return true;
|
|
}
|
|
|
|
void MbedTester::pin_map_set(PinName physical, LogicalPin logical)
|
|
{
|
|
int index = _form_factor.index(physical);
|
|
if (index < 0) {
|
|
error("Pin %i not in form factor", physical);
|
|
return;
|
|
}
|
|
if (logical >= LogicalPinTotal) {
|
|
error("Invalid logical pin %i", logical);
|
|
return;
|
|
}
|
|
pin_map_index(index, logical);
|
|
}
|
|
|
|
void MbedTester::pin_map_reset()
|
|
{
|
|
for (uint32_t i = 0; i < sizeof(_mapping) / sizeof(_mapping[0]); i++) {
|
|
_mapping[i] = PHYSICAL_NC;
|
|
}
|
|
|
|
uint8_t pin_buf[PHYSICAL_PINS + LOGICAL_PINS];
|
|
memset(pin_buf, 0xFF, sizeof(pin_buf));
|
|
write(TESTER_REMAP, pin_buf, sizeof(pin_buf));
|
|
}
|
|
|
|
void MbedTester::peripherals_reset()
|
|
{
|
|
uint8_t buf = TESTER_CONTROL_RESET_PERIPHERALS;
|
|
write(TESTER_CONTROL_RESET, &buf, sizeof(buf));
|
|
}
|
|
|
|
void MbedTester::reset()
|
|
{
|
|
// Reset pullup settings
|
|
pin_pull_reset_all();
|
|
|
|
// Reset the FPGA
|
|
uint8_t buf = TESTER_CONTROL_RESET_ALL;
|
|
write(TESTER_CONTROL_RESET, &buf, sizeof(buf));
|
|
|
|
// Reset the pinmap
|
|
// NOTE - this is only needed for compatibility with
|
|
// older firmware which resets the mapping
|
|
// of all pins to 0x00 rather than 0xFF.
|
|
pin_map_reset();
|
|
|
|
// Reset internal state variables
|
|
_reset();
|
|
}
|
|
|
|
void MbedTester::reprogram()
|
|
{
|
|
// Trigger reprogramming
|
|
uint8_t buf = TESTER_CONTROL_REPROGRAM;
|
|
write(TESTER_CONTROL_RESET, &buf, sizeof(buf));
|
|
|
|
// Reset internal state variables
|
|
_reset();
|
|
}
|
|
|
|
uint32_t MbedTester::version()
|
|
{
|
|
uint32_t software_version;
|
|
|
|
read(TESTER_CONTROL_VERSION, (uint8_t *)&software_version, sizeof(software_version));
|
|
|
|
return software_version;
|
|
}
|
|
|
|
void MbedTester::select_peripheral(Peripheral peripheral)
|
|
{
|
|
uint8_t data = peripheral;
|
|
write(TESTER_PERIPHERAL_SELECT, &data, sizeof(data));
|
|
}
|
|
|
|
void MbedTester::pin_pull_reset_all()
|
|
{
|
|
_init_io_exp_rst_flag = 1;
|
|
sys_pin_write(I2CReset, 0, true);
|
|
wait_us(1);
|
|
sys_pin_write(I2CReset, 0, false);
|
|
}
|
|
|
|
int MbedTester::pin_set_pull(PinName pin, PullMode mode)
|
|
{
|
|
int index = _form_factor.index(pin);
|
|
if ((index < 0) || (index > 127)) {
|
|
error("Pin %i not in form factor", pin);
|
|
return -1;
|
|
}
|
|
|
|
return pin_set_pull_index(index, mode);
|
|
}
|
|
|
|
int MbedTester::pin_set_pull_index(int index, PullMode mode)
|
|
{
|
|
// Reset IO expanders once after Mbed reset if user attempts
|
|
// to read/write them without explicitly reseting them
|
|
if (_init_io_exp_rst_flag == 0) {
|
|
pin_pull_reset_all();
|
|
}
|
|
uint8_t chip_num;//can be 0-5
|
|
uint16_t dev_addr;//can be 0x44 or 0x46
|
|
uint8_t port_num;//can be 0-23
|
|
uint8_t output_port_reg;//can be 4, 5, or 6
|
|
uint8_t config_reg;//can be 12, 13, or 14
|
|
uint8_t reg_bit;//can be 0-7
|
|
uint8_t cmd0[2];//for writing configuration register
|
|
uint8_t cmd1[2];//for writing output port register
|
|
uint8_t i2c_index;//can be 0, 1, or 2 for TESTER_SYS_IO_MODE_I2C_IO_EXPANDER0/1/2
|
|
|
|
chip_num = index / 24;
|
|
if ((chip_num == 0) || (chip_num == 1)) {
|
|
i2c_index = 0;
|
|
} else if ((chip_num == 2) || (chip_num == 3)) {
|
|
i2c_index = 1;
|
|
} else if ((chip_num == 4) || (chip_num == 5)) {
|
|
i2c_index = 2;
|
|
} else {
|
|
error("Corrupt index %i, should be 0-127\r\n", index);
|
|
return -1;
|
|
}
|
|
dev_addr = (chip_num % 2) ? 0x44 : 0x46;
|
|
port_num = index % 24;
|
|
output_port_reg = 4 + (port_num / 8);
|
|
config_reg = 12 + (port_num / 8);
|
|
reg_bit = port_num % 8;
|
|
|
|
uint8_t read_config_byte[1];
|
|
uint8_t read_output_byte[1];
|
|
if (io_expander_i2c_read(i2c_index, dev_addr, config_reg, read_config_byte, 1) != 0) {
|
|
return -1;
|
|
}
|
|
if (io_expander_i2c_read(i2c_index, dev_addr, output_port_reg, read_output_byte, 1) != 0) {
|
|
return -1;
|
|
}
|
|
cmd0[0] = config_reg;
|
|
if ((mode == PullDown) || (mode == PullUp)) {
|
|
cmd0[1] = read_config_byte[0] & ~(1 << reg_bit);
|
|
cmd1[0] = output_port_reg;
|
|
if (mode == PullDown) {
|
|
cmd1[1] = read_output_byte[0] & ~(1 << reg_bit);
|
|
} else if (mode == PullUp) {
|
|
cmd1[1] = read_output_byte[0] | (1 << reg_bit);
|
|
}
|
|
} else if (mode == PullNone) {
|
|
cmd0[1] = read_config_byte[0] | (1 << reg_bit);
|
|
}
|
|
|
|
//write configuration register for all 3 modes
|
|
if (io_expander_i2c_write(i2c_index, dev_addr, cmd0, 2) != 0) {
|
|
return -1;
|
|
}
|
|
//only write output register for pulldown and pullup
|
|
if ((mode == PullDown) || (mode == PullUp)) {
|
|
if (io_expander_i2c_write(i2c_index, dev_addr, cmd1, 2) != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint8_t MbedTester::io_expander_read(PinName pin, IOExpanderReg reg_type)
|
|
{
|
|
int index = _form_factor.index(pin);
|
|
|
|
return io_expander_read_index(index, reg_type);
|
|
}
|
|
|
|
uint8_t MbedTester::io_expander_read_index(int index, IOExpanderReg reg_type)
|
|
{
|
|
// Reset IO expanders once after Mbed reset if user attempts
|
|
// to read/write them without explicitly reseting them
|
|
if (_init_io_exp_rst_flag == 0) {
|
|
pin_pull_reset_all();
|
|
}
|
|
uint8_t read_byte[1] = {0};
|
|
uint8_t chip_num;//can be 0-5
|
|
uint16_t dev_addr;//can be 0x44 or 0x46
|
|
uint8_t port_num;//can be 0-23
|
|
uint8_t input_port_reg;//can be 0, 1, or 2
|
|
uint8_t output_port_reg;//can be 4, 5, or 6
|
|
uint8_t config_reg;//can be 12, 13, or 14
|
|
uint8_t reg_bit;//can be 0-7
|
|
uint8_t i2c_index;
|
|
|
|
chip_num = index / 24;
|
|
if ((chip_num == 0) || (chip_num == 1)) {
|
|
i2c_index = 0;
|
|
} else if ((chip_num == 2) || (chip_num == 3)) {
|
|
i2c_index = 1;
|
|
} else if ((chip_num == 4) || (chip_num == 5)) {
|
|
i2c_index = 2;
|
|
} else {
|
|
i2c_index = 0xFF;
|
|
error("Invalid pin index, index should be in the range of 0-127");
|
|
}
|
|
dev_addr = (chip_num % 2) ? 0x44 : 0x46;
|
|
port_num = index % 24;
|
|
input_port_reg = (port_num / 8);
|
|
output_port_reg = 4 + (port_num / 8);
|
|
config_reg = 12 + (port_num / 8);
|
|
reg_bit = port_num % 8;
|
|
uint8_t reg;
|
|
if (reg_type == RegInput) {
|
|
reg = input_port_reg;
|
|
} else if (reg_type == RegOutput) {
|
|
reg = output_port_reg;
|
|
} else if (reg_type == RegConfig) {
|
|
reg = config_reg;
|
|
} else {
|
|
reg = 0xFF;
|
|
error("Invalid register type, should be: INPUT, OUTPUT, or RegConfig");
|
|
}
|
|
|
|
int read_success = io_expander_i2c_read(i2c_index, dev_addr, reg, read_byte, 1);
|
|
MBED_ASSERT(read_success == 0);
|
|
uint8_t bit = (read_byte[0] & (1 << reg_bit)) >> reg_bit;
|
|
return bit;
|
|
}
|
|
|
|
int MbedTester::io_expander_i2c_read(uint8_t i2c_index, uint8_t dev_addr, uint8_t start_reg, uint8_t *data, int length)
|
|
{
|
|
_update_control_pins();
|
|
//sda_in = _miso_index
|
|
//sda_val = _aux_index
|
|
//scl_in = _mosi_index (PHYSICAL_NC)
|
|
//scl_val = _clk_index
|
|
mbed::DigitalInOut *sda_in = _miso;
|
|
mbed::DigitalInOut *sda_val = _aux;
|
|
mbed::DigitalInOut *scl_val = _clk;
|
|
sda_in->input();
|
|
sda_val->output();
|
|
*sda_val = 1;
|
|
scl_val->output();
|
|
sys_pin_mode_i2c_io_expander(i2c_index, _miso_index, _aux_index, PHYSICAL_NC, _clk_index);
|
|
|
|
//start condition
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*sda_val = 0;
|
|
wait_ns(2500);
|
|
|
|
// begin writing data, dev_addr first
|
|
uint8_t send_bit;
|
|
for (int j = 0; j < 2; j += 1) {
|
|
*scl_val = 0;
|
|
*sda_val = 0;
|
|
wait_ns(2500);
|
|
for (int i = 7; i > -1; i -= 1) {
|
|
if (j == 0) {
|
|
send_bit = (dev_addr & (1 << i)) >> i;
|
|
} else {
|
|
send_bit = (start_reg & (1 << i)) >> i;
|
|
}
|
|
*sda_val = send_bit;
|
|
wait_ns(500);
|
|
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*scl_val = 0;
|
|
wait_ns(1000);
|
|
*sda_val = 0;
|
|
wait_ns(1000);
|
|
}
|
|
// receive ACK from IO extender
|
|
*sda_val = 1;//make sda high z to receive ACK
|
|
//clk the ACK
|
|
*scl_val = 1;
|
|
//read sda to check for ACK or NACK
|
|
if (*sda_in) {
|
|
return -1;//NACK - write failed
|
|
}
|
|
wait_ns(2500);
|
|
*scl_val = 0;
|
|
wait_ns(2500);
|
|
}
|
|
|
|
//start condition
|
|
*sda_val = 1;
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*sda_val = 0;
|
|
wait_ns(2500);
|
|
|
|
// begin reading data, write (dev_addr | 1) first
|
|
dev_addr |= 1;
|
|
for (int j = -1; j < length; j += 1) {
|
|
uint8_t read_byte = 0;
|
|
for (int i = 7; i > -1; i -= 1) {
|
|
if (j == -1) {
|
|
*scl_val = 0;
|
|
*sda_val = 0;
|
|
send_bit = (dev_addr & (1 << i)) >> i;
|
|
*sda_val = send_bit;
|
|
wait_ns(500);
|
|
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*scl_val = 0;
|
|
wait_ns(1000);
|
|
*sda_val = 0;
|
|
wait_ns(1000);
|
|
} else {
|
|
*scl_val = 1;
|
|
read_byte |= (*sda_in << i);
|
|
wait_ns(2500);
|
|
*scl_val = 0;
|
|
wait_ns(2500);
|
|
}
|
|
}
|
|
if (j > -1) {
|
|
data[j] = read_byte;
|
|
}
|
|
if (j == -1) {
|
|
// receive ACK from IO extender
|
|
*sda_val = 1;//make sda high z to receive ACK
|
|
//clk the ACK
|
|
*scl_val = 1;
|
|
//read sda to check for ACK or NACK
|
|
if (*sda_in) {
|
|
return -1;//NACK - write failed
|
|
}
|
|
wait_ns(2500);
|
|
*scl_val = 0;
|
|
wait_ns(2500);
|
|
} else {
|
|
if (j == (length - 1)) { //NACK to signal end of read
|
|
*sda_val = 1;
|
|
wait_ns(1000);
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*scl_val = 0;
|
|
wait_ns(1500);
|
|
} else {//ACK to signal read will continue
|
|
*sda_val = 0;
|
|
wait_ns(1000);
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*scl_val = 0;
|
|
wait_ns(500);
|
|
*sda_val = 1;
|
|
wait_ns(1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
//stop condition
|
|
*sda_val = 0;
|
|
wait_ns(2500);
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*sda_val = 1;
|
|
wait_ns(2500);
|
|
|
|
sys_pin_mode_disabled();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int MbedTester::io_expander_i2c_write(uint8_t i2c_index, uint8_t dev_addr, uint8_t *data, int length)
|
|
{
|
|
_update_control_pins();
|
|
//sda_in = _miso_index
|
|
//sda_val = _aux_index
|
|
//scl_in = _mosi_index (PHYSICAL_NC)
|
|
//scl_val = _clk_index
|
|
mbed::DigitalInOut *sda_in = _miso;
|
|
mbed::DigitalInOut *sda_val = _aux;
|
|
mbed::DigitalInOut *scl_val = _clk;
|
|
sda_in->input();
|
|
sda_val->output();
|
|
*sda_val = 1;
|
|
scl_val->output();
|
|
sys_pin_mode_i2c_io_expander(i2c_index, _miso_index, _aux_index, PHYSICAL_NC, _clk_index);
|
|
|
|
//start condition
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*sda_val = 0;
|
|
wait_ns(2500);
|
|
|
|
// begin writing data, dev_addr first
|
|
uint8_t send_bit;
|
|
for (int j = -1; j < length; j += 1) {
|
|
*scl_val = 0;
|
|
*sda_val = 0;
|
|
for (int i = 7; i > -1; i -= 1) {
|
|
if (j == -1) {
|
|
send_bit = (dev_addr & (1 << i)) >> i;
|
|
} else {
|
|
send_bit = (data[j] & (1 << i)) >> i;
|
|
}
|
|
|
|
*sda_val = send_bit;
|
|
wait_ns(500);
|
|
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*scl_val = 0;
|
|
wait_ns(1000);
|
|
*sda_val = 0;
|
|
wait_ns(1000);
|
|
}
|
|
// receive ACK from IO extender
|
|
*sda_val = 1;//make sda high z to receive ACK
|
|
//clk the ACK
|
|
*scl_val = 1;
|
|
//read sda to check for ACK or NACK
|
|
if (*sda_in) {
|
|
return -1;//NACK - write failed
|
|
}
|
|
wait_ns(2500);
|
|
*scl_val = 0;
|
|
wait_ns(2500);
|
|
}
|
|
|
|
//stop condition
|
|
*sda_val = 0;
|
|
wait_ns(2500);
|
|
*scl_val = 1;
|
|
wait_ns(2500);
|
|
*sda_val = 1;
|
|
wait_ns(2500);
|
|
|
|
sys_pin_mode_disabled();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int MbedTester::pin_set_pull_bb(PinName pin, PullMode mode)
|
|
{
|
|
int index = _form_factor.index(pin);
|
|
if ((index < 0) || (index > 127)) {
|
|
error("Pin %i not in form factor", pin);
|
|
return -1;
|
|
}
|
|
uint8_t chip_num;//can be 0-5
|
|
SystemPin sda;//can be I2CSda0, I2CSda1, or I2CSda2
|
|
SystemPin scl;//can be I2CScl0, I2CScl1, or I2CScl2
|
|
uint16_t dev_addr;//can be 0x44 or 0x46
|
|
uint8_t port_num;//can be 0-23
|
|
uint8_t output_port_reg;//can be 4, 5, or 6
|
|
uint8_t config_reg;//can be 12, 13, or 14
|
|
uint8_t reg_bit;//can be 0-7
|
|
uint8_t cmd0[2];//for writing configuration register
|
|
uint8_t cmd1[2];//for writing output port register
|
|
|
|
chip_num = index / 24;
|
|
if ((chip_num == 0) || (chip_num == 1)) {
|
|
sda = I2CSda0;
|
|
scl = I2CScl0;
|
|
} else if ((chip_num == 2) || (chip_num == 3)) {
|
|
sda = I2CSda1;
|
|
scl = I2CScl1;
|
|
} else if ((chip_num == 4) || (chip_num == 5)) {
|
|
sda = I2CSda2;
|
|
scl = I2CScl2;
|
|
} else {
|
|
error("Pin %i not in form factor", pin);
|
|
return -1;
|
|
}
|
|
dev_addr = (chip_num % 2) ? 0x44 : 0x46;
|
|
port_num = index % 24;
|
|
output_port_reg = 4 + (port_num / 8);
|
|
config_reg = 12 + (port_num / 8);
|
|
reg_bit = port_num % 8;
|
|
|
|
uint8_t read_config_byte[1];
|
|
uint8_t read_output_byte[1];
|
|
if (io_expander_i2c_read_bb(sda, scl, dev_addr, config_reg, read_config_byte, 1) != 0) {
|
|
return -1;
|
|
}
|
|
if (io_expander_i2c_read_bb(sda, scl, dev_addr, output_port_reg, read_output_byte, 1) != 0) {
|
|
return -1;
|
|
}
|
|
cmd0[0] = config_reg;
|
|
if ((mode == PullDown) || (mode == PullUp)) {
|
|
cmd0[1] = read_config_byte[0] & ~(1 << reg_bit);
|
|
cmd1[0] = output_port_reg;
|
|
if (mode == PullDown) {
|
|
cmd1[1] = read_output_byte[0] & ~(1 << reg_bit);
|
|
} else if (mode == PullUp) {
|
|
cmd1[1] = read_output_byte[0] | (1 << reg_bit);
|
|
}
|
|
} else if (mode == PullNone) {
|
|
cmd0[1] = read_config_byte[0] | (1 << reg_bit);
|
|
}
|
|
|
|
//write configuration register for all 3 modes
|
|
if (io_expander_i2c_write_bb(sda, scl, dev_addr, cmd0, 2) != 0) {
|
|
return -1;
|
|
}
|
|
//only write output register for pulldown and pullup
|
|
if ((mode == PullDown) || (mode == PullUp)) {
|
|
if (io_expander_i2c_write_bb(sda, scl, dev_addr, cmd1, 2) != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint8_t MbedTester::io_expander_read_bb(PinName pin, IOExpanderReg reg_type)
|
|
{
|
|
int index = _form_factor.index(pin);
|
|
uint8_t read_byte[1] = {0};
|
|
uint8_t chip_num;//can be 0-5
|
|
SystemPin sda;//can be I2CSda0, I2CSda1, or I2CSda2
|
|
SystemPin scl;//can be I2CScl0, I2CScl1, or I2CScl2
|
|
uint16_t dev_addr;//can be 0x44 or 0x46
|
|
uint8_t port_num;//can be 0-23
|
|
uint8_t input_port_reg;//can be 0, 1, or 2
|
|
uint8_t output_port_reg;//can be 4, 5, or 6
|
|
uint8_t config_reg;//can be 12, 13, or 14
|
|
uint8_t reg_bit;//can be 0-7
|
|
|
|
chip_num = index / 24;
|
|
if ((chip_num == 0) || (chip_num == 1)) {
|
|
sda = I2CSda0;
|
|
scl = I2CScl0;
|
|
} else if ((chip_num == 2) || (chip_num == 3)) {
|
|
sda = I2CSda1;
|
|
scl = I2CScl1;
|
|
} else if ((chip_num == 4) || (chip_num == 5)) {
|
|
sda = I2CSda2;
|
|
scl = I2CScl2;
|
|
} else {
|
|
sda = (SystemPin) - 1;
|
|
scl = (SystemPin) - 1;
|
|
error("Invalid pin index, index should be in the range of 0-127");
|
|
}
|
|
|
|
dev_addr = (chip_num % 2) ? 0x44 : 0x46;
|
|
port_num = index % 24;
|
|
input_port_reg = (port_num / 8);
|
|
output_port_reg = 4 + (port_num / 8);
|
|
config_reg = 12 + (port_num / 8);
|
|
reg_bit = port_num % 8;
|
|
uint8_t reg;
|
|
if (reg_type == RegInput) {
|
|
reg = input_port_reg;
|
|
} else if (reg_type == RegOutput) {
|
|
reg = output_port_reg;
|
|
} else if (reg_type == RegConfig) {
|
|
reg = config_reg;
|
|
} else {
|
|
reg = 0xFF;
|
|
error("Invalid register type, should be: INPUT, OUTPUT, or CONFIG");
|
|
}
|
|
|
|
int read_success = io_expander_i2c_read_bb(sda, scl, dev_addr, reg, read_byte, 1);
|
|
MBED_ASSERT(read_success == 0);
|
|
uint8_t bit = (read_byte[0] & (1 << reg_bit)) >> reg_bit;
|
|
return bit;
|
|
}
|
|
|
|
int MbedTester::io_expander_i2c_read_bb(SystemPin sda, SystemPin scl, uint8_t dev_addr, uint8_t start_reg, uint8_t *data, int length)
|
|
{
|
|
//start condition
|
|
sys_pin_write(sda, 0, false);
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(sda, 0, true);
|
|
|
|
// begin writing data, dev_addr first
|
|
uint8_t send_bit;
|
|
for (int j = 0; j < 2; j += 1) {
|
|
sys_pin_write(scl, 0, true);
|
|
sys_pin_write(sda, 0, true);
|
|
for (int i = 7; i > -1; i -= 1) {
|
|
if (j == 0) {
|
|
send_bit = (dev_addr & (1 << i)) >> i;
|
|
} else {
|
|
send_bit = (start_reg & (1 << i)) >> i;
|
|
}
|
|
if (send_bit == 1) {
|
|
sys_pin_write(sda, 0, false);
|
|
} else if (send_bit == 0) {
|
|
sys_pin_write(sda, 0, true);
|
|
}
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(scl, 0, true);
|
|
sys_pin_write(sda, 0, true);
|
|
}
|
|
// receive ACK from IO extender
|
|
sys_pin_write(sda, 0, false);//make sda high z to receive ACK
|
|
//clk the ACK
|
|
sys_pin_write(scl, 0, false);
|
|
//read sda to check for ACK or NACK
|
|
if (sys_pin_read(sda)) {
|
|
return -1;//NACK - write failed
|
|
}
|
|
sys_pin_write(scl, 0, true);
|
|
}
|
|
|
|
//start condition
|
|
sys_pin_write(sda, 0, false);
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(sda, 0, true);
|
|
|
|
// begin reading data, write (dev_addr | 1) first
|
|
dev_addr |= 1;
|
|
for (int j = -1; j < length; j += 1) {
|
|
uint8_t read_byte = 0;
|
|
for (int i = 7; i > -1; i -= 1) {
|
|
if (j == -1) {
|
|
sys_pin_write(scl, 0, true);
|
|
sys_pin_write(sda, 0, true);
|
|
send_bit = (dev_addr & (1 << i)) >> i;
|
|
if (send_bit == 1) {
|
|
sys_pin_write(sda, 0, false);
|
|
} else if (send_bit == 0) {
|
|
sys_pin_write(sda, 0, true);
|
|
}
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(scl, 0, true);
|
|
sys_pin_write(sda, 0, true);
|
|
} else {
|
|
sys_pin_write(scl, 0, false);
|
|
read_byte |= (sys_pin_read(sda) << i);
|
|
sys_pin_write(scl, 0, true);
|
|
}
|
|
}
|
|
if (j > -1) {
|
|
data[j] = read_byte;
|
|
}
|
|
if (j == -1) {
|
|
// receive ACK from IO extender
|
|
sys_pin_write(sda, 0, false);//make sda high z to receive ACK
|
|
//clk the ACK
|
|
sys_pin_write(scl, 0, false);
|
|
//read sda to check for ACK or NACK
|
|
if (sys_pin_read(sda)) {
|
|
return -1;//NACK - write failed
|
|
}
|
|
sys_pin_write(scl, 0, true);
|
|
} else {
|
|
if (j == (length - 1)) { //NACK to signal end of read
|
|
sys_pin_write(sda, 0, false);
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(scl, 0, true);
|
|
} else {//ACK to signal read will continue
|
|
sys_pin_write(sda, 0, true);
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(scl, 0, true);
|
|
sys_pin_write(sda, 0, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
//stop condition
|
|
sys_pin_write(sda, 0, true);
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(sda, 0, false);
|
|
return 0;
|
|
}
|
|
|
|
int MbedTester::io_expander_i2c_write_bb(SystemPin sda, SystemPin scl, uint8_t dev_addr, uint8_t *data, int length)
|
|
{
|
|
//start condition
|
|
sys_pin_write(sda, 0, false);
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(sda, 0, true);
|
|
|
|
// begin writing data, dev_addr first
|
|
uint8_t send_bit;
|
|
for (int j = -1; j < length; j += 1) {
|
|
sys_pin_write(scl, 0, true);
|
|
sys_pin_write(sda, 0, true);
|
|
for (int i = 7; i > -1; i -= 1) {
|
|
if (j == -1) {
|
|
send_bit = (dev_addr & (1 << i)) >> i;
|
|
} else {
|
|
send_bit = (data[j] & (1 << i)) >> i;
|
|
}
|
|
if (send_bit == 1) {
|
|
sys_pin_write(sda, 0, false);
|
|
} else if (send_bit == 0) {
|
|
sys_pin_write(sda, 0, true);
|
|
}
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(scl, 0, true);
|
|
sys_pin_write(sda, 0, true);
|
|
}
|
|
// receive ACK from IO extender
|
|
sys_pin_write(sda, 0, false);//make sda high z to receive ACK
|
|
//clk the ACK
|
|
sys_pin_write(scl, 0, false);
|
|
//read sda to check for ACK or NACK
|
|
if (sys_pin_read(sda)) {
|
|
return -1;//NACK - write failed
|
|
}
|
|
sys_pin_write(scl, 0, true);
|
|
}
|
|
|
|
//stop condition
|
|
sys_pin_write(sda, 0, true);
|
|
sys_pin_write(scl, 0, false);
|
|
sys_pin_write(sda, 0, false);
|
|
return 0;
|
|
}
|
|
|
|
void MbedTester::set_analog_out(bool enable, float voltage)
|
|
{
|
|
uint32_t cycles_high = (int)(100 * voltage);
|
|
uint32_t period = 100;
|
|
set_pwm_period_and_cycles_high(period, cycles_high);
|
|
set_pwm_enable(enable);
|
|
}
|
|
|
|
int MbedTester::set_mux_addr(PinName pin)
|
|
{
|
|
int index = _form_factor.index(pin);
|
|
if ((index < 0) || (index > 127)) {
|
|
error("Pin %i not in form factor", pin);
|
|
return -1;
|
|
}
|
|
|
|
return set_mux_addr_index(index);
|
|
}
|
|
|
|
int MbedTester::set_mux_addr_index(int index)
|
|
{
|
|
sys_pin_write(AnalogMuxAddr0, index & 0x01, true);
|
|
sys_pin_write(AnalogMuxAddr1, (index & 0x02) >> 1, true);
|
|
sys_pin_write(AnalogMuxAddr2, (index & 0x04) >> 2, true);
|
|
sys_pin_write(AnalogMuxAddr3, (index & 0x08) >> 3, true);
|
|
sys_pin_write(AnalogMuxAddr4, (index & 0x10) >> 4, true);
|
|
sys_pin_write(AnalogMuxAddr5, (index & 0x20) >> 5, true);
|
|
sys_pin_write(AnalogMuxAddr6, (index & 0x40) >> 6, true);
|
|
sys_pin_write(AnalogMuxAddr7, (index & 0x80) >> 7, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MbedTester::set_mux_enable(bool val)
|
|
{
|
|
if (val == true) {
|
|
sys_pin_write(AnalogMuxEnable, 0, true);//enable analog MUXes
|
|
} else if (val == false) {
|
|
sys_pin_write(AnalogMuxEnable, 1, true);//disable analog MUXes
|
|
}
|
|
wait_us(10);
|
|
}
|
|
|
|
void MbedTester::set_pwm_enable(bool val)
|
|
{
|
|
uint8_t data;
|
|
if (val == true) {
|
|
data = 1;
|
|
} else if (val == false) {
|
|
data = 0;
|
|
}
|
|
write(TESTER_SYS_IO_PWM_ENABLE, &data, sizeof(data));
|
|
}
|
|
|
|
bool MbedTester::get_pwm_enable()
|
|
{
|
|
uint8_t val = 0;
|
|
read(TESTER_SYS_IO_PWM_ENABLE, &val, sizeof(val));
|
|
if (val == 1) {
|
|
return true;
|
|
} else if (val == 0) {
|
|
return false;
|
|
} else {
|
|
error("Corrupt pwm enable value");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void MbedTester::set_pwm_period_and_cycles_high(uint32_t period, uint32_t cycles_high)
|
|
{
|
|
set_pwm_enable(false);
|
|
uint32_t p = period - 1;//period in cycles
|
|
uint32_t d = cycles_high;//number of cycles pwm out is high
|
|
write(TESTER_SYS_IO_PWM_PERIOD, (uint8_t *)&p, sizeof(p));
|
|
write(TESTER_SYS_IO_PWM_CYCLES_HIGH, (uint8_t *)&d, sizeof(d));
|
|
set_pwm_enable(true);
|
|
}
|
|
|
|
uint32_t MbedTester::get_pwm_period()
|
|
{
|
|
uint32_t period = 0;
|
|
read(TESTER_SYS_IO_PWM_PERIOD, (uint8_t *)&period, sizeof(period));
|
|
return period + 1;//clk cycles
|
|
}
|
|
|
|
uint8_t MbedTester::get_pwm_cycles_high()
|
|
{
|
|
uint8_t cycles_high = 0;
|
|
read(TESTER_SYS_IO_PWM_CYCLES_HIGH, &cycles_high, sizeof(cycles_high));
|
|
return cycles_high;
|
|
}
|
|
|
|
uint16_t MbedTester::get_analogmuxin_measurement()
|
|
{
|
|
rtos::ThisThread::sleep_for(1);//wait for value to stabalize
|
|
//take snapshot of conversion value to make safe for reading
|
|
set_snapshot();
|
|
uint16_t an_mux_analogin_measurement = 0;
|
|
read(TESTER_SYS_IO_AN_MUX_ANALOGIN_MEASUREMENT, (uint8_t *)&an_mux_analogin_measurement, sizeof(an_mux_analogin_measurement));
|
|
return an_mux_analogin_measurement;
|
|
}
|
|
|
|
uint16_t MbedTester::get_anin_measurement(int index)
|
|
{
|
|
//check index is in bounds
|
|
if ((index < 0) || (index >= ANALOG_COUNT)) {
|
|
error("AnalogIn index is out of bounds");
|
|
}
|
|
//take snapshot of conversion value to make safe for reading
|
|
set_snapshot();
|
|
uint16_t anin_measurement = 0;
|
|
read((TESTER_SYS_IO_ANIN0_MEASUREMENT + (index * 10)), (uint8_t *)&anin_measurement, sizeof(anin_measurement)); //10 because sizeof measurement + sizeof measurements_sum = 10
|
|
return anin_measurement;
|
|
}
|
|
|
|
void MbedTester::get_anin_sum_samples_cycles(int index, uint64_t *sum, uint32_t *samples, uint64_t *cycles)
|
|
{
|
|
//check index is in bounds
|
|
if ((index < 0) || (index >= ANALOG_COUNT)) {
|
|
error("AnalogIn index is out of bounds");
|
|
}
|
|
//take snapshot of the sum/samples/cycles so that all 3 values are correct in relation to each other
|
|
set_snapshot();
|
|
read((TESTER_SYS_IO_ANIN0_MEASUREMENTS_SUM + (index * 10)), (uint8_t *)sum, sizeof(*sum)); //10 because sizeof measurement + sizeof measurements_sum = 10
|
|
read(TESTER_SYS_IO_NUM_POWER_SAMPLES, (uint8_t *)samples, sizeof(*samples));
|
|
read(TESTER_SYS_IO_NUM_POWER_CYCLES, (uint8_t *)cycles, sizeof(*cycles));
|
|
}
|
|
|
|
void MbedTester::set_snapshot()
|
|
{
|
|
uint8_t data = 1;
|
|
write(TESTER_SYS_IO_ADC_SNAPSHOT, &data, sizeof(data));
|
|
wait_us(1);
|
|
}
|
|
|
|
void MbedTester::set_sample_adc(bool val)
|
|
{
|
|
uint8_t data;
|
|
if (val == true) {
|
|
data = 1;
|
|
} else if (val == false) {
|
|
data = 0;
|
|
}
|
|
write(TESTER_SYS_IO_SAMPLE_ADC, &data, sizeof(data));
|
|
}
|
|
|
|
float MbedTester::get_analog_in()
|
|
{
|
|
uint16_t data = get_analogmuxin_measurement();
|
|
float data_f = (float)data / 4095.0f;
|
|
return data_f;
|
|
}
|
|
|
|
float MbedTester::get_anin_voltage(int index)
|
|
{
|
|
uint16_t data = get_anin_measurement(index);
|
|
float data_f = (float)data / 4095.0f;
|
|
return data_f;
|
|
}
|
|
|
|
int MbedTester::gpio_read(LogicalPin gpio)
|
|
{
|
|
if (gpio >= LogicalPinCount) {
|
|
error("Invalid pin for gpio_read");
|
|
return 0;
|
|
}
|
|
uint8_t data = 0;
|
|
read(TESTER_GPIO + gpio, &data, sizeof(data));
|
|
return data;
|
|
}
|
|
|
|
void MbedTester::gpio_write(LogicalPin gpio, int value, bool drive)
|
|
{
|
|
if (gpio >= LogicalPinCount) {
|
|
error("Invalid pin for gpio_write");
|
|
return;
|
|
}
|
|
uint8_t data = 0;
|
|
data |= value ? (1 << 0) : 0;
|
|
data |= drive ? (1 << 1) : 0;
|
|
write(TESTER_GPIO + gpio, &data, sizeof(data));
|
|
}
|
|
|
|
void MbedTester::io_metrics_start()
|
|
{
|
|
uint8_t data = TESTER_IO_METRICS_CTRL_RESET_BIT;
|
|
write(TESTER_IO_METRICS_CTRL, &data, sizeof(data));
|
|
|
|
data = TESTER_IO_METRICS_CTRL_ACTIVE_BIT;
|
|
write(TESTER_IO_METRICS_CTRL, &data, sizeof(data));
|
|
}
|
|
|
|
void MbedTester::io_metrics_stop()
|
|
{
|
|
uint8_t data = 0;
|
|
write(TESTER_IO_METRICS_CTRL, &data, sizeof(data));
|
|
}
|
|
|
|
void MbedTester::io_metrics_continue()
|
|
{
|
|
uint8_t data = TESTER_IO_METRICS_CTRL_ACTIVE_BIT;
|
|
write(TESTER_IO_METRICS_CTRL, &data, sizeof(data));
|
|
}
|
|
|
|
uint32_t MbedTester::io_metrics_min_pulse_low(LogicalPin pin)
|
|
{
|
|
if (pin >= LogicalPinCount) {
|
|
error("Invalid pin for io_metrics");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t data = 0;
|
|
read(TESTER_IO_METRICS_MIN_PULSE_LOW(pin), (uint8_t *)&data, sizeof(data));
|
|
return data;
|
|
}
|
|
|
|
uint32_t MbedTester::io_metrics_min_pulse_high(LogicalPin pin)
|
|
{
|
|
if (pin >= LogicalPinCount) {
|
|
error("Invalid pin for io_metrics");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t data = 0;
|
|
read(TESTER_IO_METRICS_MIN_PULSE_HIGH(pin), (uint8_t *)&data, sizeof(data));
|
|
return data;
|
|
}
|
|
|
|
uint32_t MbedTester::io_metrics_max_pulse_low(LogicalPin pin)
|
|
{
|
|
if (pin >= LogicalPinCount) {
|
|
error("Invalid pin for io_metrics");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t data = 0;
|
|
read(TESTER_IO_METRICS_MAX_PULSE_LOW(pin), (uint8_t *)&data, sizeof(data));
|
|
return data;
|
|
}
|
|
|
|
uint32_t MbedTester::io_metrics_max_pulse_high(LogicalPin pin)
|
|
{
|
|
if (pin >= LogicalPinCount) {
|
|
error("Invalid pin for io_metrics");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t data = 0;
|
|
read(TESTER_IO_METRICS_MAX_PULSE_HIGH(pin), (uint8_t *)&data, sizeof(data));
|
|
return data;
|
|
}
|
|
|
|
uint32_t MbedTester::io_metrics_rising_edges(LogicalPin pin)
|
|
{
|
|
if (pin >= LogicalPinCount) {
|
|
error("Invalid pin for io_metrics");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t data = 0;
|
|
read(TESTER_IO_METRICS_RISING_EDGES(pin), (uint8_t *)&data, sizeof(data));
|
|
return data;
|
|
}
|
|
|
|
uint32_t MbedTester::io_metrics_falling_edges(LogicalPin pin)
|
|
{
|
|
if (pin >= LogicalPinCount) {
|
|
error("Invalid pin for io_metrics");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t data = 0;
|
|
read(TESTER_IO_METRICS_FALLING_EDGES(pin), (uint8_t *)&data, sizeof(data));
|
|
return data;
|
|
}
|
|
|
|
bool MbedTester::sys_pin_read(SystemPin pin)
|
|
{
|
|
|
|
if (pin >= SystemPinCount) {
|
|
error("Invalid pin for gpio_read");
|
|
return 0;
|
|
}
|
|
uint8_t data = 0;
|
|
read(TESTER_SYS_IO + pin, &data, sizeof(data));
|
|
return data;
|
|
}
|
|
|
|
void MbedTester::sys_pin_write(SystemPin pin, int value, bool drive)
|
|
{
|
|
if (pin >= SystemPinCount) {
|
|
error("Invalid pin for gpio_write");
|
|
return;
|
|
}
|
|
uint8_t data = 0;
|
|
data |= value ? (1 << 0) : 0;
|
|
data |= drive ? (1 << 1) : 0;
|
|
write(TESTER_SYS_IO + pin, &data, sizeof(data));
|
|
}
|
|
|
|
void MbedTester::sys_pin_mode_disabled()
|
|
{
|
|
const uint32_t base = LogicalPinTotal;
|
|
|
|
pin_map_index(PHYSICAL_NC, (LogicalPin)(base + 0));
|
|
pin_map_index(PHYSICAL_NC, (LogicalPin)(base + 1));
|
|
pin_map_index(PHYSICAL_NC, (LogicalPin)(base + 2));
|
|
pin_map_index(PHYSICAL_NC, (LogicalPin)(base + 3));
|
|
|
|
uint8_t mode = TESTER_SYS_IO_MODE_DISABLED;
|
|
write(TESTER_SYS_IO_MODE, &mode, sizeof(mode));
|
|
}
|
|
|
|
void MbedTester::sys_pin_mode_spi_serial_flash(PhysicalIndex mosi, PhysicalIndex miso, PhysicalIndex clk, PhysicalIndex ssel)
|
|
{
|
|
const uint32_t base = LogicalPinTotal;
|
|
|
|
pin_map_index(mosi, (LogicalPin)(base + 0));
|
|
pin_map_index(miso, (LogicalPin)(base + 1));
|
|
pin_map_index(clk, (LogicalPin)(base + 2));
|
|
pin_map_index(ssel, (LogicalPin)(base + 3));
|
|
|
|
uint8_t mode = TESTER_SYS_IO_MODE_SPI_SERIAL_FLASH;
|
|
write(TESTER_SYS_IO_MODE, &mode, sizeof(mode));
|
|
}
|
|
|
|
void MbedTester::sys_pin_mode_i2c_io_expander(int index, PhysicalIndex sda_in, PhysicalIndex sda_val, PhysicalIndex scl_in, PhysicalIndex scl_val)
|
|
{
|
|
const uint32_t base = LogicalPinTotal;
|
|
|
|
pin_map_index(sda_in, (LogicalPin)(base + 0));
|
|
pin_map_index(sda_val, (LogicalPin)(base + 1));
|
|
pin_map_index(scl_in, (LogicalPin)(base + 2));
|
|
pin_map_index(scl_val, (LogicalPin)(base + 3));
|
|
|
|
uint8_t mode = 0;
|
|
if (index == 0) {
|
|
mode = TESTER_SYS_IO_MODE_I2C_IO_EXPANDER0;
|
|
} else if (index == 1) {
|
|
mode = TESTER_SYS_IO_MODE_I2C_IO_EXPANDER1;
|
|
} else if (index == 2) {
|
|
mode = TESTER_SYS_IO_MODE_I2C_IO_EXPANDER2;
|
|
} else {
|
|
error("Invalid index for sys_pin_mode_i2c_io_expander");
|
|
}
|
|
|
|
write(TESTER_SYS_IO_MODE, &mode, sizeof(mode));
|
|
}
|
|
|
|
void MbedTester::pin_map_index(PhysicalIndex physical_index, LogicalPin logical)
|
|
{
|
|
uint8_t remap;
|
|
if ((physical_index >= PHYSICAL_PINS) && (physical_index != PHYSICAL_NC)) {
|
|
error("Invalid physical pin index %i", physical_index);
|
|
return;
|
|
}
|
|
if (logical >= sizeof(_mapping) / sizeof(_mapping[0])) {
|
|
error("Invalid logical pin %i", logical);
|
|
return;
|
|
}
|
|
|
|
// Unmap the previous pin if it had been mapped
|
|
if (_mapping[logical] < PHYSICAL_PINS) {
|
|
remap = PHYSICAL_NC;
|
|
write(TESTER_REMAP + _mapping[logical], &remap, sizeof(remap));
|
|
}
|
|
_mapping[logical] = physical_index;
|
|
|
|
// Remap physical pin if it is not PHYSICAL_NC
|
|
if (physical_index < PHYSICAL_PINS) {
|
|
remap = logical;
|
|
write(TESTER_REMAP + physical_index, &remap, sizeof(remap));
|
|
}
|
|
// Remap logical pin
|
|
remap = physical_index;
|
|
write(TESTER_REMAP + PHYSICAL_PINS + logical, &remap, sizeof(remap));
|
|
}
|
|
|
|
void MbedTester::write(uint32_t addr, const uint8_t *data, uint32_t size)
|
|
{
|
|
_update_control_pins();
|
|
|
|
mbed_tester_command(_clk, _mosi, _miso, _miso_index, addr, true, (uint8_t *)data, size);
|
|
}
|
|
|
|
void MbedTester::read(uint32_t addr, uint8_t *data, uint32_t size)
|
|
{
|
|
_update_control_pins();
|
|
|
|
mbed_tester_command(_clk, _mosi, _miso, _miso_index, addr, false, data, size);
|
|
}
|
|
|
|
bool MbedTester::self_test_all()
|
|
{
|
|
return self_test_control_channels() && self_test_control_miso();
|
|
}
|
|
|
|
bool MbedTester::self_test_control_channels()
|
|
{
|
|
for (uint32_t i = 0; i < _form_factor.count() / 2; i++) {
|
|
const int clk_index = i * 2 + 0;
|
|
const int mosi_index = i * 2 + 1;
|
|
const PinName clk = _form_factor.get(clk_index);
|
|
const PinName mosi = _form_factor.get(mosi_index);
|
|
|
|
// Check if the control pair is allowed and skip if it is not
|
|
if (_exclude_pins.has_pin(clk) || _exclude_pins.has_pin(mosi)) {
|
|
mbed_tester_printf("Skipping pin indexes clk=%i, mosi=%i\r\n", i * 2 + 0, i * 2 + 1);
|
|
continue;
|
|
}
|
|
|
|
// Find a pin to use as miso
|
|
int miso_index = 0;
|
|
PinName miso = NC;
|
|
DynamicPinList more_restricted(_exclude_pins);
|
|
more_restricted.add(clk);
|
|
more_restricted.add(mosi);
|
|
for (uint32_t j = 0; j < _form_factor.count(); j++) {
|
|
miso_index = j;
|
|
const PinName temp = _form_factor.get(miso_index);
|
|
if (!more_restricted.has_pin(temp)) {
|
|
miso = temp;
|
|
break;
|
|
}
|
|
}
|
|
if (miso == NC) {
|
|
set_control_pins_auto();
|
|
return false;
|
|
}
|
|
|
|
// Find a pin to use as aux
|
|
int aux_index = 0;
|
|
PinName aux = NC;
|
|
more_restricted.add(miso);
|
|
for (uint32_t j = 0; j < _form_factor.count(); j++) {
|
|
aux_index = j;
|
|
const PinName temp = _form_factor.get(aux_index);
|
|
if (!more_restricted.has_pin(temp)) {
|
|
aux = temp;
|
|
break;
|
|
}
|
|
}
|
|
if (aux == NC) {
|
|
set_control_pins_auto();
|
|
return false;
|
|
}
|
|
|
|
// Write and read back a value
|
|
mbed_tester_printf("Testing clk_index=%2i, mosi_index=%2i, miso_index=%2i, aux_index=%2i\r\n", clk_index, mosi_index, miso_index, aux_index);
|
|
set_control_pins_manual(clk, mosi, miso, aux);
|
|
if (!self_test_control_current()) {
|
|
mbed_tester_printf(" Fail\r\n");
|
|
set_control_pins_auto();
|
|
return false;
|
|
}
|
|
mbed_tester_printf(" Pass\r\n");
|
|
}
|
|
|
|
set_control_pins_auto();
|
|
return true;
|
|
}
|
|
|
|
bool MbedTester::self_test_control_miso()
|
|
{
|
|
for (uint32_t i = 0; i < _form_factor.count(); i++) {
|
|
const int miso_index = i;
|
|
const PinName miso = _form_factor.get(miso_index);
|
|
|
|
if (_exclude_pins.has_pin(miso)) {
|
|
mbed_tester_printf("Skipping miso index %i\r\n", i);
|
|
continue;
|
|
}
|
|
|
|
// Find pins to use as clk and mosi
|
|
int clk_index = 0;
|
|
int mosi_index = 0;
|
|
PinName clk = NC;
|
|
PinName mosi = NC;
|
|
DynamicPinList more_restricted(_exclude_pins);
|
|
more_restricted.add(miso);
|
|
for (uint32_t j = 0; j < _form_factor.count() / 2; j++) {
|
|
clk_index = j * 2 + 0;
|
|
mosi_index = j * 2 + 1;
|
|
const PinName possible_clk = _form_factor.get(clk_index);
|
|
const PinName possible_mosi = _form_factor.get(mosi_index);
|
|
|
|
// Check if the control pair is allowed and skip if it is not
|
|
if (!more_restricted.has_pin(possible_clk) && !more_restricted.has_pin(possible_mosi)) {
|
|
clk = possible_clk;
|
|
mosi = possible_mosi;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((clk == NC) && (mosi == NC)) {
|
|
set_control_pins_auto();
|
|
return false;
|
|
}
|
|
|
|
// Find aux pin
|
|
int aux_index = 0;
|
|
PinName aux = NC;
|
|
more_restricted.add(clk);
|
|
more_restricted.add(mosi);
|
|
for (uint32_t j = 0; j < _form_factor.count(); j++) {
|
|
aux_index = j;
|
|
const PinName possible_aux = _form_factor.get(aux_index);
|
|
|
|
// Check if the control pair is allowed and skip if it is not
|
|
if (!more_restricted.has_pin(possible_aux)) {
|
|
aux = possible_aux;
|
|
break;
|
|
}
|
|
}
|
|
if (aux == NC) {
|
|
set_control_pins_auto();
|
|
return false;
|
|
}
|
|
|
|
|
|
mbed_tester_printf("Testing clk_index=%2i, mosi_index=%2i, miso_index=%2i, aux_index=%2i\r\n", clk_index, mosi_index, miso_index, aux_index);
|
|
set_control_pins_manual(clk, mosi, miso, aux);
|
|
if (!self_test_control_current()) {
|
|
mbed_tester_printf(" Fail\r\n");
|
|
set_control_pins_auto();
|
|
return false;
|
|
}
|
|
mbed_tester_printf(" Pass\r\n");
|
|
}
|
|
set_control_pins_auto();
|
|
return true;
|
|
}
|
|
|
|
bool MbedTester::self_test_control_current()
|
|
{
|
|
uint8_t buf[4];
|
|
|
|
read(TESTER_CONTROL, buf, sizeof(buf));
|
|
|
|
return memcmp(buf, "mbed", sizeof(buf)) == 0;
|
|
}
|
|
|
|
bool MbedTester::_find_control_indexes(PhysicalIndex &clk_out, PhysicalIndex &mosi_out, PhysicalIndex &miso_out, PhysicalIndex &aux_out)
|
|
{
|
|
MbedTesterBitMap<PHYSICAL_PINS> allowed;
|
|
const size_t max_pins = _form_factor.count();
|
|
const size_t max_controls = max_pins / 2;
|
|
|
|
for (size_t i = 0; i < max_pins; i++) {
|
|
PinName pin = _form_factor.get(i);
|
|
if ((pin == NC) || _exclude_pins.has_pin(pin)) {
|
|
// Skip these pins
|
|
continue;
|
|
}
|
|
|
|
// Pin is allowed
|
|
allowed.set(i);
|
|
}
|
|
for (size_t i = 0; i < LogicalPinTotal; i++) {
|
|
PhysicalIndex index = _mapping[i];
|
|
if (index < PHYSICAL_PINS) {
|
|
allowed.clear(index);
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < max_controls; i++) {
|
|
uint8_t clk_index = i * 2 + 0;
|
|
uint8_t mosi_index = i * 2 + 1;
|
|
if (!allowed.get(clk_index) || !allowed.get(mosi_index)) {
|
|
continue;
|
|
}
|
|
|
|
// Free CLK and MOSI pins found. Mark them as unavailable
|
|
allowed.clear(clk_index);
|
|
allowed.clear(mosi_index);
|
|
mbed::DigitalInOut clk(_form_factor.get(clk_index), PIN_OUTPUT, ::PullNone, 0);
|
|
mbed::DigitalInOut mosi(_form_factor.get(mosi_index), PIN_OUTPUT, ::PullNone, 0);
|
|
|
|
for (uint8_t miso_index = 0; miso_index < max_pins; miso_index++) {
|
|
if (!allowed.get(miso_index)) {
|
|
continue;
|
|
}
|
|
|
|
mbed::DigitalInOut miso(_form_factor.get(miso_index));
|
|
if (!mbed_tester_test(&clk, &mosi, &miso, miso_index)) {
|
|
// Pin doesn't work
|
|
continue;
|
|
}
|
|
|
|
// MISO found so find AUX starting from where miso left off
|
|
for (uint8_t aux_index = miso_index + 1; aux_index < max_pins; aux_index++) {
|
|
if (!allowed.get(aux_index)) {
|
|
continue;
|
|
}
|
|
|
|
mbed::DigitalInOut aux(_form_factor.get(aux_index));
|
|
if (!mbed_tester_test(&clk, &mosi, &aux, aux_index)) {
|
|
// Pin doesn't work
|
|
continue;
|
|
}
|
|
|
|
// Found all 4 pins
|
|
clk_out = clk_index;
|
|
mosi_out = mosi_index;
|
|
miso_out = miso_index;
|
|
aux_out = aux_index;
|
|
clk.input();
|
|
mosi.input();
|
|
return true;
|
|
}
|
|
|
|
// A valid control channel was found but the system
|
|
// does not have enough working pins.
|
|
clk.input();
|
|
mosi.input();
|
|
return false;
|
|
}
|
|
|
|
// Set CLK and MOSI pins don't work so set them back to available
|
|
clk.input();
|
|
mosi.input();
|
|
allowed.set(clk_index);
|
|
allowed.set(mosi_index);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MbedTester::_setup_control_pins()
|
|
{
|
|
_clk = new mbed::DigitalInOut(_form_factor.get(_clk_index), PIN_OUTPUT, ::PullNone, 0);
|
|
_mosi = new mbed::DigitalInOut(_form_factor.get(_mosi_index), PIN_OUTPUT, ::PullNone, 0);
|
|
_miso = new mbed::DigitalInOut(_form_factor.get(_miso_index));
|
|
_aux = new mbed::DigitalInOut(_form_factor.get(_aux_index));
|
|
}
|
|
|
|
void MbedTester::_free_control_pins()
|
|
{
|
|
if (_clk) {
|
|
_clk->input();
|
|
delete _clk;
|
|
}
|
|
_clk = NULL;
|
|
_clk_index = PHYSICAL_NC;
|
|
if (_mosi) {
|
|
_mosi->input();
|
|
delete _mosi;
|
|
}
|
|
_mosi = NULL;
|
|
_mosi_index = PHYSICAL_NC;
|
|
if (_miso) {
|
|
_miso->input();
|
|
delete _miso;
|
|
}
|
|
_miso = NULL;
|
|
_miso_index = PHYSICAL_NC;
|
|
if (_aux) {
|
|
_aux->input();
|
|
delete _aux;
|
|
}
|
|
_aux = NULL;
|
|
_aux_index = PHYSICAL_NC;
|
|
_control_valid = false;
|
|
}
|
|
|
|
void MbedTester::_update_control_pins()
|
|
{
|
|
if (_update_needed()) {
|
|
|
|
if (!_control_auto) {
|
|
error("Invalid control channel configuration");
|
|
}
|
|
|
|
_free_control_pins();
|
|
if (_find_control_indexes(_clk_index, _mosi_index, _miso_index, _aux_index)) {
|
|
mbed_tester_printf("Updating control pins to clk=%i, mosi=%i, miso=%i, aux=%i\r\n", _clk_index, _mosi_index, _miso_index, _aux_index);
|
|
_setup_control_pins();
|
|
_control_valid = true;
|
|
return;
|
|
} else {
|
|
error("An MbedTester communication channel could not be created");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MbedTester::_update_needed()
|
|
{
|
|
if (!_control_valid) {
|
|
return true;
|
|
}
|
|
// Note - mappings beyond the the first two logical banks are allowed to overlap
|
|
// with the control channels. These mappings are used for firmware updates and
|
|
// IO expander control.
|
|
for (size_t i = 0; i < LogicalPinTotal; i++) {
|
|
PhysicalIndex pin = _mapping[i];
|
|
if ((pin == _clk_index) || (pin == _mosi_index) || (pin == _miso_index) || (pin == _aux_index)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MbedTester::_reset()
|
|
{
|
|
for (uint32_t i = 0; i < sizeof(_mapping) / sizeof(_mapping[0]); i++) {
|
|
_mapping[i] = PHYSICAL_NC;
|
|
}
|
|
_free_control_pins();
|
|
_control_auto = true;
|
|
}
|