513 lines
16 KiB
C
513 lines
16 KiB
C
/* Copyright 2018 Canaan Inc.
|
|
*
|
|
* 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 <FreeRTOS.h>
|
|
#include <driver.h>
|
|
#include <fpioa.h>
|
|
#include <hal.h>
|
|
#include <io.h>
|
|
#include <math.h>
|
|
#include <semphr.h>
|
|
#include <spi.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sysctl.h>
|
|
#include "fpioa_cfg.h"
|
|
|
|
/* SPI Controller */
|
|
|
|
#define COMMON_ENTRY \
|
|
spi_data* data = (spi_data*)userdata; \
|
|
volatile struct spi_t* spi = (volatile struct spi_t*)data->base_addr; \
|
|
(void)spi;
|
|
|
|
#define TMOD_MASK (3 << data->tmod_off)
|
|
#define TMOD_VALUE(value) (value << data->tmod_off)
|
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
|
|
|
typedef struct
|
|
{
|
|
enum sysctl_clock_e clock;
|
|
uintptr_t base_addr;
|
|
uint8_t dfs_off;
|
|
uint8_t tmod_off;
|
|
uint8_t frf_off;
|
|
enum sysctl_dma_select_e dma_req_base;
|
|
|
|
struct
|
|
{
|
|
spi_frame_format frame_format;
|
|
size_t chip_select_line;
|
|
size_t buffer_width;
|
|
size_t inst_width;
|
|
size_t addr_width;
|
|
SemaphoreHandle_t free_mutex;
|
|
};
|
|
} spi_data;
|
|
|
|
static void spi_install(void* userdata)
|
|
{
|
|
spi_data* data = (spi_data*)userdata;
|
|
|
|
/* GPIO clock under APB0 clock, so enable APB0 clock firstly */
|
|
sysctl_clock_enable(data->clock);
|
|
data->free_mutex = xSemaphoreCreateMutex();
|
|
}
|
|
|
|
static int spi_open(void* userdata)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void spi_close(void* userdata)
|
|
{
|
|
}
|
|
|
|
/* SPI Device */
|
|
|
|
#define COMMON_DEV_ENTRY \
|
|
spi_dev_data* dev_data = (spi_dev_data*)userdata; \
|
|
spi_data* data = (spi_data*)dev_data->spi_data;
|
|
|
|
typedef struct
|
|
{
|
|
spi_data* spi_data;
|
|
spi_mode mode;
|
|
spi_frame_format frame_format;
|
|
size_t chip_select_line;
|
|
size_t data_bit_length;
|
|
size_t instruction_length;
|
|
size_t address_length;
|
|
size_t wait_cycles;
|
|
spi_addr_inst_trans_mode trans_mode;
|
|
uint32_t baud_rate;
|
|
} spi_dev_data;
|
|
|
|
static void spi_dev_install(void* userdata);
|
|
static int spi_dev_open(void* userdata);
|
|
static void spi_dev_close(void* userdata);
|
|
static int spi_dev_read(char* buffer, size_t len, void* userdata);
|
|
static int spi_dev_write(const char* buffer, size_t len, void* userdata);
|
|
static void spi_dev_config(size_t instruction_length, size_t address_length, size_t wait_cycles, spi_addr_inst_trans_mode trans_mode, void* userdata);
|
|
static double spi_dev_set_speed(double speed, void* userdata);
|
|
static int spi_dev_transfer_sequential(const char* write_buffer, size_t write_len, char* read_buffer, size_t read_len, void* userdata);
|
|
static int spi_dev_transfer_full_duplex(const char* write_buffer, size_t write_len, char* read_buffer, size_t read_len, void* userdata);
|
|
static void spi_dev_fill(size_t instruction, size_t address, uint32_t value, size_t count, void* userdata);
|
|
|
|
static spi_device_driver_t* spi_get_device(spi_mode mode, spi_frame_format frame_format, size_t chip_select_line, size_t data_bit_length, void* userdata)
|
|
{
|
|
spi_device_driver_t* driver = (spi_device_driver_t*)malloc(sizeof(spi_device_driver_t));
|
|
memset(driver, 0, sizeof(spi_device_driver_t));
|
|
|
|
spi_dev_data* dev_data = (spi_dev_data*)malloc(sizeof(spi_dev_data));
|
|
dev_data->spi_data = userdata;
|
|
dev_data->mode = mode;
|
|
dev_data->frame_format = frame_format;
|
|
dev_data->chip_select_line = chip_select_line;
|
|
dev_data->data_bit_length = data_bit_length;
|
|
dev_data->baud_rate = 0x2;
|
|
|
|
driver->base.userdata = dev_data;
|
|
driver->base.install = spi_dev_install;
|
|
driver->base.open = spi_dev_open;
|
|
driver->base.close = spi_dev_close;
|
|
driver->read = spi_dev_read;
|
|
driver->write = spi_dev_write;
|
|
driver->config = spi_dev_config;
|
|
driver->set_speed = spi_dev_set_speed;
|
|
driver->transfer_sequential = spi_dev_transfer_sequential;
|
|
driver->transfer_full_duplex = spi_dev_transfer_full_duplex;
|
|
driver->fill = spi_dev_fill;
|
|
return driver;
|
|
}
|
|
|
|
static int get_buffer_width(size_t data_bit_length)
|
|
{
|
|
if (data_bit_length <= 8)
|
|
return 1;
|
|
else if (data_bit_length <= 16)
|
|
return 2;
|
|
return 4;
|
|
}
|
|
|
|
static int get_inst_addr_width(size_t length)
|
|
{
|
|
if (length == 0)
|
|
return 0;
|
|
else if (length <= 8)
|
|
return 1;
|
|
else if (length <= 16)
|
|
return 2;
|
|
else if (length <= 24)
|
|
return 3;
|
|
return 4;
|
|
}
|
|
|
|
static void spi_config_as_master(spi_mode mode, spi_frame_format frame_format, size_t chip_select_line, size_t data_bit_length, uint32_t baud_rate, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
configASSERT(data_bit_length >= 4 && data_bit_length <= 32);
|
|
configASSERT(chip_select_line);
|
|
|
|
switch (frame_format)
|
|
{
|
|
case SPI_FF_DUAL:
|
|
configASSERT(data_bit_length % 2 == 0);
|
|
break;
|
|
case SPI_FF_QUAD:
|
|
configASSERT(data_bit_length % 4 == 0);
|
|
break;
|
|
case SPI_FF_OCTAL:
|
|
configASSERT(data_bit_length % 8 == 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
spi->baudr = baud_rate;
|
|
spi->imr = 0x00;
|
|
spi->dmacr = 0x00;
|
|
spi->dmatdlr = 0x10;
|
|
spi->dmardlr = 0x0;
|
|
spi->ser = 0x00;
|
|
spi->ssienr = 0x00;
|
|
spi->ctrlr0 = (mode << 6) | (frame_format << data->frf_off) | ((data_bit_length - 1) << data->dfs_off);
|
|
spi->spi_ctrlr0 = 0;
|
|
|
|
data->chip_select_line = chip_select_line;
|
|
data->frame_format = frame_format;
|
|
data->buffer_width = get_buffer_width(data_bit_length);
|
|
data->inst_width = 0;
|
|
data->addr_width = 0;
|
|
}
|
|
|
|
static void spi_config(size_t instruction_length, size_t address_length, size_t wait_cycles, spi_addr_inst_trans_mode trans_mode, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
configASSERT(data->frame_format != SPI_FF_STANDARD);
|
|
configASSERT(wait_cycles < (1 << 5));
|
|
|
|
uint32_t inst_l = 0;
|
|
switch (instruction_length)
|
|
{
|
|
case 0:
|
|
inst_l = 0;
|
|
break;
|
|
case 4:
|
|
inst_l = 1;
|
|
break;
|
|
case 8:
|
|
inst_l = 2;
|
|
break;
|
|
case 16:
|
|
inst_l = 3;
|
|
break;
|
|
default:
|
|
configASSERT("Invalid instruction length");
|
|
break;
|
|
}
|
|
|
|
uint32_t trans = 0;
|
|
switch (trans_mode)
|
|
{
|
|
case SPI_AITM_STANDARD:
|
|
trans = 0;
|
|
break;
|
|
case SPI_AITM_ADDR_STANDARD:
|
|
trans = 1;
|
|
break;
|
|
case SPI_AITM_AS_FRAME_FORMAT:
|
|
trans = 2;
|
|
break;
|
|
default:
|
|
configASSERT("Invalid trans mode");
|
|
break;
|
|
}
|
|
|
|
configASSERT(address_length % 4 == 0 && address_length <= 60);
|
|
uint32_t addr_l = address_length / 4;
|
|
|
|
spi->spi_ctrlr0 = (wait_cycles << 11) | (inst_l << 8) | (addr_l << 2) | trans;
|
|
data->inst_width = get_inst_addr_width(instruction_length);
|
|
data->addr_width = get_inst_addr_width(address_length);
|
|
}
|
|
|
|
static void write_inst_addr(volatile uint32_t* dr, const char** buffer, size_t width)
|
|
{
|
|
configASSERT(width <= 4);
|
|
if (width)
|
|
{
|
|
uint32_t cmd = 0;
|
|
char* pcmd = (char*)&cmd;
|
|
size_t i;
|
|
for (i = 0; i < width; i++)
|
|
{
|
|
pcmd[i] = **buffer;
|
|
++(*buffer);
|
|
}
|
|
|
|
*dr = cmd;
|
|
}
|
|
}
|
|
|
|
static int spi_read(char* buffer, size_t len, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
size_t frames = len / data->buffer_width;
|
|
uintptr_t dma_read = dma_open_free();
|
|
dma_set_select_request(dma_read, data->dma_req_base);
|
|
|
|
char* ori_buffer = buffer;
|
|
|
|
set_bit_mask(&spi->ctrlr0, TMOD_MASK, TMOD_VALUE(2));
|
|
spi->ctrlr1 = frames - 1;
|
|
spi->dmacr = 0x1;
|
|
spi->ssienr = 0x01;
|
|
|
|
SemaphoreHandle_t event_read = xSemaphoreCreateBinary();
|
|
|
|
dma_transmit_async(dma_read, &spi->dr[0], ori_buffer, 0, 1, data->buffer_width, frames, 1, event_read);
|
|
|
|
write_inst_addr(spi->dr, (const char**)&buffer, data->inst_width);
|
|
write_inst_addr(spi->dr, (const char**)&buffer, data->addr_width);
|
|
spi->ser = data->chip_select_line;
|
|
|
|
configASSERT(xSemaphoreTake(event_read, portMAX_DELAY) == pdTRUE);
|
|
dma_close(dma_read);
|
|
vSemaphoreDelete(event_read);
|
|
|
|
spi->ser = 0x00;
|
|
spi->ssienr = 0x00;
|
|
spi->dmacr = 0x00;
|
|
return len;
|
|
}
|
|
|
|
static int spi_write(const char* buffer, size_t len, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
uintptr_t dma_write = dma_open_free();
|
|
dma_set_select_request(dma_write, data->dma_req_base + 1);
|
|
|
|
set_bit_mask(&spi->ctrlr0, TMOD_MASK, TMOD_VALUE(1));
|
|
spi->dmacr = 0x2;
|
|
spi->ssienr = 0x01;
|
|
|
|
write_inst_addr(spi->dr, &buffer, data->inst_width);
|
|
write_inst_addr(spi->dr, &buffer, data->addr_width);
|
|
|
|
size_t frames = (len - (data->inst_width + data->addr_width)) / data->buffer_width;
|
|
SemaphoreHandle_t event_write = xSemaphoreCreateBinary();
|
|
dma_transmit_async(dma_write, buffer, &spi->dr[0], 1, 0, data->buffer_width, frames, 4, event_write);
|
|
|
|
spi->ser = data->chip_select_line;
|
|
configASSERT(xSemaphoreTake(event_write, portMAX_DELAY) == pdTRUE);
|
|
dma_close(dma_write);
|
|
vSemaphoreDelete(event_write);
|
|
|
|
while ((spi->sr & 0x05) != 0x04)
|
|
;
|
|
spi->ser = 0x00;
|
|
spi->ssienr = 0x00;
|
|
spi->dmacr = 0x00;
|
|
return len;
|
|
}
|
|
|
|
void spi_fill(size_t instruction, size_t address, uint32_t value, size_t count, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
uintptr_t dma_write = dma_open_free();
|
|
dma_set_select_request(dma_write, data->dma_req_base + 1);
|
|
|
|
set_bit_mask(&spi->ctrlr0, TMOD_MASK, TMOD_VALUE(1));
|
|
spi->dmacr = 0x2;
|
|
spi->ssienr = 0x01;
|
|
|
|
const char* buffer = (const char*)&instruction;
|
|
write_inst_addr(spi->dr, &buffer, data->inst_width);
|
|
buffer = (const char*)&address;
|
|
write_inst_addr(spi->dr, &buffer, data->addr_width);
|
|
|
|
SemaphoreHandle_t event_write = xSemaphoreCreateBinary();
|
|
dma_transmit_async(dma_write, &value, &spi->dr[0], 0, 0, sizeof(uint32_t), count, 4, event_write);
|
|
|
|
spi->ser = data->chip_select_line;
|
|
configASSERT(xSemaphoreTake(event_write, portMAX_DELAY) == pdTRUE);
|
|
dma_close(dma_write);
|
|
vSemaphoreDelete(event_write);
|
|
|
|
while ((spi->sr & 0x05) != 0x04)
|
|
;
|
|
spi->ser = 0x00;
|
|
spi->ssienr = 0x00;
|
|
spi->dmacr = 0x00;
|
|
}
|
|
|
|
static int spi_read_write(const char* write_buffer, size_t write_len, char* read_buffer, size_t read_len, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
configASSERT(data->frame_format == SPI_FF_STANDARD);
|
|
|
|
uintptr_t dma_write = dma_open_free();
|
|
uintptr_t dma_read = dma_open_free();
|
|
|
|
dma_set_select_request(dma_write, data->dma_req_base + 1);
|
|
dma_set_select_request(dma_read, data->dma_req_base);
|
|
|
|
size_t tx_frames = write_len / data->buffer_width;
|
|
size_t rx_frames = read_len / data->buffer_width;
|
|
|
|
spi->ctrlr1 = rx_frames - 1;
|
|
spi->dmacr = 0x3;
|
|
spi->ssienr = 0x01;
|
|
spi->ser = data->chip_select_line;
|
|
SemaphoreHandle_t event_read = xSemaphoreCreateBinary(), event_write = xSemaphoreCreateBinary();
|
|
|
|
dma_transmit_async(dma_read, &spi->dr[0], read_buffer, 0, 1, data->buffer_width, rx_frames, 1, event_read);
|
|
dma_transmit_async(dma_write, write_buffer, &spi->dr[0], 1, 0, data->buffer_width, tx_frames, 4, event_write);
|
|
|
|
configASSERT(xSemaphoreTake(event_read, portMAX_DELAY) == pdTRUE && xSemaphoreTake(event_write, portMAX_DELAY) == pdTRUE);
|
|
|
|
dma_close(dma_write);
|
|
dma_close(dma_read);
|
|
vSemaphoreDelete(event_read);
|
|
vSemaphoreDelete(event_write);
|
|
|
|
spi->ser = 0x00;
|
|
spi->ssienr = 0x00;
|
|
spi->dmacr = 0x00;
|
|
|
|
return read_len;
|
|
}
|
|
|
|
static int spi_transfer_full_duplex(const char* write_buffer, size_t write_len, char* read_buffer, size_t read_len, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
configASSERT(data->frame_format == SPI_FF_STANDARD);
|
|
set_bit_mask(&spi->ctrlr0, TMOD_MASK, TMOD_VALUE(0));
|
|
return spi_read_write(write_buffer, write_len, read_buffer, read_len, userdata);
|
|
}
|
|
|
|
static int spi_transfer_sequential(const char* write_buffer, size_t write_len, char* read_buffer, size_t read_len, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
configASSERT(data->frame_format == SPI_FF_STANDARD);
|
|
set_bit_mask(&spi->ctrlr0, TMOD_MASK, TMOD_VALUE(3));
|
|
return spi_read_write(write_buffer, write_len, read_buffer, read_len, userdata);
|
|
}
|
|
|
|
static void entry_exclusive(spi_dev_data* dev_data)
|
|
{
|
|
spi_data* data = (spi_data*)dev_data->spi_data;
|
|
configASSERT(xSemaphoreTake(data->free_mutex, portMAX_DELAY) == pdTRUE);
|
|
spi_config_as_master(dev_data->mode, dev_data->frame_format, dev_data->chip_select_line, dev_data->data_bit_length, dev_data->baud_rate, data);
|
|
if (dev_data->frame_format != SPI_FF_STANDARD)
|
|
spi_config(dev_data->instruction_length, dev_data->address_length, dev_data->wait_cycles, dev_data->trans_mode, data);
|
|
}
|
|
|
|
static void exit_exclusive(spi_dev_data* dev_data)
|
|
{
|
|
spi_data* data = (spi_data*)dev_data->spi_data;
|
|
xSemaphoreGive(data->free_mutex);
|
|
}
|
|
|
|
static void spi_dev_install(void* userdata)
|
|
{
|
|
}
|
|
|
|
static int spi_dev_open(void* userdata)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void spi_dev_close(void* userdata)
|
|
{
|
|
}
|
|
|
|
static int spi_dev_read(char* buffer, size_t len, void* userdata)
|
|
{
|
|
COMMON_DEV_ENTRY;
|
|
entry_exclusive(dev_data);
|
|
int ret = spi_read(buffer, len, data);
|
|
exit_exclusive(dev_data);
|
|
return ret;
|
|
}
|
|
|
|
static int spi_dev_write(const char* buffer, size_t len, void* userdata)
|
|
{
|
|
COMMON_DEV_ENTRY;
|
|
entry_exclusive(dev_data);
|
|
int ret = spi_write(buffer, len, data);
|
|
exit_exclusive(dev_data);
|
|
return ret;
|
|
}
|
|
|
|
static void spi_dev_config(size_t instruction_length, size_t address_length, size_t wait_cycles, spi_addr_inst_trans_mode trans_mode, void* userdata)
|
|
{
|
|
spi_dev_data* dev_data = (spi_dev_data*)userdata;
|
|
dev_data->instruction_length = instruction_length;
|
|
dev_data->address_length = address_length;
|
|
dev_data->wait_cycles = wait_cycles;
|
|
dev_data->trans_mode = trans_mode;
|
|
}
|
|
|
|
static double spi_dev_set_speed(double speed, void* userdata)
|
|
{
|
|
COMMON_DEV_ENTRY;
|
|
double clk = (double)sysctl_clock_get_freq(data->clock);
|
|
uint32_t div = min(65534, max((uint32_t)ceil(clk / speed), 2));
|
|
if (div & 1)
|
|
div++;
|
|
dev_data->baud_rate = div;
|
|
return clk / div;
|
|
}
|
|
|
|
static int spi_dev_transfer_sequential(const char* write_buffer, size_t write_len, char* read_buffer, size_t read_len, void* userdata)
|
|
{
|
|
COMMON_DEV_ENTRY;
|
|
entry_exclusive(dev_data);
|
|
int ret = spi_transfer_sequential(write_buffer, write_len, read_buffer, read_len, data);
|
|
exit_exclusive(dev_data);
|
|
return ret;
|
|
}
|
|
|
|
static int spi_dev_transfer_full_duplex(const char* write_buffer, size_t write_len, char* read_buffer, size_t read_len, void* userdata)
|
|
{
|
|
COMMON_DEV_ENTRY;
|
|
entry_exclusive(dev_data);
|
|
int ret = spi_transfer_full_duplex(write_buffer, write_len, read_buffer, read_len, data);
|
|
exit_exclusive(dev_data);
|
|
return ret;
|
|
}
|
|
|
|
static void spi_dev_fill(size_t instruction, size_t address, uint32_t value, size_t count, void* userdata)
|
|
{
|
|
COMMON_DEV_ENTRY;
|
|
entry_exclusive(dev_data);
|
|
spi_fill(instruction, address, value, count, data);
|
|
exit_exclusive(dev_data);
|
|
}
|
|
|
|
static spi_data dev0_data = {SYSCTL_CLOCK_SPI0, SPI0_BASE_ADDR, 16, 8, 21, SYSCTL_DMA_SELECT_SSI0_RX_REQ, {0}};
|
|
static spi_data dev1_data = {SYSCTL_CLOCK_SPI1, SPI1_BASE_ADDR, 16, 8, 21, SYSCTL_DMA_SELECT_SSI1_RX_REQ, {0}};
|
|
static spi_data dev3_data = {SYSCTL_CLOCK_SPI3, SPI3_BASE_ADDR, 0, 10, 22, SYSCTL_DMA_SELECT_SSI3_RX_REQ, {0}};
|
|
|
|
const spi_driver_t g_spi_driver_spi0 = {{&dev0_data, spi_install, spi_open, spi_close}, spi_get_device};
|
|
const spi_driver_t g_spi_driver_spi1 = {{&dev1_data, spi_install, spi_open, spi_close}, spi_get_device};
|
|
const spi_driver_t g_spi_driver_spi3 = {{&dev3_data, spi_install, spi_open, spi_close}, spi_get_device};
|