blob: f4f1a0df238680273a1f136d787bb39dfb128ae0 [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.
// Notes and limitations:
// 1. This driver _almost_ implements the standard SDHCI spec but doesn't quite
// conform entirely due to idiosyncrasies in the Pi3's controller. For
// example, this driver relies on the VC-mailbox device to get the base clock
// rate for the device and to power the device on. Additionally, the Pi3's
// controller does not appear to support any type of DMA natively and relies
// on the BCM28xx's DMA controller for DMA. For this reason, this driver uses
// PIO to communicate with the device. A more complete (and generic) driver
// might attempt [S/A]DMA and fall back on PIO in case of failure.
// Additionally, the Pi's controller doesn't appear to populate the SDHCI
// capabilities registers to expose what capabilities the EMMC controller
// provides.
//
// 2. This driver only supports SDHCv3 and above. Lower versions of SD are not
// currently supported. The driver should fail gracefully if a lower version
// card is detected.
// Standard Includes
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// DDK Includes
#include <ddk/binding.h>
#include <ddk/device.h>
#include <ddk/iotxn.h>
#include <ddk/protocol/bcm-bus.h>
#include <ddk/protocol/platform-device.h>
#include <ddk/protocol/sdhci.h>
#include <magenta/process.h>
// BCM28xx Specific Includes
#include <bcm/bcm28xx.h>
#include <bcm/ioctl.h>
#define TRACE 0
#if TRACE
#define xprintf(fmt...) printf(fmt)
#else
#define xprintf(fmt...) \
do { \
} while (0)
#endif
#define PAGE_MASK_4K (0xFFF)
#define SDMMC_PAGE_START (EMMC_BASE & (~PAGE_MASK_4K))
#define SDMMC_PAGE_SIZE (0x1000)
#define MMIO_INDEX 0
#define IRQ_INDEX 0
typedef struct emmc {
mx_device_t* mxdev;
mx_device_t* parent;
platform_device_protocol_t pdev;
void* mmio_base;
size_t mmio_size;
mx_handle_t mmio_handle;
} emmc_t;
static mx_handle_t emmc_sdhci_get_interrupt(void* ctx) {
emmc_t* emmc = ctx;
mx_handle_t handle;
if (pdev_map_interrupt(&emmc->pdev, IRQ_INDEX, &handle) == MX_OK) {
return handle;
} else {
return MX_HANDLE_INVALID;
}
}
static mx_status_t emmc_sdhci_get_mmio(void* ctx, volatile sdhci_regs_t** out) {
emmc_t* emmc = ctx;
if (!emmc->mmio_base) {
mx_status_t status = pdev_map_mmio(&emmc->pdev, MMIO_INDEX, MX_CACHE_POLICY_UNCACHED_DEVICE,
&emmc->mmio_base, &emmc->mmio_size, &emmc->mmio_handle);
if (status != MX_OK) {
return status;
}
}
*out = emmc->mmio_base;
return MX_OK;
}
static uint32_t emmc_sdhci_get_base_clock(void* ctx) {
uint32_t base_clock = 0;
emmc_t* emmc = ctx;
bcm_bus_protocol_t bus_proto;
mx_status_t st = pdev_get_protocol(&emmc->pdev, MX_PROTOCOL_BCM_BUS, (void*)&bus_proto);
if (st != MX_OK) {
xprintf("emmc: could not find MX_PROTOCOL_BCM_BUS\n");
goto out;
}
const uint32_t bcm28xX_core_clock_id = 1;
st = bcm_bus_get_clock_rate(&bus_proto, bcm28xX_core_clock_id, &base_clock);
if (st < 0 || base_clock == 0) {
xprintf("emmc: failed to get base clock rate, retcode = %d\n", st);
}
out:
return base_clock;
}
static mx_paddr_t emmc_sdhci_get_dma_offset(void* ctx) {
return BCM_SDRAM_BUS_ADDR_BASE;
}
static uint64_t emmc_sdhci_get_quirks(void* ctx) {
return SDHCI_QUIRK_STRIP_RESPONSE_CRC;
}
static sdhci_protocol_ops_t emmc_sdhci_proto = {
.get_interrupt = emmc_sdhci_get_interrupt,
.get_mmio = emmc_sdhci_get_mmio,
.get_base_clock = emmc_sdhci_get_base_clock,
.get_dma_offset = emmc_sdhci_get_dma_offset,
.get_quirks = emmc_sdhci_get_quirks,
};
static void emmc_unbind(void* ctx) {
emmc_t* emmc = ctx;
device_remove(emmc->mxdev);
}
static void emmc_release(void* ctx) {
emmc_t* emmc = ctx;
if (emmc->mmio_base) {
mx_vmar_unmap(mx_vmar_root_self(), (uintptr_t)emmc->mmio_base, emmc->mmio_size);
mx_handle_close(emmc->mmio_handle);
}
free(emmc);
}
static mx_protocol_device_t emmc_device_proto = {
.version = DEVICE_OPS_VERSION,
.unbind = emmc_unbind,
.release = emmc_release,
};
static mx_status_t emmc_bind(void* drv_ctx, mx_device_t* dev, void** cookie) {
emmc_t* emmc = calloc(1, sizeof(emmc_t));
if (!emmc) {
return MX_ERR_NO_MEMORY;
}
mx_status_t st = device_get_protocol(dev, MX_PROTOCOL_PLATFORM_DEV, &emmc->pdev);
if (st != MX_OK) {
free(emmc);
return st;
}
emmc->parent = dev;
// Create the device.
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "bcm-emmc",
.ctx = emmc,
.ops = &emmc_device_proto,
.proto_id = MX_PROTOCOL_SDHCI,
.proto_ops = &emmc_sdhci_proto,
};
st = device_add(emmc->parent, &args, &emmc->mxdev);
if (st != MX_OK) {
goto fail;
}
return MX_OK;
fail:
free(emmc);
return st;
}
static mx_driver_ops_t emmc_dwc_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = emmc_bind,
};
// The formatter does not play nice with these macros.
// clang-format off
MAGENTA_DRIVER_BEGIN(bcm_emmc, emmc_dwc_driver_ops, "magenta", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, MX_PROTOCOL_PLATFORM_DEV),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_BROADCOMM),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_BROADCOMM_EMMC),
MAGENTA_DRIVER_END(bcm_emmc)
// clang-format on