blob: 46fa6981cd08d51be9a5ce83230f4ce42d7d0af5 [file] [log] [blame]
Nico Huber044c9dc2023-12-29 23:26:57 +01001/*
2 * This file is part of the flashrom project.
3 *
4 * Copyright (C) 2024 Nico Huber <nico.h@gmx.de>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17#include <assert.h>
18#include <stdint.h>
19#include <stdlib.h>
20#include <string.h>
21#include <unistd.h>
22#include <libusb.h>
23
24#include "platform.h"
25#include "programmer.h"
26#include "flash.h"
27#include "spi_command.h"
28#include "spi.h"
29
30#define FT4222_RESET_REQUEST 0x00
31#define FT4222_RESET_SIO 0x0000
32#define FT4222_OUTPUT_FLUSH 0x0001
33#define FT4222_INPUT_FLUSH 0x0002
34
35#define FT4222_INFO_REQUEST 0x20
36#define FT4222_GET_VERSION 0x00
37#define FT4222_GET_CONFIG 0x01
38
39#define FT4222_CONFIG_REQUEST 0x21
40#define FT4222_SET_CLOCK 0x04
41#define FT4222_SET_MODE 0x05
42#define FT4222_I2C_MASTER 1
43#define FT4222_I2C_SLAVE 2
44#define FT4222_SPI_MASTER 3
45#define FT4222_SPI_SLAVE 4
46#define FT4222_SPI_SET_IO_LINES 0x42
47#define FT4222_SPI_SET_CS_ACTIVE 0x43
48#define FT4222_SPI_CS_ACTIVE_LOW 0
49#define FT4222_SPI_CS_ACTIVE_HIGH 1
50#define FT4222_SPI_SET_CLK_DIV 0x44
51#define FT4222_SPI_SET_CLK_IDLE 0x45
52#define FT4222_CLK_IDLE_LOW 0
53#define FT4222_CLK_IDLE_HIGH 1
54#define FT4222_SPI_SET_CAPTURE 0x46
55#define FT4222_LEADING_CLK 0
56#define FT4222_TRAILING_CLK 1
57#define FT4222_SPI_SET_CS_MASK 0x48
58#define FT4222_SPI_CS_MASK(cs) (1 << (cs))
59#define FT4222_SPI_RESET_TRANSACTION 0x49
60#define FT4222_SPI_RESET 0x4a
61#define FT4222_RESET_FULL 0
62#define FT4222_RESET_LINE_NUM 1
63
64#define READ_BUFFER_SIZE 2048 /* Any power-of-2 >= 512 seems to work. */
65#define READ_MAX_XFERS 4 /* Should be >1 to avoid starvation. */
66
67#define USB_TIMEOUT 2000 /* In milliseconds. */
68
69#define FTDI_VID 0x0403
70#define FTDI_FT4222H_PID 0x601c
71
72static const struct dev_entry devs[] = {
73 {FTDI_VID, FTDI_FT4222H_PID, OK, "FTDI", "FT4222H"},
74 {0},
75};
76
77struct ft4222_clock {
78 unsigned short sys_idx;
79 unsigned short div_log2;
80};
81
82struct ft4222_write_info {
83 bool success;
84 bool done;
85};
86
87struct ft4222_read_info {
88 unsigned char xfer_buf[READ_MAX_XFERS * READ_BUFFER_SIZE];
89 unsigned char *target_buf;
90 unsigned int active;
91 size_t total;
92 size_t skip;
93 size_t done;
94};
95
96struct ft4222 {
97 struct libusb_context *usb_context;
98 struct libusb_device_handle *usb_handle;
99 struct ft4222_write_info write_info;
100 struct ft4222_write_info dummy_write_info;
101 struct ft4222_write_info deassert_cs_info;
102 struct ft4222_read_info read_info;
103 unsigned char control_index;
104 unsigned char in_ep, out_ep;
105 unsigned char io_lines;
106};
107
108static struct ft4222_clock ft4222_find_spi_clock(const struct ft4222 *ft4222, const unsigned int target_khz)
109{
110 const unsigned int sys_clks[] = { 60000, 24000, 48000, 80000 };
111 struct ft4222_clock found = { .sys_idx = 1, .div_log2 = 9 };
112 unsigned int found_khz = sys_clks[found.sys_idx] / (1 << found.div_log2);
113 unsigned int sys, div;
114
115 if (target_khz < found_khz) {
116 msg_pwarn("No compatible clock found, using minimum of %ukHz.\n", found_khz);
117 return found;
118 }
119
120 /* look for the highest clock below given target */
121 for (sys = 0; sys < ARRAY_SIZE(sys_clks); ++sys) {
122 for (div = 9; div > 0; --div) {
123 const unsigned int this_khz = sys_clks[sys] / (1 << div);
124 if (this_khz > target_khz)
125 break;
126 if (this_khz < found_khz) /* accept equal khz for higher sys clk */
127 continue;
128
129 found_khz = this_khz;
130 found.sys_idx = sys;
131 found.div_log2 = div;
132 }
133 }
134
135 msg_pinfo("Using %ukHz SPI clock.\n", found_khz);
136 return found;
137}
138
139static int receive_control(const struct ft4222 *ft4222, unsigned char *data, size_t len,
140 uint8_t request, uint16_t value, uint16_t index)
141{
142 return libusb_control_transfer(
143 ft4222->usb_handle, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR,
144 request, value, index, data, len, USB_TIMEOUT);
145}
146
147static int ft4222_get_version(const struct ft4222 *ft4222, uint32_t *chip_version,
148 uint32_t *version2, uint32_t *version3)
149{
150 unsigned char buf[12];
151
152 int ret = receive_control(ft4222, buf, sizeof(buf), FT4222_INFO_REQUEST,
153 FT4222_GET_VERSION, ft4222->control_index);
154 if (ret < 0) {
155 msg_perr("Failed to query version: %s (%d)\n", libusb_strerror(ret), ret);
156 return SPI_PROGRAMMER_ERROR;
157 }
158
159 if (chip_version)
160 *chip_version = read_be32(buf, 0);
161 if (version2)
162 *version2 = read_be32(buf, 4);
163 if (version3)
164 *version3 = read_be32(buf, 8);
165
166 return 0;
167}
168
169static int ft4222_get_num_channels(const struct ft4222 *ft4222, unsigned int *channels)
170{
171 unsigned char buf[13];
172
173 int ret = receive_control(ft4222, buf, sizeof(buf), FT4222_INFO_REQUEST,
174 FT4222_GET_CONFIG, ft4222->control_index);
175 if (ret < 0) {
176 msg_perr("Failed to query config: %s (%d)\n", libusb_strerror(ret), ret);
177 return SPI_PROGRAMMER_ERROR;
178 }
179
180 switch (buf[0]) {
181 case 0: *channels = 1; return 0;
182 case 1: *channels = 3; return 0;
183 case 2: *channels = 4; return 0;
184 case 3: *channels = 1; return 0;
185 }
186
187 msg_perr("Failed to determine number of channels. Mode byte: 0x%02x\n", buf[0]);
188 return SPI_PROGRAMMER_ERROR;
189}
190
191static int send_control(const struct ft4222 *ft4222,
192 uint8_t request, uint16_t value, uint16_t index)
193{
194 return libusb_control_transfer(
195 ft4222->usb_handle, LIBUSB_REQUEST_TYPE_VENDOR,
196 request, value, index, NULL, 0, USB_TIMEOUT);
197}
198
199static void ft4222_flush(const struct ft4222 *ft4222, const uint16_t index)
200{
201 int i, ret;
202
203 for (i = 0; i < 6; ++i) {
204 ret = send_control(ft4222, 0, FT4222_OUTPUT_FLUSH, index);
205 if (ret < 0) {
206 msg_pwarn("FT4222 output flush failed: %s (%d)\n",
207 libusb_strerror(ret), ret);
208 break;
209 }
210 }
211
212 ret = send_control(ft4222, 0, FT4222_INPUT_FLUSH, index);
213 if (ret < 0)
214 msg_pwarn("FT4222 input flush failed: %s (%d)\n", libusb_strerror(ret), ret);
215}
216
217static void ft4222_reset(const struct ft4222 *ft4222)
218{
219 const int ret = send_control(ft4222, 0, FT4222_RESET_SIO, 0);
220 if (ret < 0)
221 msg_pwarn("FT4222 device reset failed: %s (%d)\n", libusb_strerror(ret), ret);
222
223 ft4222_flush(ft4222, ft4222->control_index);
224}
225
226static int ft4222_config_request(const struct ft4222 *ft4222, uint8_t cmd, uint8_t data)
227{
228 const int ret = send_control(ft4222, FT4222_CONFIG_REQUEST,
229 (data << 8) | cmd, ft4222->control_index);
230 if (ret < 0) {
231 msg_perr("FT4222 config command 0x%02x failed: %s (%d)\n",
232 cmd, libusb_strerror(ret), ret);
233 return SPI_PROGRAMMER_ERROR;
234 }
235
236 return 0;
237}
238
239static int ft4222_set_sys_clock(const struct ft4222 *ft4222, struct ft4222_clock clock)
240{
241 return ft4222_config_request(ft4222, FT4222_SET_CLOCK, clock.sys_idx);
242}
243
244static int ft4222_spi_set_io_lines(struct ft4222 *ft4222, const unsigned int lines)
245{
246 assert(lines == 1 || lines == 2 || lines == 4);
247
248 if (ft4222->io_lines == lines)
249 return 0;
250
251 int ret = ft4222_config_request(ft4222, FT4222_SPI_SET_IO_LINES, lines);
252 if (ret)
253 return ret;
254
255 ret = ft4222_config_request(ft4222, FT4222_SPI_RESET, FT4222_RESET_LINE_NUM);
256 if (!ret)
257 ft4222->io_lines = lines;
258
259 return ret;
260}
261
262static int ft4222_configure_spi_master(struct ft4222 *ft4222, struct ft4222_clock clock, unsigned int cs)
263{
264 assert(cs < 4);
265
266 /* LibFT4222 always does this for spiIdx 0. Assuming that's the
267 interface channel tied to a CS pin, and given that I couldn't
268 figure out how to make it use other channels, let's do this for
269 the channel we are going to use: */
270 if (ft4222_config_request(ft4222, FT4222_SPI_RESET_TRANSACTION, /* idx => */cs))
271 return SPI_PROGRAMMER_ERROR;
272
273 if (ft4222_spi_set_io_lines(ft4222, 1) ||
274 ft4222_config_request(ft4222, FT4222_SPI_SET_CLK_DIV, clock.div_log2) ||
275 ft4222_config_request(ft4222, FT4222_SPI_SET_CLK_IDLE, FT4222_CLK_IDLE_LOW) ||
276 ft4222_config_request(ft4222, FT4222_SPI_SET_CAPTURE, FT4222_LEADING_CLK) ||
277 ft4222_config_request(ft4222, FT4222_SPI_SET_CS_ACTIVE, FT4222_SPI_CS_ACTIVE_LOW) ||
278 ft4222_config_request(ft4222, FT4222_SPI_SET_CS_MASK, FT4222_SPI_CS_MASK(cs)) ||
279 ft4222_config_request(ft4222, FT4222_SET_MODE, FT4222_SPI_MASTER))
280 return SPI_PROGRAMMER_ERROR;
281
282 return 0;
283}
284
285static void ft4222_async_write_callback(struct libusb_transfer *transfer)
286{
287 struct ft4222_write_info *const async_info = transfer->user_data;
288
289 async_info->success = transfer->status == LIBUSB_TRANSFER_COMPLETED;
290 async_info->done = true;
291}
292
293static int ft4222_async_write(const struct ft4222 *const ft4222,
294 struct ft4222_write_info *const async_info,
295 const unsigned char *const buf, const size_t len)
296{
297 unsigned char *const out_buf = buf ? (unsigned char *)buf : malloc(len);
298 struct libusb_transfer *const transfer = libusb_alloc_transfer(0);
299
300 if (!out_buf || !transfer) {
301 msg_perr("Out of memory!\n");
302 goto err_ret;
303 }
304
305 if (out_buf != buf)
306 memset(out_buf, 0xff, len);
307 async_info->done = false;
308
309 libusb_fill_bulk_transfer(
310 transfer, ft4222->usb_handle, ft4222->out_ep,
311 out_buf, len, ft4222_async_write_callback, async_info, 16*USB_TIMEOUT);
312 transfer->flags |= LIBUSB_TRANSFER_SHORT_NOT_OK | LIBUSB_TRANSFER_FREE_TRANSFER;
313 if (out_buf != buf)
314 transfer->flags |= LIBUSB_TRANSFER_FREE_BUFFER;
315
316 const int ret = libusb_submit_transfer(transfer);
317 if (ret != LIBUSB_SUCCESS) {
318 msg_perr("Failed to queue %zuB transfer: %s (%d)\n",
319 len, libusb_strerror(ret), ret);
320 goto err_ret;
321 }
322
323 return 0;
324
325err_ret:
326 libusb_free_transfer(transfer);
327 if (out_buf != buf)
328 free(out_buf);
329
330 return SPI_GENERIC_ERROR;
331}
332
333static unsigned int ft4222_num_async_reads(const struct ft4222_read_info *info)
334{
335 return MIN(READ_MAX_XFERS,
336 (info->total - info->done + READ_BUFFER_SIZE - 1) / READ_BUFFER_SIZE);
337}
338
339static void ft4222_async_read_callback(struct libusb_transfer *const transfer)
340{
341 struct ft4222_read_info *const info = transfer->user_data;
342 bool warned_status = false;
343
344 if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
345 msg_perr("Read failure: %s (%d)\n",
346 libusb_strerror(transfer->status), transfer->status);
347 goto free_transfer;
348 }
349
350 /* A transfer contains multiple packages of up to 512B. Each one
351 starts with a 2B status (libftdi calls it modem status). */
352 size_t actual_len = transfer->actual_length;
353 const unsigned char *packet = transfer->buffer;
354 while (actual_len > 0) {
355 const size_t packet_len = MIN(actual_len, 512);
356 msg_pspew("%s: packet of %zu bytes\n", __func__, packet_len);
357
358 if (packet_len < 2) {
359 msg_perr("Read failure: Broken packet\n");
360 goto free_transfer;
361 }
362
363 /* So far we always received the same status bytes.
364 Libftdi ignores them, so only warn if we get some-
365 thing different. */
366 if (!warned_status && (packet[0] != 0x02 || packet[1] != 0x00)) {
367 msg_pwarn("Unknown status code %02x %02x\n", packet[0], packet[1]);
368 warned_status = true;
369 }
370
371 if (packet_len == 2) {
372 msg_pdbg2("%s: Empty packet (%u active transfers)\n", __func__, info->active);
373 break;
374 }
375
376 const size_t done_here = MIN(packet_len - 2, info->total - info->done);
377 if (info->done + done_here > info->skip) {
378 size_t buffer_off, packet_off;
379 if (info->done < info->skip) {
380 buffer_off = 0;
381 packet_off = info->skip - info->done;
382 } else {
383 buffer_off = info->done - info->skip;
384 packet_off = 0;
385 }
386 const size_t copy = MIN(done_here - packet_off,
387 info->total - info->skip - buffer_off);
388 memcpy(info->target_buf + buffer_off, packet + 2 + packet_off, copy);
389 }
390 info->done += done_here;
391 msg_pspew("%s: Processed %zuB\n", __func__, done_here);
392
393 actual_len -= packet_len;
394 packet += packet_len;
395 }
396
397 if (info->active <= ft4222_num_async_reads(info)) {
398 const int ret = libusb_submit_transfer(transfer);
399 if (ret != LIBUSB_SUCCESS)
400 msg_perr("Failed to re-queue %dB transfer: %s (%d)\n",
401 transfer->length, libusb_strerror(ret), ret);
402 else /* do not free re-submitted transfer */
403 return;
404 }
405
406free_transfer:
407 libusb_free_transfer(transfer);
408 --info->active;
409}
410
411static int ft4222_async_read(const struct ft4222 *const ft4222,
412 struct ft4222_read_info *const info,
413 unsigned char *const dst, const size_t len, const size_t skip)
414{
415 info->target_buf = dst;
416 info->active = 0;
417 info->total = len + skip;
418 info->skip = skip;
419 info->done = 0;
420
421 unsigned int i;
422 for (i = 0; i < ft4222_num_async_reads(info); ++i) {
423 struct libusb_transfer *const transfer = libusb_alloc_transfer(0);
424 if (!transfer) {
425 msg_perr("Out of memory!\n");
426 return SPI_GENERIC_ERROR;
427 }
428
429 unsigned char *const buf = info->xfer_buf + i * READ_BUFFER_SIZE;
430 libusb_fill_bulk_transfer(
431 transfer, ft4222->usb_handle, ft4222->in_ep,
432 buf, READ_BUFFER_SIZE, ft4222_async_read_callback,
433 info, USB_TIMEOUT);
434
435 const int ret = libusb_submit_transfer(transfer);
436 if (ret != LIBUSB_SUCCESS) {
437 msg_perr("Failed to queue %dB transfer: %s (%d)\n",
438 transfer->length, libusb_strerror(ret), ret);
439 libusb_free_transfer(transfer);
440 return SPI_GENERIC_ERROR;
441 }
442 ++info->active;
443 }
444
445 return 0;
446}
447
448static void ft4222_async_init(struct ft4222 *ft4222)
449{
450 /* initialize such that ft4222_async_done() thinks we're done */
451 const struct ft4222_write_info success = { .success = true, .done = true };
452 ft4222->write_info = success;
453 ft4222->dummy_write_info = success;
454 ft4222->deassert_cs_info = success;
455 ft4222->read_info.active = ft4222->read_info.total = ft4222->read_info.done = 0;
456}
457
458static bool ft4222_async_done(const struct ft4222 *ft4222)
459{
460 return ft4222->write_info.done &&
461 ft4222->dummy_write_info.done &&
462 ft4222->deassert_cs_info.done &&
463 ft4222->read_info.active == 0;
464}
465
466static int ft4222_async_poll(const struct ft4222 *ft4222)
467{
468 while (!ft4222_async_done(ft4222)) {
469 struct timeval timeout = { 10, 0 };
470 const int ret = libusb_handle_events_timeout(ft4222->usb_context, &timeout);
471 if (ret != LIBUSB_SUCCESS) {
472 msg_perr("Polling transfers failed: %s!\n", libusb_error_name(ret));
473 return SPI_GENERIC_ERROR;
474 }
475 }
476
477 if (!ft4222->write_info.success ||
478 !ft4222->dummy_write_info.success ||
479 !ft4222->deassert_cs_info.success ||
480 ft4222->read_info.done < ft4222->read_info.total)
481 return SPI_GENERIC_ERROR;
482
483 return 0;
484}
485
486static int ft4222_spi_send_command(
487 const struct flashctx *const flash,
488 const unsigned int writecnt, const unsigned int readcnt,
489 const unsigned char *const writearr, unsigned char *const readarr)
490{
491 struct ft4222 *const ft4222 = flash->mst.spi->data;
492 int ret, poll_ret;
493
494 ret = ft4222_spi_set_io_lines(ft4222, 1);
495 if (ret)
496 return ret;
497
498 /*
499 * Single-i/o mode is full-duplex. So we send
500 * o `writecnt` real bytes,
501 * o `readcnt` dummy bytes, and
502 * o an empty packet to deassert CS.
503 * Then we read but discard
504 * o `writecnt` dummy bytes, and read
505 * o `readcnt` real bytes.
506 */
507
508 ft4222_async_init(ft4222);
509
510 ret = ft4222_async_write(ft4222, &ft4222->write_info, writearr, writecnt);
511 if (ret)
512 goto poll;
513
514 ret = ft4222_async_write(ft4222, &ft4222->dummy_write_info, NULL, readcnt);
515 if (ret)
516 goto poll;
517
518 ret = ft4222_async_write(ft4222, &ft4222->deassert_cs_info, NULL, 0);
519 if (ret)
520 goto poll;
521
522 ret = ft4222_async_read(ft4222, &ft4222->read_info, readarr,
523 /* len => */readcnt, /* skip => */writecnt);
524
525poll: /* we should always poll, in case we partially started transfers */
526 poll_ret = ft4222_async_poll(ft4222);
527 return ret ? ret : poll_ret;
528}
529
530static int ft4222_spi_send_multi_io(struct ft4222 *ft4222, const struct spi_command *cmd)
531{
532 const size_t read_total = cmd->high_z_len + cmd->read_len;
533 size_t write_single = 0, write_multi = 0;
534 unsigned int io_lines = 4;
535 int ret, poll_ret;
536
537 switch (cmd->io_mode) {
538 case DUAL_OUT_1_1_2:
539 io_lines = 2;
540 /* fall-through */
541 case QUAD_OUT_1_1_4:
542 write_single = cmd->opcode_len + cmd->address_len + cmd->write_len;
543 break;
544
545 case DUAL_IO_1_2_2:
546 io_lines = 2;
547 /* fall-through */
548 case QUAD_IO_1_4_4:
549 write_single = cmd->opcode_len;
550 write_multi = cmd->address_len + cmd->write_len;
551 break;
552
553 case QPI_4_4_4:
554 write_multi = cmd->opcode_len + cmd->address_len + cmd->write_len;
555 break;
556
557 default:
558 return SPI_FLASHPROG_BUG;
559 }
560
561 ret = ft4222_spi_set_io_lines(ft4222, io_lines);
562 if (ret)
563 return ret;
564
565 /*
566 * Multi-i/o mode is half-duplex. We can send up to 15B ahead
567 * as single-i/o. Then write and read up to 65535 bytes each
568 * as multi-i/o. Looks suspiciously tailored to our use case. :)
569 *
570 * The lengths are controlled by a 5B header:
571 * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
572 * | 4 bit | 4 bit | 2B big-endian | 2B big-endian |
573 * +-------+----------------+---------------------+--------------------+
574 * | 0x8 | single-i/o len | multi-i/o write len | multi-i/o read len |
575 * +-------+----------------+---------------------+--------------------+
576 */
577
578 if (write_single > 15 || write_multi > UINT16_MAX || read_total > UINT16_MAX)
579 return SPI_INVALID_LENGTH;
580
581 unsigned char *const write_buf = malloc(5 + write_single + write_multi);
582 if (!write_buf)
583 return SPI_GENERIC_ERROR;
584
585 write_buf[0] = 0x80 | write_single;
586 write_buf[1] = write_multi >> 8 & 0xff;
587 write_buf[2] = write_multi >> 0 & 0xff;
588 write_buf[3] = read_total >> 8 & 0xff;
589 write_buf[4] = read_total >> 0 & 0xff;
590 memcpy(write_buf + 5, cmd->writearr, write_single + write_multi);
591
592 ft4222_async_init(ft4222);
593
594 ret = ft4222_async_write(ft4222, &ft4222->write_info, write_buf, 5 + write_single + write_multi);
595 if (ret)
596 goto poll;
597
598 ret = ft4222_async_read(ft4222, &ft4222->read_info, cmd->readarr,
599 /* len => */cmd->read_len, /* skip => */cmd->high_z_len);
600
601poll: /* we should always poll, in case we partially started transfers */
602 poll_ret = ft4222_async_poll(ft4222);
603
604 free(write_buf);
605
606 return ret ? ret : poll_ret;
607}
608
609static int ft4222_spi_send_multicommand(const struct flashctx *flash, struct spi_command *cmds)
610{
611 struct ft4222 *const ft4222 = flash->mst.spi->data;
612
613 for (; !spi_is_empty(cmds); ++cmds) {
614 int ret;
615 if (cmds->io_mode == SINGLE_IO_1_1_1) {
616 ret = ft4222_spi_send_command(flash, spi_write_len(cmds),
617 spi_read_len(cmds), cmds->writearr, cmds->readarr);
618 } else {
619 ret = ft4222_spi_send_multi_io(ft4222, cmds);
620 }
621 if (ret)
622 return ret;
623 }
624
625 return 0;
626}
627
628static int ft4222_shutdown(void *data)
629{
630 struct ft4222 *const ft4222 = data;
631 libusb_close(ft4222->usb_handle);
632 libusb_exit(ft4222->usb_context);
633 free(data);
634 return 0;
635}
636
637static const struct spi_master spi_master_ft4222 = {
638 .features = SPI_MASTER_4BA | SPI_MASTER_DUAL,
639 .max_data_read = 65530,
640 .max_data_write = MAX_DATA_WRITE_UNLIMITED,
641 .command = ft4222_spi_send_command,
642 .multicommand = ft4222_spi_send_multicommand,
643 .read = default_spi_read,
644 .write_256 = default_spi_write_256,
645 .shutdown = ft4222_shutdown,
646 .probe_opcode = default_spi_probe_opcode,
647};
648
649/* Returns 0 upon success, a negative number upon errors. */
650static int ft4222_spi_init(struct flashprog_programmer *const prog)
651{
652 struct spi_master master = spi_master_ft4222;
653 uint32_t chip_version, version2, version3;
654 unsigned long speed_khz = 10*1000;
655 unsigned long cs = 0;
656 unsigned int num_cs, i;
657 char *endp;
658
659 char *const cs_arg = extract_programmer_param("cs");
660 if (cs_arg) {
661 cs = strtoul(cs_arg, &endp, 10);
662 if (cs_arg == endp || cs > 3) {
663 msg_perr("Invalid cs setting: %s\n", cs_arg);
664 free(cs_arg);
665 return SPI_GENERIC_ERROR;
666 }
667 }
668 msg_pdbg("Using CS#%lu.\n", cs);
669 free(cs_arg);
670
671 char *const spispeed = extract_programmer_param("spispeed");
672 if (spispeed) {
673 speed_khz = strtoul(spispeed, &endp, 10);
674 if (spispeed == endp || speed_khz == 0 || speed_khz > UINT_MAX) {
675 msg_perr("Invalid spispeed setting: %s kHz\n", spispeed);
676 free(spispeed);
677 return SPI_GENERIC_ERROR;
678 }
679 } else {
680 msg_pinfo("Using default %lukHz clock. Use 'spispeed' parameter to override.\n",
681 speed_khz);
682 }
683 free(spispeed);
684
685 char *const io_mode = extract_programmer_param("iomode");
686 if (io_mode) {
687 if (strcmp(io_mode, "single") == 0) {
688 master.features &= ~SPI_MASTER_DUAL;
689 } else if (strcmp(io_mode, "dual") == 0) {
690 /* dual-i/o mode is enabled by default */
691 } else if (strcmp(io_mode, "quad") == 0) {
692 master.features |= SPI_MASTER_QUAD | SPI_MASTER_QPI;
693 } else {
694 msg_perr("Invalid iomode setting: %s\n", io_mode);
695 return SPI_GENERIC_ERROR;
696 }
697 }
698 free(io_mode);
699
700 struct ft4222 *const ft4222 = calloc(1, sizeof(*ft4222));
701 if (!ft4222) {
702 msg_perr("Could not allocate space for FT4222 context\n");
703 return SPI_GENERIC_ERROR;
704 }
705
706 int ret = libusb_init(&ft4222->usb_context);
707 if (ret != LIBUSB_SUCCESS) {
708 msg_perr("Could not initialize libusb: %s\n", libusb_error_name(ret));
709 free(ft4222);
710 return SPI_GENERIC_ERROR;
711 }
712
713 /* Enable information, warning, and error messages (only). */
714 libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
715
716 const uint16_t vid = devs[0].vendor_id;
717 const uint16_t pid = devs[0].device_id;
718 ft4222->usb_handle = libusb_open_device_with_vid_pid(ft4222->usb_context, vid, pid);
719 if (ft4222->usb_handle == NULL) {
720 msg_perr("Couldn't open device %04x:%04x.\n", vid, pid);
721 libusb_exit(ft4222->usb_context);
722 free(ft4222);
723 return SPI_GENERIC_ERROR;
724 }
725
726 struct libusb_config_descriptor *config;
727 ret = libusb_get_active_config_descriptor(libusb_get_device(ft4222->usb_handle), &config);
728 if (ret != LIBUSB_SUCCESS) {
729 msg_perr("Couldn't get config descriptor: %s (%d)\n", libusb_strerror(ret), ret);
730 ret = SPI_GENERIC_ERROR;
731 goto shutdown;
732 }
733
734 if (config->bNumInterfaces > 1) {
735 /* LibFT4222 does this. So far it's
736 not known to make a difference. */
737 ft4222->control_index = 1;
738 }
739
740 ret = ft4222_get_version(ft4222, &chip_version, &version2, &version3);
741 if (ret)
742 goto free_config_shutdown;
743 msg_pinfo("Found %s, chip version %08x (%08x %08x)\n",
744 devs[0].device_name, chip_version, version2, version3);
745
746 ret = ft4222_get_num_channels(ft4222, &num_cs);
747 if (ret)
748 goto free_config_shutdown;
749 if (cs >= num_cs) {
750 msg_perr("Invalid cs setting: %lu, maximum is %u.\n", cs, num_cs - 1);
751 ret = SPI_GENERIC_ERROR;
752 goto free_config_shutdown;
753 }
754
755 if (cs >= config->bNumInterfaces) {
756 msg_perr("Error: Device supports less interfaces than expected.\n");
757 ret = SPI_GENERIC_ERROR;
758 goto free_config_shutdown;
759 }
760
761 ret = libusb_claim_interface(ft4222->usb_handle, cs);
762 if (ret != LIBUSB_SUCCESS) {
763 msg_perr("Couldn't claim interface %lu: %s (%d)\n", cs, libusb_strerror(ret), ret);
764 ret = SPI_GENERIC_ERROR;
765 goto free_config_shutdown;
766 }
767
768 const struct libusb_interface_descriptor *const interface =
769 config->interface[cs].altsetting;
770
771 /* Try first alternate setting if there are more than one. */
772 if (config->interface[cs].num_altsetting > 1) {
773 ret = libusb_set_interface_alt_setting(
774 ft4222->usb_handle, cs, interface->bAlternateSetting);
775 if (ret != LIBUSB_SUCCESS) {
776 msg_perr("Failed to select alternate interface: %s (%d)\n",
777 libusb_strerror(ret), ret);
778 ret = SPI_GENERIC_ERROR;
779 goto free_config_shutdown;
780 }
781 }
782
783 for (i = 0; i < interface->bNumEndpoints; ++i) {
784 if (interface->endpoint[i].bEndpointAddress & LIBUSB_ENDPOINT_IN)
785 ft4222->in_ep = interface->endpoint[i].bEndpointAddress;
786 else
787 ft4222->out_ep = interface->endpoint[i].bEndpointAddress;
788 if (ft4222->in_ep && ft4222->out_ep)
789 break;
790 }
791 if (!ft4222->in_ep || !ft4222->out_ep) {
792 msg_perr("Error: Couldn't find compatible endpoints.\n");
793 ret = SPI_GENERIC_ERROR;
794 goto free_config_shutdown;
795 }
796
797 libusb_free_config_descriptor(config);
798
799 ft4222_reset(ft4222);
800
801 const struct ft4222_clock clock = ft4222_find_spi_clock(ft4222, speed_khz);
802 ret = ft4222_set_sys_clock(ft4222, clock);
803 if (ret)
804 goto shutdown;
805
806 ret = ft4222_configure_spi_master(ft4222, clock, cs);
807 if (ret)
808 goto shutdown;
809
810 return register_spi_master(&master, 0, ft4222);
811
812free_config_shutdown:
813 libusb_free_config_descriptor(config);
814shutdown:
815 ft4222_shutdown(ft4222);
816 return ret;
817}
818
819const struct programmer_entry programmer_ft4222_spi = {
820 .name = "ft4222_spi",
821 .type = USB,
822 .devs.dev = devs,
823 .init = ft4222_spi_init,
824};