]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
fcf5c041 MY |
2 | /* |
3 | * (C) Copyright 2015 Miao Yan <[email protected]> | |
5b0b43e0 | 4 | * (C) Copyright 2021 Asherah Connor <[email protected]> |
fcf5c041 MY |
5 | */ |
6 | ||
5b0b43e0 AC |
7 | #define LOG_CATEGORY UCLASS_QFW |
8 | ||
c659ac7c | 9 | #include <acpi/acpi_table.h> |
dd4bd9ad SG |
10 | #include <bootdev.h> |
11 | #include <bootflow.h> | |
12 | #include <bootmeth.h> | |
fcf5c041 MY |
13 | #include <command.h> |
14 | #include <errno.h> | |
f7ae49fc | 15 | #include <log.h> |
fcf5c041 | 16 | #include <malloc.h> |
18686590 | 17 | #include <qfw.h> |
5b0b43e0 AC |
18 | #include <dm.h> |
19 | #include <misc.h> | |
0679cca5 | 20 | #include <tables_csum.h> |
854624c2 | 21 | #include <asm/acpi_table.h> |
fcf5c041 | 22 | |
5b0b43e0 AC |
23 | static void qfw_read_entry_io(struct qfw_dev *qdev, u16 entry, u32 size, |
24 | void *address) | |
fcf5c041 | 25 | { |
5b0b43e0 AC |
26 | struct dm_qfw_ops *ops = dm_qfw_get_ops(qdev->dev); |
27 | ||
28 | debug("%s: entry 0x%x, size %u address %p\n", __func__, entry, size, | |
29 | address); | |
fcf5c041 | 30 | |
5b0b43e0 | 31 | ops->read_entry_io(qdev->dev, entry, size, address); |
fcf5c041 MY |
32 | } |
33 | ||
5b0b43e0 AC |
34 | static void qfw_read_entry_dma(struct qfw_dev *qdev, u16 entry, u32 size, |
35 | void *address) | |
fcf5c041 | 36 | { |
5b0b43e0 | 37 | struct dm_qfw_ops *ops = dm_qfw_get_ops(qdev->dev); |
fcf5c041 | 38 | |
5b0b43e0 AC |
39 | struct qfw_dma dma = { |
40 | .length = cpu_to_be32(size), | |
41 | .address = cpu_to_be64((uintptr_t)address), | |
42 | .control = cpu_to_be32(FW_CFG_DMA_READ), | |
43 | }; | |
fcf5c041 MY |
44 | |
45 | /* | |
5b0b43e0 AC |
46 | * writing FW_CFG_INVALID will cause read operation to resume at last |
47 | * offset, otherwise read will start at offset 0 | |
fcf5c041 MY |
48 | */ |
49 | if (entry != FW_CFG_INVALID) | |
50 | dma.control |= cpu_to_be32(FW_CFG_DMA_SELECT | (entry << 16)); | |
51 | ||
5b0b43e0 | 52 | debug("%s: entry 0x%x, size %u address %p, control 0x%x\n", __func__, |
2e82e745 | 53 | entry, size, address, be32_to_cpu(dma.control)); |
fcf5c041 | 54 | |
5b0b43e0 | 55 | barrier(); |
fcf5c041 | 56 | |
5b0b43e0 | 57 | ops->read_entry_dma(qdev->dev, &dma); |
fcf5c041 MY |
58 | } |
59 | ||
5b0b43e0 | 60 | void qfw_read_entry(struct udevice *dev, u16 entry, u32 size, void *address) |
fcf5c041 | 61 | { |
5b0b43e0 | 62 | struct qfw_dev *qdev = dev_get_uclass_priv(dev); |
fcf5c041 | 63 | |
5b0b43e0 AC |
64 | if (qdev->dma_present) |
65 | qfw_read_entry_dma(qdev, entry, size, address); | |
fcf5c041 | 66 | else |
5b0b43e0 | 67 | qfw_read_entry_io(qdev, entry, size, address); |
fcf5c041 MY |
68 | } |
69 | ||
5b0b43e0 | 70 | int qfw_register(struct udevice *dev) |
fcf5c041 | 71 | { |
5b0b43e0 AC |
72 | struct qfw_dev *qdev = dev_get_uclass_priv(dev); |
73 | u32 qemu, dma_enabled; | |
fcf5c041 | 74 | |
5b0b43e0 AC |
75 | qdev->dev = dev; |
76 | INIT_LIST_HEAD(&qdev->fw_list); | |
fcf5c041 | 77 | |
5b0b43e0 AC |
78 | qfw_read_entry_io(qdev, FW_CFG_SIGNATURE, 4, &qemu); |
79 | if (be32_to_cpu(qemu) != QEMU_FW_CFG_SIGNATURE) | |
80 | return -ENODEV; | |
fcf5c041 | 81 | |
5b0b43e0 AC |
82 | qfw_read_entry_io(qdev, FW_CFG_ID, 1, &dma_enabled); |
83 | if (dma_enabled & FW_CFG_DMA_ENABLED) | |
84 | qdev->dma_present = true; | |
fcf5c041 MY |
85 | |
86 | return 0; | |
fcf5c041 MY |
87 | } |
88 | ||
dd4bd9ad SG |
89 | static int qfw_post_bind(struct udevice *dev) |
90 | { | |
91 | int ret; | |
92 | ||
93 | ret = bootdev_setup_for_dev(dev, "qfw_bootdev"); | |
94 | if (ret) | |
95 | return log_msg_ret("dev", ret); | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
100 | static int qfw_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, | |
101 | struct bootflow *bflow) | |
102 | { | |
103 | const struct udevice *media = dev_get_parent(dev); | |
104 | int ret; | |
105 | ||
106 | if (!CONFIG_IS_ENABLED(BOOTSTD)) | |
107 | return -ENOSYS; | |
108 | ||
109 | log_debug("media=%s\n", media->name); | |
110 | ret = bootmeth_check(bflow->method, iter); | |
111 | if (ret) | |
112 | return log_msg_ret("check", ret); | |
113 | ||
114 | log_debug("iter->part=%d\n", iter->part); | |
115 | ||
116 | /* We only support the whole device, not partitions */ | |
117 | if (iter->part) | |
118 | return log_msg_ret("max", -ESHUTDOWN); | |
119 | ||
120 | log_debug("reading bootflow with method: %s\n", bflow->method->name); | |
121 | ret = bootmeth_read_bootflow(bflow->method, bflow); | |
122 | if (ret) | |
123 | return log_msg_ret("method", ret); | |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
128 | static int qfw_bootdev_bind(struct udevice *dev) | |
129 | { | |
130 | struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); | |
131 | ||
132 | ucp->prio = BOOTDEVP_4_SCAN_FAST; | |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
137 | static int qfw_bootdev_hunt(struct bootdev_hunter *info, bool show) | |
138 | { | |
139 | int ret; | |
140 | ||
141 | ret = uclass_probe_all(UCLASS_QFW); | |
142 | if (ret && ret != -ENOENT) | |
143 | return log_msg_ret("vir", ret); | |
144 | ||
145 | return 0; | |
146 | } | |
147 | ||
5b0b43e0 AC |
148 | UCLASS_DRIVER(qfw) = { |
149 | .id = UCLASS_QFW, | |
150 | .name = "qfw", | |
dd4bd9ad | 151 | .post_bind = qfw_post_bind, |
5b0b43e0 AC |
152 | .per_device_auto = sizeof(struct qfw_dev), |
153 | }; | |
dd4bd9ad SG |
154 | |
155 | struct bootdev_ops qfw_bootdev_ops = { | |
156 | .get_bootflow = qfw_get_bootflow, | |
157 | }; | |
158 | ||
159 | static const struct udevice_id qfw_bootdev_ids[] = { | |
160 | { .compatible = "u-boot,bootdev-qfw" }, | |
161 | { } | |
162 | }; | |
163 | ||
164 | U_BOOT_DRIVER(qfw_bootdev) = { | |
165 | .name = "qfw_bootdev", | |
166 | .id = UCLASS_BOOTDEV, | |
167 | .ops = &qfw_bootdev_ops, | |
168 | .bind = qfw_bootdev_bind, | |
169 | .of_match = qfw_bootdev_ids, | |
170 | }; | |
171 | ||
172 | BOOTDEV_HUNTER(qfw_bootdev_hunter) = { | |
173 | .prio = BOOTDEVP_4_SCAN_FAST, | |
174 | .uclass = UCLASS_QFW, | |
175 | .hunt = qfw_bootdev_hunt, | |
176 | .drv = DM_DRIVER_REF(qfw_bootdev), | |
177 | }; |