| // Copyright 2021 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /* |
| * FTDI device interface layer. |
| * FTDI APP note AN_255 and AN_108 used as reference. |
| */ |
| #include "hps/hal/ftdi.h" |
| |
| #include <iomanip> |
| #include <iostream> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include <libusb-1.0/libusb.h> |
| #include <stdlib.h> |
| |
| #include <base/check.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/threading/thread.h> |
| #include <base/time/time.h> |
| |
| namespace { |
| |
| static const int kTimeoutMS = 500; // MS timeout |
| static const int kResetDelay = 10; // MS delay after reset. |
| static const int kReadSize = 64; |
| static const bool kDebug = false; |
| |
| // Commands to FTDI module. |
| enum { |
| kByteOutRising = 0x10, |
| kByteOutFalling = 0x11, |
| kBitOutRising = 0x12, |
| kBitOutFalling = 0x13, |
| kByteInRising = 0x20, |
| kBitInRising = 0x22, |
| kByteInFalling = 0x24, |
| kBitInFalling = 0x26, |
| kSetPins = 0x80, // Write to ADBUS 0-7 |
| kFlush = 0x87, |
| }; |
| |
| // ADBUS0/ADBUS1 bits for I2C I/O |
| enum { |
| kSclock = 0x01, |
| kSdata = 0x02, |
| kGpio = 0x08, // For debugging. |
| kLevelShifter = 0x20, // Enable level shifter (ADBUS5, active high). |
| kPower = 0x40, // Enable power (ADBUS6, active low). |
| }; |
| |
| // Set the state of the I/O pins. |
| void Pins(std::vector<uint8_t>* b, uint8_t val, uint8_t dir) { |
| b->push_back(kSetPins); |
| b->push_back(val | kLevelShifter); |
| b->push_back(dir | kPower | kLevelShifter); |
| } |
| |
| // Add a I2C Start sequence to the buffer. |
| void Start(std::vector<uint8_t>* b) { |
| for (auto i = 0; i < 10; i++) { |
| Pins(b, kSclock | kSdata, kSclock | kSdata); // Let line be pulled up. |
| } |
| for (auto i = 0; i < 10; i++) { |
| Pins(b, kSclock, kSclock | kSdata); |
| } |
| for (auto i = 0; i < 10; i++) { |
| Pins(b, 0, kSclock | kSdata); |
| } |
| } |
| |
| // Add a I2C Stop sequence to the buffer. |
| void Stop(std::vector<uint8_t>* b) { |
| for (auto i = 0; i < 10; i++) { |
| Pins(b, 0, kSclock | kSdata); |
| } |
| for (auto i = 0; i < 10; i++) { |
| Pins(b, kSclock, kSclock | kSdata); |
| } |
| for (auto i = 0; i < 10; i++) { |
| Pins(b, kSclock | kSdata, kSclock | kSdata); |
| } |
| Pins(b, kSclock | kSdata, 0); |
| } |
| |
| /* |
| * Calculate clock divider from bus speed. |
| * See AN 255 for a complete explanation of the clock divider formula. |
| * For 2 phase clock: |
| * speed = 60Mhz / ((1 + divisor) * 2) |
| * For 3 phase clock, final divisor = divisor * 2 / 3; |
| * So: |
| * speed = 60MHz / (((1 + divisor) * 2 / 3) * 2) |
| * divisor = 60000 / (speedKHz * 2) - 1 |
| * divisor = divisor * 2 / 3 |
| */ |
| uint16_t ClockDivisor(uint32_t speedKHz) { |
| return (((60 * 1000) / (speedKHz * 2) - 1) * 2) / 3; |
| } |
| |
| } // namespace |
| |
| namespace hps { |
| |
| bool Ftdi::Init(uint32_t speedKHz) { |
| // Max is 1MHz, minimum is 10Khz. |
| if (speedKHz > 1000 || speedKHz < 10) { |
| std::cerr << "FTDI illegal speed, max 1MHz, min 10KHz" << std::endl; |
| return false; |
| } |
| ftdi_init(&this->context_); |
| struct ftdi_device_list* devlist; |
| |
| // Read the list of all FTDI devices. |
| // vid/pid of 0 will search for the default FTDI device types. |
| if (this->Check(ftdi_usb_find_all(&this->context_, &devlist, 0, 0) < 0, |
| "find")) |
| return false; |
| // Use the first device found. It's unlikely that multiple FTDI |
| // devices will be attached - if so, some means of selecting the |
| // correct device must be added. |
| if (this->Check(devlist == 0, "no device")) |
| return false; |
| #if 0 |
| uint8_t bus = libusb_get_bus_number(devlist->dev); |
| uint8_t addr = libusb_get_device_address(devlist->dev); |
| char m[kStrSize], d[kStrSize], s[kStrSize]; |
| bool chk = |
| this->Check(ftdi_usb_get_strings(&this->context_, devlist->dev, m, |
| kStrSize, d, kStrSize, s, kStrSize) < 0, |
| "get str"); |
| // Free the device list after all the data has been extracted from it. |
| ftdi_list_free(&devlist); |
| if (chk) |
| return false; |
| this->manuf_ = m; |
| this->descr_ = d; |
| this->serial_ = s; |
| #endif |
| if (this->Check(ftdi_usb_open_dev(&this->context_, devlist->dev) < 0, |
| "open")) { |
| ftdi_list_free(&devlist); |
| return false; |
| } |
| ftdi_list_free(&devlist); |
| if (this->Check(ftdi_set_interface(&this->context_, INTERFACE_A) < 0, |
| "set interface")) |
| return false; |
| if (this->Check(ftdi_usb_reset(&this->context_) < 0, "reset")) |
| return false; |
| if (this->Check(ftdi_usb_purge_buffers(&this->context_) < 0, "flush")) |
| return false; |
| if (this->Check(ftdi_set_event_char(&this->context_, 0, false) < 0, |
| "ev char")) |
| return false; |
| if (this->Check(ftdi_set_error_char(&this->context_, 0, false) < 0, |
| "err char")) |
| return false; |
| if (this->Check(ftdi_set_latency_timer(&this->context_, 16) < 0, |
| "set latency")) |
| return false; |
| if (this->Check(ftdi_set_bitmode(&this->context_, 0xFF, BITMODE_RESET) < 0, |
| "mode reset")) |
| return false; |
| if (this->Check(ftdi_set_bitmode(&this->context_, 0xFF, BITMODE_MPSSE) < 0, |
| "mode MPSSE")) |
| return false; |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); |
| std::vector<uint8_t> rdq; |
| // Flush any data in the queue. |
| this->GetRaw(&rdq); |
| // Device opened successfully, verify MPSSE mode. |
| std::vector<uint8_t> tx(1); |
| tx[0] = 0xAA; |
| if (this->Check(this->PutRaw(tx) != tx.size(), "verify write")) |
| return false; |
| rdq.resize(2); |
| if (this->Check( |
| !this->GetRawBlock(2, &rdq) || rdq[0] != 0xFA || rdq[1] != 0xAA, |
| "verify read")) |
| return false; |
| tx.clear(); |
| // Init MPSSE settings. |
| tx.push_back(0x8A); // Disable divide/5 clock mode |
| tx.push_back(0x97); // Disable adaptive clocking |
| tx.push_back(0x8C); // Enable 3 phase clocking |
| if (this->Check(this->PutRaw(tx) != tx.size(), "MPSEE setting")) |
| return false; |
| tx.clear(); |
| Pins(&tx, kSclock | kSdata, kSclock); |
| tx.push_back(0x86); // Set clock divisor |
| uint16_t div = ClockDivisor(speedKHz); |
| tx.push_back(div & 0xFF); |
| tx.push_back((div >> 8) & 0xFF); |
| if (this->Check(this->PutRaw(tx) != tx.size(), "MPSEE clock setting")) |
| return false; |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); |
| tx.clear(); |
| tx.push_back(0x85); // Turn off loopback. |
| if (this->Check(this->PutRaw(tx) != tx.size(), "loopback disable")) |
| return false; |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20)); |
| if (kDebug) |
| this->Dump(); |
| return true; |
| } |
| |
| void Ftdi::Close() { |
| ftdi_deinit(&this->context_); |
| } |
| |
| bool Ftdi::Read(uint8_t cmd, uint8_t* data, size_t len) { |
| if (len == 0) { |
| return false; |
| } |
| std::vector<uint8_t> b; |
| // Flush anything in the read queue. |
| this->GetRaw(&b); |
| b.clear(); |
| Start(&b); |
| if (!this->SendByte(this->address_, &b)) { |
| this->Reset(); |
| return false; |
| } |
| b.clear(); |
| if (!this->SendByte(cmd, &b)) { |
| this->Reset(); |
| return false; |
| } |
| b.clear(); |
| Start(&b); |
| if (!this->SendByte(this->address_ | 1, &b)) { |
| this->Reset(); |
| return false; |
| } |
| for (size_t i = 0; i < len; i++) { |
| if (!this->ReadByte(&data[i], i == (len - 1))) { |
| this->Reset(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool Ftdi::Write(uint8_t cmd, const uint8_t* data, size_t len) { |
| std::vector<uint8_t> b; |
| // Flush read queue. |
| this->GetRaw(&b); |
| b.clear(); |
| Start(&b); |
| if (!this->SendByte(this->address_, &b)) { |
| this->Reset(); |
| return false; |
| } |
| b.clear(); |
| if (!this->SendByte(cmd, &b)) { |
| this->Reset(); |
| return false; |
| } |
| for (int i = 0; i < len; ++i) { |
| b.clear(); |
| if (!this->SendByte(data[i], &b)) { |
| this->Reset(); |
| return false; |
| } |
| } |
| b.clear(); |
| Stop(&b); |
| if (this->PutRaw(b) != b.size()) { |
| return false; |
| } |
| return true; |
| } |
| |
| // Read a block of raw data from the FTDI chip. |
| // A timeout is used in case it hangs. |
| bool Ftdi::GetRawBlock(size_t count, std::vector<uint8_t>* input) { |
| input->clear(); |
| std::vector<uint8_t> buf; |
| int timeout = kTimeoutMS; |
| while (count > 0) { |
| // Read whatever is available. |
| if (!this->GetRaw(&buf)) { |
| return false; |
| } |
| // Append to input buffer. |
| if (buf.size() != 0) { |
| if (buf.size() > count) { |
| // Hmm extra data... |
| buf.resize(count); |
| } |
| input->insert(input->end(), buf.begin(), buf.end()); |
| count -= buf.size(); |
| } else { |
| // No data available, sleep for a while |
| // and try again. |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1)); |
| if (--timeout <= 0) { |
| std::cerr << "Rd timeout" << std::endl; |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| // Send a byte to the I2C bus and wait for an ack/nak. |
| bool Ftdi::SendByte(uint8_t data, std::vector<uint8_t>* b) { |
| // SDA/SCLK low. |
| Pins(b, 0, kSclock | kSdata); |
| b->push_back(kBitOutFalling); |
| b->push_back(0x07); |
| b->push_back(data); |
| // Switch to SDA input to read ack/nak. |
| Pins(b, 0, kSclock); |
| b->push_back(kBitInRising); |
| b->push_back(0x00); |
| b->push_back(kFlush); |
| if (this->PutRaw(*b) != b->size()) { |
| return false; |
| } |
| b->clear(); |
| std::vector<uint8_t> in; |
| // Check for nak. |
| if (!this->GetRawBlock(1, &in) || (in[0] & 0x01) != 0x00) { |
| return false; |
| } |
| return true; |
| } |
| |
| // Read a byte from the I2C and send a NAK/ACK in response. |
| bool Ftdi::ReadByte(uint8_t* result, bool nak) { |
| std::vector<uint8_t> b; |
| // SCK out/low, SDA in |
| Pins(&b, 0, kSclock); |
| b.push_back(kBitInRising); |
| b.push_back(0x07); |
| Pins(&b, 0, kSclock | kSdata); |
| b.push_back(kBitOutFalling); |
| b.push_back(0x00); |
| b.push_back(nak ? 0x80 : 0x00); |
| Pins(&b, 0, kSclock); |
| b.push_back(kFlush); |
| if (this->PutRaw(b) != b.size()) { |
| return false; |
| } |
| // Read byte. |
| b.resize(1); |
| if (!this->GetRawBlock(b.size(), &b)) { |
| return false; |
| } |
| *result = b[0]; |
| if (nak) { |
| // Send Stop. |
| b.clear(); |
| Stop(&b); |
| if (this->PutRaw(b) != b.size()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Read from the module whatever data is available. |
| bool Ftdi::GetRaw(std::vector<uint8_t>* get) { |
| get->resize(kReadSize); |
| int actual = ftdi_read_data(&this->context_, &(*get)[0], get->size()); |
| // Nothing to read, return empty vector. |
| if (actual <= 0) { |
| get->clear(); |
| return true; |
| } |
| get->resize(actual); |
| return true; |
| } |
| |
| // Write the data to the module.. |
| size_t Ftdi::PutRaw(const std::vector<uint8_t>& output) { |
| return ftdi_write_data(&this->context_, const_cast<uint8_t*>(&output[0]), |
| output.size()); |
| } |
| |
| // Reset the state of the bus to idle. |
| void Ftdi::Reset() { |
| std::vector<uint8_t> b; |
| Stop(&b); |
| this->PutRaw(b); |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(kResetDelay)); |
| } |
| |
| // Check and print error message. |
| bool Ftdi::Check(bool cond, const char* tag) { |
| if (cond) { |
| std::cerr << "FTDI error: " << tag << ": " |
| << ftdi_get_error_string(&this->context_) << std::endl; |
| this->Dump(); |
| return true; |
| } |
| return false; |
| } |
| |
| void Ftdi::Dump() { |
| std::cerr << "Type: " << this->context_.type << " Interface: " |
| << this->context_.interface << " index: " << this->context_.index |
| << " IN_EP: " << this->context_.in_ep |
| << " OUT_EP: " << this->context_.out_ep << std::endl; |
| std::cerr << "Manuf: " << this->manuf_ << " Descr: " << this->descr_ |
| << " Serial: " << this->serial_ << std::endl; |
| struct ftdi_version_info v = ftdi_get_library_version(); |
| std::cerr << "Lib version: " << v.version_str << std::endl; |
| } |
| |
| // Static factory method. |
| std::unique_ptr<DevInterface> Ftdi::Create(uint8_t address, uint32_t speedKHz) { |
| // Use new so that private constructor can be accessed. |
| auto dev = std::unique_ptr<Ftdi>(new Ftdi(address)); |
| CHECK(dev->Init(speedKHz)); |
| return std::unique_ptr<DevInterface>(std::move(dev)); |
| } |
| |
| } // namespace hps |