blob: b1b311ed2d56be14cd61a2d70af035904ef72609 [file] [log] [blame]
// Copyright 2017 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 "pci-sdhci.h"
#include <fuchsia/hardware/sdhci/cpp/banjo.h>
#include <inttypes.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/device-protocol/pci.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/status.h>
#define HOST_CONTROL1_OFFSET 0x28
#define SDHCI_EMMC_HW_RESET (1 << 12)
constexpr auto kTag = "pci-sdhci";
namespace sdhci {
PciSdhci::PciSdhci(zx_device_t* parent) : DeviceType(parent) {}
zx_status_t PciSdhci::SdhciGetInterrupt(zx::interrupt* interrupt_out) {
// select irq mode
fuchsia_hardware_pci::InterruptMode mode = fuchsia_hardware_pci::InterruptMode::kDisabled;
zx_status_t status = pci_.ConfigureInterruptMode(1, &mode);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: error setting irq mode: %s", kTag, zx_status_get_string(status));
return status;
}
// get irq handle
status = pci_.MapInterrupt(0, interrupt_out);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: error getting irq handle: %s", kTag, zx_status_get_string(status));
}
return status;
}
zx_status_t PciSdhci::SdhciGetMmio(zx::vmo* out, zx_off_t* out_offset) {
if (!mmio_.has_value()) {
zx_status_t status = pci_.MapMmio(0u, ZX_CACHE_POLICY_UNCACHED_DEVICE, &mmio_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: error mapping register window: %s", kTag, zx_status_get_string(status));
return status;
}
}
*out_offset = mmio_->get_offset();
return mmio_->get_vmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, out);
}
zx_status_t PciSdhci::SdhciGetBti(uint32_t index, zx::bti* out_bti) {
if (!bti_.is_valid()) {
zx_status_t st = pci_.GetBti(index, &bti_);
if (st != ZX_OK) {
return st;
}
}
return bti_.duplicate(ZX_RIGHT_SAME_RIGHTS, out_bti);
}
uint32_t PciSdhci::SdhciGetBaseClock() { return 0; }
uint64_t PciSdhci::SdhciGetQuirks(uint64_t* out_dma_boundary_alignment) {
*out_dma_boundary_alignment = 0;
return SDHCI_QUIRK_STRIP_RESPONSE_CRC_PRESERVE_ORDER;
}
void PciSdhci::SdhciHwReset() {
if (!mmio_.has_value()) {
return;
}
uint32_t val = mmio_->Read32(HOST_CONTROL1_OFFSET);
val |= SDHCI_EMMC_HW_RESET;
mmio_->Write32(val, HOST_CONTROL1_OFFSET);
// minimum is 1us but wait 9us for good measure
zx_nanosleep(zx_deadline_after(ZX_USEC(9)));
val &= ~SDHCI_EMMC_HW_RESET;
mmio_->Write32(val, HOST_CONTROL1_OFFSET);
// minimum is 200us but wait 300us for good measure
zx_nanosleep(zx_deadline_after(ZX_USEC(300)));
}
void PciSdhci::DdkUnbind(ddk::UnbindTxn txn) { device_unbind_reply(zxdev()); }
zx_status_t PciSdhci::Bind(void* /* unused */, zx_device_t* parent) {
auto dev = std::make_unique<PciSdhci>(parent);
if (!dev) {
zxlogf(ERROR, "%s: out of memory", kTag);
return ZX_ERR_NO_MEMORY;
}
dev->pci_ = ddk::Pci(parent, "pci");
if (!dev->pci_.is_valid()) {
zxlogf(ERROR, "%s: could not get PCI protocol", kTag);
return ZX_ERR_INTERNAL;
}
zx_status_t status = dev->pci_.SetBusMastering(true);
if (status < 0) {
zxlogf(ERROR, "%s: error in enable bus master: %s", kTag, zx_status_get_string(status));
return status;
}
status = dev->DdkAdd(ddk::DeviceAddArgs("pci-sdhci").set_proto_id(ZX_PROTOCOL_SDHCI));
if (status != ZX_OK) {
zxlogf(ERROR, "%s: error adding device: %s", kTag, zx_status_get_string(status));
return status;
}
// The object is owned by the DDK, now that it has been added. It will be deleted
// when the device is released.
[[maybe_unused]] auto ptr = dev.release();
return ZX_OK;
}
static zx_driver_ops_t pci_sdhci_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = PciSdhci::Bind,
};
void PciSdhci::DdkRelease() { delete this; }
} // namespace sdhci
ZIRCON_DRIVER(pci_sdhci, sdhci::pci_sdhci_driver_ops, "zircon", "0.1");