blob: 0ac737ea1a75cd3f33bdea89bb70368351523c61 [file] [log] [blame]
// Copyright 2020 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 "dw-spi.h"
#include <lib/device-protocol/pdev.h>
#include <lib/device-protocol/platform-device.h>
#include <string.h>
#include <threads.h>
#include <zircon/types.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/platform/bus.h>
#include <ddk/protocol/platform/device.h>
#include <ddk/protocol/spiimpl.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <hw/reg.h>
#include "registers.h"
#include "src/devices/spi/drivers/dw-spi/dw_spi-bind.h"
namespace spi {
void DwSpi::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); }
void DwSpi::DdkRelease() { delete this; }
zx_status_t DwSpi::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;
}
Enable::Get().FromValue(0).WriteTo(&mmio_);
Ctrl0::Get()
.FromValue(0)
.set_dfs(7) // 8 bits per word (bits)
.set_frf(Ctrl0::FRF_SPI)
.set_scph(0)
.set_scol(0)
.set_tmod(Ctrl0::TMOD_TR)
.WriteTo(&mmio_);
ChipEnable::Get().FromValue(1 << cs).WriteTo(&mmio_);
Enable::Get().FromValue(1).WriteTo(&mmio_);
const uint8_t* tx = txdata;
uint8_t* rx = out_rxdata;
size_t exchange_size = txdata_size ? txdata_size : rxdata_size;
size_t tx_done = 0;
size_t rx_done = 0;
while (tx_done < exchange_size || rx_done < exchange_size) {
// load TX FIFO
while (tx_done < exchange_size && Status::Get().ReadFrom(&mmio_).tf_not_full()) {
uint8_t byte = tx ? *tx++ : 0xff;
Data::Get().FromValue(byte).WriteTo(&mmio_);
tx_done++;
}
// read RX FIFO
while (rx_done < exchange_size && Status::Get().ReadFrom(&mmio_).rf_not_empty()) {
uint8_t byte = static_cast<uint8_t>(Data::Get().ReadFrom(&mmio_).reg_value());
if (rx) {
*rx++ = byte;
}
rx_done++;
}
}
if (rx) {
*out_rxdata_actual = rxdata_size;
}
return ZX_OK;
}
zx_status_t DwSpi::Create(void* ctx, zx_device_t* parent) {
ddk::PDev pdev(parent);
if (!pdev.is_valid()) {
zxlogf(ERROR, "%s: Failed to get ZX_PROTOCOL_PDEV", __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 ZX_ERR_NOT_SUPPORTED;
}
if (info.mmio_count != info.irq_count) {
zxlogf(ERROR, "%s: mmio_count %u does not match irq_count %u", __func__, info.mmio_count,
info.irq_count);
status = ZX_ERR_INVALID_ARGS;
return status;
}
for (uint32_t i = 0; i < info.mmio_count; i++) {
std::optional<ddk::MmioBuffer> mmio;
if ((status = pdev.MapMmio(i, &mmio)) != ZX_OK) {
zxlogf(ERROR, "%s: MapMmio failed %d", __func__, status);
return status;
}
// hardware setup
Enable::Get().FromValue(0).WriteTo(&*mmio);
IMR::Get().FromValue(0xff).WriteTo(&*mmio);
Enable::Get().FromValue(1).WriteTo(&*mmio);
TXFLTR::Get().FromValue(0).WriteTo(&*mmio);
// timing hardcoded for bringup
Enable::Get().FromValue(0).WriteTo(&*mmio);
BaudRate::Get().FromValue(100).WriteTo(&*mmio);
Enable::Get().FromValue(1).WriteTo(&*mmio);
fbl::AllocChecker ac;
auto* spi = new (&ac) DwSpi(parent, *std::move(mmio));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto cleanup = fbl::MakeAutoCall([&spi]() { spi->DdkRelease(); });
char devname[32];
sprintf(devname, "dw-spi-%d", i);
status = spi->DdkAdd(devname);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: DdkAdd failed for %s", __func__, devname);
return status;
}
status = spi->DdkAddMetadata(DEVICE_METADATA_PRIVATE, &i, sizeof i);
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 = DwSpi::Create;
return ops;
}();
} // namespace spi
// clang-format off
ZIRCON_DRIVER(dw_spi, spi::driver_ops, "zircon", "0.1");