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