root/src/add-ons/kernel/drivers/ports/usb_serial/FTDI.cpp
/*
 * Copyright (c) 2007-2008 by Michael Lotz
 * Heavily based on the original usb_serial driver which is:
 *
 * Copyright (c) 2003 by Siarzhuk Zharski <imker@gmx.li>
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Alexander von Gluck IV, kallisti5@unixzen.com
 */


#include "FTDI.h"
#include "FTDIRegs.h"


FTDIDevice::FTDIDevice(usb_device device, uint16 vendorID, uint16 productID,
        const char *description)
        :       SerialDevice(device, vendorID, productID, description),
                fHeaderLength(0),
                fStatusMSR(0),
                fStatusLSR(0)
{
}


status_t
FTDIDevice::AddDevice(const usb_configuration_info *config)
{
        TRACE_FUNCALLS("> FTDIDevice::AddDevice(%08x, %08x)\n", this, config);
        status_t status = ENODEV;
        if (config->interface_count > 0) {
                int32 pipesSet = 0;
                usb_interface_info *interface = config->interface[0].active;
                for (size_t i = 0; i < interface->endpoint_count; i++) {
                        usb_endpoint_info *endpoint = &interface->endpoint[i];
                        if (endpoint->descr->attributes == USB_ENDPOINT_ATTR_BULK) {
                                if (endpoint->descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN) {
                                        SetReadPipe(endpoint->handle);
                                        if (++pipesSet >= 3)
                                                break;
                                } else {
                                        if (endpoint->descr->endpoint_address) {
                                                SetControlPipe(endpoint->handle);
                                                SetWritePipe(endpoint->handle);
                                                pipesSet += 2;
                                                if (pipesSet >= 3)
                                                        break;
                                        }
                                }
                        }
                }

                if (pipesSet >= 3) {
                        if (ProductID() == 0x8372) // AU100AX
                                fHeaderLength = 1;
                        else
                                fHeaderLength = 0;

                        status = B_OK;
                }
        }

        TRACE_FUNCRET("< FTDIDevice::AddDevice() returns: 0x%08x\n", status);
        return status;
}


status_t
FTDIDevice::ResetDevice()
{
        TRACE_FUNCALLS("> FTDIDevice::ResetDevice(0x%08x)\n", this);

        size_t length = 0;
        status_t status = gUSBModule->send_request(Device(),
                USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT,
                FTDI_SIO_RESET, FTDI_SIO_RESET_SIO,
                FTDI_PIT_DEFAULT, 0, NULL, &length);

        TRACE_FUNCRET("< FTDIDevice::ResetDevice() returns:%08x\n", status);
        return status;
}


status_t
FTDIDevice::SetLineCoding(usb_cdc_line_coding *lineCoding)
{
        TRACE_FUNCALLS("> FTDIDevice::SetLineCoding(0x%08x, {%d, 0x%02x, 0x%02x, 0x%02x})\n",
                this, lineCoding->speed, lineCoding->stopbits, lineCoding->parity,
                lineCoding->databits);

        int32 rate = 0;
        if (ProductID() == 0x8372) {
                // AU100AX
                switch (lineCoding->speed) {
                        case 300: rate = ftdi_sio_b300; break;
                        case 600: rate = ftdi_sio_b600; break;
                        case 1200: rate = ftdi_sio_b1200; break;
                        case 2400: rate = ftdi_sio_b2400; break;
                        case 4800: rate = ftdi_sio_b4800; break;
                        case 9600: rate = ftdi_sio_b9600; break;
                        case 19200: rate = ftdi_sio_b19200; break;
                        case 38400: rate = ftdi_sio_b38400; break;
                        case 57600: rate = ftdi_sio_b57600; break;
                        case 115200: rate = ftdi_sio_b115200; break;
                        default:
                                rate = ftdi_sio_b19200;
                                TRACE_ALWAYS("= FTDIDevice::SetLineCoding(): Datarate: %d is "
                                        "not supported by this hardware. Defaulted to %d\n",
                                        lineCoding->speed, rate);
                        break;
                }
        } else {
                /* Compute baudrate register value as documented in AN232B-05 from FTDI.
                 Bits 13-0 are the integer divider, and bits 16-14 are the fractional
                 divider setting. 3Mbaud and 2Mbaud are special values, and at such
                 high speeds the use of the fractional divider is not possible. */
                if (lineCoding->speed == 3000000)
                        rate = 0;
                else if (lineCoding->speed == 2000000)
                        rate = 1;
                else {
                        if (lineCoding->speed > 1500000) {
                                TRACE_ALWAYS("= FTDIDevice::SetLineCoding(): Datarate: %d is "
                                        "not supported by this hardware. Defaulted to %d\n",
                                        lineCoding->speed, 19200);
                                lineCoding->speed = 19200;
                        }
                        rate = 3000000 * 8 / lineCoding->speed;
                        int frac = ftdi_8u232am_frac[rate & 0x7];
                        rate = (rate >> 3) | frac;
                }
        }

        size_t length = 0;
        status_t status = gUSBModule->send_request(Device(),
                USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT,
                FTDI_SIO_SET_BAUD_RATE, rate,
                FTDI_PIT_DEFAULT, 0, NULL, &length);
        if (status != B_OK)
                TRACE_ALWAYS("= FTDIDevice::SetLineCoding(): datarate set request failed: 0x%08x\n", status);

        int32 data = 0;
        switch (lineCoding->stopbits) {
                case USB_CDC_LINE_CODING_1_STOPBIT: data = FTDI_SIO_SET_DATA_STOP_BITS_2; break;
                case USB_CDC_LINE_CODING_2_STOPBITS: data = FTDI_SIO_SET_DATA_STOP_BITS_1; break;
                default:
                        TRACE_ALWAYS("= FTDIDevice::SetLineCoding(): Wrong stopbits param: %d\n",
                                lineCoding->stopbits);
                        break;
        }

        switch (lineCoding->parity) {
                case USB_CDC_LINE_CODING_NO_PARITY: data |= FTDI_SIO_SET_DATA_PARITY_NONE; break;
                case USB_CDC_LINE_CODING_EVEN_PARITY: data |= FTDI_SIO_SET_DATA_PARITY_EVEN; break;
                case USB_CDC_LINE_CODING_ODD_PARITY:    data |= FTDI_SIO_SET_DATA_PARITY_ODD; break;
                default:
                        TRACE_ALWAYS("= FTDIDevice::SetLineCoding(): Wrong parity param: %d\n",
                                lineCoding->parity);
                        break;
        }

        switch (lineCoding->databits) {
                case 8: data |= FTDI_SIO_SET_DATA_BITS(8); break;
                case 7: data |= FTDI_SIO_SET_DATA_BITS(7); break;
                default:
                        TRACE_ALWAYS("= FTDIDevice::SetLineCoding(): Wrong databits param: %d\n",
                                lineCoding->databits);
                        break;
        }

        length = 0;
        status = gUSBModule->send_request(Device(),
                USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT,
                FTDI_SIO_SET_DATA, data,
                FTDI_PIT_DEFAULT, 0, NULL, &length);
        if (status != B_OK)
                TRACE_ALWAYS("= FTDIDevice::SetLineCoding(): data set request failed: %08x\n", status);

        TRACE_FUNCRET("< FTDIDevice::SetLineCoding() returns: 0x%08x\n", status);
        return status;
}


status_t
FTDIDevice::SetControlLineState(uint16 state)
{
        TRACE_FUNCALLS("> FTDIDevice::SetControlLineState(0x%08x, 0x%04x)\n",
                this, state);

        int32 control;
        control = (state & USB_CDC_CONTROL_SIGNAL_STATE_RTS) ? FTDI_SIO_SET_RTS_HIGH
                : FTDI_SIO_SET_RTS_LOW;

        size_t length = 0;
        status_t status = gUSBModule->send_request(Device(),
                USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT,
                FTDI_SIO_MODEM_CTRL, control,
                FTDI_PIT_DEFAULT, 0, NULL, &length);

        if (status != B_OK) {
                TRACE_ALWAYS("= FTDIDevice::SetControlLineState(): "
                        "control set request failed: 0x%08x\n", status);
        }

        control = (state & USB_CDC_CONTROL_SIGNAL_STATE_DTR) ? FTDI_SIO_SET_DTR_HIGH
                : FTDI_SIO_SET_DTR_LOW;

        status = gUSBModule->send_request(Device(),
                USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT,
                FTDI_SIO_MODEM_CTRL, control,
                FTDI_PIT_DEFAULT, 0, NULL, &length);

        if (status != B_OK) {
                TRACE_ALWAYS("= FTDIDevice::SetControlLineState(): "
                        "control set request failed: 0x%08x\n", status);
        }

        TRACE_FUNCRET("< FTDIDevice::SetControlLineState() returns: 0x%08x\n",
                status);
        return status;
}


status_t
FTDIDevice::SetHardwareFlowControl(bool enable)
{
        TRACE_FUNCALLS("> FTDIDevice::SetHardwareFlowControl(0x%08x, %d)\n",
                this, enable);

        uint32 control = enable ? FTDI_SIO_RTS_CTS_HS : FTDI_SIO_DISABLE_FLOW_CTRL;

        size_t length = 0;
        status_t status = gUSBModule->send_request(Device(),
                USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT,
                FTDI_SIO_SET_FLOW_CTRL, 0,
                FTDI_PIT_DEFAULT | (control << 8), 0, NULL, &length);

        if (status != B_OK)
                TRACE_ALWAYS("= FTDIDevice::SetHardwareFlowControl(): "
                        "request failed: 0x%08x\n", status);

        TRACE_FUNCRET("< FTDIDevice::SetHardwareFlowControl() returns: 0x%08x\n",
                status);
        return status;
}


void
FTDIDevice::OnRead(char **buffer, size_t *numBytes)
{
        /* The input consists of 64-byte packets, in which the first two bytes
         * give the status (16C550 like) of the UART. A single buffer may have
         * several of these packets in it.
         */

        size_t i = 0;
        size_t j = 0;

        while (i < *numBytes) {
                if ((i % 64) == 0) {
                        fStatusMSR = FTDI_GET_MSR(*buffer + i);
                        fStatusLSR = FTDI_GET_LSR(*buffer + i);
                        TRACE("FTDIDevice::OnRead(): MSR: 0x%02x LSR: 0x%02x\n",
                                fStatusMSR, fStatusLSR);

                        // Skip over the buffer header
                        i += 2;
                } else {
                        // Group normal bytes towards the start of the buffer
                        (*buffer)[j++] = (*buffer)[i++];
                }
        }

        // Tell the caller about the number of "real" bytes remaining in the buffer
        *numBytes = j;
}


void
FTDIDevice::OnWrite(const char *buffer, size_t *numBytes, size_t *packetBytes)
{
        if (*numBytes > FTDI_BUFFER_SIZE)
                *numBytes = *packetBytes = FTDI_BUFFER_SIZE;

        char *writeBuffer = WriteBuffer();
        if (fHeaderLength > 0) {
                if (*numBytes > WriteBufferSize() - fHeaderLength)
                        *numBytes = *packetBytes = WriteBufferSize() - fHeaderLength;

                *writeBuffer = FTDI_OUT_TAG(*numBytes, FTDI_PIT_DEFAULT);
        }

        memcpy(writeBuffer + fHeaderLength, buffer, *packetBytes);
        *packetBytes += fHeaderLength;
}