blob: 6fa192a63d60bd44bfe71b978b324473d3ab016d [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* 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 "sw/device/lib/spi_to_host/spi_to_host.h"
#include "hw/top_matcha/sw/autogen/top_matcha.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/util.h"
#define kTxWatermarkBytes (128)
#define kRxWatermarkBytes (8)
typedef enum spi_read_memory_state {
kWaitingForLocAndSize,
kDataTransfer,
kAllBytesWritten,
} spi_read_memory_state_t;
static spi_read_memory_state_t state = kWaitingForLocAndSize;
static uint32_t address, length, remaining;
dif_result_t spi_to_host_init(spi_to_host_t* spi_to_host,
dif_spi_device_handle_t* spi_device,
dif_gpio_t* gpio, dif_gpio_pin_t gpio_pin) {
spi_to_host->spi_device = spi_device;
spi_to_host->gpio = gpio;
spi_to_host->gpio_pin = gpio_pin;
dif_spi_device_config_t spi_config = {
.clock_polarity = kDifSpiDeviceEdgePositive,
.data_phase = kDifSpiDeviceEdgeNegative,
.tx_order = kDifSpiDeviceBitOrderMsbToLsb,
.rx_order = kDifSpiDeviceBitOrderMsbToLsb,
.device_mode = kDifSpiDeviceModeGeneric,
.mode_cfg =
{
.generic =
{
.rx_fifo_commit_wait = 63,
.rx_fifo_len = kDifSpiDeviceBufferLen / 2,
.tx_fifo_len = kDifSpiDeviceBufferLen / 2,
},
},
};
CHECK_DIF_OK(dif_spi_device_configure(spi_device, spi_config));
CHECK_DIF_OK(dif_gpio_output_set_enabled(gpio, gpio_pin, kDifToggleEnabled));
CHECK_DIF_OK(dif_gpio_write(gpio, gpio_pin, kDifToggleDisabled));
return kDifOk;
}
dif_result_t spi_to_host_irq_init(spi_to_host_t* spi_to_host,
dif_rv_plic_t* rv_plic,
dif_rv_plic_target_t target) {
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
rv_plic, kTopMatchaPlicIrqIdSpiDeviceGenericRxWatermark, target,
kDifToggleEnabled));
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
rv_plic, kTopMatchaPlicIrqIdSpiDeviceGenericRxWatermark,
kDifRvPlicMaxPriority));
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
rv_plic, kTopMatchaPlicIrqIdSpiDeviceGenericTxWatermark, target,
kDifToggleEnabled));
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
rv_plic, kTopMatchaPlicIrqIdSpiDeviceGenericTxWatermark,
kDifRvPlicMaxPriority));
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
rv_plic, kTopMatchaPlicIrqIdSpiDeviceGenericTxUnderflow, target,
kDifToggleEnabled));
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
rv_plic, kTopMatchaPlicIrqIdSpiDeviceGenericTxUnderflow,
kDifRvPlicMaxPriority));
CHECK_DIF_OK(dif_spi_device_set_irq_levels(
spi_to_host->spi_device, /*rx_level=*/kRxWatermarkBytes, /*tx_level=*/0));
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericRxWatermark,
kDifToggleEnabled));
return kDifOk;
}
dif_result_t spi_to_host_irq_handler(spi_to_host_t* spi_to_host,
dif_rv_plic_irq_id_t plic_irq_id) {
switch (plic_irq_id) {
case kTopMatchaPlicIrqIdSpiDeviceGenericRxWatermark: {
bool pending;
CHECK_DIF_OK(dif_spi_device_irq_is_pending(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericRxWatermark,
&pending));
CHECK_DIF_OK(dif_spi_device_irq_acknowledge(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericRxWatermark));
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericRxWatermark,
kDifToggleDisabled));
if (!pending) {
break;
}
if (state == kWaitingForLocAndSize) {
CHECK_DIF_OK(dif_spi_device_recv(spi_to_host->spi_device, &address,
sizeof(address), NULL));
CHECK_DIF_OK(dif_spi_device_recv(spi_to_host->spi_device, &length,
sizeof(length), NULL));
address = __builtin_bswap32(address);
length = __builtin_bswap32(length);
remaining = length;
CHECK_DIF_OK(
dif_spi_device_reset_generic_tx_fifo(spi_to_host->spi_device));
CHECK_DIF_OK(dif_spi_device_irq_acknowledge(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericTxWatermark));
size_t fill_count = MIN(kTxWatermarkBytes, remaining);
size_t sent;
CHECK_DIF_OK(dif_spi_device_send(
spi_to_host->spi_device, (uint8_t*)address + (length - remaining),
fill_count, &sent));
remaining -= sent;
if (remaining) {
state = kDataTransfer;
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericTxWatermark,
kDifToggleEnabled));
} else {
state = kAllBytesWritten;
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericTxWatermark,
kDifToggleDisabled));
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericTxUnderflow,
kDifToggleEnabled));
}
CHECK_DIF_OK(dif_spi_device_set_irq_levels(
spi_to_host->spi_device, /*rx_level=*/kDifSpiDeviceBufferLen,
/*tx_level=*/kTxWatermarkBytes));
CHECK_DIF_OK(dif_gpio_write(spi_to_host->gpio, spi_to_host->gpio_pin,
kDifToggleEnabled));
}
break;
}
case kTopMatchaPlicIrqIdSpiDeviceGenericTxWatermark: {
CHECK_DIF_OK(dif_spi_device_irq_acknowledge(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericTxWatermark));
if (state == kDataTransfer) {
size_t fill_count = MIN(kTxWatermarkBytes, remaining);
size_t sent;
CHECK_DIF_OK(dif_spi_device_send(
spi_to_host->spi_device, (uint8_t*)address + (length - remaining),
fill_count, &sent));
remaining -= sent;
CHECK_DIF_OK(
dif_spi_device_reset_generic_rx_fifo(spi_to_host->spi_device));
if (!remaining) {
state = kAllBytesWritten;
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericTxWatermark,
kDifToggleDisabled));
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericRxWatermark,
kDifToggleEnabled));
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericTxUnderflow,
kDifToggleEnabled));
}
}
break;
}
case kTopMatchaPlicIrqIdSpiDeviceGenericTxUnderflow: {
CHECK(state == kAllBytesWritten);
CHECK_DIF_OK(dif_spi_device_irq_acknowledge(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericTxUnderflow));
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericTxUnderflow,
kDifToggleDisabled));
state = kWaitingForLocAndSize;
// Clear RX FIFO and watermark interrupt
uint32_t drain;
size_t drained;
do {
CHECK_DIF_OK(dif_spi_device_recv(spi_to_host->spi_device, &drain,
sizeof(drain), &drained));
} while (drained != 0);
CHECK_DIF_OK(
dif_spi_device_reset_generic_tx_fifo(spi_to_host->spi_device));
CHECK_DIF_OK(
dif_spi_device_reset_generic_rx_fifo(spi_to_host->spi_device));
CHECK_DIF_OK(dif_spi_device_set_irq_levels(spi_to_host->spi_device,
/*rx_level=*/kRxWatermarkBytes,
/*tx_level=*/0));
CHECK_DIF_OK(dif_spi_device_irq_acknowledge(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericRxWatermark));
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_to_host->spi_device->dev, kDifSpiDeviceIrqGenericRxWatermark,
kDifToggleEnabled));
CHECK_DIF_OK(dif_gpio_write(spi_to_host->gpio, spi_to_host->gpio_pin,
kDifToggleDisabled));
break;
}
default:
LOG_FATAL("unhandled SPI irq");
break;
}
return kDifOk;
}