blob: c00abbc5241dc84a289ab18c285ab2e75ef50fb0 [file] [log] [blame]
// 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");