// Copyright 2021 The Periph Authors. All rights reserved. // Use of this source code is governed under the Apache License, Version 2.0 // that can be found in the LICENSE file. package ftdi import ( "context" "errors" "fmt" "io" "periph.io/x/conn/v3/physic" "periph.io/x/d2xx" ) // // bitMode is used by SetBitMode to change the chip behavior. type bitMode uint8 const ( // Resets all Pins to their default value bitModeReset bitMode = 0x00 // Sets the DBus to asynchronous bit-bang. bitModeAsyncBitbang bitMode = 0x01 // Switch to MPSSE mode (FT2232, FT2232H, FT4232H and FT232H). bitModeMpsse bitMode = 0x02 // Sets the DBus to synchronous bit-bang (FT232R, FT245R, FT2232, FT2232H, // FT4232H and FT232H). bitModeSyncBitbang bitMode = 0x04 // Switch to MCU host bus emulation (FT2232, FT2232H, FT4232H and FT232H). bitModeMcuHost bitMode = 0x08 // Switch to fast opto-isolated serial mode (FT2232, FT2232H, FT4232H and // FT232H). bitModeFastSerial bitMode = 0x10 // Sets the CBus in 4 bits bit-bang mode (FT232R and FT232H) // In this case, upper nibble controls which pin is output/input, lower // controls which of outputs are high and low. bitModeCbusBitbang bitMode = 0x20 // Single Channel Synchronous 245 FIFO mode (FT2232H and FT232H). bitModeSyncFifo bitMode = 0x40 ) // numDevices returns the number of detected devices. func numDevices() (int, error) { num, e := d2xx.CreateDeviceInfoList() if e != 0 { return 0, toErr("GetNumDevices initialization failed", e) } return num, nil } func openHandle(opener func(i int) (d2xx.Handle, d2xx.Err), i int) (*handle, error) { h, e := opener(i) if e != 0 { return nil, toErr("Open", e) } d := &handle{h: h} t, vid, did, e := h.GetDeviceInfo() if e != 0 { _ = d.Close() return nil, toErr("GetDeviceInfo", e) } d.t = DevType(t) d.venID = vid d.devID = did return d, nil } // handle is a thin wrapper around the low level d2xx device handle to make it // more go-idiomatic. // // It also implements many utility functions to help with initialization and // device management. type handle struct { // It is just above 'handle' which directly maps to D2XX function calls. // // Dev converts the int error type into Go native error and handles higher // level functionality like reading and writing to the USB connection. // // The content of the struct is immutable after initialization. h d2xx.Handle t DevType venID uint16 devID uint16 } func (h *handle) Close() error { // Not yet called. return toErr("Close", h.h.Close()) } // Init is the general setup for common devices. // // It tries first the 'happy path' which doesn't reset the device. By doing so, // the goal is to reduce the amount of glitches on the GPIO pins, on a best // effort basis. On all devices, the GPIOs are still reset as inputs, since // there is no way to determine if each GPIO is an input or output. func (h *handle) Init() error { // Driver: maximum packet size. Note that this clears any data in the buffer, // so it is good to do it immediately after a reset. The 'out' parameter is // ignored. // TODO(maruel): The FT232H doc claims a 512 byte packets support in hi-speed // mode, which means that this would likely be better to use this value. if e := h.h.SetUSBParameters(65536, 0); e != 0 { return toErr("SetUSBParameters", e) } // Driver: Set I/O timeouts to 15 sec. The reason is that we want the // timeouts to be very visible, at least as the driver is being developed. if e := h.h.SetTimeouts(15000, 15000); e != 0 { return toErr("SetTimeouts", e) } // Not sure: Disable event/error characters. if e := h.h.SetChars(0, false, 0, false); e != 0 { return toErr("SetChars", e) } // Not sure: Latency timer at 1ms. if e := h.h.SetLatencyTimer(1); e != 0 { return toErr("SetLatencyTimer", e) } // Not sure: Turn on flow control to synchronize IN requests. if e := h.h.SetFlowControl(); e != 0 { return toErr("SetFlowControl", e) } // Just in case. It's a very small cost. return h.Flush() } // Reset resets the device. func (h *handle) Reset() error { if e := h.h.ResetDevice(); e != 0 { return toErr("Reset", e) } if err := h.SetBitMode(0, bitModeReset); err != nil { return err } // USB/driver: Flush any pending read buffer that had been sent by the device // before it reset. Do not return any error there, as the device may spew a // read error right after being initialized. _ = h.Flush() return nil } // GetBitMode returns the current bit mode. // // This is device-dependent. func (h *handle) GetBitMode() (byte, error) { l, e := h.h.GetBitMode() if e != 0 { return 0, toErr("GetBitMode", e) } return l, nil } // SetBitMode change the mode of operation of the device. // // mask sets which pins are inputs and outputs for bitModeCbusBitbang. func (h *handle) SetBitMode(mask byte, mode bitMode) error { return toErr("SetBitMode", h.h.SetBitMode(mask, byte(mode))) } // Flush flushes any data left in the read buffer. func (h *handle) Flush() error { var buf [128]byte for { p, err := h.Read(buf[:]) if err != nil { return err } if p == 0 { return nil } } } // Read returns as much as available in the read buffer without blocking. func (h *handle) Read(b []byte) (int, error) { // GetQueueStatus() 60µs is relatively slow compared to Read() 4µs, // but surprisingly if GetQueueStatus() is *not* called, Read() // becomes largely slower (800µs). // // TODO(maruel): This asks for more perf testing before settling on the best // solution. // TODO(maruel): Investigate FT_GetStatus(). p, e := h.h.GetQueueStatus() if p == 0 || e != 0 { return int(p), toErr("Read/GetQueueStatus", e) } v := int(p) if v > len(b) { v = len(b) } n, e := h.h.Read(b[:v]) return n, toErr("Read", e) } // ReadAll blocks to return all the data. // // Similar to ioutil.ReadAll() except that it will stop if the context is // canceled. func (h *handle) ReadAll(ctx context.Context, b []byte) (int, error) { // TODO(maruel): Use FT_SetEventNotification() instead of looping when // waiting for bytes. for offset := 0; offset != len(b); { if ctx.Err() != nil { return offset, io.EOF } chunk := len(b) - offset if chunk > 4096 { chunk = 4096 } n, err := h.Read(b[offset : offset+chunk]) if offset += n; err != nil { return offset, err } } return len(b), nil } // WriteFast writes to the USB device. // // In practice this takes at least 0.1ms, which limits the effective rate. // // There's no guarantee that the data is all written, so it is important to // check the return value. func (h *handle) WriteFast(b []byte) (int, error) { n, e := h.h.Write(b) return n, toErr("Write", e) } // Write blocks until all data is written. func (h *handle) Write(b []byte) (int, error) { for offset := 0; offset != len(b); { chunk := len(b) - offset if chunk > 4096 { chunk = 4096 } p, err := h.WriteFast(b[offset : offset+chunk]) if err != nil { return offset + p, err } if p != 0 { offset += p } } return len(b), nil } // ReadEEPROM reads the EEPROM. func (h *handle) ReadEEPROM(ee *EEPROM) error { // The raw data size must be exactly what the device contains. eepromSize := h.t.EEPROMSize() if len(ee.Raw) < eepromSize { ee.Raw = make([]byte, eepromSize) } else if len(ee.Raw) > eepromSize { ee.Raw = ee.Raw[:eepromSize] } ee2 := d2xx.EEPROM{Raw: ee.Raw} e := h.h.EEPROMRead(uint32(h.t), &ee2) ee.Manufacturer = ee2.Manufacturer ee.ManufacturerID = ee2.ManufacturerID ee.Desc = ee2.Desc ee.Serial = ee2.Serial if e != 0 { // 15 == FT_EEPROM_NOT_PROGRAMMED if e != 15 { return toErr("EEPROMRead", e) } // It's a fresh new device. Devices bought via Adafruit already have // their EEPROM programmed with Adafruit branding but fake devices sold by // CJMCU are not. Since GetDeviceInfo() above succeeded, we know the // device type via the USB descriptor, which is sufficient to load the // driver, which permits to program the EEPROM to "bootstrap" it. // // Fill it with an empty yet valid EEPROM content. We don't want to set // VenID or DevID to 0! Nobody would do that, right? ee.Raw = make([]byte, h.t.EEPROMSize()) hdr := ee.AsHeader() hdr.DeviceType = h.t hdr.VendorID = h.venID hdr.ProductID = h.devID } return nil } // WriteEEPROM programs the EEPROM. func (h *handle) WriteEEPROM(ee *EEPROM) error { if err := ee.Validate(); err != nil { return err } if len(ee.Raw) != 0 { hdr := ee.AsHeader() if hdr == nil { return errors.New("ftdi: unexpected EEPROM header size") } if hdr.DeviceType != h.t { return errors.New("ftdi: unexpected device type set while programming EEPROM") } if hdr.VendorID != h.venID { return errors.New("ftdi: unexpected VenID set while programming EEPROM") } if hdr.ProductID != h.devID { return errors.New("ftdi: unexpected DevID set while programming EEPROM") } } ee2 := d2xx.EEPROM{ Raw: ee.Raw, Manufacturer: ee.Manufacturer, ManufacturerID: ee.ManufacturerID, Desc: ee.Desc, Serial: ee.Serial, } return toErr("EEPROMWrite", h.h.EEPROMProgram(&ee2)) } // EraseEEPROM erases all the EEPROM. // // Will fail on FT232R and FT245R. func (h *handle) EraseEEPROM() error { return toErr("EraseEE", h.h.EraseEE()) } // ReadUA reads the EEPROM user area. // // May return nil when there's nothing programmed yet. func (h *handle) ReadUA() ([]byte, error) { size, e := h.h.EEUASize() if e != 0 { return nil, toErr("EEUASize", e) } if size == 0 { // Happens on uninitialized EEPROM. return nil, nil } b := make([]byte, size) if e := h.h.EEUARead(b); e != 0 { return nil, toErr("EEUARead", e) } return b, nil } // WriteUA writes to the EEPROM user area. func (h *handle) WriteUA(ua []byte) error { size, e := h.h.EEUASize() if e != 0 { return toErr("EEUASize", e) } if size == 0 { return errors.New("ftdi: please program EEPROM first") } if size < len(ua) { return fmt.Errorf("ftdi: maximum user area size is %d bytes", size) } if size != len(ua) { b := make([]byte, size) copy(b, ua) ua = b } if e := h.h.EEUAWrite(ua); e != 0 { return toErr("EEUAWrite", e) } return nil } // SetBaudRate sets the baud rate. func (h *handle) SetBaudRate(f physic.Frequency) error { if f >= physic.GigaHertz { return errors.New("ftdi: baud rate too high") } v := uint32(f / physic.Hertz) return toErr("SetBaudRate", h.h.SetBaudRate(v)) } // func toErr(s string, e d2xx.Err) error { if e == 0 { return nil } return errors.New("ftdi: " + s + ": " + e.String()) }