| /* |
| * NPCM7xx SD-3.0 / eMMC-4.51 Host Controller |
| * |
| * Copyright (c) 2021 Google LLC |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| */ |
| |
| #include "qemu/osdep.h" |
| |
| #include "hw/sd/sdhci.h" |
| #include "hw/sd/npcm7xx_sdhci.h" |
| #include "migration/vmstate.h" |
| #include "sdhci-internal.h" |
| #include "qemu/log.h" |
| |
| static uint64_t npcm7xx_sdhci_read(void *opaque, hwaddr addr, unsigned int size) |
| { |
| NPCM7xxSDHCIState *s = opaque; |
| uint64_t val = 0; |
| |
| switch (addr) { |
| case NPCM7XX_PRSTVALS_0: |
| case NPCM7XX_PRSTVALS_1: |
| case NPCM7XX_PRSTVALS_2: |
| case NPCM7XX_PRSTVALS_3: |
| case NPCM7XX_PRSTVALS_4: |
| case NPCM7XX_PRSTVALS_5: |
| val = s->regs.prstvals[(addr - NPCM7XX_PRSTVALS_0) / 2]; |
| break; |
| case NPCM7XX_BOOTTOCTRL: |
| val = s->regs.boottoctrl; |
| break; |
| default: |
| qemu_log_mask(LOG_GUEST_ERROR, "SDHCI read of nonexistent reg: 0x%02" |
| HWADDR_PRIx, addr); |
| break; |
| } |
| |
| return val; |
| } |
| |
| static void npcm7xx_sdhci_write(void *opaque, hwaddr addr, uint64_t val, |
| unsigned int size) |
| { |
| NPCM7xxSDHCIState *s = opaque; |
| |
| switch (addr) { |
| case NPCM7XX_BOOTTOCTRL: |
| s->regs.boottoctrl = val; |
| break; |
| default: |
| qemu_log_mask(LOG_GUEST_ERROR, "SDHCI write of nonexistent reg: 0x%02" |
| HWADDR_PRIx, addr); |
| break; |
| } |
| } |
| |
| static bool npcm7xx_sdhci_check_mem_op(void *opaque, hwaddr addr, |
| unsigned size, bool is_write, |
| MemTxAttrs attrs) |
| { |
| switch (addr) { |
| case NPCM7XX_PRSTVALS_0: |
| case NPCM7XX_PRSTVALS_1: |
| case NPCM7XX_PRSTVALS_2: |
| case NPCM7XX_PRSTVALS_3: |
| case NPCM7XX_PRSTVALS_4: |
| case NPCM7XX_PRSTVALS_5: |
| /* RO Word */ |
| return !is_write && size == 2; |
| case NPCM7XX_BOOTTOCTRL: |
| /* R/W Dword */ |
| return size == 4; |
| default: |
| return false; |
| } |
| } |
| |
| static const MemoryRegionOps npcm7xx_sdhci_ops = { |
| .read = npcm7xx_sdhci_read, |
| .write = npcm7xx_sdhci_write, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| .valid = { |
| .min_access_size = 1, |
| .max_access_size = 4, |
| .unaligned = false, |
| .accepts = npcm7xx_sdhci_check_mem_op, |
| }, |
| }; |
| |
| static void npcm7xx_sdhci_realize(DeviceState *dev, Error **errp) |
| { |
| NPCM7xxSDHCIState *s = NPCM7XX_SDHCI(dev); |
| SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
| SysBusDevice *sbd_sdhci = SYS_BUS_DEVICE(&s->sdhci); |
| |
| memory_region_init(&s->container, OBJECT(s), |
| "npcm7xx.sdhci-container", 0x1000); |
| sysbus_init_mmio(sbd, &s->container); |
| |
| memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_sdhci_ops, s, |
| TYPE_NPCM7XX_SDHCI, NPCM7XX_SDHCI_REGSIZE); |
| memory_region_add_subregion_overlap(&s->container, NPCM7XX_PRSTVALS, |
| &s->iomem, 1); |
| |
| sysbus_realize(sbd_sdhci, errp); |
| memory_region_add_subregion(&s->container, 0, |
| sysbus_mmio_get_region(sbd_sdhci, 0)); |
| |
| /* propagate irq and "sd-bus" from generic-sdhci */ |
| sysbus_pass_irq(sbd, sbd_sdhci); |
| s->bus = qdev_get_child_bus(DEVICE(sbd_sdhci), "sd-bus"); |
| |
| /* Set the read only preset values. */ |
| memset(s->regs.prstvals, 0, sizeof(s->regs.prstvals)); |
| s->regs.prstvals[0] = NPCM7XX_PRSTVALS_0_RESET; |
| s->regs.prstvals[1] = NPCM7XX_PRSTVALS_1_RESET; |
| s->regs.prstvals[3] = NPCM7XX_PRSTVALS_3_RESET; |
| } |
| |
| static void npcm7xx_sdhci_reset(DeviceState *dev) |
| { |
| NPCM7xxSDHCIState *s = NPCM7XX_SDHCI(dev); |
| device_cold_reset(DEVICE(&s->sdhci)); |
| s->regs.boottoctrl = 0; |
| |
| s->sdhci.prnsts = NPCM7XX_PRSNTS_RESET; |
| s->sdhci.blkgap = NPCM7XX_BLKGAP_RESET; |
| s->sdhci.capareg = NPCM7XX_CAPAB_RESET; |
| s->sdhci.maxcurr = NPCM7XX_MAXCURR_RESET; |
| s->sdhci.version = NPCM7XX_HCVER_RESET; |
| } |
| |
| static const VMStateDescription vmstate_npcm7xx_sdhci = { |
| .name = TYPE_NPCM7XX_SDHCI, |
| .version_id = 0, |
| .fields = (const VMStateField[]) { |
| VMSTATE_UINT32(regs.boottoctrl, NPCM7xxSDHCIState), |
| VMSTATE_END_OF_LIST(), |
| }, |
| }; |
| |
| static void npcm7xx_sdhci_class_init(ObjectClass *classp, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(classp); |
| |
| dc->desc = "NPCM7xx SD/eMMC Host Controller"; |
| dc->realize = npcm7xx_sdhci_realize; |
| device_class_set_legacy_reset(dc, npcm7xx_sdhci_reset); |
| dc->vmsd = &vmstate_npcm7xx_sdhci; |
| } |
| |
| static void npcm7xx_sdhci_instance_init(Object *obj) |
| { |
| NPCM7xxSDHCIState *s = NPCM7XX_SDHCI(obj); |
| |
| object_initialize_child(OBJECT(s), TYPE_SYSBUS_SDHCI, &s->sdhci, |
| TYPE_SYSBUS_SDHCI); |
| } |
| |
| static const TypeInfo npcm7xx_sdhci_types[] = { |
| { |
| .name = TYPE_NPCM7XX_SDHCI, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(NPCM7xxSDHCIState), |
| .instance_init = npcm7xx_sdhci_instance_init, |
| .class_init = npcm7xx_sdhci_class_init, |
| }, |
| }; |
| |
| DEFINE_TYPES(npcm7xx_sdhci_types) |