]>
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 | |
19 | #define ASPEED_SDHCI_INFO_RESET 0x00030000 | |
20 | #define ASPEED_SDHCI_DEBOUNCE 0x04 | |
21 | #define ASPEED_SDHCI_DEBOUNCE_RESET 0x00000005 | |
22 | #define ASPEED_SDHCI_BUS 0x08 | |
23 | #define ASPEED_SDHCI_SDIO_140 0x10 | |
24 | #define ASPEED_SDHCI_SDIO_148 0x18 | |
25 | #define ASPEED_SDHCI_SDIO_240 0x20 | |
26 | #define ASPEED_SDHCI_SDIO_248 0x28 | |
27 | #define ASPEED_SDHCI_WP_POL 0xec | |
28 | #define ASPEED_SDHCI_CARD_DET 0xf0 | |
29 | #define ASPEED_SDHCI_IRQ_STAT 0xfc | |
30 | ||
31 | #define TO_REG(addr) ((addr) / sizeof(uint32_t)) | |
32 | ||
33 | static uint64_t aspeed_sdhci_read(void *opaque, hwaddr addr, unsigned int size) | |
34 | { | |
35 | uint32_t val = 0; | |
36 | AspeedSDHCIState *sdhci = opaque; | |
37 | ||
38 | switch (addr) { | |
39 | case ASPEED_SDHCI_SDIO_140: | |
40 | val = (uint32_t)sdhci->slots[0].capareg; | |
41 | break; | |
42 | case ASPEED_SDHCI_SDIO_148: | |
43 | val = (uint32_t)sdhci->slots[0].maxcurr; | |
44 | break; | |
45 | case ASPEED_SDHCI_SDIO_240: | |
46 | val = (uint32_t)sdhci->slots[1].capareg; | |
47 | break; | |
48 | case ASPEED_SDHCI_SDIO_248: | |
49 | val = (uint32_t)sdhci->slots[1].maxcurr; | |
50 | break; | |
51 | default: | |
52 | if (addr < ASPEED_SDHCI_REG_SIZE) { | |
53 | val = sdhci->regs[TO_REG(addr)]; | |
54 | } else { | |
55 | qemu_log_mask(LOG_GUEST_ERROR, | |
56 | "%s: Out-of-bounds read at 0x%" HWADDR_PRIx "\n", | |
57 | __func__, addr); | |
58 | } | |
59 | } | |
60 | ||
61 | return (uint64_t)val; | |
62 | } | |
63 | ||
64 | static void aspeed_sdhci_write(void *opaque, hwaddr addr, uint64_t val, | |
65 | unsigned int size) | |
66 | { | |
67 | AspeedSDHCIState *sdhci = opaque; | |
68 | ||
69 | switch (addr) { | |
70 | case ASPEED_SDHCI_SDIO_140: | |
71 | sdhci->slots[0].capareg = (uint64_t)(uint32_t)val; | |
72 | break; | |
73 | case ASPEED_SDHCI_SDIO_148: | |
74 | sdhci->slots[0].maxcurr = (uint64_t)(uint32_t)val; | |
75 | break; | |
76 | case ASPEED_SDHCI_SDIO_240: | |
77 | sdhci->slots[1].capareg = (uint64_t)(uint32_t)val; | |
78 | break; | |
79 | case ASPEED_SDHCI_SDIO_248: | |
80 | sdhci->slots[1].maxcurr = (uint64_t)(uint32_t)val; | |
81 | break; | |
82 | default: | |
83 | if (addr < ASPEED_SDHCI_REG_SIZE) { | |
84 | sdhci->regs[TO_REG(addr)] = (uint32_t)val; | |
85 | } else { | |
86 | qemu_log_mask(LOG_GUEST_ERROR, | |
87 | "%s: Out-of-bounds write at 0x%" HWADDR_PRIx "\n", | |
88 | __func__, addr); | |
89 | } | |
90 | } | |
91 | } | |
92 | ||
93 | static const MemoryRegionOps aspeed_sdhci_ops = { | |
94 | .read = aspeed_sdhci_read, | |
95 | .write = aspeed_sdhci_write, | |
96 | .endianness = DEVICE_NATIVE_ENDIAN, | |
97 | .valid.min_access_size = 4, | |
98 | .valid.max_access_size = 4, | |
99 | }; | |
100 | ||
101 | static void aspeed_sdhci_set_irq(void *opaque, int n, int level) | |
102 | { | |
103 | AspeedSDHCIState *sdhci = opaque; | |
104 | ||
105 | if (level) { | |
106 | sdhci->regs[TO_REG(ASPEED_SDHCI_IRQ_STAT)] |= BIT(n); | |
107 | ||
108 | qemu_irq_raise(sdhci->irq); | |
109 | } else { | |
110 | sdhci->regs[TO_REG(ASPEED_SDHCI_IRQ_STAT)] &= ~BIT(n); | |
111 | ||
112 | qemu_irq_lower(sdhci->irq); | |
113 | } | |
114 | } | |
115 | ||
116 | static void aspeed_sdhci_realize(DeviceState *dev, Error **errp) | |
117 | { | |
118 | Error *err = NULL; | |
119 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); | |
120 | AspeedSDHCIState *sdhci = ASPEED_SDHCI(dev); | |
121 | ||
122 | /* Create input irqs for the slots */ | |
123 | qdev_init_gpio_in_named_with_opaque(DEVICE(sbd), aspeed_sdhci_set_irq, | |
0e2c24c6 | 124 | sdhci, NULL, sdhci->num_slots); |
2bea128c EJ |
125 | |
126 | sysbus_init_irq(sbd, &sdhci->irq); | |
127 | memory_region_init_io(&sdhci->iomem, OBJECT(sdhci), &aspeed_sdhci_ops, | |
128 | sdhci, TYPE_ASPEED_SDHCI, 0x1000); | |
129 | sysbus_init_mmio(sbd, &sdhci->iomem); | |
130 | ||
0e2c24c6 | 131 | for (int i = 0; i < sdhci->num_slots; ++i) { |
2bea128c EJ |
132 | Object *sdhci_slot = OBJECT(&sdhci->slots[i]); |
133 | SysBusDevice *sbd_slot = SYS_BUS_DEVICE(&sdhci->slots[i]); | |
134 | ||
5325cc34 | 135 | object_property_set_int(sdhci_slot, "sd-spec-version", 2, &err); |
2bea128c EJ |
136 | if (err) { |
137 | error_propagate(errp, err); | |
138 | return; | |
139 | } | |
140 | ||
5325cc34 MA |
141 | object_property_set_uint(sdhci_slot, "capareg", |
142 | ASPEED_SDHCI_CAPABILITIES, &err); | |
2bea128c EJ |
143 | if (err) { |
144 | error_propagate(errp, err); | |
145 | return; | |
146 | } | |
147 | ||
118bfd76 | 148 | if (!sysbus_realize(sbd_slot, &err)) { |
2bea128c EJ |
149 | error_propagate(errp, err); |
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); | |
164 | sdhci->regs[TO_REG(ASPEED_SDHCI_INFO)] = ASPEED_SDHCI_INFO_RESET; | |
165 | sdhci->regs[TO_REG(ASPEED_SDHCI_DEBOUNCE)] = ASPEED_SDHCI_DEBOUNCE_RESET; | |
166 | } | |
167 | ||
168 | static const VMStateDescription vmstate_aspeed_sdhci = { | |
169 | .name = TYPE_ASPEED_SDHCI, | |
170 | .version_id = 1, | |
171 | .fields = (VMStateField[]) { | |
172 | VMSTATE_UINT32_ARRAY(regs, AspeedSDHCIState, ASPEED_SDHCI_NUM_REGS), | |
173 | VMSTATE_END_OF_LIST(), | |
174 | }, | |
175 | }; | |
176 | ||
0e2c24c6 AJ |
177 | static Property aspeed_sdhci_properties[] = { |
178 | DEFINE_PROP_UINT8("num-slots", AspeedSDHCIState, num_slots, 0), | |
179 | DEFINE_PROP_END_OF_LIST(), | |
180 | }; | |
181 | ||
2bea128c EJ |
182 | static void aspeed_sdhci_class_init(ObjectClass *classp, void *data) |
183 | { | |
184 | DeviceClass *dc = DEVICE_CLASS(classp); | |
185 | ||
186 | dc->realize = aspeed_sdhci_realize; | |
187 | dc->reset = aspeed_sdhci_reset; | |
188 | dc->vmsd = &vmstate_aspeed_sdhci; | |
0e2c24c6 | 189 | device_class_set_props(dc, aspeed_sdhci_properties); |
2bea128c EJ |
190 | } |
191 | ||
192 | static TypeInfo aspeed_sdhci_info = { | |
193 | .name = TYPE_ASPEED_SDHCI, | |
194 | .parent = TYPE_SYS_BUS_DEVICE, | |
195 | .instance_size = sizeof(AspeedSDHCIState), | |
196 | .class_init = aspeed_sdhci_class_init, | |
197 | }; | |
198 | ||
199 | static void aspeed_sdhci_register_types(void) | |
200 | { | |
201 | type_register_static(&aspeed_sdhci_info); | |
202 | } | |
203 | ||
204 | type_init(aspeed_sdhci_register_types) |