]>
Commit | Line | Data |
---|---|---|
2bea128c EJ |
1 | /* |
2 | * Aspeed SD Host Controller | |
3 | * Eddie James <eajames@linux.ibm.com> | |
4 | * | |
5 | * Copyright (C) 2019 IBM Corp | |
6 | * SPDX-License-Identifer: GPL-2.0-or-later | |
7 | */ | |
8 | ||
9 | #include "qemu/osdep.h" | |
10 | #include "qemu/log.h" | |
11 | #include "qemu/error-report.h" | |
12 | #include "hw/sd/aspeed_sdhci.h" | |
13 | #include "qapi/error.h" | |
14 | #include "hw/irq.h" | |
15 | #include "migration/vmstate.h" | |
0e2c24c6 | 16 | #include "hw/qdev-properties.h" |
2bea128c EJ |
17 | |
18 | #define ASPEED_SDHCI_INFO 0x00 | |
f31e8f13 CLG |
19 | #define ASPEED_SDHCI_INFO_SLOT1 (1 << 17) |
20 | #define ASPEED_SDHCI_INFO_SLOT0 (1 << 16) | |
21 | #define ASPEED_SDHCI_INFO_RESET (1 << 0) | |
2bea128c EJ |
22 | #define ASPEED_SDHCI_DEBOUNCE 0x04 |
23 | #define ASPEED_SDHCI_DEBOUNCE_RESET 0x00000005 | |
24 | #define ASPEED_SDHCI_BUS 0x08 | |
25 | #define ASPEED_SDHCI_SDIO_140 0x10 | |
26 | #define ASPEED_SDHCI_SDIO_148 0x18 | |
27 | #define ASPEED_SDHCI_SDIO_240 0x20 | |
28 | #define ASPEED_SDHCI_SDIO_248 0x28 | |
29 | #define ASPEED_SDHCI_WP_POL 0xec | |
30 | #define ASPEED_SDHCI_CARD_DET 0xf0 | |
31 | #define ASPEED_SDHCI_IRQ_STAT 0xfc | |
32 | ||
33 | #define TO_REG(addr) ((addr) / sizeof(uint32_t)) | |
34 | ||
35 | static uint64_t aspeed_sdhci_read(void *opaque, hwaddr addr, unsigned int size) | |
36 | { | |
37 | uint32_t val = 0; | |
38 | AspeedSDHCIState *sdhci = opaque; | |
39 | ||
40 | switch (addr) { | |
41 | case ASPEED_SDHCI_SDIO_140: | |
42 | val = (uint32_t)sdhci->slots[0].capareg; | |
43 | break; | |
44 | case ASPEED_SDHCI_SDIO_148: | |
45 | val = (uint32_t)sdhci->slots[0].maxcurr; | |
46 | break; | |
47 | case ASPEED_SDHCI_SDIO_240: | |
48 | val = (uint32_t)sdhci->slots[1].capareg; | |
49 | break; | |
50 | case ASPEED_SDHCI_SDIO_248: | |
51 | val = (uint32_t)sdhci->slots[1].maxcurr; | |
52 | break; | |
53 | default: | |
54 | if (addr < ASPEED_SDHCI_REG_SIZE) { | |
55 | val = sdhci->regs[TO_REG(addr)]; | |
56 | } else { | |
57 | qemu_log_mask(LOG_GUEST_ERROR, | |
58 | "%s: Out-of-bounds read at 0x%" HWADDR_PRIx "\n", | |
59 | __func__, addr); | |
60 | } | |
61 | } | |
62 | ||
63 | return (uint64_t)val; | |
64 | } | |
65 | ||
66 | static void aspeed_sdhci_write(void *opaque, hwaddr addr, uint64_t val, | |
67 | unsigned int size) | |
68 | { | |
69 | AspeedSDHCIState *sdhci = opaque; | |
70 | ||
71 | switch (addr) { | |
f31e8f13 CLG |
72 | case ASPEED_SDHCI_INFO: |
73 | /* The RESET bit automatically clears. */ | |
74 | sdhci->regs[TO_REG(addr)] = (uint32_t)val & ~ASPEED_SDHCI_INFO_RESET; | |
75 | break; | |
2bea128c EJ |
76 | case ASPEED_SDHCI_SDIO_140: |
77 | sdhci->slots[0].capareg = (uint64_t)(uint32_t)val; | |
78 | break; | |
79 | case ASPEED_SDHCI_SDIO_148: | |
80 | sdhci->slots[0].maxcurr = (uint64_t)(uint32_t)val; | |
81 | break; | |
82 | case ASPEED_SDHCI_SDIO_240: | |
83 | sdhci->slots[1].capareg = (uint64_t)(uint32_t)val; | |
84 | break; | |
85 | case ASPEED_SDHCI_SDIO_248: | |
86 | sdhci->slots[1].maxcurr = (uint64_t)(uint32_t)val; | |
87 | break; | |
88 | default: | |
89 | if (addr < ASPEED_SDHCI_REG_SIZE) { | |
90 | sdhci->regs[TO_REG(addr)] = (uint32_t)val; | |
91 | } else { | |
92 | qemu_log_mask(LOG_GUEST_ERROR, | |
93 | "%s: Out-of-bounds write at 0x%" HWADDR_PRIx "\n", | |
94 | __func__, addr); | |
95 | } | |
96 | } | |
97 | } | |
98 | ||
99 | static const MemoryRegionOps aspeed_sdhci_ops = { | |
100 | .read = aspeed_sdhci_read, | |
101 | .write = aspeed_sdhci_write, | |
102 | .endianness = DEVICE_NATIVE_ENDIAN, | |
103 | .valid.min_access_size = 4, | |
104 | .valid.max_access_size = 4, | |
105 | }; | |
106 | ||
107 | static void aspeed_sdhci_set_irq(void *opaque, int n, int level) | |
108 | { | |
109 | AspeedSDHCIState *sdhci = opaque; | |
110 | ||
111 | if (level) { | |
112 | sdhci->regs[TO_REG(ASPEED_SDHCI_IRQ_STAT)] |= BIT(n); | |
113 | ||
114 | qemu_irq_raise(sdhci->irq); | |
115 | } else { | |
116 | sdhci->regs[TO_REG(ASPEED_SDHCI_IRQ_STAT)] &= ~BIT(n); | |
117 | ||
118 | qemu_irq_lower(sdhci->irq); | |
119 | } | |
120 | } | |
121 | ||
122 | static void aspeed_sdhci_realize(DeviceState *dev, Error **errp) | |
123 | { | |
2bea128c EJ |
124 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
125 | AspeedSDHCIState *sdhci = ASPEED_SDHCI(dev); | |
126 | ||
127 | /* Create input irqs for the slots */ | |
128 | qdev_init_gpio_in_named_with_opaque(DEVICE(sbd), aspeed_sdhci_set_irq, | |
0e2c24c6 | 129 | sdhci, NULL, sdhci->num_slots); |
2bea128c EJ |
130 | |
131 | sysbus_init_irq(sbd, &sdhci->irq); | |
132 | memory_region_init_io(&sdhci->iomem, OBJECT(sdhci), &aspeed_sdhci_ops, | |
133 | sdhci, TYPE_ASPEED_SDHCI, 0x1000); | |
134 | sysbus_init_mmio(sbd, &sdhci->iomem); | |
135 | ||
0e2c24c6 | 136 | for (int i = 0; i < sdhci->num_slots; ++i) { |
2bea128c EJ |
137 | Object *sdhci_slot = OBJECT(&sdhci->slots[i]); |
138 | SysBusDevice *sbd_slot = SYS_BUS_DEVICE(&sdhci->slots[i]); | |
139 | ||
668f62ec | 140 | if (!object_property_set_int(sdhci_slot, "sd-spec-version", 2, errp)) { |
2bea128c EJ |
141 | return; |
142 | } | |
143 | ||
778a2dc5 | 144 | if (!object_property_set_uint(sdhci_slot, "capareg", |
668f62ec | 145 | ASPEED_SDHCI_CAPABILITIES, errp)) { |
2bea128c EJ |
146 | return; |
147 | } | |
148 | ||
668f62ec | 149 | if (!sysbus_realize(sbd_slot, errp)) { |
2bea128c EJ |
150 | return; |
151 | } | |
152 | ||
153 | sysbus_connect_irq(sbd_slot, 0, qdev_get_gpio_in(DEVICE(sbd), i)); | |
154 | memory_region_add_subregion(&sdhci->iomem, (i + 1) * 0x100, | |
155 | &sdhci->slots[i].iomem); | |
156 | } | |
157 | } | |
158 | ||
159 | static void aspeed_sdhci_reset(DeviceState *dev) | |
160 | { | |
161 | AspeedSDHCIState *sdhci = ASPEED_SDHCI(dev); | |
162 | ||
163 | memset(sdhci->regs, 0, ASPEED_SDHCI_REG_SIZE); | |
f31e8f13 CLG |
164 | |
165 | sdhci->regs[TO_REG(ASPEED_SDHCI_INFO)] = ASPEED_SDHCI_INFO_SLOT0; | |
166 | if (sdhci->num_slots == 2) { | |
167 | sdhci->regs[TO_REG(ASPEED_SDHCI_INFO)] |= ASPEED_SDHCI_INFO_SLOT1; | |
168 | } | |
2bea128c EJ |
169 | sdhci->regs[TO_REG(ASPEED_SDHCI_DEBOUNCE)] = ASPEED_SDHCI_DEBOUNCE_RESET; |
170 | } | |
171 | ||
172 | static const VMStateDescription vmstate_aspeed_sdhci = { | |
173 | .name = TYPE_ASPEED_SDHCI, | |
174 | .version_id = 1, | |
175 | .fields = (VMStateField[]) { | |
176 | VMSTATE_UINT32_ARRAY(regs, AspeedSDHCIState, ASPEED_SDHCI_NUM_REGS), | |
177 | VMSTATE_END_OF_LIST(), | |
178 | }, | |
179 | }; | |
180 | ||
0e2c24c6 AJ |
181 | static Property aspeed_sdhci_properties[] = { |
182 | DEFINE_PROP_UINT8("num-slots", AspeedSDHCIState, num_slots, 0), | |
183 | DEFINE_PROP_END_OF_LIST(), | |
184 | }; | |
185 | ||
2bea128c EJ |
186 | static void aspeed_sdhci_class_init(ObjectClass *classp, void *data) |
187 | { | |
188 | DeviceClass *dc = DEVICE_CLASS(classp); | |
189 | ||
190 | dc->realize = aspeed_sdhci_realize; | |
191 | dc->reset = aspeed_sdhci_reset; | |
192 | dc->vmsd = &vmstate_aspeed_sdhci; | |
0e2c24c6 | 193 | device_class_set_props(dc, aspeed_sdhci_properties); |
2bea128c EJ |
194 | } |
195 | ||
196 | static TypeInfo aspeed_sdhci_info = { | |
197 | .name = TYPE_ASPEED_SDHCI, | |
198 | .parent = TYPE_SYS_BUS_DEVICE, | |
199 | .instance_size = sizeof(AspeedSDHCIState), | |
200 | .class_init = aspeed_sdhci_class_init, | |
201 | }; | |
202 | ||
203 | static void aspeed_sdhci_register_types(void) | |
204 | { | |
205 | type_register_static(&aspeed_sdhci_info); | |
206 | } | |
207 | ||
208 | type_init(aspeed_sdhci_register_types) |