346 lines
11 KiB
C
346 lines
11 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 <i2c.h>
|
|
#include <plic.h>
|
|
#include <semphr.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sysctl.h>
|
|
#include "fpioa_cfg.h"
|
|
|
|
/* I2C Controller */
|
|
|
|
#define COMMON_ENTRY \
|
|
i2c_data* data = (i2c_data*)userdata; \
|
|
volatile struct i2c_t* i2c = (volatile struct i2c_t*)data->base_addr; \
|
|
(void)i2c;
|
|
|
|
typedef struct
|
|
{
|
|
enum sysctl_clock_e clock;
|
|
enum sysctl_threshold_e threshold;
|
|
enum sysctl_dma_select_e dma_req_base;
|
|
uintptr_t base_addr;
|
|
struct
|
|
{
|
|
SemaphoreHandle_t free_mutex;
|
|
i2c_slave_handler slave_handler;
|
|
};
|
|
} i2c_data;
|
|
|
|
static void i2c_install(void* userdata)
|
|
{
|
|
i2c_data* data = (i2c_data*)userdata;
|
|
|
|
/* GPIO clock under APB0 clock, so enable APB0 clock firstly */
|
|
sysctl_clock_enable(data->clock);
|
|
sysctl_clock_set_threshold(data->threshold, 3);
|
|
data->free_mutex = xSemaphoreCreateMutex();
|
|
}
|
|
|
|
static int i2c_open(void* userdata)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void i2c_close(void* userdata)
|
|
{
|
|
}
|
|
|
|
/* I2C Device */
|
|
|
|
#define COMMON_DEV_ENTRY \
|
|
i2c_dev_data* dev_data = (i2c_dev_data*)userdata; \
|
|
i2c_data* data = (i2c_data*)dev_data->i2c_data;
|
|
|
|
typedef struct
|
|
{
|
|
i2c_data* i2c_data;
|
|
size_t slave_address;
|
|
size_t address_width;
|
|
i2c_bus_speed_mode bus_speed_mode;
|
|
} i2c_dev_data;
|
|
|
|
static void i2c_dev_install(void* userdata);
|
|
static int i2c_dev_open(void* userdata);
|
|
static void i2c_dev_close(void* userdata);
|
|
static int i2c_dev_read(char* buffer, size_t len, void* userdata);
|
|
static int i2c_dev_write(const char* buffer, size_t len, void* userdata);
|
|
static int i2c_dev_transfer_sequential(const char* write_buffer, size_t write_len, char* read_buffer, size_t read_len, void* userdata);
|
|
|
|
static i2c_device_driver_t* i2c_get_device(size_t slave_address, size_t address_width, i2c_bus_speed_mode bus_speed_mode, void* userdata)
|
|
{
|
|
i2c_device_driver_t* driver = (i2c_device_driver_t*)malloc(sizeof(i2c_device_driver_t));
|
|
memset(driver, 0, sizeof(i2c_device_driver_t));
|
|
|
|
i2c_dev_data* dev_data = (i2c_dev_data*)malloc(sizeof(i2c_dev_data));
|
|
dev_data->slave_address = slave_address;
|
|
dev_data->address_width = address_width;
|
|
dev_data->bus_speed_mode = bus_speed_mode;
|
|
dev_data->i2c_data = userdata;
|
|
|
|
driver->base.userdata = dev_data;
|
|
driver->base.install = i2c_dev_install;
|
|
driver->base.open = i2c_dev_open;
|
|
driver->base.close = i2c_dev_close;
|
|
driver->read = i2c_dev_read;
|
|
driver->write = i2c_dev_write;
|
|
driver->transfer_sequential = i2c_dev_transfer_sequential;
|
|
return driver;
|
|
}
|
|
|
|
static void i2c_config_as_master(size_t slave_address, size_t address_width, i2c_bus_speed_mode bus_speed_mode, void* userdata)
|
|
{
|
|
configASSERT(address_width == 7 || address_width == 10);
|
|
COMMON_ENTRY;
|
|
|
|
int speed_mode;
|
|
switch (bus_speed_mode)
|
|
{
|
|
case I2C_BS_STANDARD:
|
|
speed_mode = 1;
|
|
break;
|
|
default:
|
|
configASSERT(!"I2C bus speed is not supported.");
|
|
break;
|
|
}
|
|
|
|
i2c->enable = 0;
|
|
i2c->con = I2C_CON_MASTER_MODE | I2C_CON_SLAVE_DISABLE | I2C_CON_RESTART_EN | (address_width == 10 ? I2C_CON_10BITADDR_SLAVE : 0) | I2C_CON_SPEED(speed_mode);
|
|
i2c->ss_scl_hcnt = I2C_SS_SCL_HCNT_COUNT(37);
|
|
i2c->ss_scl_lcnt = I2C_SS_SCL_LCNT_COUNT(40);
|
|
i2c->tar = I2C_TAR_ADDRESS(slave_address);
|
|
i2c->intr_mask = 0;
|
|
|
|
i2c->dma_cr = 0x3;
|
|
i2c->dma_rdlr = 0;
|
|
i2c->dma_tdlr = 4;
|
|
|
|
i2c->enable = I2C_ENABLE_ENABLE;
|
|
}
|
|
|
|
static int i2c_read(char* buffer, size_t len, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
uint8_t fifo_len, index;
|
|
uint8_t rx_len = len;
|
|
size_t read = 0;
|
|
|
|
fifo_len = len < 7 ? len : 7;
|
|
for (index = 0; index < fifo_len; index++)
|
|
i2c->data_cmd = I2C_DATA_CMD_CMD;
|
|
len -= fifo_len;
|
|
while (len || rx_len)
|
|
{
|
|
fifo_len = i2c->rxflr;
|
|
fifo_len = rx_len < fifo_len ? rx_len : fifo_len;
|
|
for (index = 0; index < fifo_len; index++)
|
|
*buffer++ = i2c->data_cmd;
|
|
rx_len -= fifo_len;
|
|
read += fifo_len;
|
|
fifo_len = 8 - i2c->txflr;
|
|
fifo_len = len < fifo_len ? len : fifo_len;
|
|
for (index = 0; index < fifo_len; index++)
|
|
i2c->data_cmd = I2C_DATA_CMD_CMD;
|
|
if (i2c->tx_abrt_source != 0)
|
|
return read;
|
|
len -= fifo_len;
|
|
}
|
|
|
|
return read;
|
|
}
|
|
|
|
static int i2c_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);
|
|
dma_transmit(dma_write, buffer, &i2c->data_cmd, 1, 0, 1, len, 4);
|
|
dma_close(dma_write);
|
|
|
|
while (i2c->status & I2C_STATUS_ACTIVITY)
|
|
{
|
|
if (i2c->tx_abrt_source != 0)
|
|
configASSERT(!"source abort");
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static int i2c_transfer_sequential(const char* write_buffer, size_t write_len, char* read_buffer, size_t read_len, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
uint32_t* write_cmd = malloc(sizeof(uint32_t) * (write_len + read_len));
|
|
size_t i;
|
|
for (i = 0; i < write_len; i++)
|
|
write_cmd[i] = write_buffer[i];
|
|
for (i = 0; i < read_len; i++)
|
|
write_cmd[i + write_len] = I2C_DATA_CMD_CMD;
|
|
|
|
uintptr_t dma_write = dma_open_free();
|
|
uintptr_t dma_read = dma_open_free();
|
|
SemaphoreHandle_t event_read = xSemaphoreCreateBinary(), event_write = xSemaphoreCreateBinary();
|
|
|
|
dma_set_select_request(dma_write, data->dma_req_base + 1);
|
|
dma_set_select_request(dma_read, data->dma_req_base);
|
|
|
|
dma_transmit_async(dma_read, &i2c->data_cmd, read_buffer, 0, 1, 1 /*sizeof(uint32_t)*/, read_len, 1 /*4*/, event_read);
|
|
dma_transmit_async(dma_write, write_cmd, &i2c->data_cmd, 1, 0, sizeof(uint32_t), write_len + read_len, 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);
|
|
free(write_cmd);
|
|
return read_len;
|
|
}
|
|
|
|
static void entry_exclusive(i2c_dev_data* dev_data)
|
|
{
|
|
i2c_data* data = (i2c_data*)dev_data->i2c_data;
|
|
configASSERT(xSemaphoreTake(data->free_mutex, portMAX_DELAY) == pdTRUE);
|
|
i2c_config_as_master(dev_data->slave_address, dev_data->address_width, dev_data->bus_speed_mode, data);
|
|
}
|
|
|
|
static void exit_exclusive(i2c_dev_data* dev_data)
|
|
{
|
|
i2c_data* data = (i2c_data*)dev_data->i2c_data;
|
|
xSemaphoreGive(data->free_mutex);
|
|
}
|
|
|
|
static void i2c_dev_install(void* userdata)
|
|
{
|
|
}
|
|
|
|
static int i2c_dev_open(void* userdata)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void i2c_dev_close(void* userdata)
|
|
{
|
|
}
|
|
|
|
static int i2c_dev_read(char* buffer, size_t len, void* userdata)
|
|
{
|
|
COMMON_DEV_ENTRY;
|
|
entry_exclusive(dev_data);
|
|
int ret = i2c_read(buffer, len, data);
|
|
exit_exclusive(dev_data);
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_dev_write(const char* buffer, size_t len, void* userdata)
|
|
{
|
|
COMMON_DEV_ENTRY;
|
|
entry_exclusive(dev_data);
|
|
int ret = i2c_write(buffer, len, data);
|
|
exit_exclusive(dev_data);
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_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 = i2c_transfer_sequential(write_buffer, write_len, read_buffer, read_len, data);
|
|
exit_exclusive(dev_data);
|
|
return ret;
|
|
}
|
|
|
|
static void on_i2c_irq(void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
uint32_t status = i2c->intr_stat;
|
|
uint32_t dummy;
|
|
|
|
if (status & I2C_INTR_STAT_START_DET)
|
|
{
|
|
data->slave_handler.on_event(I2C_EV_START);
|
|
dummy = i2c->clr_start_det;
|
|
}
|
|
if (status & I2C_INTR_STAT_STOP_DET)
|
|
{
|
|
data->slave_handler.on_event(I2C_EV_STOP);
|
|
dummy = i2c->clr_stop_det;
|
|
}
|
|
if (status & I2C_INTR_STAT_RX_FULL)
|
|
{
|
|
data->slave_handler.on_receive(i2c->data_cmd);
|
|
}
|
|
if (status & I2C_INTR_STAT_RD_REQ)
|
|
{
|
|
i2c->data_cmd = data->slave_handler.on_transmit();
|
|
}
|
|
|
|
(void)dummy;
|
|
}
|
|
|
|
static void i2c_config_as_slave(size_t slave_address, size_t address_width, i2c_bus_speed_mode bus_speed_mode, i2c_slave_handler* handler, void* userdata)
|
|
{
|
|
configASSERT(address_width == 7 || address_width == 10);
|
|
COMMON_ENTRY;
|
|
|
|
int speed_mode;
|
|
switch (bus_speed_mode)
|
|
{
|
|
case I2C_BS_STANDARD:
|
|
speed_mode = 1;
|
|
break;
|
|
default:
|
|
configASSERT(!"I2C bus speed is not supported.");
|
|
break;
|
|
}
|
|
|
|
data->slave_handler.on_event = handler->on_event;
|
|
data->slave_handler.on_receive = handler->on_receive;
|
|
data->slave_handler.on_transmit = handler->on_transmit;
|
|
|
|
i2c->enable = 0;
|
|
i2c->con = (address_width == 10 ? I2C_CON_10BITADDR_SLAVE : 0) | I2C_CON_SPEED(speed_mode) | I2C_CON_STOP_DET_IFADDRESSED;
|
|
i2c->ss_scl_hcnt = I2C_SS_SCL_HCNT_COUNT(37);
|
|
i2c->ss_scl_lcnt = I2C_SS_SCL_LCNT_COUNT(40);
|
|
i2c->sar = I2C_SAR_ADDRESS(slave_address);
|
|
i2c->rx_tl = I2C_RX_TL_VALUE(0);
|
|
i2c->tx_tl = I2C_TX_TL_VALUE(0);
|
|
i2c->intr_mask = I2C_INTR_MASK_RX_FULL | I2C_INTR_MASK_START_DET | I2C_INTR_MASK_STOP_DET | I2C_INTR_MASK_RD_REQ;
|
|
|
|
int i2c_idx = data->clock - SYSCTL_CLOCK_I2C0;
|
|
pic_set_irq_priority(IRQN_I2C0_INTERRUPT + i2c_idx, 1);
|
|
pic_set_irq_handler(IRQN_I2C0_INTERRUPT + i2c_idx, on_i2c_irq, userdata);
|
|
pic_set_irq_enable(IRQN_I2C0_INTERRUPT + i2c_idx, 1);
|
|
|
|
i2c->enable = I2C_ENABLE_ENABLE;
|
|
}
|
|
|
|
static i2c_data dev0_data = {SYSCTL_CLOCK_I2C0, SYSCTL_THRESHOLD_I2C0, SYSCTL_DMA_SELECT_I2C0_RX_REQ, I2C0_BASE_ADDR, {0}};
|
|
static i2c_data dev1_data = {SYSCTL_CLOCK_I2C1, SYSCTL_THRESHOLD_I2C1, SYSCTL_DMA_SELECT_I2C1_RX_REQ, I2C1_BASE_ADDR, {0}};
|
|
static i2c_data dev2_data = {SYSCTL_CLOCK_I2C2, SYSCTL_THRESHOLD_I2C2, SYSCTL_DMA_SELECT_I2C2_RX_REQ, I2C2_BASE_ADDR, {0}};
|
|
|
|
const i2c_driver_t g_i2c_driver_i2c0 = {{&dev0_data, i2c_install, i2c_open, i2c_close}, i2c_get_device, i2c_config_as_slave};
|
|
const i2c_driver_t g_i2c_driver_i2c1 = {{&dev1_data, i2c_install, i2c_open, i2c_close}, i2c_get_device, i2c_config_as_slave};
|
|
const i2c_driver_t g_i2c_driver_i2c2 = {{&dev2_data, i2c_install, i2c_open, i2c_close}, i2c_get_device, i2c_config_as_slave};
|