blob: bd2830051677ce49502d0c450f80d5eaf92655bd [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 "spi.h"
#include <fidl/fuchsia.hardware.spi.businfo/cpp/wire.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/metadata.h>
#include <fbl/auto_lock.h>
#include "src/devices/spi/drivers/spi/spi_bind.h"
namespace spi {
void SpiDevice::Shutdown() {
fbl::AutoLock lock(&lock_);
if (!shutdown_) {
shutdown_ = true;
// Stop the loop so that all unbind hooks run, and all child references are released.
loop_.Shutdown();
children_.reset();
}
}
void SpiDevice::DdkUnbind(ddk::UnbindTxn txn) {
Shutdown();
txn.Reply();
}
void SpiDevice::DdkRelease() {
Shutdown();
delete this;
}
zx_status_t SpiDevice::Create(void* ctx, zx_device_t* parent) {
ddk::SpiImplProtocolClient spi(parent);
if (!spi.is_valid()) {
return ZX_ERR_NO_RESOURCES;
}
uint32_t bus_id;
size_t actual;
zx_status_t status =
device_get_metadata(parent, DEVICE_METADATA_PRIVATE, &bus_id, sizeof bus_id, &actual);
if (status != ZX_OK) {
return status;
}
fbl::AllocChecker ac;
std::unique_ptr<SpiDevice> device(new (&ac) SpiDevice(parent, bus_id));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = device->DdkAdd(ddk::DeviceAddArgs("spi").set_flags(DEVICE_ADD_NON_BINDABLE));
if (status != ZX_OK) {
return status;
}
device->AddChildren(spi);
__UNUSED auto* dummy = device.release();
return ZX_OK;
}
void SpiDevice::AddChildren(const ddk::SpiImplProtocolClient& spi) {
auto decoded = ddk::GetEncodedMetadata<fuchsia_hardware_spi_businfo::wire::SpiBusMetadata>(
zxdev(), DEVICE_METADATA_SPI_CHANNELS);
if (!decoded.is_ok()) {
return;
}
fuchsia_hardware_spi_businfo::wire::SpiBusMetadata* metadata = decoded->PrimaryObject();
if (!metadata->has_channels()) {
zxlogf(INFO, "No channels supplied.");
return;
}
zxlogf(INFO, "%zu channels supplied.", metadata->channels().count());
fbl::AutoLock lock(&lock_);
bool has_siblings = metadata->channels().count() > 1;
for (auto& channel : metadata->channels()) {
const auto bus_id = channel.has_bus_id() ? channel.bus_id() : 0;
if (bus_id != bus_id_) {
continue;
}
const auto cs = channel.has_cs() ? channel.cs() : 0;
const auto vid = channel.has_vid() ? channel.vid() : 0;
const auto pid = channel.has_pid() ? channel.pid() : 0;
const auto did = channel.has_did() ? channel.did() : 0;
fbl::AllocChecker ac;
auto dev = fbl::MakeRefCountedChecked<SpiChild>(&ac, zxdev(), spi, cs, this, has_siblings);
if (!ac.check()) {
zxlogf(ERROR, "Out of memory");
return;
}
char name[20];
snprintf(name, sizeof(name), "spi-%u-%u", bus_id, cs);
zx_status_t status;
if (vid || pid || did) {
zx_device_prop_t props[] = {
{BIND_SPI_BUS_ID, 0, bus_id}, {BIND_SPI_CHIP_SELECT, 0, cs},
{BIND_PLATFORM_DEV_VID, 0, vid}, {BIND_PLATFORM_DEV_PID, 0, pid},
{BIND_PLATFORM_DEV_DID, 0, did},
};
status = dev->DdkAdd(ddk::DeviceAddArgs(name).set_props(props).set_proto_id(ZX_PROTOCOL_SPI));
} else {
zx_device_prop_t props[] = {
{BIND_SPI_BUS_ID, 0, bus_id},
{BIND_SPI_CHIP_SELECT, 0, cs},
};
status = dev->DdkAdd(ddk::DeviceAddArgs(name).set_props(props).set_proto_id(ZX_PROTOCOL_SPI));
}
if (status != ZX_OK) {
zxlogf(ERROR, "DdkAdd failed %d", status);
return;
}
dev->AddRef(); // DdkAdd succeeded -- increment the counter now that the DDK has a reference.
// save a reference for cleanup
children_.push_back(dev);
}
}
void SpiDevice::ConnectServer(zx::channel server, fbl::RefPtr<SpiChild> child) {
fbl::AutoLock lock(&lock_);
if (shutdown_) {
fidl::ServerEnd<fuchsia_hardware_spi::Device>(std::move(server)).Close(ZX_ERR_ALREADY_BOUND);
return;
}
if (!loop_started_) {
zx_status_t status;
if ((status = loop_.StartThread("spi-child-thread")) != ZX_OK) {
zxlogf(ERROR, "Failed to start async loop: %d", status);
} else {
loop_started_ = true;
}
}
// Make sure that the child reference counter doesn't get decreased when it goes out of scope
// here. The dispatcher now holds a pointer to the child, so the child can't be freed until after
// the callback runs.
fidl::BindServer(loop_.dispatcher(), std::move(server), fbl::ExportToRawPtr(&child),
[](SpiChild* ref_counted_child, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_hardware_spi::Device> server) {
fbl::RefPtr<SpiChild> child_ref_ptr = fbl::ImportFromRawPtr(ref_counted_child);
child_ref_ptr->OnUnbound();
});
}
static zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = SpiDevice::Create;
return ops;
}();
} // namespace spi
ZIRCON_DRIVER(spi, spi::driver_ops, "zircon", "0.1");