| // 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 "aml-spi.h" |
| |
| #include <fuchsia/hardware/spiimpl/c/banjo.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/device.h> |
| #include <lib/ddk/hw/reg.h> |
| #include <lib/ddk/metadata.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/device-protocol/pdev.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| |
| #include <fbl/alloc_checker.h> |
| |
| #include "registers.h" |
| #include "src/devices/spi/drivers/aml-spi/aml_spi_bind.h" |
| |
| namespace spi { |
| |
| static constexpr size_t kBurstMax = 16; |
| |
| void AmlSpi::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); } |
| |
| void AmlSpi::DdkRelease() { delete this; } |
| |
| #define dump_reg(reg) zxlogf(ERROR, "%-21s (+%02x): %08x", #reg, reg, mmio_.Read32(reg)) |
| |
| void AmlSpi::DumpState() { |
| // skip registers with side-effects |
| // dump_reg(AML_SPI_RXDATA); |
| // dump_reg(AML_SPI_TXDATA); |
| dump_reg(AML_SPI_CONREG); |
| dump_reg(AML_SPI_INTREG); |
| dump_reg(AML_SPI_DMAREG); |
| dump_reg(AML_SPI_STATREG); |
| dump_reg(AML_SPI_PERIODREG); |
| dump_reg(AML_SPI_TESTREG); |
| dump_reg(AML_SPI_DRADDR); |
| dump_reg(AML_SPI_DWADDR); |
| dump_reg(AML_SPI_LD_CNTL0); |
| dump_reg(AML_SPI_LD_CNTL1); |
| dump_reg(AML_SPI_LD_RADDR); |
| dump_reg(AML_SPI_LD_WADDR); |
| dump_reg(AML_SPI_ENHANCE_CNTL); |
| dump_reg(AML_SPI_ENHANCE_CNTL1); |
| dump_reg(AML_SPI_ENHANCE_CNTL2); |
| } |
| |
| #undef dump_reg |
| |
| zx::status<fbl::Span<uint8_t>> AmlSpi::GetVmoSpan(uint32_t chip_select, uint32_t vmo_id, |
| uint64_t offset, uint64_t size, uint32_t right) { |
| vmo_store::StoredVmo<OwnedVmoInfo>* const vmo_info = |
| chips_[chip_select].registered_vmos.GetVmo(vmo_id); |
| if (!vmo_info) { |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| |
| if ((vmo_info->meta().rights & right) == 0) { |
| return zx::error(ZX_ERR_ACCESS_DENIED); |
| } |
| |
| if (offset + size > vmo_info->meta().size) { |
| return zx::error(ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| return zx::ok(vmo_info->data().subspan(vmo_info->meta().offset + offset)); |
| } |
| |
| zx_status_t AmlSpi::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)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| size_t exchange_size = txdata_size ? txdata_size : rxdata_size; |
| |
| // transfer settings |
| auto conreg = ConReg::Get() |
| .FromValue(0) |
| .set_en(1) |
| .set_mode(ConReg::kModeMaster) |
| .set_bits_per_word(8 - 1) |
| .WriteTo(&mmio_); |
| |
| // reset both fifos |
| auto testreg = TestReg::Get().FromValue(0).set_fiforst(3).WriteTo(&mmio_); |
| do { |
| testreg.ReadFrom(&mmio_); |
| } while ((testreg.rxcnt() != 0) || (testreg.txcnt() != 0)); |
| |
| const uint8_t* tx = txdata; |
| uint8_t* rx = out_rxdata; |
| |
| chips_[cs].gpio.Write(0); |
| |
| while (exchange_size) { |
| uint32_t burst_size = (uint32_t)std::min(kBurstMax, exchange_size); |
| |
| // fill FIFO |
| for (uint32_t i = 0; i < burst_size; i++) { |
| if (tx) { |
| mmio_.Write32(*tx++, AML_SPI_TXDATA); |
| } else { |
| mmio_.Write32(0xff, AML_SPI_TXDATA); |
| } |
| } |
| |
| // start burst |
| auto statreg = StatReg::Get().FromValue(0).set_tc(1).WriteTo(&mmio_); |
| conreg.set_burst_length(burst_size - 1).set_xch(1).WriteTo(&mmio_); |
| |
| // wait for completion |
| do { |
| statreg.ReadFrom(&mmio_); |
| } while (!statreg.tc()); |
| |
| // read |
| if (rx) { |
| for (uint32_t i = 0; i < burst_size; i++) { |
| *rx++ = static_cast<uint8_t>(mmio_.Read32(AML_SPI_RXDATA)); |
| } |
| } else { |
| for (uint32_t i = 0; i < burst_size; i++) { |
| mmio_.Read32(AML_SPI_RXDATA); |
| } |
| } |
| |
| exchange_size -= burst_size; |
| } |
| |
| chips_[cs].gpio.Write(1); |
| |
| if (rx && out_rxdata_actual) { |
| *out_rxdata_actual = rxdata_size; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlSpi::SpiImplRegisterVmo(uint32_t chip_select, uint32_t vmo_id, zx::vmo vmo, |
| uint64_t offset, uint64_t size, uint32_t rights) { |
| if (chip_select >= SpiImplGetChipSelectCount()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| if (rights & ~(SPI_VMO_RIGHT_READ | SPI_VMO_RIGHT_WRITE)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| vmo_store::StoredVmo<OwnedVmoInfo> stored_vmo(std::move(vmo), OwnedVmoInfo{ |
| .offset = offset, |
| .size = size, |
| .rights = rights, |
| }); |
| const zx_vm_option_t map_opts = ((rights & SPI_VMO_RIGHT_READ) ? ZX_VM_PERM_READ : 0) | |
| ((rights & SPI_VMO_RIGHT_WRITE) ? ZX_VM_PERM_WRITE : 0); |
| zx_status_t status = stored_vmo.Map(map_opts); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| return chips_[chip_select].registered_vmos.RegisterWithKey(vmo_id, std::move(stored_vmo)); |
| } |
| |
| zx_status_t AmlSpi::SpiImplUnregisterVmo(uint32_t chip_select, uint32_t vmo_id, zx::vmo* out_vmo) { |
| if (chip_select >= SpiImplGetChipSelectCount()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| vmo_store::StoredVmo<OwnedVmoInfo>* const vmo_info = |
| chips_[chip_select].registered_vmos.GetVmo(vmo_id); |
| if (!vmo_info) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx_status_t status = vmo_info->vmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, out_vmo); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| auto result = chips_[chip_select].registered_vmos.Unregister(vmo_id); |
| if (result.is_error()) { |
| return result.status_value(); |
| } |
| |
| *out_vmo = std::move(result.value()); |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlSpi::SpiImplTransmitVmo(uint32_t chip_select, uint32_t vmo_id, uint64_t offset, |
| uint64_t size) { |
| if (chip_select >= SpiImplGetChipSelectCount()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| zx::status<fbl::Span<const uint8_t>> buffer = |
| GetVmoSpan(chip_select, vmo_id, offset, size, SPI_VMO_RIGHT_READ); |
| if (buffer.is_error()) { |
| return buffer.error_value(); |
| } |
| |
| return SpiImplExchange(chip_select, buffer->data(), size, nullptr, 0, nullptr); |
| } |
| |
| zx_status_t AmlSpi::SpiImplReceiveVmo(uint32_t chip_select, uint32_t vmo_id, uint64_t offset, |
| uint64_t size) { |
| if (chip_select >= SpiImplGetChipSelectCount()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| zx::status<fbl::Span<uint8_t>> buffer = |
| GetVmoSpan(chip_select, vmo_id, offset, size, SPI_VMO_RIGHT_WRITE); |
| if (buffer.is_error()) { |
| return buffer.error_value(); |
| } |
| |
| return SpiImplExchange(chip_select, nullptr, 0, buffer->data(), size, nullptr); |
| } |
| |
| zx_status_t AmlSpi::SpiImplExchangeVmo(uint32_t chip_select, uint32_t tx_vmo_id, uint64_t tx_offset, |
| uint32_t rx_vmo_id, uint64_t rx_offset, uint64_t size) { |
| if (chip_select >= SpiImplGetChipSelectCount()) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| zx::status<fbl::Span<uint8_t>> tx_buffer = |
| GetVmoSpan(chip_select, tx_vmo_id, tx_offset, size, SPI_VMO_RIGHT_READ); |
| if (tx_buffer.is_error()) { |
| return tx_buffer.error_value(); |
| } |
| |
| zx::status<fbl::Span<uint8_t>> rx_buffer = |
| GetVmoSpan(chip_select, rx_vmo_id, rx_offset, size, SPI_VMO_RIGHT_WRITE); |
| if (rx_buffer.is_error()) { |
| return rx_buffer.error_value(); |
| } |
| |
| return SpiImplExchange(chip_select, tx_buffer->data(), size, rx_buffer->data(), size, nullptr); |
| } |
| |
| fbl::Array<AmlSpi::ChipInfo> AmlSpi::InitChips(amlspi_cs_map_t* map, zx_device_t* device) { |
| fbl::Array<ChipInfo> chips(new ChipInfo[map->cs_count], map->cs_count); |
| if (!chips) { |
| return chips; |
| } |
| |
| for (uint32_t i = 0; i < map->cs_count; i++) { |
| uint32_t index = map->cs[i]; |
| char fragment_name[32] = {}; |
| snprintf(fragment_name, 32, "gpio-cs-%d", index); |
| chips[i].gpio = ddk::GpioProtocolClient(device, fragment_name); |
| if (!chips[i].gpio.is_valid()) { |
| zxlogf(ERROR, "%s: failed to acquire gpio for SS%d", __func__, i); |
| return fbl::Array<ChipInfo>(); |
| } |
| } |
| |
| return chips; |
| } |
| |
| zx_status_t AmlSpi::Create(void* ctx, zx_device_t* device) { |
| auto pdev = ddk::PDev::FromFragment(device); |
| if (!pdev.is_valid()) { |
| zxlogf(ERROR, "%s: ZX_PROTOCOL_PDEV not available", __func__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| pdev_device_info_t info; |
| zx_status_t status = pdev.GetDeviceInfo(&info); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: pdev_get_device_info failed", __func__); |
| return status; |
| } |
| |
| size_t actual; |
| amlspi_cs_map_t gpio_map = {}; |
| status = device_get_metadata(device, DEVICE_METADATA_AMLSPI_CS_MAPPING, &gpio_map, |
| sizeof gpio_map, &actual); |
| if ((status != ZX_OK) || (actual != sizeof gpio_map)) { |
| zxlogf(ERROR, "%s: failed to read GPIO/chip select map", __func__); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> mmio; |
| status = pdev.MapMmio(0, &mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: pdev_map_&mmio__buffer failed %d", __func__, status); |
| return status; |
| } |
| |
| fbl::Array<ChipInfo> chips = InitChips(&gpio_map, device); |
| if (!chips) { |
| return ZX_ERR_NO_RESOURCES; |
| } |
| if (chips.size() == 0) { |
| return ZX_OK; |
| } |
| |
| fbl::AllocChecker ac; |
| std::unique_ptr<AmlSpi> spi(new (&ac) AmlSpi(device, *std::move(mmio), std::move(chips))); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| char devname[32]; |
| sprintf(devname, "aml-spi-%d", gpio_map.bus_id); |
| |
| status = spi->DdkAdd(devname); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: DdkDeviceAdd failed for %s", __func__, devname); |
| return status; |
| } |
| |
| status = spi->DdkAddMetadata(DEVICE_METADATA_PRIVATE, &gpio_map.bus_id, sizeof gpio_map.bus_id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: DdkAddMetadata failed for %s", __func__, devname); |
| return status; |
| } |
| |
| __UNUSED auto* _ = spi.release(); |
| |
| return ZX_OK; |
| } |
| |
| static zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = AmlSpi::Create; |
| return ops; |
| }(); |
| |
| } // namespace spi |
| |
| ZIRCON_DRIVER(aml_spi, spi::driver_ops, "zircon", "0.1"); |