blob: dc8a0d32c6a643b07e7800c52fa933b5ee8c9982 [file] [log] [blame]
// Copyright (c) 2011 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.
#include <assert.h>
#include "ftdi_common.h"
#include "ftdigpio.h"
int fgpio_init(struct fgpio_context *fgc, struct ftdi_context *fc) {
assert(fgc);
memset(fgc, 0, sizeof(*fgc));
fgc->fc = fc;
fgc->gpio.direction = 0;
fgc->gpio.value = 0;
fgc->gpio.mask = 0xff;
if (FTDI_HAS_CBUS(fc)) {
// only a nibble worth of gpios
fgc->gpio.mask = 0x0f;
}
return FGPIO_ERR_NONE;
}
int fgpio_open(struct fgpio_context *fgc, struct ftdi_common_args *fargs) {
int rv = 0;
ftdi_set_interface(fgc->fc, fargs->interface);
if (!IS_FTDI_OPEN(fgc->fc)) {
rv = ftdi_usb_open_desc(fgc->fc, fargs->vendor_id, fargs->product_id,
NULL, fargs->serialname);
// TODO(tbroch) investigate rmmod for ftdi_sio and retrying open when the
// return value is -5 (unable to claim device)
if (rv < 0) {
ERROR_FTDI("Opening usb connection", fgc->fc);
prn_error("vid:0x%02x pid:0x%02x serial:%s\n", fargs->vendor_id,
fargs->product_id, fargs->serialname);
return rv;
}
}
assert(fgc->fc);
if (!FTDI_HAS_CBUS(fgc->fc)) {
rv = fcom_cfg(fgc->fc, fargs->interface, BITMODE_BITBANG, 0);
}
return rv;
}
int fgpio_wr_rd(struct fgpio_context *fgc, struct gpio_s *new_gpio,
uint8_t *rd_val, enum ftdi_interface_type itype) {
uint8_t buf[4]; /* only three bytes used */
int dir_chg = 0;
int val_chg = 0;
struct ftdi_context *fc = fgc->fc;
struct gpio_s *gpio = &fgc->gpio;
if (new_gpio != NULL) {
if ((gpio->mask | new_gpio->mask) != gpio->mask) {
prn_dbg("GPIO mask mismatch 0x%02x != 0x%02x for this interface\n",
gpio->mask, new_gpio->mask);
return FGPIO_ERR_MASK;
}
// direction register is changing
if (new_gpio->mask & (gpio->direction ^ new_gpio->direction)) {
dir_chg = 1;
gpio->direction = (new_gpio->mask & new_gpio->direction) |
(~new_gpio->mask & gpio->direction);
prn_dbg("Changing direction register to 0x%02x\n", gpio->direction);
}
// value is changing
if (new_gpio->mask & (gpio->value ^ new_gpio->value)) {
val_chg = 1;
gpio->value = (new_gpio->mask & new_gpio->value) |
(~new_gpio->mask & gpio->value);
prn_dbg("Changing value register to 0x%02x\n", gpio->value);
}
if (FTDI_HAS_CBUS(fgc->fc) && (val_chg || dir_chg)) {
buf[0] = FGPIO_CBUS_GPIO(gpio->direction, gpio->value);
prn_dbg("cbus write of 0x%02x\n", buf[0]);
CHECK_FTDI(ftdi_set_bitmode(fc, buf[0], BITMODE_CBUS),
"write cbus gpio", fc);
} else {
// traditional 8-bit interfaces
if (itype == UART) {
// TODO(tbroch) implement this but requires libusb plumbing in all
// likelihood. For now error
return FGPIO_ERR_NOIMP;
}
if ((itype == GPIO) && dir_chg) {
CHECK_FTDI(ftdi_set_bitmode(fc, gpio->direction, BITMODE_BITBANG),
"re-cfg gpio direction", fc);
prn_dbg("Wrote direction to 0x%02x\n", gpio->direction);
}
// dir change takes effect on ftdi_write_data done below
if (val_chg || dir_chg) {
int wr_bytes = 0;
int bytes_to_wr = 0;
if (itype != GPIO) {
// all non-gpio interfaces (spi,jtag,i2c) rely on MPSSE mode
// TODO(tbroch) PRI=2, add support for chips w/ hi/low support
buf[bytes_to_wr++] = SET_BITS_LOW;
buf[bytes_to_wr++] = gpio->value;
buf[bytes_to_wr++] = gpio->direction;
} else {
buf[bytes_to_wr++] = gpio->value;
}
wr_bytes = ftdi_write_data(fc, buf, bytes_to_wr);
if (wr_bytes != bytes_to_wr) {
ERROR_FTDI("writing gpio data", fc);
return FGPIO_ERR_WR;
}
prn_dbg("Wrote dir:0x%02x val:0x%02x\n", gpio->direction, gpio->value);
}
}
}
if (rd_val != NULL) {
CHECK_FTDI(ftdi_read_pins(fc, rd_val), "reading gpios", fc);
if (FTDI_HAS_CBUS(fgc->fc)) {
*rd_val &= 0xf;
}
prn_dbg("Read value to 0x%02x\n", *rd_val);
}
return FGPIO_ERR_NONE;
}
int fgpio_close(struct fgpio_context *fgc) {
int rv = FGPIO_ERR_NONE;
CHECK_FTDI(ftdi_disable_bitbang(fgc->fc), "disable bitbang", fgc->fc);
CHECK_FTDI(ftdi_usb_close(fgc->fc), "usb close", fgc->fc);
ftdi_deinit(fgc->fc);
return rv;
}