Commit 9fc8706d65fc812f4e1a2da78d3b4411c866db63
Committed by
Anatolij Gustschin
1 parent
a63e54ab5f
Exists in
smarc_8mq_lf_v2020.04
and in
11 other branches
axi: Add ihs_axi driver
Add a driver for the gdsys IHS AXI bus used on IHS FPGAs. Reviewed-by: Simon Glass <sjg@chromium.org> Signed-off-by: Mario Six <mario.six@gdsys.cc>
Showing 4 changed files with 328 additions and 0 deletions Side-by-side Diff
Documentation/devicetree/bindings/axi/gdsys,ihs_axi.txt
1 | +gdsys AXI busses of IHS FPGA devices | |
2 | + | |
3 | +Certain gdsys IHS FPGAs offer a interface to their built-in AXI bus with which | |
4 | +the connected devices (usually IP cores) can be controlled via software. | |
5 | + | |
6 | +Required properties: | |
7 | +- compatible: must be "gdsys,ihs_axi" | |
8 | +- reg: describes the address and length of the AXI bus's register map (within | |
9 | + the FPGA's register space) | |
10 | + | |
11 | +Example: | |
12 | + | |
13 | +fpga0_axi_video0 { | |
14 | + #address-cells = <1>; | |
15 | + #size-cells = <1>; | |
16 | + compatible = "gdsys,ihs_axi"; | |
17 | + reg = <0x170 0x10>; | |
18 | + | |
19 | + axi_dev_1 { | |
20 | + ... | |
21 | + }; | |
22 | +}; |
drivers/axi/Kconfig
... | ... | @@ -11,4 +11,16 @@ |
11 | 11 | for now). |
12 | 12 | |
13 | 13 | Other similar bus architectures may be compatible as well. |
14 | + | |
15 | +if AXI | |
16 | + | |
17 | +config IHS_AXI | |
18 | + bool "Enable IHS AXI driver" | |
19 | + depends on DM | |
20 | + help | |
21 | + Support for gdsys Integrated Hardware Systems Advanced eXtensible | |
22 | + Interface (IHS AXI) bus on a gdsys IHS FPGA used to communicate with | |
23 | + IP cores in the FPGA (e.g. video transmitter cores). | |
24 | + | |
25 | +endif |
drivers/axi/Makefile
drivers/axi/ihs_axi.c
1 | +// SPDX-License-Identifier: GPL-2.0+ | |
2 | +/* | |
3 | + * (C) Copyright 2016 | |
4 | + * Dirk Eibach, Guntermann & Drunck GmbH, dirk.eibach@gdsys.cc | |
5 | + * | |
6 | + * (C) Copyright 2017, 2018 | |
7 | + * Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc | |
8 | + */ | |
9 | + | |
10 | +#include <common.h> | |
11 | +#include <axi.h> | |
12 | +#include <dm.h> | |
13 | +#include <regmap.h> | |
14 | + | |
15 | +/** | |
16 | + * struct ihs_axi_regs - Structure for the register map of a IHS AXI device | |
17 | + * @interrupt_status: Status register to indicate certain events (e.g. | |
18 | + * error during transfer, transfer complete, etc.) | |
19 | + * @interrupt_enable_control: Register to both control which statuses will be | |
20 | + * indicated in the interrupt_status register, and | |
21 | + * to change bus settings | |
22 | + * @address_lsb: Least significant 16-bit word of the address of a | |
23 | + * device to transfer data from/to | |
24 | + * @address_msb: Most significant 16-bit word of the address of a | |
25 | + * device to transfer data from/to | |
26 | + * @write_data_lsb: Least significant 16-bit word of the data to be | |
27 | + * written to a device | |
28 | + * @write_data_msb: Most significant 16-bit word of the data to be | |
29 | + * written to a device | |
30 | + * @read_data_lsb: Least significant 16-bit word of the data read | |
31 | + * from a device | |
32 | + * @read_data_msb: Most significant 16-bit word of the data read | |
33 | + * from a device | |
34 | + */ | |
35 | +struct ihs_axi_regs { | |
36 | + u16 interrupt_status; | |
37 | + u16 interrupt_enable_control; | |
38 | + u16 address_lsb; | |
39 | + u16 address_msb; | |
40 | + u16 write_data_lsb; | |
41 | + u16 write_data_msb; | |
42 | + u16 read_data_lsb; | |
43 | + u16 read_data_msb; | |
44 | +}; | |
45 | + | |
46 | +/** | |
47 | + * ihs_axi_set() - Convenience macro to set values in register map | |
48 | + * @map: The register map to write to | |
49 | + * @member: The member of the ihs_axi_regs structure to write | |
50 | + * @val: The value to write to the register map | |
51 | + */ | |
52 | +#define ihs_axi_set(map, member, val) \ | |
53 | + regmap_set(map, struct ihs_axi_regs, member, val) | |
54 | + | |
55 | +/** | |
56 | + * ihs_axi_get() - Convenience macro to read values from register map | |
57 | + * @map: The register map to read from | |
58 | + * @member: The member of the ihs_axi_regs structure to read | |
59 | + * @valp: Pointer to a buffer to receive the value read | |
60 | + */ | |
61 | +#define ihs_axi_get(map, member, valp) \ | |
62 | + regmap_get(map, struct ihs_axi_regs, member, valp) | |
63 | + | |
64 | +/** | |
65 | + * struct ihs_axi_priv - Private data structure of IHS AXI devices | |
66 | + * @map: Register map for the IHS AXI device | |
67 | + */ | |
68 | +struct ihs_axi_priv { | |
69 | + struct regmap *map; | |
70 | +}; | |
71 | + | |
72 | +/** | |
73 | + * enum status_reg - Description of bits in the interrupt_status register | |
74 | + * @STATUS_READ_COMPLETE_EVENT: A read transfer was completed | |
75 | + * @STATUS_WRITE_COMPLETE_EVENT: A write transfer was completed | |
76 | + * @STATUS_TIMEOUT_EVENT: A timeout has occurred during the transfer | |
77 | + * @STATUS_ERROR_EVENT: A error has occurred during the transfer | |
78 | + * @STATUS_AXI_INT: A AXI interrupt has occurred | |
79 | + * @STATUS_READ_DATA_AVAILABLE: Data is available to be read | |
80 | + * @STATUS_BUSY: The bus is busy | |
81 | + * @STATUS_INIT_DONE: The bus has finished initializing | |
82 | + */ | |
83 | +enum status_reg { | |
84 | + STATUS_READ_COMPLETE_EVENT = BIT(15), | |
85 | + STATUS_WRITE_COMPLETE_EVENT = BIT(14), | |
86 | + STATUS_TIMEOUT_EVENT = BIT(13), | |
87 | + STATUS_ERROR_EVENT = BIT(12), | |
88 | + STATUS_AXI_INT = BIT(11), | |
89 | + STATUS_READ_DATA_AVAILABLE = BIT(7), | |
90 | + STATUS_BUSY = BIT(6), | |
91 | + STATUS_INIT_DONE = BIT(5), | |
92 | +}; | |
93 | + | |
94 | +/** | |
95 | + * enum control_reg - Description of bit fields in the interrupt_enable_control | |
96 | + * register | |
97 | + * @CONTROL_READ_COMPLETE_EVENT_ENABLE: STATUS_READ_COMPLETE_EVENT will be | |
98 | + * raised in the interrupt_status register | |
99 | + * @CONTROL_WRITE_COMPLETE_EVENT_ENABLE: STATUS_WRITE_COMPLETE_EVENT will be | |
100 | + * raised in the interrupt_status register | |
101 | + * @CONTROL_TIMEOUT_EVENT_ENABLE: STATUS_TIMEOUT_EVENT will be raised in | |
102 | + * the interrupt_status register | |
103 | + * @CONTROL_ERROR_EVENT_ENABLE: STATUS_ERROR_EVENT will be raised in | |
104 | + * the interrupt_status register | |
105 | + * @CONTROL_AXI_INT_ENABLE: STATUS_AXI_INT will be raised in the | |
106 | + * interrupt_status register | |
107 | + * @CONTROL_CMD_NOP: Configure bus to send a NOP command | |
108 | + * for the next transfer | |
109 | + * @CONTROL_CMD_WRITE: Configure bus to do a write transfer | |
110 | + * @CONTROL_CMD_WRITE_POST_INC: Auto-increment address after write | |
111 | + * transfer | |
112 | + * @CONTROL_CMD_READ: Configure bus to do a read transfer | |
113 | + * @CONTROL_CMD_READ_POST_INC: Auto-increment address after read | |
114 | + * transfer | |
115 | + */ | |
116 | +enum control_reg { | |
117 | + CONTROL_READ_COMPLETE_EVENT_ENABLE = BIT(15), | |
118 | + CONTROL_WRITE_COMPLETE_EVENT_ENABLE = BIT(14), | |
119 | + CONTROL_TIMEOUT_EVENT_ENABLE = BIT(13), | |
120 | + CONTROL_ERROR_EVENT_ENABLE = BIT(12), | |
121 | + CONTROL_AXI_INT_ENABLE = BIT(11), | |
122 | + | |
123 | + CONTROL_CMD_NOP = 0x0, | |
124 | + CONTROL_CMD_WRITE = 0x8, | |
125 | + CONTROL_CMD_WRITE_POST_INC = 0x9, | |
126 | + CONTROL_CMD_READ = 0xa, | |
127 | + CONTROL_CMD_READ_POST_INC = 0xb, | |
128 | +}; | |
129 | + | |
130 | +/** | |
131 | + * enum axi_cmd - Determine if transfer is read or write transfer | |
132 | + * @AXI_CMD_READ: The transfer should be a read transfer | |
133 | + * @AXI_CMD_WRITE: The transfer should be a write transfer | |
134 | + */ | |
135 | +enum axi_cmd { | |
136 | + AXI_CMD_READ, | |
137 | + AXI_CMD_WRITE, | |
138 | +}; | |
139 | + | |
140 | +/** | |
141 | + * ihs_axi_transfer() - Run transfer on the AXI bus | |
142 | + * @bus: The AXI bus device on which to run the transfer on | |
143 | + * @address: The address to use in the transfer (i.e. which address to | |
144 | + * read/write from/to) | |
145 | + * @cmd: Should the transfer be a read or write transfer? | |
146 | + * | |
147 | + * Return: 0 if OK, -ve on error | |
148 | + */ | |
149 | +static int ihs_axi_transfer(struct udevice *bus, ulong address, | |
150 | + enum axi_cmd cmd) | |
151 | +{ | |
152 | + struct ihs_axi_priv *priv = dev_get_priv(bus); | |
153 | + /* Try waiting for events up to 10 times */ | |
154 | + const uint WAIT_TRIES = 10; | |
155 | + u16 wait_mask = STATUS_TIMEOUT_EVENT | | |
156 | + STATUS_ERROR_EVENT; | |
157 | + u16 complete_flag; | |
158 | + u16 status; | |
159 | + uint k; | |
160 | + | |
161 | + if (cmd == AXI_CMD_READ) { | |
162 | + complete_flag = STATUS_READ_COMPLETE_EVENT; | |
163 | + cmd = CONTROL_CMD_READ; | |
164 | + } else { | |
165 | + complete_flag = STATUS_WRITE_COMPLETE_EVENT; | |
166 | + cmd = CONTROL_CMD_WRITE; | |
167 | + } | |
168 | + | |
169 | + wait_mask |= complete_flag; | |
170 | + | |
171 | + /* Lower 16 bit */ | |
172 | + ihs_axi_set(priv->map, address_lsb, address & 0xffff); | |
173 | + /* Upper 16 bit */ | |
174 | + ihs_axi_set(priv->map, address_msb, (address >> 16) & 0xffff); | |
175 | + | |
176 | + ihs_axi_set(priv->map, interrupt_status, wait_mask); | |
177 | + ihs_axi_set(priv->map, interrupt_enable_control, cmd); | |
178 | + | |
179 | + for (k = WAIT_TRIES; k > 0; --k) { | |
180 | + ihs_axi_get(priv->map, interrupt_status, &status); | |
181 | + if (status & wait_mask) | |
182 | + break; | |
183 | + udelay(1); | |
184 | + } | |
185 | + | |
186 | + /* | |
187 | + * k == 0 -> Tries ran out with no event we were waiting for actually | |
188 | + * occurring. | |
189 | + */ | |
190 | + if (!k) | |
191 | + ihs_axi_get(priv->map, interrupt_status, &status); | |
192 | + | |
193 | + if (status & complete_flag) | |
194 | + return 0; | |
195 | + | |
196 | + if (status & STATUS_ERROR_EVENT) { | |
197 | + debug("%s: Error occurred during transfer\n", bus->name); | |
198 | + return -EIO; | |
199 | + } | |
200 | + | |
201 | + debug("%s: Transfer timed out\n", bus->name); | |
202 | + return -ETIMEDOUT; | |
203 | +} | |
204 | + | |
205 | +/* | |
206 | + * API | |
207 | + */ | |
208 | + | |
209 | +static int ihs_axi_read(struct udevice *dev, ulong address, void *data, | |
210 | + enum axi_size_t size) | |
211 | +{ | |
212 | + struct ihs_axi_priv *priv = dev_get_priv(dev); | |
213 | + int ret; | |
214 | + u16 data_lsb, data_msb; | |
215 | + u32 *p = data; | |
216 | + | |
217 | + if (size != AXI_SIZE_32) { | |
218 | + debug("%s: transfer size '%d' not supported\n", | |
219 | + dev->name, size); | |
220 | + return -ENOSYS; | |
221 | + } | |
222 | + | |
223 | + ret = ihs_axi_transfer(dev, address, AXI_CMD_READ); | |
224 | + if (ret < 0) { | |
225 | + debug("%s: Error during AXI transfer (err = %d)\n", | |
226 | + dev->name, ret); | |
227 | + return ret; | |
228 | + } | |
229 | + | |
230 | + ihs_axi_get(priv->map, read_data_lsb, &data_lsb); | |
231 | + ihs_axi_get(priv->map, read_data_msb, &data_msb); | |
232 | + | |
233 | + /* Assemble data from two 16-bit words */ | |
234 | + *p = (data_msb << 16) | data_lsb; | |
235 | + | |
236 | + return 0; | |
237 | +} | |
238 | + | |
239 | +static int ihs_axi_write(struct udevice *dev, ulong address, void *data, | |
240 | + enum axi_size_t size) | |
241 | +{ | |
242 | + struct ihs_axi_priv *priv = dev_get_priv(dev); | |
243 | + int ret; | |
244 | + u32 *p = data; | |
245 | + | |
246 | + if (size != AXI_SIZE_32) { | |
247 | + debug("%s: transfer size '%d' not supported\n", | |
248 | + dev->name, size); | |
249 | + return -ENOSYS; | |
250 | + } | |
251 | + | |
252 | + /* Lower 16 bit */ | |
253 | + ihs_axi_set(priv->map, write_data_lsb, *p & 0xffff); | |
254 | + /* Upper 16 bit */ | |
255 | + ihs_axi_set(priv->map, write_data_msb, (*p >> 16) & 0xffff); | |
256 | + | |
257 | + ret = ihs_axi_transfer(dev, address, AXI_CMD_WRITE); | |
258 | + if (ret < 0) { | |
259 | + debug("%s: Error during AXI transfer (err = %d)\n", | |
260 | + dev->name, ret); | |
261 | + return ret; | |
262 | + } | |
263 | + | |
264 | + return 0; | |
265 | +} | |
266 | + | |
267 | +static const struct udevice_id ihs_axi_ids[] = { | |
268 | + { .compatible = "gdsys,ihs_axi" }, | |
269 | + { /* sentinel */ } | |
270 | +}; | |
271 | + | |
272 | +static const struct axi_ops ihs_axi_ops = { | |
273 | + .read = ihs_axi_read, | |
274 | + .write = ihs_axi_write, | |
275 | +}; | |
276 | + | |
277 | +static int ihs_axi_probe(struct udevice *dev) | |
278 | +{ | |
279 | + struct ihs_axi_priv *priv = dev_get_priv(dev); | |
280 | + | |
281 | + regmap_init_mem(dev_ofnode(dev), &priv->map); | |
282 | + | |
283 | + return 0; | |
284 | +} | |
285 | + | |
286 | +U_BOOT_DRIVER(ihs_axi_bus) = { | |
287 | + .name = "ihs_axi_bus", | |
288 | + .id = UCLASS_AXI, | |
289 | + .of_match = ihs_axi_ids, | |
290 | + .ops = &ihs_axi_ops, | |
291 | + .priv_auto_alloc_size = sizeof(struct ihs_axi_priv), | |
292 | + .probe = ihs_axi_probe, | |
293 | +}; |