blob: 56b53081dacbe17e2cc98961faf449c51c848a63 [file] [log] [blame]
// 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