blob: a5eb83589827bb6cca1a8597714fe44d3c47769d [file] [log] [blame]
// Copyright 2016 The Fuchsia 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 <ddk/binding.h>
#include <driver/usb.h>
#include <sync/completion.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/usb.h>
#include <zircon/listnode.h>
#include <zircon/device/usb.h>
#include <inttypes.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include "ftdi.h"
#define FTDI_STATUS_SIZE 2
#define FTDI_RX_HEADER_SIZE 4
#define READ_REQ_COUNT 8
#define WRITE_REQ_COUNT 4
#define INTR_REQ_COUNT 4
#define USB_BUF_SIZE 2048
#define INTR_REQ_SIZE 4
#define FIFOSIZE 256
#define FIFOMASK (FIFOSIZE - 1)
typedef struct {
zx_device_t* device;
zx_device_t* usb_device;
zx_driver_t* driver;
uint16_t ftditype; //FTDI device type
uint32_t baudrate;
uint8_t status[INTR_REQ_SIZE];
// pool of free USB requests
list_node_t free_read_reqs;
list_node_t free_write_reqs;
// list of received packets not yet read by upper layer
list_node_t completed_reads;
// the last signals we reported
//zx_signals_t signals;
size_t read_offset;
mtx_t mutex;
} ftdi_t;
/*
#define get_ftdi(dev) ((ftdi_t*)dev->ctx)
static void update_signals_locked(ftdi_t* ftdi) {
mx_signals_t new_signals = 0;
// if (eth->dead)
// new_signals |= (DEV_STATE_READABLE | DEV_STATE_ERROR);
if (!list_is_empty(&ftdi->completed_reads))
new_signals |= DEV_STATE_READABLE;
if (!list_is_empty(&ftdi->free_write_reqs))
new_signals |= DEV_STATE_WRITABLE;
if (new_signals != ftdi->signals) {
//device_state_set_clr(ftdi->device, new_signals & ~ftdi->signals, ftdi->signals & ~new_signals);
ftdi->signals = new_signals;
}
}
static void requeue_read_request_locked(ftdi_t* ftdi, iotxn_t* req) {
iotxn_queue(ftdi->usb_device, req);
}
static void ftdi_read_complete(iotxn_t* request, void* cookie) {
ftdi_t* ftdi = (ftdi_t*)cookie;
//printf("FTDI: read complete\n");
if (request->status == ERR_REMOTE_CLOSED) {
printf("FTDI: remote closed\n");
request->ops->release(request);
return;
}
mtx_lock(&ftdi->mutex);
if ((request->status == NO_ERROR) && (request->actual > 2)) {
//printf("FTDI: read complete\n");
list_add_tail(&ftdi->completed_reads, &request->node);
} else {
requeue_read_request_locked(ftdi, request);
}
//update_signals_locked(ftdi);
mtx_unlock(&ftdi->mutex);
}
static void ftdi_write_complete(iotxn_t* request, void* cookie) {
//printf("ftdi write complete - status=%d\n",request->status);
ftdi_t* ftdi = (ftdi_t*)cookie;
if (request->status == ERR_REMOTE_CLOSED) {
request->ops->release(request);
return;
}
mtx_lock(&ftdi->mutex);
list_add_tail(&ftdi->free_write_reqs, &request->node);
update_signals_locked(ftdi);
mtx_unlock(&ftdi->mutex);
}
static mx_status_t ftdi_calc_dividers(uint32_t* baudrate, uint32_t clock, uint32_t divisor,
uint16_t* integer_div, uint16_t* fraction_div) {
static const uint8_t frac_lookup[8] = {0, 3, 2, 4, 1, 5, 6, 7};
uint32_t base_clock = clock/divisor;
// integer dividers of 1 and 0 are special cases. 0=base_clock and 1 = 2/3 of base clock
if (*baudrate >= base_clock) { // return with max baud rate achievable
*fraction_div = 0;
*integer_div = 0;
*baudrate = base_clock;
}
else if (*baudrate >= (base_clock* 2 )/3) {
*integer_div = 1;
*fraction_div = 0;
*baudrate = (base_clock * 2)/3;
} else {
// create a 28.4 fractional integer
uint32_t ratio = (base_clock * 16) / *baudrate;
ratio++; //round up if needed
ratio = ratio & 0xfffffffe;
*baudrate = (base_clock << 4) / ratio;
*integer_div = ratio >> 4;
*fraction_div = frac_lookup[ (ratio >> 1) & 0x07 ];
}
return NO_ERROR;
}
static ssize_t ftdi_write(mx_device_t* dev, const void* data, size_t len, mx_off_t off) {
//mx_status_t ftdi_write(ftdi_t* ftdi, const uint8_t* buff, size_t length) {
ftdi_t* ftdi =get_ftdi(dev);
uint8_t* buff = (uint8_t*)data;
mx_status_t status=NO_ERROR;
mtx_lock(&ftdi->mutex);
list_node_t* node = list_remove_head(&ftdi->free_write_reqs);
if (!node) {
printf("shit broke yo...\n");
status = ERR_BUFFER_TOO_SMALL;
goto out;
}
iotxn_t* request = containerof(node, iotxn_t, node);
if (!request) {
printf("null request node\n");
goto out;
}
request->ops->copyto(request, buff, len, 0);
request->length=len;
iotxn_queue(ftdi->usb_device, request);
out:
update_signals_locked(ftdi);
mtx_unlock(&ftdi->mutex);
return status;
}
static ssize_t ftdi_read(mx_device_t* dev, void* data, size_t len, mx_off_t off) {
ftdi_t* ftdi = get_ftdi(dev);
size_t bytes_copied = 0;
size_t offset = ftdi->read_offset;
uint8_t* buffer = (uint8_t*)data;
mtx_lock(&ftdi->mutex);
list_node_t* node = list_peek_head(&ftdi->completed_reads);
while ((node) && (bytes_copied < len)) {
iotxn_t* request = containerof(node, iotxn_t, node);
size_t to_copy = request->actual - offset - FTDI_STATUS_SIZE;
//printf("actual=%zu offset=%zu\n",request->actual, offset);
if ( (to_copy + bytes_copied) > len) {
to_copy = len - bytes_copied;
}
request->ops->copyfrom(request, &buffer[bytes_copied], to_copy, offset + FTDI_STATUS_SIZE);
bytes_copied = bytes_copied + to_copy;
if ((to_copy + offset + FTDI_STATUS_SIZE) < request->actual) {
offset = offset + to_copy;
goto out;
} else {
list_remove_head(&ftdi->completed_reads);
requeue_read_request_locked(ftdi, request);
offset = 0;
}
node = list_peek_head(&ftdi->completed_reads);
}
out:
ftdi->read_offset = offset;
update_signals_locked(ftdi);
mtx_unlock(&ftdi->mutex);
return bytes_copied;
}
static mx_status_t ftdi_set_baudrate(ftdi_t* ftdi, uint32_t baudrate){
uint16_t whole,fraction,value,index;
mx_status_t status;
if (ftdi == NULL) return ERR_INVALID_ARGS;
switch(ftdi->ftditype) {
case FTDI_TYPE_R:
case FTDI_TYPE_2232C:
case FTDI_TYPE_BM:
ftdi_calc_dividers(&baudrate,FTDI_C_CLK,16,&whole,&fraction);
ftdi->baudrate = baudrate;
break;
default:
return ERR_INVALID_ARGS;
}
value = (whole & 0x3fff) | (fraction << 14);
index = fraction >> 2;
status = usb_control(ftdi->usb_device, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
FTDI_SIO_SET_BAUDRATE, value, index, NULL, 0);
//supported_baud = getbaud
return status;
}
static mx_status_t ftdi_reset(ftdi_t* ftdi) {
if (ftdi == NULL || ftdi->usb_device == NULL)
return ERR_INVALID_ARGS;
return usb_control(ftdi->usb_device, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
FTDI_SIO_RESET_REQUEST, FTDI_SIO_RESET, 0, NULL, 0);
}
const char teststr[]="Eric0123456789abcdefghijklmnop";
static mx_protocol_device_t ftdi_device_proto = {
//.unbind = ax88772b_unbind,
//.release = ax88772b_release,
.read = ftdi_read,
.write = ftdi_write,
};
static int ftdi_start_thread(void* arg) {
ftdi_t* ftdi = (ftdi_t*)arg;
mx_status_t status = 0;
printf("Initializing FTDI...\n");
if (ftdi_reset(ftdi) < 0) {
printf("reset failed\n");
} else {
printf("FTDI: reset complete\n");
}
status = ftdi_set_baudrate(ftdi, 115200);
if (status < 0) {
printf("FTDI: set baudrate failed\n");
}
printf("FTDI: set baudrate complete\n");
status = device_create(&ftdi->device, ftdi->driver, "usb-serial", &ftdi_device_proto);
if (status < 0) {
printf("FTDI: failed to create device: %d\n", status);
return 0;
}
status = device_add(ftdi->device, ftdi->usb_device);
if (status != NO_ERROR) {
free(ftdi->device);
return status;
}
status = device_add(ftdi->device, driver_get_misc_device());
iotxn_t* req;
iotxn_t* prev;
list_for_every_entry_safe (&ftdi->free_read_reqs, req, prev, iotxn_t, node) {
list_delete(&req->node);
requeue_read_request_locked(ftdi, req);
}
update_signals_locked(ftdi);
#if 0
size_t numbytes;
uint8_t inbuff[100];
while(1) {
status = ftdi_write(ftdi,(uint8_t*)teststr,30);
printf("did write\n");
if (status < 0) printf("FTDI: write failed\n");
mx_nanosleep(MX_MSEC(1000));
numbytes = ftdi_read(ftdi,inbuff,10);
printf("Read #1 - read %zu bytes: ",numbytes);
for (uint i=0; i<numbytes; i++)
printf("%c",inbuff[i]);
printf("\n");
numbytes = ftdi_read(ftdi,inbuff,100);
printf("Read #1 - read %zu bytes: ",numbytes);
for (uint i=0; i<numbytes; i++)
printf("%c",inbuff[i]);
printf("\n");
mx_nanosleep(MX_MSEC(1000));
}
#endif
return 0;
}
*/
static zx_status_t ftdi_bind(void* ctx, zx_device_t* device, void** cookie) {
printf("FTDI: usbserial - attempting to bind\n");
usb_protocol_t usb;
zx_status_t result = device_get_protocol(device, ZX_PROTOCOL_USB, &usb);
if (result != ZX_OK) {
return result;
}
// find our endpoints
usb_desc_iter_t iter;
zx_status_t status = ZX_OK;
result = usb_desc_iter_init(&usb, &iter);
if (result < 0)
return result;
usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, true);
printf("FTDI: returned %d endpoints\n", intf->bNumEndpoints);
//uint8_t bulk_in_addr = 0;
//uint8_t bulk_out_addr = 0;
/*
usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter);
while (endp) {
if (usb_ep_direction(endp) == USB_ENDPOINT_OUT) {
if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
bulk_out_addr = endp->bEndpointAddress;
printf("lan9514 bulk out endpoint:%x\n", bulk_out_addr);
}
} else {
if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
bulk_in_addr = endp->bEndpointAddress;
printf("lan9514 bulk in endpoint:%x\n", bulk_in_addr);
}
}
endp = usb_desc_iter_next_endpoint(&iter);
}
usb_desc_iter_release(&iter);
if (!bulk_in_addr || !bulk_out_addr ) {
printf("FTDI: could not find all endpoints\n");
return ERR_NOT_SUPPORTED;
}
ftdi_t* ftdi = calloc(1, sizeof(ftdi_t));
if (!ftdi) {
printf("FTDI: Not enough memory\n");
printf("FTDI: bind failed!\n");
return ERR_NO_MEMORY;
}
usb_device_descriptor_t devdesc;
result = device->ops->ioctl(device, IOCTL_USB_GET_DEVICE_DESC, NULL, 0, &devdesc, sizeof(devdesc));
ftdi->ftditype = devdesc.bcdDevice;
printf("FTDI: Device type = %04x\n",ftdi->ftditype);
list_initialize(&ftdi->free_read_reqs);
list_initialize(&ftdi->free_write_reqs);
list_initialize(&ftdi->completed_reads);
ftdi->usb_device = device;
ftdi->driver = driver;
for (int i = 0; i < READ_REQ_COUNT; i++) {
iotxn_t* req = usb_alloc_iotxn(bulk_in_addr, USB_BUF_SIZE, 0);
if (!req) {
status = ERR_NO_MEMORY;
goto fail;
}
req->length = USB_BUF_SIZE;
req->complete_cb = ftdi_read_complete;
req->cookie = ftdi;
list_add_head(&ftdi->free_read_reqs, &req->node);
}
for (int i = 0; i < WRITE_REQ_COUNT; i++) {
iotxn_t* req = usb_alloc_iotxn(bulk_out_addr, USB_BUF_SIZE, 0);
if (!req) {
status = ERR_NO_MEMORY;
goto fail;
}
req->length = USB_BUF_SIZE;
req->complete_cb = ftdi_write_complete;
req->cookie = ftdi;
list_add_head(&ftdi->free_write_reqs, &req->node);
}
printf("ftdi bind successful?\n");
#if 1
thrd_t thread;
thrd_create_with_name(&thread, ftdi_start_thread, ftdi, "ftdi_start_thread");
thrd_detach(thread);
return NO_ERROR;
#endif
fail:
printf("ftdi_bind failed: %d\n", status);
//ftdi_free(ftdi);
return status;
*/
return status;
}
static zx_driver_ops_t ftdi_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = ftdi_bind,
};
ZIRCON_DRIVER_BEGIN(driver_ftdi,ftdi_driver_ops, "zircon", "0.1", 2)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB),
BI_MATCH_IF(EQ, BIND_USB_VID, FTDI_VID),
// BI_MATCH_IF(EQ, BIND_USB_PID, FTDI_232_PID),
ZIRCON_DRIVER_END(driver_ftdi)