blob: 71725a9f5828b2960c3aee54ceea66c4f560640a [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 "registers.h"
#include <string.h>
#include <threads.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/composite.h>
#include <ddk/protocol/spiimpl.h>
#include <hw/reg.h>
#include <lib/device-protocol/platform-device.h>
#include <zircon/types.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
namespace spi {
static constexpr size_t kBurstMax = 16;
enum {
COMPONENT_PDEV,
COMPONENT_GPIO0,
};
void AmlSpi::DdkUnbind() { DdkRemove(); }
void AmlSpi::DdkRelease() { delete this; }
#define dump_reg(reg) zxlogf(ERROR, "%-21s (+%02x): %08x\n", #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_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;
gpio_[cs].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));
}
}
exchange_size -= burst_size;
}
gpio_[cs].Write(1);
if (rx) {
*out_rxdata_actual = rxdata_size;
}
return ZX_OK;
}
zx_status_t AmlSpi::GpioInit(amlspi_cs_map_t* map, zx_device_t** gpio_components, size_t count) {
for (uint32_t i = 0; i < map->cs_count; i++) {
gpio_protocol_t gpio;
uint32_t index = map->cs[i];
zx_status_t status = device_get_protocol(gpio_components[index], ZX_PROTOCOL_GPIO, &gpio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: failed to acquire gpio for SS%d\n", __func__, i);
return status;
}
ddk::GpioProtocolClient gpio_client(&gpio);
gpio_.push_back(gpio_client);
}
return ZX_OK;
}
zx_status_t AmlSpi::Create(void* ctx, zx_device_t* device) {
composite_protocol_t composite;
auto status = device_get_protocol(device, ZX_PROTOCOL_COMPOSITE, &composite);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: could not get composite protocol\n", __func__);
return status;
}
size_t component_count = composite_get_component_count(&composite);
zx_device_t* components[component_count];
size_t actual;
composite_get_components(&composite, components, component_count, &actual);
if (actual != component_count) {
zxlogf(ERROR, "%s: could not get components\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
pdev_protocol_t pdev;
status = device_get_protocol(components[COMPONENT_PDEV], ZX_PROTOCOL_PDEV, &pdev);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: ZX_PROTOCOL_PDEV not available\n", __func__);
return status;
}
pdev_device_info_t info;
status = pdev_get_device_info(&pdev, &info);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_get_device_info failed\n", __func__);
return status;
}
if (info.mmio_count != info.irq_count) {
zxlogf(ERROR, "%s: mmio_count %u does not match irq_count %u\n", __func__, info.mmio_count,
info.irq_count);
status = ZX_ERR_INVALID_ARGS;
return status;
}
amlspi_cs_map_t gpio_map[info.mmio_count];
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\n", __func__);
return status;
}
for (uint32_t i = 0; i < info.mmio_count; i++) {
mmio_buffer_t mmio;
status = pdev_map_mmio_buffer(&pdev, i, ZX_CACHE_POLICY_UNCACHED_DEVICE, &mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_map_&mmio__buffer #%d failed %d\n", __func__, i, status);
return status;
}
fbl::AllocChecker ac;
auto* spi = new (&ac) AmlSpi(device, ddk::MmioBuffer(mmio));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto cleanup = fbl::MakeAutoCall([&spi]() { spi->DdkRelease(); });
status = spi->GpioInit(&gpio_map[i], &components[COMPONENT_GPIO0], component_count - 1);
if (status != ZX_OK) {
return status;
}
char devname[32];
sprintf(devname, "aml-spi-%d", i);
status = spi->DdkAdd(devname);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: DdkDeviceAdd failed for %s\n", __func__, devname);
return status;
}
status = spi->DdkAddMetadata(DEVICE_METADATA_PRIVATE, &i, sizeof i);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: DdkAddMetadata failed for %s\n", __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 = AmlSpi::Create;
return ops;
}();
} // namespace spi
// clang-format off
ZIRCON_DRIVER_BEGIN(aml_spi, spi::driver_ops, "zircon", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_SPI),
ZIRCON_DRIVER_END(aml_spi)