| // Copyright 2018 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. |
| |
| // Standard Includes |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| |
| // DDK Includes |
| #include <ddk/binding.h> |
| #include <ddk/device.h> |
| #include <ddk/debug.h> |
| #include <ddk/io-buffer.h> |
| #include <ddk/phys-iter.h> |
| #include <ddk/protocol/platform-defs.h> |
| #include <ddk/protocol/platform-device.h> |
| #include <ddk/protocol/platform-bus.h> |
| #include <ddk/protocol/sdmmc.h> |
| #include <ddk/protocol/sdhci.h> |
| #include <hw/sdmmc.h> |
| #include <zircon/types.h> |
| |
| |
| // Zircon Includes |
| #include <fdio/watcher.h> |
| #include <zircon/threads.h> |
| #include <zircon/assert.h> |
| #include <sync/completion.h> |
| #include <pretty/hexdump.h> |
| |
| #include "imx-sdhci.h" |
| |
| #define SDHCI_ERROR(fmt, ...) zxlogf(ERROR, "[%s %d]" fmt, __func__, __LINE__, ##__VA_ARGS__) |
| #define SDHCI_INFO(fmt, ...) zxlogf(ERROR, "[%s %d]" fmt, __func__, __LINE__, ##__VA_ARGS__) |
| #define SDHCI_TRACE(fmt, ...) zxlogf(ERROR, "[%s %d]" fmt, __func__, __LINE__, ##__VA_ARGS__) |
| #define SDHCI_FUNC_ENTRY_LOG zxlogf(ERROR, "[%s %d]\n", __func__, __LINE__) |
| |
| #define SD_FREQ_SETUP_HZ 400000 |
| |
| #define MAX_TUNING_COUNT 40 |
| |
| #define PAGE_MASK (PAGE_SIZE - 1ull) |
| |
| #define HI32(val) (((val) >> 32) & 0xffffffff) |
| #define LO32(val) ((val) & 0xffffffff) |
| #define SDMMC_COMMAND(c) ((c) << 24) |
| |
| typedef struct sdhci_adma64_desc { |
| union { |
| struct { |
| uint8_t valid : 1; |
| uint8_t end : 1; |
| uint8_t intr : 1; |
| uint8_t rsvd0 : 1; |
| uint8_t act1 : 1; |
| uint8_t act2 : 1; |
| uint8_t rsvd1 : 2; |
| uint8_t rsvd2; |
| } __PACKED; |
| uint16_t attr; |
| } __PACKED; |
| uint16_t length; |
| uint64_t address; |
| } __PACKED sdhci_adma64_desc_t; |
| |
| static_assert(sizeof(sdhci_adma64_desc_t) == 12, "unexpected ADMA2 descriptor size"); |
| |
| // 64k max per descriptor |
| #define ADMA2_DESC_MAX_LENGTH 0x10000 // 64k |
| // for 2M max transfer size for fully discontiguous |
| // also see SDMMC_PAGES_COUNT in ddk/protocol/sdmmc.h |
| #define DMA_DESC_COUNT 512 |
| |
| typedef struct imx_sdhci_device { |
| platform_device_protocol_t pdev; |
| platform_bus_protocol_t pbus; |
| zx_device_t* zxdev; |
| io_buffer_t mmios; |
| zx_handle_t irq_handle; |
| |
| volatile imx_sdhci_regs_t* regs; |
| uint64_t regs_size; |
| zx_handle_t regs_handle; |
| zx_handle_t bti_handle; |
| |
| // DMA descriptors |
| io_buffer_t iobuf; |
| sdhci_adma64_desc_t* descs; |
| |
| // Held when a command or action is in progress. |
| mtx_t mtx; |
| |
| // Current command request |
| sdmmc_req_t* cmd_req; |
| // Current data line request |
| sdmmc_req_t* data_req; |
| // Current block id to transfer (PIO) |
| uint16_t data_blockid; |
| uint16_t reserved; |
| // Set to true if the data stage completed before the command stage |
| bool data_done; |
| // used to signal request complete |
| completion_t req_completion; |
| |
| // Controller info |
| sdmmc_host_info_t info; |
| |
| // Controller specific quirks |
| uint64_t quirks; |
| |
| // Base clock rate |
| uint32_t base_clock; |
| |
| } imx_sdhci_device_t; |
| |
| static const uint32_t error_interrupts = ( |
| IMX_SDHC_INT_STAT_DMAE | |
| IMX_SDHC_INT_STAT_TNE | |
| IMX_SDHC_INT_STAT_AC12E | |
| IMX_SDHC_INT_STAT_DEBE | |
| IMX_SDHC_INT_STAT_DCE | |
| IMX_SDHC_INT_STAT_DTOE | |
| IMX_SDHC_INT_STAT_CIE | |
| IMX_SDHC_INT_STAT_CEBE | |
| IMX_SDHC_INT_STAT_CCE | |
| IMX_SDHC_INT_STAT_CTOE |
| ); |
| |
| static const uint32_t normal_interrupts = ( |
| IMX_SDHC_INT_STAT_BRR | |
| IMX_SDHC_INT_STAT_BWR | |
| IMX_SDHC_INT_STAT_DINT | |
| IMX_SDHC_INT_STAT_BGE | |
| IMX_SDHC_INT_STAT_TC | |
| IMX_SDHC_INT_STAT_CC |
| ); |
| |
| static bool imx_sdmmc_cmd_rsp_busy(uint32_t cmd) { |
| uint32_t resp = cmd & SDMMC_RESP_MASK; |
| return resp == SDMMC_RESP_LEN_48B; |
| } |
| |
| static bool imx_sdmmc_has_data(uint32_t resp_type) { |
| return resp_type & SDMMC_RESP_DATA_PRESENT; |
| } |
| |
| |
| static bool imx_sdmmc_supports_adma2_64bit(imx_sdhci_device_t* dev) { |
| return (0); // TODO: iMX8 doesn't support 64bit ADMA2 |
| } |
| |
| static zx_status_t imx_sdhci_wait_for_reset(imx_sdhci_device_t* dev, |
| const uint32_t mask, zx_time_t timeout) { |
| zx_time_t deadline = zx_clock_get(ZX_CLOCK_MONOTONIC) + timeout; |
| while (true) { |
| if (((dev->regs->sys_ctrl) & mask) == 0) { |
| break; |
| } |
| if (zx_clock_get(ZX_CLOCK_MONOTONIC) > deadline) { |
| SDHCI_ERROR("time out while waiting for reset\n"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| static void imx_sdhci_complete_request_locked(imx_sdhci_device_t* dev, sdmmc_req_t* req, |
| zx_status_t status) { |
| SDHCI_TRACE("complete cmd 0x%08x status %d\n", req->cmd, status); |
| |
| // Disable interrupts when no pending transfer |
| dev->regs->int_signal_en = 0; |
| |
| dev->cmd_req = NULL; |
| dev->data_req = NULL; |
| dev->data_blockid = 0; |
| dev->data_done = false; |
| |
| req->status = status; |
| completion_signal(&dev->req_completion); |
| } |
| |
| static void imx_sdhci_cmd_stage_complete_locked(imx_sdhci_device_t* dev) { |
| SDHCI_TRACE("Got CC interrupt\n"); |
| |
| if (!dev->cmd_req) { |
| SDHCI_TRACE("Spurious CC interupt\n"); |
| return; |
| } |
| |
| sdmmc_req_t* req = dev->cmd_req; |
| volatile struct imx_sdhci_regs* regs = dev->regs; |
| uint32_t cmd = SDMMC_COMMAND(req->cmd) | req->resp_type; |
| |
| // Read the response data |
| if (cmd & SDMMC_RESP_LEN_136) { |
| // TODO: need any quirks?? |
| req->response[0] = (regs->cmd_rsp0 << 8); |
| req->response[1] = (regs->cmd_rsp1 << 8) | ((regs->cmd_rsp0 >> 24) & 0xFF); |
| req->response[2] = (regs->cmd_rsp2 << 8) | ((regs->cmd_rsp1 >> 24) & 0xFF); |
| req->response[3] = (regs->cmd_rsp3 << 8) | ((regs->cmd_rsp2 >> 24) & 0xFF); |
| |
| } else if (cmd & (SDMMC_RESP_LEN_48 | SDMMC_RESP_LEN_48B)) { |
| req->response[0] = regs->cmd_rsp0; |
| req->response[1] = regs->cmd_rsp1; |
| } |
| |
| // We're done if the command has no data stage or if the data stage completed early |
| if (!dev->data_req || dev->data_done) { |
| imx_sdhci_complete_request_locked(dev, dev->cmd_req, ZX_OK); |
| } else { |
| dev->cmd_req = NULL; |
| } |
| } |
| |
| static void imx_sdhci_data_stage_read_ready_locked(imx_sdhci_device_t* dev) { |
| SDHCI_TRACE("Got BRR Interrupts\n"); |
| |
| if (!dev->data_req || !imx_sdmmc_has_data(dev->data_req->resp_type)) { |
| SDHCI_TRACE("Spurious BRR Interrupt\n"); |
| return; |
| } |
| |
| sdmmc_req_t* req = dev->data_req; |
| |
| if (dev->data_req->cmd == MMC_SEND_TUNING_BLOCK) { |
| // tuing commnad is done here |
| imx_sdhci_complete_request_locked(dev, dev->data_req, ZX_OK); |
| } else { |
| // Sequentially read each block |
| for (size_t byteid = 0; byteid < req->blocksize; byteid += 4) { |
| const size_t offset = dev->data_blockid * req->blocksize + byteid; |
| uint32_t* wrd = req->virt + offset; |
| *wrd = dev->regs->data_buff_acc_port; //TODO: Can't read this if DMA is enabled! |
| } |
| dev->data_blockid += 1; |
| } |
| } |
| |
| static void imx_sdhci_data_stage_write_ready_locked(imx_sdhci_device_t* dev) { |
| SDHCI_TRACE("Got BWR Interrupt\n"); |
| |
| if (!dev->data_req || !imx_sdmmc_has_data(dev->data_req->resp_type)) { |
| SDHCI_TRACE("Spurious BWR Interrupt\n"); |
| return; |
| } |
| |
| sdmmc_req_t* req = dev->data_req; |
| |
| // Sequentially write each block |
| for (size_t byteid = 0; byteid < req->blocksize; byteid += 4) { |
| const size_t offset = dev->data_blockid * req->blocksize + byteid; |
| uint32_t* wrd = req->virt + offset; |
| dev->regs->data_buff_acc_port = *wrd; //TODO: Can't write if DMA is enabled |
| } |
| dev->data_blockid += 1; |
| } |
| |
| static void imx_sdhci_transfer_complete_locked(imx_sdhci_device_t* dev) { |
| SDHCI_TRACE("Got TC Interrupt\n"); |
| if (!dev->data_req) { |
| SDHCI_TRACE("Spurious TC Interrupt\n"); |
| return; |
| } |
| |
| if (dev->cmd_req) { |
| dev->data_done = true; |
| } else { |
| imx_sdhci_complete_request_locked(dev, dev->data_req, ZX_OK); |
| } |
| } |
| |
| static void imx_sdhci_error_recovery_locked(imx_sdhci_device_t* dev) { |
| // Reset internal state machines |
| dev->regs->sys_ctrl |= IMX_SDHC_SYS_CTRL_RSTC; |
| imx_sdhci_wait_for_reset(dev, IMX_SDHC_SYS_CTRL_RSTC, ZX_SEC(1)); |
| dev->regs->sys_ctrl |= IMX_SDHC_SYS_CTRL_RSTD; |
| imx_sdhci_wait_for_reset(dev, IMX_SDHC_SYS_CTRL_RSTD, ZX_SEC(1)); |
| |
| // Complete any pending txn with error status |
| if (dev->cmd_req != NULL) { |
| imx_sdhci_complete_request_locked(dev, dev->cmd_req, ZX_ERR_IO); |
| } else if (dev->data_req != NULL) { |
| imx_sdhci_complete_request_locked(dev, dev->data_req, ZX_ERR_IO); |
| } |
| } |
| |
| static uint32_t get_clock_divider(const uint32_t base_clock, const uint32_t target_rate) { |
| uint32_t pre_div = 1; |
| uint32_t div = 1; |
| |
| if (target_rate >= base_clock) { |
| // A clock divider of 0 means "don't divide the clock" |
| // If the base clock is already slow enough to use as the SD clock then |
| // we don't need to divide it any further. |
| return 0; |
| } |
| |
| SDHCI_TRACE("base %d, pre_div %d, div = %d, target_rate %d\n", |
| base_clock, pre_div, div, target_rate); |
| while (base_clock / pre_div / 16 > target_rate && pre_div < 256) { |
| SDHCI_TRACE("base %d, pre_div %d, div = %d, target_rate %d\n", |
| base_clock, pre_div, div, target_rate); |
| pre_div *= 2; |
| } |
| |
| while (base_clock / pre_div / div > target_rate && div < 16) { |
| SDHCI_TRACE("base %d, pre_div %d, div = %d, target_rate %d\n", |
| base_clock, pre_div, div, target_rate); |
| div++; |
| } |
| |
| SDHCI_TRACE("base %d, pre_div %d, div = %d, target_rate %d\n", |
| base_clock, pre_div, div, target_rate); |
| |
| pre_div >>= 1; |
| div -= 1; |
| |
| return (((pre_div & 0xFF) << 16)| (div & 0xF)); |
| } |
| |
| |
| static int imx_sdhci_irq_thread(void *args) { |
| zx_status_t wait_res; |
| imx_sdhci_device_t* dev = (imx_sdhci_device_t*)args; |
| volatile struct imx_sdhci_regs* regs = dev->regs; |
| zx_handle_t irq_handle = dev->irq_handle; |
| |
| while(true) { |
| uint64_t slots; |
| wait_res = zx_interrupt_wait(irq_handle, &slots); |
| if (wait_res != ZX_OK) { |
| SDHCI_ERROR("Interrupt_wait failed with retcode %d\n", wait_res); |
| break; |
| } |
| |
| const uint32_t irq = regs->int_status; |
| SDHCI_TRACE("got irq 0x%08x 0x%08x en 0x%08x sig 0x%08x\n", regs->int_status, irq, |
| regs->int_status_en, regs->int_signal_en); |
| |
| // Acknowledge the IRQs that we stashed. |
| regs->int_status = irq; |
| |
| mtx_lock(&dev->mtx); |
| if (irq & IMX_SDHC_INT_STAT_CC) { |
| imx_sdhci_cmd_stage_complete_locked(dev); |
| } |
| if (irq & IMX_SDHC_INT_STAT_BRR) { |
| imx_sdhci_data_stage_read_ready_locked(dev); |
| } |
| if (irq & IMX_SDHC_INT_STAT_BWR) { |
| imx_sdhci_data_stage_write_ready_locked(dev); |
| } |
| if (irq & IMX_SDHC_INT_STAT_TC) { |
| imx_sdhci_transfer_complete_locked(dev); |
| } |
| if (irq & error_interrupts) { |
| if (irq & IMX_SDHC_INT_STAT_DMAE) { |
| SDHCI_TRACE("ADMA error 0x%x ADMAADDR0 0x%x\n", |
| regs->adma_err_status, regs->adma_sys_addr); |
| } |
| imx_sdhci_error_recovery_locked(dev); |
| } |
| mtx_unlock(&dev->mtx); |
| } |
| return ZX_OK; |
| } |
| |
| static zx_status_t imx_sdhci_build_dma_desc(imx_sdhci_device_t* dev, sdmmc_req_t* req) { |
| SDHCI_FUNC_ENTRY_LOG; |
| return ZX_OK; |
| } |
| |
| static zx_status_t imx_sdhci_start_req_locked(imx_sdhci_device_t* dev, sdmmc_req_t* req) { |
| volatile struct imx_sdhci_regs* regs = dev->regs; |
| const uint32_t arg = req->arg; |
| const uint16_t blkcnt = req->blockcount; |
| const uint16_t blksiz = req->blocksize; |
| uint32_t cmd = SDMMC_COMMAND(req->cmd) | req->resp_type; |
| bool has_data = imx_sdmmc_has_data(req->resp_type); |
| |
| if (req->use_dma && !imx_sdmmc_supports_adma2_64bit(dev)) { |
| SDHCI_INFO("Host does not support 64BIT DMA\n"); |
| // iMX8 support ADMA2 32bit!! why not use that! |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if (req->use_dma) { |
| SDHCI_INFO("we don't support dma yet\t"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| SDHCI_TRACE("start_req cmd=0x%08x (data %d dma %d bsy %d) blkcnt %u blksiz %u\n", |
| cmd, has_data, req->use_dma, imx_sdmmc_cmd_rsp_busy(cmd), blkcnt, blksiz); |
| |
| // Every command requires that the Commnad Inhibit bit is unset |
| uint32_t inhibit_mask = IMX_SDHC_PRES_STATE_CIHB; |
| |
| // Busy type commands must also wait for the DATA Inhibit to be 0 unless it's an abort |
| // command which can be issued with the data lines active |
| if (((cmd & SDMMC_RESP_LEN_48B) == SDMMC_RESP_LEN_48B) && |
| ((cmd & SDMMC_CMD_TYPE_ABORT) == 0)) { |
| inhibit_mask |= IMX_SDHC_PRES_STATE_CDIHB; |
| } |
| |
| // Wait for the inhibit masks from above to become 0 before issueing the command |
| while(regs->pres_state & inhibit_mask) { |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(1))); |
| } |
| |
| // zx_status_t status = ZX_OK; |
| if (has_data) { |
| if (cmd & SDMMC_CMD_MULTI_BLK) { |
| cmd |= SDMMC_CMD_AUTO12; |
| } |
| } |
| |
| regs->blk_att = (blksiz | (blkcnt << 16)); |
| |
| regs->cmd_arg = arg; |
| |
| // Clear any pending interrupts before starting the transaction |
| regs->int_status = regs->int_signal_en; |
| |
| // Unmask and enable interrupts |
| regs->int_signal_en = error_interrupts | normal_interrupts; |
| regs->int_status_en = error_interrupts | normal_interrupts; |
| |
| // Start command |
| regs->cmd_xfr_typ = cmd; |
| |
| dev->cmd_req = req; |
| |
| if (has_data || imx_sdmmc_cmd_rsp_busy(cmd)) { |
| dev->data_req = req; |
| } else { |
| dev->data_req = NULL; |
| } |
| dev->data_blockid = 0; |
| dev->data_done = false; |
| return ZX_OK; |
| // err: |
| // return status; |
| } |
| |
| static zx_status_t imx_sdhci_finish_req(imx_sdhci_device_t* dev, sdmmc_req_t* req) { |
| zx_status_t status = ZX_OK; |
| |
| if (req->use_dma && req->pmt != ZX_HANDLE_INVALID) { |
| status = zx_pmt_unpin(req->pmt); |
| if (status != ZX_OK) { |
| SDHCI_ERROR("error %d in pmt_unpin\n", status); |
| } |
| req->pmt = ZX_HANDLE_INVALID; |
| } |
| return status; |
| } |
| |
| /* SDMMC PROTOCOL Implementations: host_info */ |
| static zx_status_t imx_sdhci_host_info(void* ctx, sdmmc_host_info_t* info) { |
| SDHCI_FUNC_ENTRY_LOG; |
| return ZX_OK; |
| } |
| |
| /* SDMMC PROTOCOL Implementations: set_signal_voltage */ |
| static zx_status_t imx_sdhci_set_signal_voltage(void* ctx, sdmmc_voltage_t voltage) { |
| SDHCI_FUNC_ENTRY_LOG; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| /* SDMMC PROTOCOL Implementations: set_bus_width */ |
| static zx_status_t imx_sdhci_set_bus_width(void* ctx, uint32_t bus_width) { |
| SDHCI_FUNC_ENTRY_LOG; |
| if (bus_width >= SDMMC_BUS_WIDTH_MAX) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| zx_status_t status = ZX_OK; |
| imx_sdhci_device_t* dev = ctx; |
| |
| mtx_lock(&dev->mtx); |
| |
| if ((bus_width == SDMMC_BUS_WIDTH_8) && !(dev->info.caps & SDMMC_HOST_CAP_BUS_WIDTH_8)) { |
| SDHCI_TRACE("8-bit bus width not supported\n"); |
| status = ZX_ERR_NOT_SUPPORTED; |
| goto unlock; |
| } |
| |
| switch (bus_width) { |
| case SDMMC_BUS_WIDTH_1: |
| dev->regs->prot_ctrl &= ~IMX_SDHC_PROT_CTRL_DTW_MASK; |
| dev->regs->prot_ctrl |= IMX_SDHC_PROT_CTRL_DTW_1; |
| break; |
| case SDMMC_BUS_WIDTH_4: |
| dev->regs->prot_ctrl &= ~IMX_SDHC_PROT_CTRL_DTW_MASK; |
| dev->regs->prot_ctrl |= IMX_SDHC_PROT_CTRL_DTW_4; |
| break; |
| case SDMMC_BUS_WIDTH_8: |
| dev->regs->prot_ctrl &= ~IMX_SDHC_PROT_CTRL_DTW_MASK; |
| dev->regs->prot_ctrl |= IMX_SDHC_PROT_CTRL_DTW_8; |
| break; |
| default: |
| break; |
| } |
| |
| SDHCI_TRACE("set bus width to %d\n", bus_width); |
| |
| unlock: |
| mtx_unlock(&dev->mtx); |
| return status; |
| } |
| |
| /* SDMMC PROTOCOL Implementations: set_bus_freq */ |
| static zx_status_t imx_sdhci_set_bus_freq(void* ctx, uint32_t bus_freq) { |
| SDHCI_FUNC_ENTRY_LOG; |
| zx_status_t status = ZX_OK; |
| imx_sdhci_device_t* dev = ctx; |
| |
| mtx_lock(&dev->mtx); |
| |
| const uint32_t divider = get_clock_divider(dev->base_clock, bus_freq); |
| const uint8_t pre_div = (divider >> 16) & 0xFF; |
| const uint8_t div = (divider & 0xF); |
| SDHCI_TRACE("divider %d, pre_div %d, div = %d\n", |
| divider, pre_div, div); |
| |
| volatile struct imx_sdhci_regs* regs = dev->regs; |
| |
| uint32_t iterations = 0; |
| while (regs->pres_state & (IMX_SDHC_PRES_STATE_CIHB | IMX_SDHC_PRES_STATE_CDIHB)) { |
| if (++iterations > 1000) { |
| status = ZX_ERR_TIMED_OUT; |
| goto unlock; |
| } |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(1))); |
| } |
| |
| regs->vend_spec &= ~(IMX_SDHC_VEND_SPEC_FRC_SDCLK_ON); |
| |
| // turn off clocks |
| regs->sys_ctrl &= ~(IMX_SDHC_SYS_CTRL_CLOCK_PEREN | |
| IMX_SDHC_SYS_CTRL_CLOCK_HCKEN | |
| IMX_SDHC_SYS_CTRL_CLOCK_IPGEN | |
| IMX_SDHC_SYS_CTRL_CLOCK_MASK); |
| |
| regs->sys_ctrl |= (IMX_SDHC_SYS_CTRL_CLOCK_PEREN | |
| IMX_SDHC_SYS_CTRL_CLOCK_HCKEN | |
| IMX_SDHC_SYS_CTRL_CLOCK_IPGEN | |
| (pre_div << IMX_SDHC_SYS_CTRL_PREDIV_SHIFT) | |
| (div << IMX_SDHC_SYS_CTRL_DIVIDER_SHIFT)); |
| |
| regs->vend_spec |= (IMX_SDHC_VEND_SPEC_FRC_SDCLK_ON); |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(2))); |
| |
| SDHCI_TRACE("desired freq = %d, actual = %d, (%d, %d. %d)\n", |
| bus_freq, dev->base_clock / (pre_div<<1) / (div+1), dev->base_clock, pre_div, div); |
| |
| unlock: |
| mtx_unlock(&dev->mtx); |
| return status; |
| } |
| |
| /* SDMMC PROTOCOL Implementations: set_timing */ |
| static zx_status_t imx_sdhci_set_timing(void* ctx, sdmmc_timing_t timing) { |
| SDHCI_FUNC_ENTRY_LOG; |
| return ZX_OK; |
| } |
| |
| /* SDMMC PROTOCOL Implementations: hw_reset */ |
| static void imx_sdhci_hw_reset(void* ctx) { |
| SDHCI_FUNC_ENTRY_LOG; |
| } |
| |
| /* SDMMC PROTOCOL Implementations: perform_tuning */ |
| static zx_status_t imx_sdhci_perform_tuning(void* ctx) { |
| SDHCI_FUNC_ENTRY_LOG; |
| return ZX_OK; |
| } |
| |
| /* SDMMC PROTOCOL Implementations: request */ |
| static zx_status_t imx_sdhci_request(void* ctx, sdmmc_req_t* req) { |
| SDHCI_FUNC_ENTRY_LOG; |
| zx_status_t status = ZX_OK; |
| imx_sdhci_device_t* dev = ctx; |
| |
| mtx_lock(&dev->mtx); |
| |
| // one command at a time |
| if ((dev->cmd_req != NULL) || (dev->data_req != NULL)) { |
| status = ZX_ERR_SHOULD_WAIT; |
| goto unlock_out; |
| } |
| |
| status = imx_sdhci_start_req_locked(dev, req); |
| if (status != ZX_OK) { |
| goto unlock_out; |
| } |
| |
| mtx_unlock(&dev->mtx); |
| |
| completion_wait(&dev->req_completion, ZX_TIME_INFINITE); |
| |
| imx_sdhci_finish_req(dev, req); |
| |
| completion_reset(&dev->req_completion); |
| |
| return req->status; |
| |
| unlock_out: |
| mtx_unlock(&dev->mtx); |
| imx_sdhci_finish_req(dev, req); |
| return status; |
| } |
| |
| |
| static sdmmc_protocol_ops_t sdmmc_proto = { |
| .host_info = imx_sdhci_host_info, |
| .set_signal_voltage = imx_sdhci_set_signal_voltage, |
| .set_bus_width = imx_sdhci_set_bus_width, |
| .set_bus_freq = imx_sdhci_set_bus_freq, |
| .set_timing = imx_sdhci_set_timing, |
| .hw_reset = imx_sdhci_hw_reset, |
| .perform_tuning = imx_sdhci_perform_tuning, |
| .request = imx_sdhci_request, |
| }; |
| |
| static void imx_sdhci_unbind(void* ctx) { |
| imx_sdhci_device_t* dev = ctx; |
| device_remove(dev->zxdev); |
| } |
| |
| static void imx_sdhci_release(void* ctx) { |
| imx_sdhci_device_t* dev = ctx; |
| if (dev->regs != NULL) { |
| zx_handle_close(dev->regs_handle); |
| } |
| zx_handle_close(dev->bti_handle); |
| free(dev); |
| } |
| |
| static zx_protocol_device_t imx_sdhci_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .unbind = imx_sdhci_unbind, |
| .release = imx_sdhci_release, |
| |
| }; |
| |
| |
| static zx_status_t imx_sdhci_bind(void* ctx, zx_device_t* parent) { |
| zx_status_t status; |
| |
| imx_sdhci_device_t* dev = calloc(1, sizeof(imx_sdhci_device_t)); |
| if (!dev) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &dev->pdev); |
| if (status != ZX_OK) { |
| SDHCI_ERROR("ZX_PROTOCOL_PLATFORM_DEV not available %d \n", status); |
| goto fail; |
| } |
| |
| status = pdev_map_mmio_buffer(&dev->pdev, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &dev->mmios); |
| if (status != ZX_OK) { |
| SDHCI_ERROR("pdev_map_mmio_buffer failed %d\n", status); |
| goto fail; |
| } |
| |
| // hook up mmio to dev->regs |
| dev->regs = io_buffer_virt(&dev->mmios); |
| |
| status = pdev_get_bti(&dev->pdev, 0, &dev->bti_handle); |
| if (status != ZX_OK) { |
| SDHCI_ERROR("Could not get BTI handle %d\n", status); |
| goto fail; |
| } |
| |
| status = pdev_map_interrupt(&dev->pdev, 0, &dev->irq_handle); |
| if (status != ZX_OK) { |
| SDHCI_ERROR("pdev_map_interrupt failed %d\n", status); |
| goto fail; |
| } |
| |
| thrd_t irq_thread; |
| if (thrd_create_with_name(&irq_thread, imx_sdhci_irq_thread, |
| dev, "imx_sdhci_irq_thread") != thrd_success) { |
| SDHCI_ERROR("Failed to create irq thread\n"); |
| } |
| thrd_detach(irq_thread); |
| |
| dev->base_clock = 200000000; // TODO: Better way of doing this obviously |
| |
| uint32_t caps0 = dev->regs->host_ctrl_cap; |
| SDHCI_ERROR("caps = 0x%x\n", caps0); |
| |
| dev->info.caps |= SDMMC_HOST_CAP_BUS_WIDTH_8; |
| if (caps0 & SDHCI_CORECFG_3P3_VOLT_SUPPORT) { |
| dev->info.caps |= SDMMC_HOST_CAP_VOLTAGE_330; |
| } |
| |
| |
| // Reset host controller |
| dev->regs->sys_ctrl |= IMX_SDHC_SYS_CTRL_RSTA; |
| if (imx_sdhci_wait_for_reset(dev, IMX_SDHC_SYS_CTRL_RSTA, ZX_SEC(1)) != ZX_OK) { |
| SDHCI_ERROR("Did not recover from reset 0x%x\n", dev->regs->sys_ctrl); |
| goto fail; |
| } |
| |
| // RSTA does not reset MMC_BOOT register. so do it manually |
| dev->regs->mmc_boot = 0; |
| |
| // Reset MIX_CTRL and CLK_TUNE_CTRL regs to 0 as well |
| dev->regs->mix_ctrl = 0; |
| dev->regs->clk_tune_ctrl_status = 0; |
| |
| // disable DLL_CTRL delay lie |
| dev->regs->dll_ctrl = 0; |
| |
| // dev->regs->prot_ctrl = 0x00000020 |
| |
| // no dma for now |
| dev->info.max_transfer_size = 0; |
| |
| // enable clocks |
| imx_sdhci_set_bus_freq(dev, SD_FREQ_SETUP_HZ); |
| |
| dev->regs->sys_ctrl |= (0xe << 16); |
| |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(100))); |
| |
| // Disable all interrupts |
| dev->regs->int_signal_en = 0; |
| dev->regs->int_status = 0xffffffff; |
| |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "imx-sdhci", |
| .ctx = dev, |
| .ops = &imx_sdhci_device_proto, |
| .proto_id = ZX_PROTOCOL_SDMMC, |
| .proto_ops = &sdmmc_proto, |
| }; |
| |
| status = device_add(parent, &args, &dev->zxdev); |
| if (status != ZX_OK) { |
| SDHCI_ERROR("device_add failed %d\n", status); |
| goto fail; |
| } |
| |
| return ZX_OK; |
| |
| fail: |
| imx_sdhci_release(dev); |
| return status; |
| } |
| |
| static zx_driver_ops_t imx_sdhci_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = imx_sdhci_bind, |
| }; |
| |
| ZIRCON_DRIVER_BEGIN(imx_sdhci, imx_sdhci_driver_ops, "zircon", "0.1", 4) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_NXP), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_DID, PDEV_DID_IMX_SDHCI), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_IMX8MEVK), |
| ZIRCON_DRIVER_END(imx_sdhci) |