uftdi_dsd.c revision de81e71e031139a0a7f13b7bf64152c3faa76698
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* FTDI FT232R USB UART device-specific driver
*
* May work on the (many) devices based on earlier versions of the chip.
*/
#define USBDRV_MAJOR_VER 2
#define USBDRV_MINOR_VER 0
/*
* DSD operations
*/
static int uftdi_attach(ds_attach_info_t *);
static void uftdi_detach(ds_hdl_t);
/* power management */
static int uftdi_usb_power(ds_hdl_t, int, int, int *);
static int uftdi_suspend(ds_hdl_t);
static int uftdi_resume(ds_hdl_t);
static int uftdi_disconnect(ds_hdl_t);
static int uftdi_reconnect(ds_hdl_t);
/* standard UART operations */
/* data xfer */
/* polled I/O support */
/*
* Sub-routines
*/
/* configuration routines */
static void uftdi_cleanup(uftdi_state_t *, int);
static int uftdi_dev_attach(uftdi_state_t *);
static int uftdi_open_hw_port(uftdi_state_t *, int, int);
/* hotplug */
static int uftdi_restore_device_state(uftdi_state_t *);
static int uftdi_restore_port_state(uftdi_state_t *, int);
/* power management */
static int uftdi_create_pm_components(uftdi_state_t *);
static void uftdi_destroy_pm_components(uftdi_state_t *);
static int uftdi_pm_set_busy(uftdi_state_t *);
static void uftdi_pm_set_idle(uftdi_state_t *);
static int uftdi_pwrlvl0(uftdi_state_t *);
static int uftdi_pwrlvl1(uftdi_state_t *);
static int uftdi_pwrlvl2(uftdi_state_t *);
static int uftdi_pwrlvl3(uftdi_state_t *);
/* pipe operations */
static int uftdi_open_pipes(uftdi_state_t *);
static void uftdi_close_pipes(uftdi_state_t *);
static void uftdi_disconnect_pipes(uftdi_state_t *);
static int uftdi_reconnect_pipes(uftdi_state_t *);
/* pipe callbacks */
/* data transfer routines */
static int uftdi_rx_start(uftdi_state_t *);
static void uftdi_tx_start(uftdi_state_t *, int *);
static int uftdi_wait_tx_drain(uftdi_state_t *, int);
/* vendor-specific commands */
static int uftdi_cmd_vendor_write0(uftdi_state_t *,
/* misc */
/*
* DSD ops structure
*/
ds_ops_t uftdi_ds_ops = {
NULL, /* no loopback support */
};
/* debug support */
/*
* ds_attach
*/
static int
{
int recognized;
/* only one port */
return (USB_FAILURE);
}
return (USB_FAILURE);
}
/*
* This device and its clones has numerous physical instantiations.
*/
recognized = B_TRUE;
case USB_VENDOR_FTDI:
case USB_PRODUCT_FTDI_CFA_631:
case USB_PRODUCT_FTDI_CFA_632:
case USB_PRODUCT_FTDI_CFA_633:
case USB_PRODUCT_FTDI_CFA_634:
case USB_PRODUCT_FTDI_CFA_635:
case USB_PRODUCT_FTDI_MX2_3:
case USB_PRODUCT_FTDI_MX4_5:
case USB_PRODUCT_FTDI_LK202:
case USB_PRODUCT_FTDI_LK204:
case USB_PRODUCT_FTDI_EISCOU:
case USB_PRODUCT_FTDI_UOPTBR:
case USB_PRODUCT_FTDI_EMCU2D:
case USB_PRODUCT_FTDI_PCMSFU:
case USB_PRODUCT_FTDI_EMCU2H:
break;
default:
break;
}
break;
case USB_VENDOR_SIIG2:
case USB_PRODUCT_SIIG2_US2308:
break;
default:
break;
}
break;
case USB_VENDOR_INTREPIDCS:
break;
default:
break;
}
break;
case USB_VENDOR_BBELECTRONICS:
break;
default:
break;
}
break;
case USB_VENDOR_MELCO:
break;
default:
break;
}
break;
default:
break;
}
/*
* Set 'uftdi_attach_unrecognized' to non-zero to
* experiment with newer devices ..
*/
if (!recognized && !uftdi_attach_unrecognized) {
return (USB_FAILURE);
}
"uftdi: matched vendor 0x%x product 0x%x",
return (USB_FAILURE);
}
return (USB_FAILURE);
}
return (USB_FAILURE);
}
/*
* TODO: modern ftdi devices have deeper (and asymmetric)
* fifos than this minimal 64 bytes .. but how to tell
* -safely- ?
*/
#define FTDI_MAX_XFERSIZE 64
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
#define FTDI_CLEANUP_LEVEL_MAX 6
/*
* ds_detach
*/
static void
{
}
/*
* ds_register_cb
*/
/*ARGSUSED*/
static int
{
return (USB_SUCCESS);
}
/*
* ds_unregister_cb
*/
/*ARGSUSED*/
static void
{
}
/*
* ds_open_port
*/
/*ARGSUSED*/
static int
{
int rval;
return (USB_FAILURE);
}
return (rval);
/* initialize hardware serial port */
if (rval == USB_SUCCESS) {
/* start to receive data */
return (USB_FAILURE);
}
} else
return (rval);
}
/*
* ds_close_port
*/
/*ARGSUSED*/
static int
{
/* free resources and finalize state */
return (USB_SUCCESS);
}
/*
* ds_usb_power
*/
/*ARGSUSED*/
static int
{
int rval;
if (!pm)
return (USB_FAILURE);
/*
* check if we are transitioning to a legal power level
*/
"illegal power level %d, pwr_states=0x%x",
return (USB_FAILURE);
}
/*
* if we are about to raise power and asked to lower power, fail
*/
return (USB_FAILURE);
}
switch (level) {
case USB_DEV_OS_PWR_OFF:
break;
case USB_DEV_OS_PWR_1:
break;
case USB_DEV_OS_PWR_2:
break;
case USB_DEV_OS_FULL_PWR:
/*
* If usbser dev_state is DISCONNECTED or SUSPENDED, it shows
* that the usb serial device is disconnected/suspended while it
* is under power down state, now the device is powered up
* before it is reconnected/resumed. xxx_pwrlvl3() will set dev
* state to ONLINE, we need to set the dev state back to
*/
if (rval == USB_SUCCESS &&
(*new_state == USB_DEV_DISCONNECTED ||
*new_state == USB_DEV_SUSPENDED))
break;
default:
ASSERT(0); /* cannot happen */
}
return (rval);
}
/*
* ds_suspend
*/
static int
{
int state = USB_DEV_SUSPENDED;
/*
* If the device is suspended while it is under PWRED_DOWN state, we
* need to keep the PWRED_DOWN state so that it could be powered up
* later. In the mean while, usbser dev state will be changed to
* SUSPENDED state.
*/
return (state);
}
/*
* ds_resume
*/
static int
{
int current_state;
int rval;
if (current_state == USB_DEV_ONLINE)
rval = USB_SUCCESS;
else
return (rval);
}
/*
* ds_disconnect
*/
static int
{
int state = USB_DEV_DISCONNECTED;
/*
* If the device is disconnected while it is under PWRED_DOWN state, we
* need to keep the PWRED_DOWN state so that it could be powered up
* later. In the mean while, usbser dev state will be changed to
* DISCONNECTED state.
*/
return (state);
}
/*
* ds_reconnect
*/
static int
{
return (uftdi_restore_device_state(uf));
}
/* translate parameters into device-specific bits */
static int
{
int i;
ur->ur_flowval = 0;
case DS_PARAM_BAUD:
case B300:
break;
case B600:
break;
case B1200:
break;
case B2400:
break;
case B4800:
break;
case B9600:
break;
case B19200:
break;
case B38400:
break;
case B57600:
break;
case B115200:
break;
case B230400:
break;
case B460800:
break;
case B921600:
break;
default:
"uftdi_param2regs: bad baud %d",
return (USB_FAILURE);
}
break;
case DS_PARAM_PARITY:
else
} else {
/* LINTED [E_EXPR_NULL_EFFECT] */
}
break;
case DS_PARAM_STOPB:
else {
/* LINTED [E_EXPR_NULL_EFFECT] */
}
break;
case DS_PARAM_CHARSZ:
case CS5:
break;
case CS6:
break;
case CS7:
break;
case CS8:
default:
break;
}
break;
case DS_PARAM_XON_XOFF: /* Software flow control */
}
break;
case DS_PARAM_FLOW_CTL: /* Hardware flow control */
ur->ur_flowval = 0;
}
ur->ur_flowval = 0;
}
break;
default:
break;
}
}
return (USB_SUCCESS);
}
/*
* Write the register set to the device and update the state structure.
* If there are errors, return the device to its previous state.
*/
static int
{
int rval;
if (rval != USB_SUCCESS) {
goto out;
} else {
}
if (rval != USB_SUCCESS) {
goto out;
} else {
}
if (rval != USB_SUCCESS) {
goto out;
} else {
}
out:
return (rval);
}
/*
* ds_set_port_params
*/
static int
{
int rval;
if (rval == USB_SUCCESS)
return (rval);
}
/*
* ds_set_modem_ctl
*/
static int
{
int rval;
/*
* Note that we cannot set DTR and RTS simultaneously, so
* we do separate operations for each bit.
*/
if (rval == USB_SUCCESS) {
} else
return (rval);
}
if (rval == USB_SUCCESS) {
}
}
return (rval);
}
/*
* ds_get_modem_ctl
*/
static int
{
/*
* This status info is delivered to us at least every 40ms
* while the receive pipe is active
*/
/*
* Note, this status info is simply a replay of what we
* asked it to be in some previous "set" command, and
* is *not* directly sensed from the hardware.
*/
return (USB_SUCCESS);
}
/*
* ds_break_ctl
*/
static int
{
}
/*
* ds_tx
*/
/*ARGSUSED*/
static int
{
return (USB_SUCCESS);
}
/*
* ds_rx
*/
/*ARGSUSED*/
static mblk_t *
{
return (mp);
}
/*
* ds_stop
*/
/*ARGSUSED*/
static void
{
}
}
/*
* ds_start
*/
/*ARGSUSED*/
static void
{
}
}
}
/*
* ds_fifo_flush
*/
/*ARGSUSED*/
static int
{
"uftdi_fifo_flush: dir=0x%x", dir);
}
}
(void) uftdi_cmd_vendor_write0(uf,
(void) uftdi_cmd_vendor_write0(uf,
return (USB_SUCCESS);
}
/*
* ds_fifo_drain
*/
/*ARGSUSED*/
static int
{
int rval = USB_SUCCESS;
return (USB_FAILURE);
}
/* wait 500 ms until hw fifo drains */
return (rval);
}
/*
* configuration clean up
*/
static void
{
switch (level) {
default:
case 6:
/*FALLTHROUGH*/
case 5:
/*FALLTHROUGH*/
case 4:
/*FALLTHROUGH*/
case 3:
/*FALLTHROUGH*/
case 2:
/*FALLTHROUGH*/
case 1:
break;
}
}
/*
* device specific attach
*/
static int
{
return (uftdi_open_pipes(uf));
}
/*
* restore device state after CPR resume or reconnect
*/
static int
{
int state;
return (state);
return (state);
}
if (state == USB_DEV_DISCONNECTED) {
"Device has been reconnected but data may have been lost");
}
return (state);
/*
* init device state
*/
"uftdi_restore_device_state: failed");
}
return (state);
}
/*
* restore ports state after CPR resume or reconnect
*/
static int
{
int rval;
return (USB_SUCCESS);
}
/* open hardware serial port, restoring old settings */
"uftdi_restore_port_state: failed");
}
return (rval);
}
/*
* create PM components
*/
static int
{
uftdi_pm_t *pm;
"uftdi_create_pm_components: failed");
return (USB_SUCCESS);
}
return (USB_SUCCESS);
}
/*
* destroy PM components
*/
static void
{
int rval;
if (!pm)
return;
if (pm->pm_wakeup_enabled) {
if (rval != DDI_SUCCESS) {
"uftdi_destroy_pm_components: "
"raising power failed, rval=%d", rval);
}
if (rval != USB_SUCCESS) {
"uftdi_destroy_pm_components: disable "
"remote wakeup failed, rval=%d", rval);
}
}
}
}
/*
* mark device busy and raise power
*/
static int
{
int rval;
if (!pm)
return (USB_SUCCESS);
/* if already marked busy, just increment the counter */
if (pm->pm_busy_cnt++ > 0) {
return (USB_SUCCESS);
}
return (USB_SUCCESS);
}
/* need to raise power */
if (rval != DDI_SUCCESS) {
}
return (USB_SUCCESS);
}
/*
* mark device idle
*/
static void
{
if (!pm)
return;
/*
* if more ports use the device, do not mark as yet
*/
if (--pm->pm_busy_cnt > 0) {
return;
}
(void) pm_idle_component(dip, 0);
}
/*
* Functions to handle power transition for OS levels 0 -> 3
* The same level as OS state, different from USB state
*/
static int
{
int rval;
switch (uf->uf_dev_state) {
case USB_DEV_ONLINE:
/* issue USB D3 command to the device */
/*FALLTHROUGH*/
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
/* allow a disconnect/cpr'ed device to go to lower power */
return (USB_SUCCESS);
case USB_DEV_PWRED_DOWN:
default:
"uftdi_pwrlvl0: illegal device state");
return (USB_FAILURE);
}
}
static int
{
/* issue USB D2 command to the device */
return (USB_FAILURE);
}
static int
{
/* issue USB D1 command to the device */
return (USB_FAILURE);
}
static int
{
int rval;
switch (uf->uf_dev_state) {
case USB_DEV_PWRED_DOWN:
/* Issue USB D0 command to the device here */
/*FALLTHROUGH*/
case USB_DEV_ONLINE:
/* we are already in full power */
/*FALLTHROUGH*/
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
return (USB_SUCCESS);
default:
"uftdi_pwrlvl3: illegal device state");
return (USB_FAILURE);
}
}
/*
* pipe operations
*/
static int
{
/* get ep data */
alt = 0;
"uftdi_open_pipes: can't get ep data");
return (USB_FAILURE);
}
/* open pipes */
return (USB_FAILURE);
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
static void
{
if (uf->uf_bulkin_ph)
USB_FLAGS_SLEEP, 0, 0);
if (uf->uf_bulkout_ph)
USB_FLAGS_SLEEP, 0, 0);
}
static void
{
}
static int
{
return (uftdi_open_pipes(uf));
}
/*
* bulk in pipe normal and exception callback handler
*/
/*ARGSUSED*/
static void
{
int data_len;
int notify = 0;
/*
* The first two bytes of data are actually status register bytes
* that arrive with every packet from the device. Strip
* them here before handing the data on. Note that the device
* will send us these bytes at least every 40 milliseconds,
* even if there's no data ..
*/
"uftdi_bulkin_cb: status change "
"0x%02x.0x%02x, was 0x%02x.0x%02x",
/*
* If we're waiting for a modem status change,
* sending an empty message will cause us to
* reexamine the modem flags.
*/
notify = 1;
}
data_len -= 2;
}
/* save data and notify GSD */
}
/* receive more */
"uftdi_bulkin_cb: restart rx fail");
}
}
}
/*
* bulk out common and exception callback
*/
/*ARGSUSED*/
static void
{
int data_len;
"uftdi_bulkout_cb: cr=%d len=%d",
}
/* notify GSD */
/* send more */
else
}
/*
* start receiving data
*/
static int
{
int rval;
if (rval != USB_SUCCESS) {
"uftdi_rx_start: xfer failed %d", rval);
}
if (rval != USB_SUCCESS)
return (rval);
}
/*
* start data transmit
*/
static void
{
int len; /* bytes we can transmit */
int data_len; /* bytes in 'data' */
int copylen; /* bytes copy from 'mp' to 'data' */
int rval;
if (xferd)
*xferd = 0;
return;
}
"uftdi_tx_start: pipe busy");
return;
}
/* send as much data as port can receive */
if (len <= 0)
return;
return;
/*
* copy no more than 'len' bytes from mblk chain to transmit mblk 'data'
*/
data_len = 0;
} else {
}
}
if (rval != USB_SUCCESS) {
} else {
if (xferd)
}
}
static int
{
int rval;
if (rval != USB_SUCCESS) {
"uftdi_send_data: xfer failed %d", rval);
}
return (rval);
}
/*
* wait until local tx buffer drains.
* 'timeout' is in seconds, zero means wait forever
*/
static int
{
int over = 0;
if (timeout > 0) {
/* whether timedout or signal pending */
} else {
/* whether a signal is pending */
}
}
}
/*
* initialize hardware serial port
*/
static int
{
int rval;
/*
* Perform a full reset on the device
*/
if (rval != USB_SUCCESS) {
"uftdi_open_hw_port: failed to reset!");
return (rval);
}
if (dorestore) {
/*
* Restore settings from our soft copy of HW registers
*/
} else {
/*
* 9600 baud, 2 stop bits, no parity, 8-bit, h/w flow control
*/
static ds_port_param_entry_t ents[] = {
#if defined(__lock_lint)
/*
* (Sigh - wlcc doesn't understand this newer
* form of structure member initialization.)
*/
{ 0 }
#else
#endif
};
static ds_port_params_t params = {
ents,
};
if (rval != USB_SUCCESS) {
"uftdi_open_hw_port: failed 9600/2/n/8 rval %d",
rval);
}
}
return (rval);
}
static int
{
int rval;
"uftdi_cmd_vendor_write0: 0x%x 0x%x 0x%x failed %d %d 0x%x",
}
return (rval);
}
/*
* misc routines
*/
/*
* link a message block to tail of message
* account for the case when message is null
*/
static void
{
if (*mpp)
else
}
/*
* put a message block at the head of the message
* account for the case when message is null
*/
static void
{
if (*mpp)
}
/*ARGSUSED*/
static usb_pipe_handle_t
{
}
/*ARGSUSED*/
static usb_pipe_handle_t
{
}