| /* |
| * 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; |
| } |