]>
Commit | Line | Data |
---|---|---|
9fc8706d MS |
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 | }; |