]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
7155cd2e | 2 | /* |
6720e4ae | 3 | * Andestech ATCSPI200 SPI controller driver. |
7155cd2e | 4 | * |
5 | * Copyright 2017 Andes Technology, Inc. | |
6 | * Author: Rick Chen ([email protected]) | |
7155cd2e | 7 | */ |
8 | ||
d678a59d | 9 | #include <common.h> |
0cbee854 | 10 | #include <clk.h> |
f7ae49fc | 11 | #include <log.h> |
7155cd2e | 12 | #include <malloc.h> |
13 | #include <spi.h> | |
401d1c4f | 14 | #include <asm/global_data.h> |
7155cd2e | 15 | #include <asm/io.h> |
16 | #include <dm.h> | |
17 | ||
18 | DECLARE_GLOBAL_DATA_PTR; | |
19 | ||
20 | #define MAX_TRANSFER_LEN 512 | |
21 | #define CHUNK_SIZE 1 | |
22 | #define SPI_TIMEOUT 0x100000 | |
23 | #define SPI0_BUS 0 | |
24 | #define SPI1_BUS 1 | |
25 | #define SPI0_BASE 0xf0b00000 | |
26 | #define SPI1_BASE 0xf0f00000 | |
27 | #define NSPI_MAX_CS_NUM 1 | |
28 | ||
6720e4ae | 29 | struct atcspi200_spi_regs { |
7155cd2e | 30 | u32 rev; |
31 | u32 reserve1[3]; | |
32 | u32 format; /* 0x10 */ | |
33 | #define DATA_LENGTH(x) ((x-1)<<8) | |
34 | u32 pio; | |
35 | u32 reserve2[2]; | |
36 | u32 tctrl; /* 0x20 */ | |
37 | #define TRAMODE_OFFSET 24 | |
38 | #define TRAMODE_MASK (0x0F<<TRAMODE_OFFSET) | |
39 | #define TRAMODE_WR_SYNC (0<<TRAMODE_OFFSET) | |
40 | #define TRAMODE_WO (1<<TRAMODE_OFFSET) | |
41 | #define TRAMODE_RO (2<<TRAMODE_OFFSET) | |
42 | #define TRAMODE_WR (3<<TRAMODE_OFFSET) | |
43 | #define TRAMODE_RW (4<<TRAMODE_OFFSET) | |
44 | #define TRAMODE_WDR (5<<TRAMODE_OFFSET) | |
45 | #define TRAMODE_RDW (6<<TRAMODE_OFFSET) | |
46 | #define TRAMODE_NONE (7<<TRAMODE_OFFSET) | |
47 | #define TRAMODE_DW (8<<TRAMODE_OFFSET) | |
48 | #define TRAMODE_DR (9<<TRAMODE_OFFSET) | |
49 | #define WCNT_OFFSET 12 | |
50 | #define WCNT_MASK (0x1FF<<WCNT_OFFSET) | |
51 | #define RCNT_OFFSET 0 | |
52 | #define RCNT_MASK (0x1FF<<RCNT_OFFSET) | |
53 | u32 cmd; | |
54 | u32 addr; | |
55 | u32 data; | |
56 | u32 ctrl; /* 0x30 */ | |
57 | #define TXFTH_OFFSET 16 | |
58 | #define RXFTH_OFFSET 8 | |
59 | #define TXDMAEN (1<<4) | |
60 | #define RXDMAEN (1<<3) | |
61 | #define TXFRST (1<<2) | |
62 | #define RXFRST (1<<1) | |
63 | #define SPIRST (1<<0) | |
64 | u32 status; | |
65 | #define TXFFL (1<<23) | |
66 | #define TXEPTY (1<<22) | |
67 | #define TXFVE_MASK (0x1F<<16) | |
68 | #define RXFEM (1<<14) | |
69 | #define RXFVE_OFFSET (8) | |
70 | #define RXFVE_MASK (0x1F<<RXFVE_OFFSET) | |
71 | #define SPIBSY (1<<0) | |
72 | u32 inten; | |
73 | u32 intsta; | |
74 | u32 timing; /* 0x40 */ | |
75 | #define SCLK_DIV_MASK 0xFF | |
76 | }; | |
77 | ||
78 | struct nds_spi_slave { | |
6720e4ae | 79 | volatile struct atcspi200_spi_regs *regs; |
7155cd2e | 80 | int to; |
81 | unsigned int freq; | |
82 | ulong clock; | |
83 | unsigned int mode; | |
0cf207ec | 84 | u8 num_cs; |
7155cd2e | 85 | unsigned int mtiming; |
86 | size_t cmd_len; | |
87 | u8 cmd_buf[16]; | |
88 | size_t data_len; | |
89 | size_t tran_len; | |
90 | u8 *din; | |
91 | u8 *dout; | |
92 | unsigned int max_transfer_length; | |
93 | }; | |
94 | ||
6720e4ae | 95 | static int __atcspi200_spi_set_speed(struct nds_spi_slave *ns) |
7155cd2e | 96 | { |
97 | u32 tm; | |
98 | u8 div; | |
99 | tm = ns->regs->timing; | |
100 | tm &= ~SCLK_DIV_MASK; | |
101 | ||
102 | if(ns->freq >= ns->clock) | |
103 | div =0xff; | |
104 | else{ | |
105 | for (div = 0; div < 0xff; div++) { | |
106 | if (ns->freq >= ns->clock / (2 * (div + 1))) | |
107 | break; | |
108 | } | |
109 | } | |
110 | ||
111 | tm |= div; | |
112 | ns->regs->timing = tm; | |
113 | ||
114 | return 0; | |
115 | ||
116 | } | |
117 | ||
6720e4ae | 118 | static int __atcspi200_spi_claim_bus(struct nds_spi_slave *ns) |
7155cd2e | 119 | { |
120 | unsigned int format=0; | |
121 | ns->regs->ctrl |= (TXFRST|RXFRST|SPIRST); | |
122 | while((ns->regs->ctrl &(TXFRST|RXFRST|SPIRST))&&(ns->to--)) | |
123 | if(!ns->to) | |
124 | return -EINVAL; | |
125 | ||
126 | ns->cmd_len = 0; | |
127 | format = ns->mode|DATA_LENGTH(8); | |
128 | ns->regs->format = format; | |
6720e4ae | 129 | __atcspi200_spi_set_speed(ns); |
7155cd2e | 130 | |
131 | return 0; | |
132 | } | |
133 | ||
6720e4ae | 134 | static int __atcspi200_spi_release_bus(struct nds_spi_slave *ns) |
7155cd2e | 135 | { |
136 | /* do nothing */ | |
137 | return 0; | |
138 | } | |
139 | ||
6720e4ae | 140 | static int __atcspi200_spi_start(struct nds_spi_slave *ns) |
7155cd2e | 141 | { |
142 | int i,olen=0; | |
143 | int tc = ns->regs->tctrl; | |
144 | ||
145 | tc &= ~(WCNT_MASK|RCNT_MASK|TRAMODE_MASK); | |
146 | if ((ns->din)&&(ns->cmd_len)) | |
147 | tc |= TRAMODE_WR; | |
148 | else if (ns->din) | |
149 | tc |= TRAMODE_RO; | |
150 | else | |
151 | tc |= TRAMODE_WO; | |
152 | ||
153 | if(ns->dout) | |
154 | olen = ns->tran_len; | |
155 | tc |= (ns->cmd_len+olen-1) << WCNT_OFFSET; | |
156 | ||
157 | if(ns->din) | |
158 | tc |= (ns->tran_len-1) << RCNT_OFFSET; | |
159 | ||
160 | ns->regs->tctrl = tc; | |
161 | ns->regs->cmd = 1; | |
162 | ||
163 | for (i=0;i<ns->cmd_len;i++) | |
164 | ns->regs->data = ns->cmd_buf[i]; | |
165 | ||
166 | return 0; | |
167 | } | |
168 | ||
6720e4ae | 169 | static int __atcspi200_spi_stop(struct nds_spi_slave *ns) |
7155cd2e | 170 | { |
171 | ns->regs->timing = ns->mtiming; | |
172 | while ((ns->regs->status & SPIBSY)&&(ns->to--)) | |
173 | if (!ns->to) | |
174 | return -EINVAL; | |
175 | ||
176 | return 0; | |
177 | } | |
178 | ||
179 | static void __nspi_espi_tx(struct nds_spi_slave *ns, const void *dout) | |
180 | { | |
181 | ns->regs->data = *(u8 *)dout; | |
182 | } | |
183 | ||
184 | static int __nspi_espi_rx(struct nds_spi_slave *ns, void *din, unsigned int bytes) | |
185 | { | |
186 | *(u8 *)din = ns->regs->data; | |
187 | return bytes; | |
188 | } | |
189 | ||
190 | ||
6720e4ae | 191 | static int __atcspi200_spi_xfer(struct nds_spi_slave *ns, |
7155cd2e | 192 | unsigned int bitlen, const void *data_out, void *data_in, |
193 | unsigned long flags) | |
194 | { | |
195 | unsigned int event, rx_bytes; | |
196 | const void *dout = NULL; | |
197 | void *din = NULL; | |
198 | int num_blks, num_chunks, max_tran_len, tran_len; | |
199 | int num_bytes; | |
200 | u8 *cmd_buf = ns->cmd_buf; | |
201 | size_t cmd_len = ns->cmd_len; | |
6083cf38 | 202 | unsigned long data_len = bitlen / 8; |
7155cd2e | 203 | int rf_cnt; |
1412b8d4 | 204 | int ret = 0, timeout = 0; |
7155cd2e | 205 | |
206 | max_tran_len = ns->max_transfer_length; | |
207 | switch (flags) { | |
208 | case SPI_XFER_BEGIN: | |
209 | cmd_len = ns->cmd_len = data_len; | |
210 | memcpy(cmd_buf, data_out, cmd_len); | |
211 | return 0; | |
212 | ||
213 | case 0: | |
214 | case SPI_XFER_END: | |
215 | if (bitlen == 0) { | |
216 | return 0; | |
217 | } | |
218 | ns->data_len = data_len; | |
219 | ns->din = (u8 *)data_in; | |
220 | ns->dout = (u8 *)data_out; | |
221 | break; | |
222 | ||
223 | case SPI_XFER_BEGIN | SPI_XFER_END: | |
224 | ns->data_len = 0; | |
225 | ns->din = 0; | |
226 | ns->dout = 0; | |
227 | cmd_len = ns->cmd_len = data_len; | |
228 | memcpy(cmd_buf, data_out, cmd_len); | |
229 | data_out = 0; | |
230 | data_len = 0; | |
6720e4ae | 231 | __atcspi200_spi_start(ns); |
7155cd2e | 232 | break; |
233 | } | |
8fad5e0b | 234 | if (data_out) |
8ada17dd | 235 | debug("spi_xfer: data_out %08X(%p) data_in %08X(%p) data_len %lu\n", |
8fad5e0b HS |
236 | *(uint *)data_out, data_out, *(uint *)data_in, |
237 | data_in, data_len); | |
7155cd2e | 238 | num_chunks = DIV_ROUND_UP(data_len, max_tran_len); |
239 | din = data_in; | |
240 | dout = data_out; | |
241 | while (num_chunks--) { | |
6083cf38 | 242 | tran_len = min((size_t)data_len, (size_t)max_tran_len); |
7155cd2e | 243 | ns->tran_len = tran_len; |
244 | num_blks = DIV_ROUND_UP(tran_len , CHUNK_SIZE); | |
245 | num_bytes = (tran_len) % CHUNK_SIZE; | |
1412b8d4 | 246 | timeout = SPI_TIMEOUT; |
7155cd2e | 247 | if(num_bytes == 0) |
248 | num_bytes = CHUNK_SIZE; | |
6720e4ae | 249 | __atcspi200_spi_start(ns); |
7155cd2e | 250 | |
1412b8d4 | 251 | while (num_blks && (timeout--)) { |
7155cd2e | 252 | event = in_le32(&ns->regs->status); |
253 | if ((event & TXEPTY) && (data_out)) { | |
254 | __nspi_espi_tx(ns, dout); | |
255 | num_blks -= CHUNK_SIZE; | |
256 | dout += CHUNK_SIZE; | |
257 | } | |
258 | ||
259 | if ((event & RXFVE_MASK) && (data_in)) { | |
260 | rf_cnt = ((event & RXFVE_MASK)>> RXFVE_OFFSET); | |
261 | if (rf_cnt >= CHUNK_SIZE) | |
262 | rx_bytes = CHUNK_SIZE; | |
263 | else if (num_blks == 1 && rf_cnt == num_bytes) | |
264 | rx_bytes = num_bytes; | |
265 | else | |
266 | continue; | |
267 | ||
268 | if (__nspi_espi_rx(ns, din, rx_bytes) == rx_bytes) { | |
269 | num_blks -= CHUNK_SIZE; | |
270 | din = (unsigned char *)din + rx_bytes; | |
271 | } | |
272 | } | |
1412b8d4 DJ |
273 | |
274 | if (!timeout) { | |
275 | debug("spi_xfer: %s() timeout\n", __func__); | |
276 | break; | |
277 | } | |
7155cd2e | 278 | } |
279 | ||
280 | data_len -= tran_len; | |
281 | if(data_len) | |
282 | { | |
283 | ns->cmd_buf[1] += ((tran_len>>16)&0xff); | |
284 | ns->cmd_buf[2] += ((tran_len>>8)&0xff); | |
285 | ns->cmd_buf[3] += ((tran_len)&0xff); | |
286 | ns->data_len = data_len; | |
287 | } | |
6720e4ae | 288 | ret = __atcspi200_spi_stop(ns); |
7155cd2e | 289 | } |
6720e4ae | 290 | ret = __atcspi200_spi_stop(ns); |
7155cd2e | 291 | |
292 | return ret; | |
293 | } | |
294 | ||
6720e4ae | 295 | static int atcspi200_spi_set_speed(struct udevice *bus, uint max_hz) |
7155cd2e | 296 | { |
297 | struct nds_spi_slave *ns = dev_get_priv(bus); | |
298 | ||
299 | debug("%s speed %u\n", __func__, max_hz); | |
300 | ||
301 | ns->freq = max_hz; | |
6720e4ae | 302 | __atcspi200_spi_set_speed(ns); |
7155cd2e | 303 | |
304 | return 0; | |
305 | } | |
306 | ||
6720e4ae | 307 | static int atcspi200_spi_set_mode(struct udevice *bus, uint mode) |
7155cd2e | 308 | { |
309 | struct nds_spi_slave *ns = dev_get_priv(bus); | |
310 | ||
311 | debug("%s mode %u\n", __func__, mode); | |
312 | ns->mode = mode; | |
313 | ||
314 | return 0; | |
315 | } | |
316 | ||
6720e4ae | 317 | static int atcspi200_spi_claim_bus(struct udevice *dev) |
7155cd2e | 318 | { |
8a8d24bd | 319 | struct dm_spi_slave_plat *slave_plat = |
caa4daa2 | 320 | dev_get_parent_plat(dev); |
7155cd2e | 321 | struct udevice *bus = dev->parent; |
322 | struct nds_spi_slave *ns = dev_get_priv(bus); | |
323 | ||
324 | if (slave_plat->cs >= ns->num_cs) { | |
325 | printf("Invalid SPI chipselect\n"); | |
326 | return -EINVAL; | |
327 | } | |
328 | ||
6720e4ae | 329 | return __atcspi200_spi_claim_bus(ns); |
7155cd2e | 330 | } |
331 | ||
6720e4ae | 332 | static int atcspi200_spi_release_bus(struct udevice *dev) |
7155cd2e | 333 | { |
334 | struct nds_spi_slave *ns = dev_get_priv(dev->parent); | |
335 | ||
6720e4ae | 336 | return __atcspi200_spi_release_bus(ns); |
7155cd2e | 337 | } |
338 | ||
6720e4ae | 339 | static int atcspi200_spi_xfer(struct udevice *dev, unsigned int bitlen, |
7155cd2e | 340 | const void *dout, void *din, |
341 | unsigned long flags) | |
342 | { | |
343 | struct udevice *bus = dev->parent; | |
344 | struct nds_spi_slave *ns = dev_get_priv(bus); | |
345 | ||
6720e4ae | 346 | return __atcspi200_spi_xfer(ns, bitlen, dout, din, flags); |
7155cd2e | 347 | } |
348 | ||
6720e4ae | 349 | static int atcspi200_spi_get_clk(struct udevice *bus) |
7155cd2e | 350 | { |
351 | struct nds_spi_slave *ns = dev_get_priv(bus); | |
352 | struct clk clk; | |
353 | ulong clk_rate; | |
354 | int ret; | |
355 | ||
356 | ret = clk_get_by_index(bus, 0, &clk); | |
357 | if (ret) | |
358 | return -EINVAL; | |
359 | ||
360 | clk_rate = clk_get_rate(&clk); | |
361 | if (!clk_rate) | |
362 | return -EINVAL; | |
363 | ||
364 | ns->clock = clk_rate; | |
7155cd2e | 365 | |
366 | return 0; | |
367 | } | |
368 | ||
6720e4ae | 369 | static int atcspi200_spi_probe(struct udevice *bus) |
7155cd2e | 370 | { |
371 | struct nds_spi_slave *ns = dev_get_priv(bus); | |
372 | ||
373 | ns->to = SPI_TIMEOUT; | |
374 | ns->max_transfer_length = MAX_TRANSFER_LEN; | |
375 | ns->mtiming = ns->regs->timing; | |
6720e4ae | 376 | atcspi200_spi_get_clk(bus); |
7155cd2e | 377 | |
378 | return 0; | |
379 | } | |
380 | ||
6720e4ae | 381 | static int atcspi200_ofdata_to_platadata(struct udevice *bus) |
7155cd2e | 382 | { |
383 | struct nds_spi_slave *ns = dev_get_priv(bus); | |
384 | const void *blob = gd->fdt_blob; | |
385 | int node = dev_of_offset(bus); | |
386 | ||
2548493a | 387 | ns->regs = map_physmem(dev_read_addr(bus), |
6720e4ae | 388 | sizeof(struct atcspi200_spi_regs), |
7155cd2e | 389 | MAP_NOCACHE); |
390 | if (!ns->regs) { | |
391 | printf("%s: could not map device address\n", __func__); | |
392 | return -EINVAL; | |
393 | } | |
394 | ns->num_cs = fdtdec_get_int(blob, node, "num-cs", 4); | |
395 | ||
396 | return 0; | |
397 | } | |
398 | ||
6720e4ae RC |
399 | static const struct dm_spi_ops atcspi200_spi_ops = { |
400 | .claim_bus = atcspi200_spi_claim_bus, | |
401 | .release_bus = atcspi200_spi_release_bus, | |
402 | .xfer = atcspi200_spi_xfer, | |
403 | .set_speed = atcspi200_spi_set_speed, | |
404 | .set_mode = atcspi200_spi_set_mode, | |
7155cd2e | 405 | }; |
406 | ||
6720e4ae | 407 | static const struct udevice_id atcspi200_spi_ids[] = { |
7155cd2e | 408 | { .compatible = "andestech,atcspi200" }, |
409 | { } | |
410 | }; | |
411 | ||
6720e4ae RC |
412 | U_BOOT_DRIVER(atcspi200_spi) = { |
413 | .name = "atcspi200_spi", | |
7155cd2e | 414 | .id = UCLASS_SPI, |
6720e4ae RC |
415 | .of_match = atcspi200_spi_ids, |
416 | .ops = &atcspi200_spi_ops, | |
d1998a9f | 417 | .of_to_plat = atcspi200_ofdata_to_platadata, |
41575d8e | 418 | .priv_auto = sizeof(struct nds_spi_slave), |
6720e4ae | 419 | .probe = atcspi200_spi_probe, |
7155cd2e | 420 | }; |