// Copyright 2017 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"
	"strconv"
	"sync"

	"periph.io/x/conn/v3"
	"periph.io/x/conn/v3/gpio"
	"periph.io/x/conn/v3/i2c"
	"periph.io/x/conn/v3/physic"
	"periph.io/x/conn/v3/spi"
)

// Info is the information gathered about the connected FTDI device.
//
// The data is gathered from the USB descriptor.
type Info struct {
	// Opened is true if the device was successfully opened.
	Opened bool
	// Type is the FTDI device type.
	//
	// The value can be "FT232H", "FT232R", etc.
	//
	// An empty string means the type is unknown.
	Type string
	// VenID is the vendor ID from the USB descriptor information. It is expected
	// to be 0x0403 (FTDI).
	VenID uint16
	// DevID is the product ID from the USB descriptor information. It is
	// expected to be one of 0x6001, 0x6006, 0x6010, 0x6014.
	DevID uint16
}

// Dev represents one FTDI device.
//
// There can be multiple FTDI devices connected to a host.
//
// The device may also export one or multiple of I²C, SPI buses. You need to
// either cast into the right hardware, but more simply use the i2creg / spireg
// bus/port registries.
type Dev interface {
	// conn.Resource
	String() string
	Halt() error

	// Info returns information about an opened device.
	Info(i *Info)

	// Header returns the GPIO pins exposed on the chip.
	Header() []gpio.PinIO

	// SetSpeed sets the base clock for all I/O transactions.
	//
	// The device defaults to its fastest speed.
	SetSpeed(f physic.Frequency) error

	// EEPROM returns the EEPROM content.
	EEPROM(ee *EEPROM) error
	// WriteEEPROM updates the EEPROM. Must be used carefully.
	WriteEEPROM(ee *EEPROM) error
	// EraseEEPROM erases the EEPROM. Must be used carefully.
	EraseEEPROM() error
	// UserArea reads and return the EEPROM part that can be used to stored user
	// defined values.
	UserArea() ([]byte, error)
	// WriteUserArea updates the user area in the EEPROM.
	//
	// If the length of ua is less than the available space, is it zero extended.
	WriteUserArea(ua []byte) error
}

// broken represents a device that couldn't be opened correctly.
//
// It returns an error message to help the user diagnose issues.
type broken struct {
	index int
	err   error
	name  string
}

func (b *broken) String() string {
	return b.name
}

func (b *broken) Halt() error {
	return nil
}

func (b *broken) Info(i *Info) {
	i.Opened = false
}

func (b *broken) Header() []gpio.PinIO {
	return nil
}

func (b *broken) SetSpeed(f physic.Frequency) error {
	return b.err
}

func (b *broken) EEPROM(ee *EEPROM) error {
	return b.err
}

func (b *broken) WriteEEPROM(ee *EEPROM) error {
	return b.err
}

func (b *broken) EraseEEPROM() error {
	return b.err
}

func (b *broken) UserArea() ([]byte, error) {
	return nil, b.err
}

func (b *broken) WriteUserArea(ua []byte) error {
	return b.err
}

// generic represents a generic FTDI device.
//
// It is used for the models that this package doesn't fully support yet.
type generic struct {
	// Immutable after initialization.
	index int
	h     *handle
	name  string
}

func (f *generic) String() string {
	return f.name
}

// Halt implements conn.Resource.
//
// This halts all operations going through this device.
func (f *generic) Halt() error {
	return f.h.Reset()
}

// Info returns information about an opened device.
func (f *generic) Info(i *Info) {
	i.Opened = true
	i.Type = f.h.t.String()
	i.VenID = f.h.venID
	i.DevID = f.h.devID
}

// Header returns the GPIO pins exposed on the chip.
func (f *generic) Header() []gpio.PinIO {
	return nil
}

func (f *generic) SetSpeed(freq physic.Frequency) error {
	// TODO(maruel): Doc says the actual speed is 16x, confirm.
	return f.h.SetBaudRate(freq)
}

func (f *generic) EEPROM(ee *EEPROM) error {
	return f.h.ReadEEPROM(ee)
	/*
		if f.ee.Raw == nil {
			if err := f.h.readEEPROM(&f.ee); err != nil {
				return nil
			}
			if f.ee.Raw == nil {
				// It's a fresh new device. Devices bought via Adafruit already have
				// their EEPROM programmed with Adafruit branding but devices sold by
				// CJMCU are not. Since d2xxGetDeviceInfo() 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.
				f.ee.Raw = []byte{}
			}
		}
		*ee = f.ee
		return nil
	*/
}

func (f *generic) WriteEEPROM(ee *EEPROM) error {
	// TODO(maruel): Compare with the cached EEPROM, and only update the
	// different values if needed so reduce the EEPROM wear.
	// f.h.h.d2xxWriteEE()
	return f.h.WriteEEPROM(ee)
}

func (f *generic) EraseEEPROM() error {
	return f.h.EraseEEPROM()
}

func (f *generic) UserArea() ([]byte, error) {
	return f.h.ReadUA()
}

func (f *generic) WriteUserArea(ua []byte) error {
	return f.h.WriteUA(ua)
}

//

func newFT232H(g generic) (*FT232H, error) {
	f := &FT232H{
		generic: g,
		cbus:    gpiosMPSSE{h: g.h, cbus: true},
		dbus:    gpiosMPSSE{h: g.h},
		c8:      invalidPin{num: 16, n: g.name + ".C8"}, // , dp: gpio.PullUp
		c9:      invalidPin{num: 17, n: g.name + ".C9"}, // , dp: gpio.PullUp
	}
	f.cbus.init(f.name)
	f.dbus.init(f.name)

	for i := range f.dbus.pins {
		f.hdr[i] = &f.dbus.pins[i]
	}
	for i := range f.cbus.pins {
		f.hdr[i+8] = &f.cbus.pins[i]
	}
	// TODO(maruel): C8 and C9 can be used when their mux in the EEPROM is set to
	// ft232hCBusIOMode.
	f.hdr[16] = &f.c8
	f.hdr[17] = &f.c9
	f.D0 = f.hdr[0]
	f.D1 = f.hdr[1]
	f.D2 = f.hdr[2]
	f.D3 = f.hdr[3]
	f.D4 = f.hdr[4]
	f.D5 = f.hdr[5]
	f.D6 = f.hdr[6]
	f.D7 = f.hdr[7]
	f.C0 = f.hdr[8]
	f.C1 = f.hdr[9]
	f.C2 = f.hdr[10]
	f.C3 = f.hdr[11]
	f.C4 = f.hdr[12]
	f.C5 = f.hdr[13]
	f.C6 = f.hdr[14]
	f.C7 = f.hdr[15]
	f.C8 = f.hdr[16]
	f.C9 = f.hdr[17]

	// This function forces all pins as inputs.
	if err := f.h.InitMPSSE(); err != nil {
		return nil, err
	}
	f.s.c.f = f
	f.i.f = f
	return f, nil
}

// FT232H represents a FT232H device.
//
// It implements Dev.
//
// The FT232H has 1024 bytes output buffer and 1024 bytes input buffer. It
// supports 512 bytes USB packets.
//
// The device can be used in a few different modes, two modes are supported:
//
// - D0~D3 as a serial protocol (MPSEE), supporting I²C and SPI (and eventually
// UART), In this mode, D4~D7 and C0~C7 can be used as synchronized GPIO.
//
// - D0~D7 as a synchronous 8 bits bit-bang port. In this mode, only a few pins
// on CBus are usable in slow mode.
//
// Each group of pins D0~D7 and C0~C7 can be changed at once in one pass via
// DBus() or CBus().
//
// This enables usage as an 8 bit parallel port.
//
// Pins C8 and C9 can only be used in 'slow' mode via EEPROM and are currently
// not implemented.
//
// Datasheet
//
// http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232H.pdf
type FT232H struct {
	generic

	D0 gpio.PinIO // Clock output
	D1 gpio.PinIO // Data out
	D2 gpio.PinIO // Data in
	D3 gpio.PinIO // Chip select
	D4 gpio.PinIO
	D5 gpio.PinIO
	D6 gpio.PinIO
	D7 gpio.PinIO
	C0 gpio.PinIO
	C1 gpio.PinIO
	C2 gpio.PinIO
	C3 gpio.PinIO
	C4 gpio.PinIO
	C5 gpio.PinIO
	C6 gpio.PinIO
	C7 gpio.PinIO
	C8 gpio.PinIO // Not implemented
	C9 gpio.PinIO // Not implemented

	hdr  [18]gpio.PinIO
	cbus gpiosMPSSE
	dbus gpiosMPSSE
	c8   invalidPin // gpio.PullUp
	c9   invalidPin // gpio.PullUp

	mu       sync.Mutex
	usingI2C bool
	usingSPI bool
	i        i2cBus
	s        spiMPSEEPort
	// TODO(maruel): Technically speaking, a SPI port could be hacked up too in
	// sync bit-bang but there's less point when MPSEE is available.
}

// Header returns the GPIO pins exposed on the chip.
func (f *FT232H) Header() []gpio.PinIO {
	out := make([]gpio.PinIO, len(f.hdr))
	copy(out, f.hdr[:])
	return out
}

func (f *FT232H) SetSpeed(freq physic.Frequency) error {
	// TODO(maruel): When using MPSEE, use the MPSEE command. If using sync
	// bit-bang, use SetBaudRate().

	// TODO(maruel): Doc says the actual speed is 16x, confirm.
	return f.h.SetBaudRate(freq)
}

// CBus sets the values of C0 to C7 in the specified direction and value.
//
// 0 direction means input, 1 means output.
func (f *FT232H) CBus(direction, value byte) error {
	return f.h.MPSSECBus(direction, value)
}

// DBus sets the values of D0 to d7 in the specified direction and value.
//
// 0 direction means input, 1 means output.
//
// This function must be used to set Clock idle level.
func (f *FT232H) DBus(direction, value byte) error {
	return f.h.MPSSEDBus(direction, value)
}

// CBusRead reads the values of C0 to C7.
func (f *FT232H) CBusRead() (byte, error) {
	return f.h.MPSSECBusRead()
}

// DBusRead reads the values of D0 to D7.
func (f *FT232H) DBusRead() (byte, error) {
	return f.h.MPSSEDBusRead()
}

// I2C returns an I²C bus over the AD bus.
//
// pull can be either gpio.PullUp or gpio.Float. The recommended pull up
// resistors are 10kΩ for 100kHz and 2kΩ for 400kHz when using Float. The
// GPIO's pull up is 75kΩ, which may require using a lower speed for signal
// reliability. Optimal pull up resistor calculation depends on the capacitance.
//
// It uses D0, D1 and D2.
//
// D0 is SCL. It must to be pulled up externally.
//
// D1 and D2 are used for SDA. D1 is the output using open drain, D2 is the
// input. D1 and D2 must be wired together and must be pulled up externally.
//
// It is recommended to set the mode to ‘245 FIFO’ in the EEPROM of the FT232H.
//
// The FIFO mode is recommended because it allows the ADbus lines to start as
// tristate. If the chip starts in the default UART mode, then the ADbus lines
// will be in the default UART idle states until the application opens the port
// and configures it as MPSSE. Care should also be taken that the RD# input on
// ACBUS is not asserted in this initial state as this can cause the FIFO lines
// to drive out.
func (f *FT232H) I2C(pull gpio.Pull) (i2c.BusCloser, error) {
	if pull != gpio.PullUp && pull != gpio.Float {
		return nil, errors.New("d2xx: I²C pull can only be PullUp or Float")
	}
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.usingI2C {
		return nil, errors.New("d2xx: already using I²C")
	}
	if f.usingSPI {
		return nil, errors.New("d2xx: already using SPI")
	}
	if err := f.i.setupI2C(pull == gpio.PullUp); err != nil {
		_ = f.i.stopI2C()
		return nil, err
	}
	return &f.i, nil
}

// SPI returns a SPI port over the AD bus.
//
// It uses D0, D1, D2 and D3. D0 is the clock, D1 the output (MOSI), D2 is the
// input (MISO) and D3 is CS line.
func (f *FT232H) SPI() (spi.PortCloser, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.usingI2C {
		return nil, errors.New("d2xx: already using I²C")
	}
	if f.usingSPI {
		return nil, errors.New("d2xx: already using SPI")
	}
	// Don't mark it as being used yet. It only become used once Connect() is
	// called.
	return &f.s, nil
}

//

func newFT232R(g generic) (*FT232R, error) {
	f := &FT232R{
		generic: g,
		dbus:    [...]dbusPinSync{{num: 0}, {num: 1}, {num: 2}, {num: 3}, {num: 4}, {num: 5}, {num: 6}, {num: 7}},
		cbus:    [...]cbusPin{{num: 8, p: gpio.PullUp}, {num: 9, p: gpio.PullUp}, {num: 10, p: gpio.PullUp}, {num: 11, p: gpio.Float}},
	}
	// Use the UART names, as this is how all FT232R boards are marked.
	dnames := [...]string{"TX", "RX", "RTS", "CTS", "DTR", "DSR", "DCD", "RI"}
	for i := range f.dbus {
		f.dbus[i].n = f.name + "." + dnames[i]
		f.dbus[i].bus = f
		f.hdr[i] = &f.dbus[i]
	}
	for i := range f.cbus {
		f.cbus[i].n = f.name + ".C" + strconv.Itoa(i)
		f.cbus[i].bus = f
		f.hdr[i+8] = &f.cbus[i]
	}
	f.D0 = f.hdr[0]
	f.D1 = f.hdr[1]
	f.D2 = f.hdr[2]
	f.D3 = f.hdr[3]
	f.D4 = f.hdr[4]
	f.D5 = f.hdr[5]
	f.D6 = f.hdr[6]
	f.D7 = f.hdr[7]
	f.TX = f.hdr[0]
	f.RX = f.hdr[1]
	f.RTS = f.hdr[2]
	f.CTS = f.hdr[3]
	f.DTR = f.hdr[4]
	f.DSR = f.hdr[5]
	f.DCD = f.hdr[6]
	f.RI = f.hdr[7]
	f.C0 = f.hdr[8]
	f.C1 = f.hdr[9]
	f.C2 = f.hdr[10]
	f.C3 = f.hdr[11]

	// Default to 3MHz.
	if err := f.h.SetBaudRate(3 * physic.MegaHertz); err != nil {
		return nil, err
	}

	// Set all CBus pins as input.
	if err := f.h.SetBitMode(0, bitModeCbusBitbang); err != nil {
		return nil, err
	}
	// And read their value.
	// TODO(maruel): Sadly this is impossible to know which pin is input or
	// output, but we could try to guess, as the call above may generate noise on
	// the line which could interfere with the device connected.
	var err error
	if f.cbusnibble, err = f.h.GetBitMode(); err != nil {
		return nil, err
	}
	// Set all DBus as asynchronous bitbang, everything as input.
	if err := f.h.SetBitMode(0, bitModeAsyncBitbang); err != nil {
		return nil, err
	}
	// And read their value.
	var b [1]byte
	if _, err := f.h.ReadAll(context.Background(), b[:]); err != nil {
		return nil, err
	}
	f.dvalue = b[0]
	f.s.c.f = f
	return f, nil
}

// FT232R represents a FT232RL/FT232RQ device.
//
// It implements Dev.
//
// Not all pins may be physically connected on the header!
//
// Adafruit's version only has the following pins connected: RX, TX, RTS and
// CTS.
//
// SparkFun's version exports all pins *except* (inexplicably) the CBus ones.
//
// The FT232R has 128 bytes output buffer and 256 bytes input buffer.
//
// Pin C4 can only be used in 'slow' mode via EEPROM and is currently not
// implemented.
//
// Datasheet
//
// http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232R.pdf
type FT232R struct {
	generic

	// Pin and their alias to the Dn pins for user convenience. Each pair points
	// to the exact same pin.
	D0, TX  gpio.PinIO // Transmit; SPI_MOSI
	D1, RX  gpio.PinIO // Receive; SPI_MISO
	D2, RTS gpio.PinIO // Request To Send Control Output / Handshake signal; SPI_CLK
	D3, CTS gpio.PinIO // Clear to Send Control input / Handshake signal; SPI_CS
	D4, DTR gpio.PinIO // Data Terminal Ready Control Output / Handshake signal
	D5, DSR gpio.PinIO // Data Set Ready Control Input / Handshake signal
	D6, DCD gpio.PinIO // Data Carrier Detect Control input
	D7, RI  gpio.PinIO // Ring Indicator Control Input. When remote wake up is enabled in the internal EEPROM taking RI# low can be used to resume the PC USB host controller from suspend.

	// The CBus pins are slower to use, but can drive an high load, like a LED.
	C0 gpio.PinIO
	C1 gpio.PinIO
	C2 gpio.PinIO
	C3 gpio.PinIO

	dbus [8]dbusPinSync
	cbus [4]cbusPin
	hdr  [12]gpio.PinIO

	// Mutable.
	mu         sync.Mutex
	usingSPI   bool
	usingCBus  bool
	s          spiSyncPort
	dmask      uint8 // 0 input, 1 output
	dvalue     uint8
	cbusnibble uint8 // upper nibble is I/O control, lower nibble is values.
}

// Header returns the GPIO pins exposed on the chip.
func (f *FT232R) Header() []gpio.PinIO {
	out := make([]gpio.PinIO, len(f.hdr))
	copy(out, f.hdr[:])
	return out
}

// SetDBusMask sets all D0~D7 input or output mode at once.
//
// mask is the input/output pins to use. A bit value of 0 sets the
// corresponding pin to an input, a bit value of 1 sets the corresponding pin
// to an output.
//
// It should be called before calling Tx().
func (f *FT232R) SetDBusMask(mask uint8) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.usingSPI {
		return errors.New("d2xx: already using SPI")
	}
	return f.setDBusMaskLocked(mask)
}

// Tx does synchronized read-then-write on all the D0~D7 GPIOs.
//
// SetSpeed() determines the pace at which the I/O is done.
//
// SetDBusMask() determines which bits are interpreted in the w and r byte
// slice. w has its significant value masked by 'mask' and r has its
// significant value masked by '^mask'.
//
// Input sample is done *before* updating outputs. So r[0] is sampled before
// w[0] is used. The last w byte should be duplicated if an addition read is
// desired.
//
// On the Adafruit cable, only the first 4 bits D0(TX), D1(RX), D2(RTS) and
// D3(CTS) are connected. This is just enough to create a full duplex SPI bus!
func (f *FT232R) Tx(w, r []byte) error {
	if len(w) != 0 {
		if len(r) != 0 && len(w) != len(r) {
			return errors.New("d2xx: length of buffer w and r must match")
		}
	} else if len(r) == 0 {
		return errors.New("d2xx: at least one of w or r must be passed")
	}
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.usingSPI {
		return errors.New("d2xx: already using SPI")
	}
	return f.txLocked(w, r)
}

// SPI returns a SPI port over the first 4 pins.
//
// It uses D0(TX), D1(RX), D2(RTS) and D3(CTS). D2(RTS) is the clock, D0(TX)
// the output (MOSI), D1(RX) is the input (MISO) and D3(CTS) is CS line.
func (f *FT232R) SPI() (spi.PortCloser, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.usingSPI {
		return nil, errors.New("d2xx: already using SPI")
	}
	// Don't mark it as being used yet. It only become used once Connect() is
	// called.
	return &f.s, nil
}

// setDBusMaskLocked is the locked version of SetDBusMask.
func (f *FT232R) setDBusMaskLocked(mask uint8) error {
	if mask != f.dmask {
		if err := f.h.SetBitMode(mask, bitModeAsyncBitbang); err != nil {
			return err
		}
		f.dmask = mask
	}
	return nil
}

func (f *FT232R) txLocked(w, r []byte) error {
	// Investigate FT232R clock issue:
	// http://developer.intra2net.com/mailarchive/html/libftdi/2010/msg00240.html

	// The FT232R has 128 bytes TX buffer and 256 bytes RX buffer. Chunk into 64
	// bytes chunks. That's half the buffer size of the TX buffer and permits
	// pipelining and removes the risk of buffer overrun. This is important
	// otherwise there's huge gaps due to the USB transmit overhead.
	// TODO(maruel): Determine what's optimal via experimentation.
	chunk := 64
	var scratch [128]byte
	if len(w) == 0 {
		// Read only.
		for i := range scratch {
			scratch[i] = f.dvalue
		}
		for len(r) != 0 {
			// TODO(maruel): Optimize.
			c := len(r)
			if c > chunk {
				c = chunk
			}
			if _, err := f.h.Write(scratch[:c]); err != nil {
				return err
			}
			if _, err := f.h.ReadAll(context.Background(), r[:c]); err != nil {
				return err
			}
			r = r[c:]
		}
	} else if len(r) == 0 {
		// Write only.
		// The first write is 128 bytes to fill the buffer.
		chunk = 128
		for len(w) != 0 {
			c := len(w)
			if c > chunk {
				c = chunk
			}
			if _, err := f.h.Write(w[:c]); err != nil {
				return err
			}
			w = w[c:]
			chunk = 64
		}
		/*
			// Let the USB drive pace it.
			if _, err := f.h.Write(w); err != nil {
				return err
			}
		*/
	} else {
		// R/W.
		// Always write one 'w' ahead.
		// The first write is 128 bytes to fill the buffer.
		chunk = 128
		cw := len(w)
		if cw > chunk {
			cw = chunk
		}
		if _, err := f.h.Write(w[:cw]); err != nil {
			return err
		}
		w = w[cw:]
		chunk = 64
		for len(r) != 0 {
			// Read then write.
			cr := len(r)
			if cr > chunk {
				cr = chunk
			}
			if _, err := f.h.ReadAll(context.Background(), r[:cr]); err != nil {
				return err
			}
			r = r[cr:]

			cw = len(w)
			if cw > chunk {
				cw = chunk
			}
			if cw != 0 {
				if _, err := f.h.Write(w[:cw]); err != nil {
					return err
				}
				w = w[cw:]
			}
		}
	}
	return nil
}

// dbusSyncGPIOFunc implements dbusSync. It returns the function of a GPIO
// pin.
func (f *FT232R) dbusSyncGPIOFunc(n int) string {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.usingSPI {
		switch n {
		case 0:
			return "SPI_MOSI" // TX
		case 1:
			return "SPI_MISO" // RX
		case 2:
			return "SPI_CLK" // RTS
		case 3:
			return "SPI_CS" // CTS
		}
	}
	mask := uint8(1 << uint(n))
	if f.dmask&mask != 0 {
		return "Out/" + gpio.Level(f.dvalue&mask != 0).String()
	}
	return "In/" + f.dbusSyncReadLocked(n).String()
}

// dbusSyncGPIOIn implements dbusSync.
func (f *FT232R) dbusSyncGPIOIn(n int) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	// TODO(maruel): if f.usingSPI && n < 4.
	mask := uint8(1 << uint(n))
	if f.dmask&mask == 0 {
		// Already input.
		return nil
	}
	v := f.dmask &^ mask
	if err := f.h.SetBitMode(v, bitModeAsyncBitbang); err != nil {
		return err
	}
	f.dmask = v
	return nil
}

// dbusSyncGPIORead implements dbusSync.
func (f *FT232R) dbusSyncGPIORead(n int) gpio.Level {
	f.mu.Lock()
	defer f.mu.Unlock()
	return f.dbusSyncReadLocked(n)
}

func (f *FT232R) dbusSyncReadLocked(n int) gpio.Level {
	// In synchronous mode, to read we must write first to for a sample.
	b := [1]byte{f.dvalue}
	if _, err := f.h.Write(b[:]); err != nil {
		return gpio.Low
	}
	mask := uint8(1 << uint(n))
	if _, err := f.h.ReadAll(context.Background(), b[:]); err != nil {
		return gpio.Low
	}
	f.dvalue = b[0]
	return f.dvalue&mask != 0
}

// dbusSyncGPIOOut implements dbusSync.
func (f *FT232R) dbusSyncGPIOOut(n int, l gpio.Level) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	mask := uint8(1 << uint(n))
	if f.dmask&mask != 1 {
		// Was input.
		v := f.dmask | mask
		if err := f.h.SetBitMode(v, bitModeAsyncBitbang); err != nil {
			return err
		}
		f.dmask = v
	}
	return f.dbusSyncGPIOOutLocked(n, l)
}

func (f *FT232R) dbusSyncGPIOOutLocked(n int, l gpio.Level) error {
	b := [1]byte{f.dvalue}
	if _, err := f.h.Write(b[:]); err != nil {
		return err
	}
	f.dvalue = b[0]
	// In synchronous mode, we must read after writing to flush the buffer.
	if _, err := f.h.Write(b[:]); err != nil {
		return err
	}
	return nil
}

// cBusGPIOFunc implements cBusGPIO.
func (f *FT232R) cBusGPIOFunc(n int) string {
	f.mu.Lock()
	defer f.mu.Unlock()
	fmask := uint8(0x10 << uint(n))
	vmask := uint8(1 << uint(n))
	if f.cbusnibble&fmask != 0 {
		return "Out/" + gpio.Level(f.cbusnibble&vmask != 0).String()
	}
	return "In/" + f.cBusReadLocked(n).String()
}

// cBusGPIOIn implements cBusGPIO.
func (f *FT232R) cBusGPIOIn(n int) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	fmask := uint8(0x10 << uint(n))
	if f.cbusnibble&fmask == 0 {
		// Already input.
		return nil
	}
	v := f.cbusnibble &^ fmask
	if err := f.h.SetBitMode(v, bitModeCbusBitbang); err != nil {
		return err
	}
	f.cbusnibble = v
	return nil
}

// cBusGPIORead implements cBusGPIO.
func (f *FT232R) cBusGPIORead(n int) gpio.Level {
	f.mu.Lock()
	defer f.mu.Unlock()
	return f.cBusReadLocked(n)
}

func (f *FT232R) cBusReadLocked(n int) gpio.Level {
	v, err := f.h.GetBitMode()
	if err != nil {
		return gpio.Low
	}
	f.cbusnibble = v
	vmask := uint8(1 << uint(n))
	return f.cbusnibble&vmask != 0
}

// cBusGPIOOut implements cBusGPIO.
func (f *FT232R) cBusGPIOOut(n int, l gpio.Level) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	fmask := uint8(0x10 << uint(n))
	vmask := uint8(1 << uint(n))
	v := f.cbusnibble | fmask
	if l {
		v |= vmask
	} else {
		v &^= vmask
	}
	if f.cbusnibble == v {
		// Was already in the right mode.
		return nil
	}
	if err := f.h.SetBitMode(v, bitModeCbusBitbang); err != nil {
		return err
	}
	f.cbusnibble = v
	return nil
}

//

var _ conn.Resource = Dev(nil)
