| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "mtk-spi.h" |
| |
| #include <lib/device-protocol/pdev.h> |
| #include <unistd.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/metadata.h> |
| #include <ddk/metadata/spi.h> |
| #include <ddk/platform-defs.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_call.h> |
| |
| #include "registers.h" |
| |
| namespace spi { |
| |
| namespace { |
| |
| constexpr size_t kFifoAccessSize = 4; |
| constexpr size_t kMaxFifoSize = 32; // Depth of FIFO is 32 bytes. |
| constexpr uint32_t kDummy = 0xFFFFFFFF; |
| |
| } // namespace |
| |
| void MtkSpi::FifoTransferPacket(const uint8_t** tx, uint8_t** rx, size_t packet_size) { |
| // Transfer |
| { // Fill FIFO |
| for (size_t bytes_left = packet_size; bytes_left > 0;) { |
| const size_t transfer_size = std::min(kFifoAccessSize, bytes_left); |
| |
| if (*tx) { |
| uint32_t i32 = 0; |
| memcpy(&i32, *tx, transfer_size); |
| mmio_.Write32(i32, MTK_SPI_TX_DATA); |
| *tx += transfer_size; |
| } else { |
| mmio_.Write32(kDummy, MTK_SPI_TX_DATA); |
| } |
| |
| bytes_left -= transfer_size; |
| } |
| |
| // Enable transfer |
| CmdReg::Get().ReadFrom(&mmio_).set_activate(1).WriteTo(&mmio_); |
| } |
| |
| // Wait for completion |
| auto busy = Status1Reg::Get().ReadFrom(&mmio_); |
| while (!busy.busy()) { |
| busy.ReadFrom(&mmio_); |
| }; |
| |
| // Receive |
| { |
| for (size_t bytes_left = packet_size; bytes_left > 0;) { |
| const size_t transfer_size = std::min(kFifoAccessSize, bytes_left); |
| |
| auto data = mmio_.Read32(MTK_SPI_RX_DATA); |
| if (*rx) { |
| memcpy(*rx, &data, transfer_size); |
| *rx += transfer_size; |
| } |
| |
| bytes_left -= transfer_size; |
| } |
| } |
| } |
| |
| zx_status_t MtkSpi::FifoExchange(const uint8_t* txdata, uint8_t* out_rxdata, size_t data_size) { |
| CmdReg::Get().ReadFrom(&mmio_).set_tx_dma_en(0).set_rx_dma_en(0).WriteTo(&mmio_); // Disable DMA |
| |
| // Setup packet |
| auto packet_size = std::min(kMaxFifoSize, data_size); |
| auto packet_loop = static_cast<uint32_t>(data_size / packet_size); |
| Cfg1Reg::Get() |
| .ReadFrom(&mmio_) |
| .set_packet_length(static_cast<uint32_t>(packet_size - 1)) |
| .set_packet_loop_count(packet_loop - 1) |
| .WriteTo(&mmio_); |
| |
| const uint8_t* tx = txdata; |
| uint8_t* rx = out_rxdata; |
| for (uint32_t loop = 0; loop < packet_loop; loop++) { |
| FifoTransferPacket(&tx, &rx, packet_size); |
| } |
| |
| return (data_size % packet_size) ? FifoExchange(tx, rx, data_size % packet_size) : ZX_OK; |
| } |
| |
| zx_status_t MtkSpi::SpiImplExchange(uint32_t cs, const uint8_t* txdata, size_t txdata_size, |
| uint8_t* out_rxdata, size_t rxdata_size, |
| size_t* out_rxdata_actual) { |
| if (cs >= SpiImplGetChipSelectCount()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if ((txdata_size && rxdata_size && (txdata_size != rxdata_size)) || (!txdata && !out_rxdata) || |
| (static_cast<bool>(txdata) ^ static_cast<bool>(txdata_size)) || |
| (static_cast<bool>(out_rxdata) ^ static_cast<bool>(rxdata_size))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| size_t data_size = txdata_size ? txdata_size : rxdata_size; |
| |
| memset(out_rxdata, 0, rxdata_size); |
| |
| zx_status_t status = ZX_OK; |
| // Using FIFO for now, could also support DMA |
| if ((status = FifoExchange(txdata, out_rxdata, data_size)) != ZX_OK) { |
| zxlogf(ERROR, "%s: FifoExchange failed with %d", __func__, status); |
| return status; |
| } |
| |
| *out_rxdata_actual = rxdata_size; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t MtkSpi::Init() { |
| // Reset |
| CmdReg::Get().ReadFrom(&mmio_).set_reset(1).WriteTo(&mmio_); |
| CmdReg::Get().ReadFrom(&mmio_).set_reset(0).WriteTo(&mmio_); |
| |
| CmdReg::Get().ReadFrom(&mmio_).set_rx_msb_first(1).set_tx_msb_first(1).WriteTo(&mmio_); |
| |
| // Prepare transfer |
| uint32_t div = |
| (speed_hz_ < spi_clk_hz_ / 2) ? static_cast<uint32_t>((spi_clk_hz_ + 0.5) / speed_hz_) : 1; |
| uint32_t sck_time = (div + 1) / 2; |
| uint32_t cs_time = sck_time * 2; |
| Cfg0Reg::Get() |
| .ReadFrom(&mmio_) |
| .set_cs_setup_count((cs_time - 1) & 0xFF) |
| .set_cs_hold_count((cs_time - 1) & 0xFF) |
| .WriteTo(&mmio_); |
| Cfg2Reg::Get() |
| .ReadFrom(&mmio_) |
| .set_sck_low_count((sck_time - 1) & 0xFF) |
| .set_sck_high_count((sck_time - 1) & 0xFF) |
| .WriteTo(&mmio_); |
| Cfg1Reg::Get().ReadFrom(&mmio_).set_cs_idle_count((cs_time - 1) & 0xFF).WriteTo(&mmio_); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t MtkSpi::Create(void* ctx, zx_device_t* device) { |
| ddk::PDev pdev(device); |
| if (!pdev.is_valid()) { |
| zxlogf(ERROR, "%s: Could not get pdev protocol", __func__); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t status = ZX_OK; |
| size_t metadata_size, actual; |
| if ((status = device_get_metadata_size(device, DEVICE_METADATA_SPI_CHANNELS, &metadata_size)) != |
| ZX_OK) { |
| zxlogf(ERROR, "%s: device_get_metadata_size failed %d", __func__, status); |
| return ZX_ERR_INTERNAL; |
| } |
| auto channel_count = metadata_size / sizeof(spi_channel_t); |
| fbl::AllocChecker ac; |
| std::unique_ptr<spi_channel_t[]> channels(new (&ac) spi_channel_t[channel_count]); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: out of memory", __func__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| status = device_get_metadata(device, DEVICE_METADATA_SPI_CHANNELS, channels.get(), metadata_size, |
| &actual); |
| if (status != ZX_OK || actual != metadata_size) { |
| zxlogf(ERROR, "%s: device_get_metadata failed %d", __func__, status); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| for (uint32_t i = 0; i < channel_count; i++) { |
| std::optional<ddk::MmioBuffer> mmio; |
| if ((status = pdev.MapMmio(i, &mmio)) != ZX_OK) { |
| zxlogf(ERROR, "%s: could not map mmio %d", __func__, status); |
| return status; |
| } |
| |
| fbl::AllocChecker ac; |
| auto spi = fbl::make_unique_checked<MtkSpi>(&ac, device, std::move(mmio.value())); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if ((status = spi->Init()) != ZX_OK) { |
| zxlogf(ERROR, "%s could not init %d", __func__, status); |
| return status; |
| } |
| |
| char devname[32]; |
| sprintf(devname, "mtk-spi-%d", channels[i].bus_id); |
| status = spi->DdkAdd(devname); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: DdkDeviceAdd failed for %s", __func__, devname); |
| return status; |
| } |
| auto* ptr = spi.release(); |
| |
| auto cleanup = fbl::MakeAutoCall([&ptr]() { ptr->DdkAsyncRemove(); }); |
| |
| status = ptr->DdkAddMetadata(DEVICE_METADATA_PRIVATE, &channels[i].bus_id, |
| sizeof channels[i].bus_id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: DdkAddMetadata failed for %s", __func__, devname); |
| return status; |
| } |
| |
| cleanup.cancel(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| static zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = MtkSpi::Create; |
| return ops; |
| }(); |
| |
| } // namespace spi |
| |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(mtk_spi, spi::driver_ops, "zircon", "0.1", 3) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_MEDIATEK), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_MEDIATEK_SPI), |
| ZIRCON_DRIVER_END(mtk_spi) |