1// Copyright 2017 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <ddk/binding.h> 6#include <ddk/device.h> 7#include <ddk/driver.h> 8#include <ddk/protocol/pci.h> 9#include <ddk/protocol/sdhci.h> 10 11#include <hw/sdhci.h> 12 13#include <inttypes.h> 14#include <stdio.h> 15#include <stdlib.h> 16#include <string.h> 17#include <sys/param.h> 18#include <threads.h> 19#include <unistd.h> 20 21typedef struct pci_sdhci_device { 22 zx_device_t* zxdev; 23 pci_protocol_t pci; 24 25 volatile sdhci_regs_t* regs; 26 uint64_t regs_size; 27 zx_handle_t regs_handle; 28 zx_handle_t bti_handle; 29} pci_sdhci_device_t; 30 31static zx_status_t pci_sdhci_get_interrupt(void* ctx, zx_handle_t* handle_out) { 32 pci_sdhci_device_t* dev = ctx; 33 // select irq mode 34 zx_status_t status = pci_set_irq_mode(&dev->pci, ZX_PCIE_IRQ_MODE_MSI, 1); 35 if (status < 0) { 36 status = pci_set_irq_mode(&dev->pci, ZX_PCIE_IRQ_MODE_LEGACY, 1); 37 if (status < 0) { 38 printf("pci-sdhci: error %d setting irq mode\n", status); 39 return status; 40 } 41 printf("pci-sdhci: selected legacy irq mode\n"); 42 } 43 // get irq handle 44 status = pci_map_interrupt(&dev->pci, 0, handle_out); 45 if (status != ZX_OK) { 46 printf("pci-sdhci: error %d getting irq handle\n", status); 47 return status; 48 } else { 49 return ZX_OK; 50 } 51} 52 53static zx_status_t pci_sdhci_get_mmio(void* ctx, volatile sdhci_regs_t** out) { 54 pci_sdhci_device_t* dev = ctx; 55 if (dev->regs == NULL) { 56 zx_status_t status = pci_map_bar(&dev->pci, 0u, ZX_CACHE_POLICY_UNCACHED_DEVICE, 57 (void**)&dev->regs, &dev->regs_size, &dev->regs_handle); 58 if (status != ZX_OK) { 59 printf("pci-sdhci: error %d mapping register window\n", status); 60 return status; 61 } 62 } 63 *out = dev->regs; 64 return ZX_OK; 65} 66 67static zx_status_t pci_sdhci_get_bti(void* ctx, uint32_t index, zx_handle_t* out_handle) { 68 pci_sdhci_device_t* dev = ctx; 69 if (dev->bti_handle == ZX_HANDLE_INVALID) { 70 zx_status_t st = pci_get_bti(&dev->pci, index, &dev->bti_handle); 71 if (st != ZX_OK) { 72 return st; 73 } 74 } 75 return zx_handle_duplicate(dev->bti_handle, ZX_RIGHT_SAME_RIGHTS, out_handle); 76} 77 78static uint32_t pci_sdhci_get_base_clock(void* ctx) { 79 return 0; 80} 81 82static uint64_t pci_sdhci_get_quirks(void* ctx) { 83 return SDHCI_QUIRK_STRIP_RESPONSE_CRC_PRESERVE_ORDER; 84} 85 86static void pci_sdhci_hw_reset(void* ctx) { 87 pci_sdhci_device_t* dev = ctx; 88 if (!dev->regs) { 89 return; 90 } 91 uint32_t val = dev->regs->ctrl0; 92 val |= SDHCI_EMMC_HW_RESET; 93 dev->regs->ctrl0 = val; 94 // minimum is 1us but wait 9us for good measure 95 zx_nanosleep(zx_deadline_after(ZX_USEC(9))); 96 val &= ~SDHCI_EMMC_HW_RESET; 97 dev->regs->ctrl0 = val; 98 // minimum is 200us but wait 300us for good measure 99 zx_nanosleep(zx_deadline_after(ZX_USEC(300))); 100} 101 102static sdhci_protocol_ops_t pci_sdhci_sdhci_proto = { 103 .get_interrupt = pci_sdhci_get_interrupt, 104 .get_mmio = pci_sdhci_get_mmio, 105 .get_bti = pci_sdhci_get_bti, 106 .get_base_clock = pci_sdhci_get_base_clock, 107 .get_quirks = pci_sdhci_get_quirks, 108 .hw_reset = pci_sdhci_hw_reset, 109}; 110 111static void pci_sdhci_unbind(void* ctx) { 112 pci_sdhci_device_t* dev = ctx; 113 device_remove(dev->zxdev); 114} 115 116static void pci_sdhci_release(void* ctx) { 117 pci_sdhci_device_t* dev = ctx; 118 if (dev->regs != NULL) { 119 zx_handle_close(dev->regs_handle); 120 } 121 zx_handle_close(dev->bti_handle); 122 free(dev); 123} 124 125static zx_protocol_device_t pci_sdhci_device_proto = { 126 .version = DEVICE_OPS_VERSION, 127 .unbind = pci_sdhci_unbind, 128 .release = pci_sdhci_release, 129}; 130 131static zx_status_t pci_sdhci_bind(void* ctx, zx_device_t* parent) { 132 printf("pci-sdhci: bind\n"); 133 pci_sdhci_device_t* dev = calloc(1, sizeof(pci_sdhci_device_t)); 134 if (!dev) { 135 printf("pci-sdhci: out of memory\n"); 136 return ZX_ERR_NO_MEMORY; 137 } 138 139 zx_status_t status = ZX_OK; 140 if (device_get_protocol(parent, ZX_PROTOCOL_PCI, (void*)&dev->pci)) { 141 status = ZX_ERR_NOT_SUPPORTED; 142 goto fail; 143 } 144 145 status = dev->pci.ops->enable_bus_master(dev->pci.ctx, true); 146 if (status < 0) { 147 printf("pci-sdhci: error %d in enable bus master\n", status); 148 goto fail; 149 } 150 151 device_add_args_t args = { 152 .version = DEVICE_ADD_ARGS_VERSION, 153 .name = "pci-sdhci", 154 .ctx = dev, 155 .ops = &pci_sdhci_device_proto, 156 .proto_id = ZX_PROTOCOL_SDHCI, 157 .proto_ops = &pci_sdhci_sdhci_proto, 158 }; 159 160 status = device_add(parent, &args, &dev->zxdev); 161 if (status != ZX_OK) { 162 goto fail; 163 } 164 165 return ZX_OK; 166fail: 167 free(dev); 168 return status; 169} 170 171static zx_driver_ops_t pci_sdhci_driver_ops = { 172 .version = DRIVER_OPS_VERSION, 173 .bind = pci_sdhci_bind, 174}; 175 176// clang-format off 177ZIRCON_DRIVER_BEGIN(pci_sdhci, pci_sdhci_driver_ops, "zircon", "0.1", 4) 178 BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI), 179 BI_ABORT_IF(NE, BIND_PCI_CLASS, 0x08), 180 BI_ABORT_IF(NE, BIND_PCI_SUBCLASS, 0x05), 181 BI_MATCH_IF(EQ, BIND_PCI_INTERFACE, 0x01), 182ZIRCON_DRIVER_END(pci_sdhci) 183