| 1 | /* |
| 2 | * NPCM7xx SD-3.0 / eMMC-4.51 Host Controller |
| 3 | * |
| 4 | * Copyright (c) 2021 Google LLC |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify it |
| 7 | * under the terms of the GNU General Public License as published by the |
| 8 | * Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * for more details. |
| 15 | */ |
| 16 | |
| 17 | #include "qemu/osdep.h" |
| 18 | |
| 19 | #include "hw/sd/sdhci.h" |
| 20 | #include "hw/sd/npcm7xx_sdhci.h" |
| 21 | #include "migration/vmstate.h" |
| 22 | #include "sdhci-internal.h" |
| 23 | #include "qemu/log.h" |
| 24 | |
| 25 | static uint64_t npcm7xx_sdhci_read(void *opaque, hwaddr addr, unsigned int size) |
| 26 | { |
| 27 | NPCM7xxSDHCIState *s = opaque; |
| 28 | uint64_t val = 0; |
| 29 | |
| 30 | switch (addr) { |
| 31 | case NPCM7XX_PRSTVALS_0: |
| 32 | case NPCM7XX_PRSTVALS_1: |
| 33 | case NPCM7XX_PRSTVALS_2: |
| 34 | case NPCM7XX_PRSTVALS_3: |
| 35 | case NPCM7XX_PRSTVALS_4: |
| 36 | case NPCM7XX_PRSTVALS_5: |
| 37 | val = s->regs.prstvals[(addr - NPCM7XX_PRSTVALS_0) / 2]; |
| 38 | break; |
| 39 | case NPCM7XX_BOOTTOCTRL: |
| 40 | val = s->regs.boottoctrl; |
| 41 | break; |
| 42 | default: |
| 43 | qemu_log_mask(LOG_GUEST_ERROR, "SDHCI read of nonexistent reg: 0x%02" |
| 44 | HWADDR_PRIx, addr); |
| 45 | break; |
| 46 | } |
| 47 | |
| 48 | return val; |
| 49 | } |
| 50 | |
| 51 | static void npcm7xx_sdhci_write(void *opaque, hwaddr addr, uint64_t val, |
| 52 | unsigned int size) |
| 53 | { |
| 54 | NPCM7xxSDHCIState *s = opaque; |
| 55 | |
| 56 | switch (addr) { |
| 57 | case NPCM7XX_BOOTTOCTRL: |
| 58 | s->regs.boottoctrl = val; |
| 59 | break; |
| 60 | default: |
| 61 | qemu_log_mask(LOG_GUEST_ERROR, "SDHCI write of nonexistent reg: 0x%02" |
| 62 | HWADDR_PRIx, addr); |
| 63 | break; |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | static bool npcm7xx_sdhci_check_mem_op(void *opaque, hwaddr addr, |
| 68 | unsigned size, bool is_write, |
| 69 | MemTxAttrs attrs) |
| 70 | { |
| 71 | switch (addr) { |
| 72 | case NPCM7XX_PRSTVALS_0: |
| 73 | case NPCM7XX_PRSTVALS_1: |
| 74 | case NPCM7XX_PRSTVALS_2: |
| 75 | case NPCM7XX_PRSTVALS_3: |
| 76 | case NPCM7XX_PRSTVALS_4: |
| 77 | case NPCM7XX_PRSTVALS_5: |
| 78 | /* RO Word */ |
| 79 | return !is_write && size == 2; |
| 80 | case NPCM7XX_BOOTTOCTRL: |
| 81 | /* R/W Dword */ |
| 82 | return size == 4; |
| 83 | default: |
| 84 | return false; |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | static const MemoryRegionOps npcm7xx_sdhci_ops = { |
| 89 | .read = npcm7xx_sdhci_read, |
| 90 | .write = npcm7xx_sdhci_write, |
| 91 | .endianness = DEVICE_NATIVE_ENDIAN, |
| 92 | .valid = { |
| 93 | .min_access_size = 1, |
| 94 | .max_access_size = 4, |
| 95 | .unaligned = false, |
| 96 | .accepts = npcm7xx_sdhci_check_mem_op, |
| 97 | }, |
| 98 | }; |
| 99 | |
| 100 | static void npcm7xx_sdhci_realize(DeviceState *dev, Error **errp) |
| 101 | { |
| 102 | NPCM7xxSDHCIState *s = NPCM7XX_SDHCI(dev); |
| 103 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
| 104 | SysBusDevice *sbd_sdhci = SYS_BUS_DEVICE(&s->sdhci); |
| 105 | |
| 106 | memory_region_init(&s->container, OBJECT(s), |
| 107 | "npcm7xx.sdhci-container", 0x1000); |
| 108 | sysbus_init_mmio(sbd, &s->container); |
| 109 | |
| 110 | memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_sdhci_ops, s, |
| 111 | TYPE_NPCM7XX_SDHCI, NPCM7XX_SDHCI_REGSIZE); |
| 112 | memory_region_add_subregion_overlap(&s->container, NPCM7XX_PRSTVALS, |
| 113 | &s->iomem, 1); |
| 114 | |
| 115 | sysbus_realize(sbd_sdhci, errp); |
| 116 | memory_region_add_subregion(&s->container, 0, |
| 117 | sysbus_mmio_get_region(sbd_sdhci, 0)); |
| 118 | |
| 119 | /* propagate irq and "sd-bus" from generic-sdhci */ |
| 120 | sysbus_pass_irq(sbd, sbd_sdhci); |
| 121 | s->bus = qdev_get_child_bus(DEVICE(sbd_sdhci), "sd-bus"); |
| 122 | |
| 123 | /* Set the read only preset values. */ |
| 124 | memset(s->regs.prstvals, 0, sizeof(s->regs.prstvals)); |
| 125 | s->regs.prstvals[0] = NPCM7XX_PRSTVALS_0_RESET; |
| 126 | s->regs.prstvals[1] = NPCM7XX_PRSTVALS_1_RESET; |
| 127 | s->regs.prstvals[3] = NPCM7XX_PRSTVALS_3_RESET; |
| 128 | } |
| 129 | |
| 130 | static void npcm7xx_sdhci_reset(DeviceState *dev) |
| 131 | { |
| 132 | NPCM7xxSDHCIState *s = NPCM7XX_SDHCI(dev); |
| 133 | device_cold_reset(DEVICE(&s->sdhci)); |
| 134 | s->regs.boottoctrl = 0; |
| 135 | |
| 136 | s->sdhci.prnsts = NPCM7XX_PRSNTS_RESET; |
| 137 | s->sdhci.blkgap = NPCM7XX_BLKGAP_RESET; |
| 138 | s->sdhci.capareg = NPCM7XX_CAPAB_RESET; |
| 139 | s->sdhci.maxcurr = NPCM7XX_MAXCURR_RESET; |
| 140 | s->sdhci.version = NPCM7XX_HCVER_RESET; |
| 141 | } |
| 142 | |
| 143 | static const VMStateDescription vmstate_npcm7xx_sdhci = { |
| 144 | .name = TYPE_NPCM7XX_SDHCI, |
| 145 | .version_id = 0, |
| 146 | .fields = (const VMStateField[]) { |
| 147 | VMSTATE_UINT32(regs.boottoctrl, NPCM7xxSDHCIState), |
| 148 | VMSTATE_END_OF_LIST(), |
| 149 | }, |
| 150 | }; |
| 151 | |
| 152 | static void npcm7xx_sdhci_class_init(ObjectClass *classp, const void *data) |
| 153 | { |
| 154 | DeviceClass *dc = DEVICE_CLASS(classp); |
| 155 | |
| 156 | dc->desc = "NPCM7xx SD/eMMC Host Controller"; |
| 157 | dc->realize = npcm7xx_sdhci_realize; |
| 158 | device_class_set_legacy_reset(dc, npcm7xx_sdhci_reset); |
| 159 | dc->vmsd = &vmstate_npcm7xx_sdhci; |
| 160 | } |
| 161 | |
| 162 | static void npcm7xx_sdhci_instance_init(Object *obj) |
| 163 | { |
| 164 | NPCM7xxSDHCIState *s = NPCM7XX_SDHCI(obj); |
| 165 | |
| 166 | object_initialize_child(OBJECT(s), TYPE_SYSBUS_SDHCI, &s->sdhci, |
| 167 | TYPE_SYSBUS_SDHCI); |
| 168 | } |
| 169 | |
| 170 | static const TypeInfo npcm7xx_sdhci_types[] = { |
| 171 | { |
| 172 | .name = TYPE_NPCM7XX_SDHCI, |
| 173 | .parent = TYPE_SYS_BUS_DEVICE, |
| 174 | .instance_size = sizeof(NPCM7xxSDHCIState), |
| 175 | .instance_init = npcm7xx_sdhci_instance_init, |
| 176 | .class_init = npcm7xx_sdhci_class_init, |
| 177 | }, |
| 178 | }; |
| 179 | |
| 180 | DEFINE_TYPES(npcm7xx_sdhci_types) |