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
* or http://www.opensolaris.org/os/licensing.
* 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.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/termio.h>
#include <sys/termiox.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#define USBDRV_MAJOR_VER 2
#define USBDRV_MINOR_VER 0
#include <sys/usb/usba.h>
#include <sys/usb/usba/usba_types.h>
#include <sys/usb/usba/usba_impl.h>
#include <sys/usb/clients/usbser/usbser_dsdi.h>
#include <sys/usb/clients/usbser/usbftdi/uftdi_var.h>
#include <sys/usb/clients/usbser/usbftdi/uftdi_reg.h>
#include <sys/usb/usbdevs.h>
/*
* DSD operations
*/
static int uftdi_attach(ds_attach_info_t *);
static void uftdi_detach(ds_hdl_t);
static int uftdi_register_cb(ds_hdl_t, uint_t, ds_cb_t *);
static void uftdi_unregister_cb(ds_hdl_t, uint_t);
static int uftdi_open_port(ds_hdl_t, uint_t);
static int uftdi_close_port(ds_hdl_t, uint_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 */
static int uftdi_set_port_params(ds_hdl_t, uint_t, ds_port_params_t *);
static int uftdi_set_modem_ctl(ds_hdl_t, uint_t, int, int);
static int uftdi_get_modem_ctl(ds_hdl_t, uint_t, int, int *);
static int uftdi_break_ctl(ds_hdl_t, uint_t, int);
/* data xfer */
static int uftdi_tx(ds_hdl_t, uint_t, mblk_t *);
static mblk_t *uftdi_rx(ds_hdl_t, uint_t);
static void uftdi_stop(ds_hdl_t, uint_t, int);
static void uftdi_start(ds_hdl_t, uint_t, int);
static int uftdi_fifo_flush(ds_hdl_t, uint_t, int);
static int uftdi_fifo_drain(ds_hdl_t, uint_t, int);
/* polled I/O support */
static usb_pipe_handle_t uftdi_out_pipe(ds_hdl_t, uint_t);
static usb_pipe_handle_t uftdi_in_pipe(ds_hdl_t, uint_t);
/*
* 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 */
static void uftdi_bulkin_cb(usb_pipe_handle_t, usb_bulk_req_t *);
static void uftdi_bulkout_cb(usb_pipe_handle_t, usb_bulk_req_t *);
/* data transfer routines */
static int uftdi_rx_start(uftdi_state_t *);
static void uftdi_tx_start(uftdi_state_t *, int *);
static int uftdi_send_data(uftdi_state_t *, mblk_t *);
static int uftdi_wait_tx_drain(uftdi_state_t *, int);
/* vendor-specific commands */
static int uftdi_cmd_vendor_write0(uftdi_state_t *,
uint16_t, uint16_t, uint16_t);
/* misc */
static void uftdi_put_tail(mblk_t **, mblk_t *);
static void uftdi_put_head(mblk_t **, mblk_t *);
/*
* DSD ops structure
*/
ds_ops_t uftdi_ds_ops = {
DS_OPS_VERSION,
uftdi_attach,
uftdi_detach,
uftdi_register_cb,
uftdi_unregister_cb,
uftdi_open_port,
uftdi_close_port,
uftdi_usb_power,
uftdi_suspend,
uftdi_resume,
uftdi_disconnect,
uftdi_reconnect,
uftdi_set_port_params,
uftdi_set_modem_ctl,
uftdi_get_modem_ctl,
uftdi_break_ctl,
NULL, /* no loopback support */
uftdi_tx,
uftdi_rx,
uftdi_stop,
uftdi_start,
uftdi_fifo_flush,
uftdi_fifo_drain,
uftdi_out_pipe,
uftdi_in_pipe
};
/* debug support */
static uint_t uftdi_errlevel = USB_LOG_L4;
static uint_t uftdi_errmask = DPRINT_MASK_ALL;
static uint_t uftdi_instance_debug = (uint_t)-1;
static uint_t uftdi_attach_unrecognized = B_FALSE;
/*
* ds_attach
*/
static int
uftdi_attach(ds_attach_info_t *aip)
{
uftdi_state_t *uf;
usb_dev_descr_t *dd;
int recognized;
uf = kmem_zalloc(sizeof (*uf), KM_SLEEP);
uf->uf_dip = aip->ai_dip;
uf->uf_usb_events = aip->ai_usb_events;
*aip->ai_hdl = (ds_hdl_t)uf;
/* only one port */
*aip->ai_port_cnt = 1;
if (usb_client_attach(uf->uf_dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
uftdi_cleanup(uf, 1);
return (USB_FAILURE);
}
if (usb_get_dev_data(uf->uf_dip,
&uf->uf_dev_data, USB_PARSE_LVL_IF, 0) != USB_SUCCESS) {
uftdi_cleanup(uf, 2);
return (USB_FAILURE);
}
mutex_init(&uf->uf_lock, NULL, MUTEX_DRIVER,
uf->uf_dev_data->dev_iblock_cookie);
cv_init(&uf->uf_tx_cv, NULL, CV_DRIVER, NULL);
uf->uf_lh = usb_alloc_log_hdl(uf->uf_dip, "uftdi",
&uftdi_errlevel, &uftdi_errmask, &uftdi_instance_debug, 0);
/*
* This device and its clones has numerous physical instantiations.
*/
recognized = B_TRUE;
dd = uf->uf_dev_data->dev_descr;
switch (dd->idVendor) {
case USB_VENDOR_FTDI:
switch (dd->idProduct) {
case USB_PRODUCT_FTDI_SERIAL_8U232AM:
case USB_PRODUCT_FTDI_SEMC_DSS20:
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_USBSERIAL:
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_TACTRIX_OPENPORT_13M:
case USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13S:
case USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13U:
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:
recognized = B_FALSE;
break;
}
break;
case USB_VENDOR_SIIG2:
switch (dd->idProduct) {
case USB_PRODUCT_SIIG2_US2308:
break;
default:
recognized = B_FALSE;
break;
}
break;
case USB_VENDOR_INTREPIDCS:
switch (dd->idProduct) {
case USB_PRODUCT_INTREPIDCS_VALUECAN:
case USB_PRODUCT_INTREPIDCS_NEOVI:
break;
default:
recognized = B_FALSE;
break;
}
break;
case USB_VENDOR_BBELECTRONICS:
switch (dd->idProduct) {
case USB_PRODUCT_BBELECTRONICS_USOTL4:
break;
default:
recognized = B_FALSE;
break;
}
break;
case USB_VENDOR_MELCO:
switch (dd->idProduct) {
case USB_PRODUCT_MELCO_PCOPRS1:
break;
default:
recognized = B_FALSE;
break;
}
break;
default:
recognized = B_FALSE;
break;
}
/*
* Set 'uftdi_attach_unrecognized' to non-zero to
* experiment with newer devices ..
*/
if (!recognized && !uftdi_attach_unrecognized) {
uftdi_cleanup(uf, 3);
return (USB_FAILURE);
}
USB_DPRINTF_L3(DPRINT_ATTACH, uf->uf_lh,
"uftdi: matched vendor 0x%x product 0x%x",
dd->idVendor, dd->idProduct);
uf->uf_def_ph = uf->uf_dev_data->dev_default_ph;
mutex_enter(&uf->uf_lock);
uf->uf_dev_state = USB_DEV_ONLINE;
uf->uf_port_state = UFTDI_PORT_CLOSED;
mutex_exit(&uf->uf_lock);
if (uftdi_create_pm_components(uf) != USB_SUCCESS) {
uftdi_cleanup(uf, 3);
return (USB_FAILURE);
}
if (usb_register_event_cbs(uf->uf_dip,
uf->uf_usb_events, 0) != USB_SUCCESS) {
uftdi_cleanup(uf, 4);
return (USB_FAILURE);
}
if (usb_pipe_get_max_bulk_transfer_size(uf->uf_dip,
&uf->uf_xfer_sz) != USB_SUCCESS) {
uftdi_cleanup(uf, 5);
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
if (uf->uf_xfer_sz > FTDI_MAX_XFERSIZE)
uf->uf_xfer_sz = FTDI_MAX_XFERSIZE;
if (uftdi_dev_attach(uf) != USB_SUCCESS) {
uftdi_cleanup(uf, 5);
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
#define FTDI_CLEANUP_LEVEL_MAX 6
/*
* ds_detach
*/
static void
uftdi_detach(ds_hdl_t hdl)
{
uftdi_cleanup((uftdi_state_t *)hdl, FTDI_CLEANUP_LEVEL_MAX);
}
/*
* ds_register_cb
*/
/*ARGSUSED*/
static int
uftdi_register_cb(ds_hdl_t hdl, uint_t portno, ds_cb_t *cb)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
uf->uf_cb = *cb;
return (USB_SUCCESS);
}
/*
* ds_unregister_cb
*/
/*ARGSUSED*/
static void
uftdi_unregister_cb(ds_hdl_t hdl, uint_t portno)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
bzero(&uf->uf_cb, sizeof (uf->uf_cb));
}
/*
* ds_open_port
*/
/*ARGSUSED*/
static int
uftdi_open_port(ds_hdl_t hdl, uint_t portno)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
int rval;
USB_DPRINTF_L4(DPRINT_OPEN, uf->uf_lh, "uftdi_open_port %d", portno);
mutex_enter(&uf->uf_lock);
if (uf->uf_dev_state == USB_DEV_DISCONNECTED ||
uf->uf_port_state != UFTDI_PORT_CLOSED) {
mutex_exit(&uf->uf_lock);
return (USB_FAILURE);
}
mutex_exit(&uf->uf_lock);
if ((rval = uftdi_pm_set_busy(uf)) != USB_SUCCESS)
return (rval);
/* initialize hardware serial port */
rval = uftdi_open_hw_port(uf, portno, 0);
if (rval == USB_SUCCESS) {
mutex_enter(&uf->uf_lock);
/* start to receive data */
if (uftdi_rx_start(uf) != USB_SUCCESS) {
mutex_exit(&uf->uf_lock);
return (USB_FAILURE);
}
uf->uf_port_state = UFTDI_PORT_OPEN;
mutex_exit(&uf->uf_lock);
} else
uftdi_pm_set_idle(uf);
return (rval);
}
/*
* ds_close_port
*/
/*ARGSUSED*/
static int
uftdi_close_port(ds_hdl_t hdl, uint_t portno)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CLOSE, uf->uf_lh, "uftdi_close_port %d", portno);
mutex_enter(&uf->uf_lock);
/* free resources and finalize state */
freemsg(uf->uf_rx_mp);
uf->uf_rx_mp = NULL;
freemsg(uf->uf_tx_mp);
uf->uf_tx_mp = NULL;
uf->uf_port_state = UFTDI_PORT_CLOSED;
mutex_exit(&uf->uf_lock);
uftdi_pm_set_idle(uf);
return (USB_SUCCESS);
}
/*
* ds_usb_power
*/
/*ARGSUSED*/
static int
uftdi_usb_power(ds_hdl_t hdl, int comp, int level, int *new_state)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
uftdi_pm_t *pm = uf->uf_pm;
int rval;
USB_DPRINTF_L4(DPRINT_PM, uf->uf_lh, "uftdi_usb_power");
if (!pm)
return (USB_FAILURE);
mutex_enter(&uf->uf_lock);
/*
* check if we are transitioning to a legal power level
*/
if (USB_DEV_PWRSTATE_OK(pm->pm_pwr_states, level)) {
USB_DPRINTF_L2(DPRINT_PM, uf->uf_lh, "uftdi_usb_power: "
"illegal power level %d, pwr_states=0x%x",
level, pm->pm_pwr_states);
mutex_exit(&uf->uf_lock);
return (USB_FAILURE);
}
/*
* if we are about to raise power and asked to lower power, fail
*/
if (pm->pm_raise_power && (level < (int)pm->pm_cur_power)) {
mutex_exit(&uf->uf_lock);
return (USB_FAILURE);
}
switch (level) {
case USB_DEV_OS_PWR_OFF:
rval = uftdi_pwrlvl0(uf);
break;
case USB_DEV_OS_PWR_1:
rval = uftdi_pwrlvl1(uf);
break;
case USB_DEV_OS_PWR_2:
rval = uftdi_pwrlvl2(uf);
break;
case USB_DEV_OS_FULL_PWR:
rval = uftdi_pwrlvl3(uf);
/*
* 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
* DISCONNECTED/SUSPENDED.
*/
if (rval == USB_SUCCESS &&
(*new_state == USB_DEV_DISCONNECTED ||
*new_state == USB_DEV_SUSPENDED))
uf->uf_dev_state = *new_state;
break;
default:
ASSERT(0); /* cannot happen */
}
*new_state = uf->uf_dev_state;
mutex_exit(&uf->uf_lock);
return (rval);
}
/*
* ds_suspend
*/
static int
uftdi_suspend(ds_hdl_t hdl)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
int state = USB_DEV_SUSPENDED;
USB_DPRINTF_L4(DPRINT_PM, uf->uf_lh, "uftdi_suspend");
/*
* 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.
*/
mutex_enter(&uf->uf_lock);
if (uf->uf_dev_state != USB_DEV_PWRED_DOWN)
uf->uf_dev_state = USB_DEV_SUSPENDED;
mutex_exit(&uf->uf_lock);
uftdi_disconnect_pipes(uf);
return (state);
}
/*
* ds_resume
*/
static int
uftdi_resume(ds_hdl_t hdl)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
int current_state;
int rval;
USB_DPRINTF_L4(DPRINT_PM, uf->uf_lh, "uftdi_resume");
mutex_enter(&uf->uf_lock);
current_state = uf->uf_dev_state;
mutex_exit(&uf->uf_lock);
if (current_state == USB_DEV_ONLINE)
rval = USB_SUCCESS;
else
rval = uftdi_restore_device_state(uf);
return (rval);
}
/*
* ds_disconnect
*/
static int
uftdi_disconnect(ds_hdl_t hdl)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
int state = USB_DEV_DISCONNECTED;
USB_DPRINTF_L4(DPRINT_HOTPLUG, uf->uf_lh, "uftdi_disconnect");
/*
* 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.
*/
mutex_enter(&uf->uf_lock);
if (uf->uf_dev_state != USB_DEV_PWRED_DOWN)
uf->uf_dev_state = USB_DEV_DISCONNECTED;
mutex_exit(&uf->uf_lock);
uftdi_disconnect_pipes(uf);
return (state);
}
/*
* ds_reconnect
*/
static int
uftdi_reconnect(ds_hdl_t hdl)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_HOTPLUG, uf->uf_lh, "uftdi_reconnect");
return (uftdi_restore_device_state(uf));
}
/* translate parameters into device-specific bits */
static int
uftdi_param2regs(uftdi_state_t *uf, ds_port_params_t *tp, uftdi_regs_t *ur)
{
ds_port_param_entry_t *pe;
int i;
ur->ur_data = 0;
ur->ur_flowval = 0;
ur->ur_flowidx = FTDI_SIO_DISABLE_FLOW_CTRL << 8;
for (i = 0, pe = tp->tp_entries; i < tp->tp_cnt; i++, pe++) {
switch (pe->param) {
case DS_PARAM_BAUD:
switch (pe->val.ui) {
case B300:
ur->ur_baud = ftdi_8u232am_b300;
break;
case B600:
ur->ur_baud = ftdi_8u232am_b600;
break;
case B1200:
ur->ur_baud = ftdi_8u232am_b1200;
break;
case B2400:
ur->ur_baud = ftdi_8u232am_b2400;
break;
case B4800:
ur->ur_baud = ftdi_8u232am_b4800;
break;
case B9600:
ur->ur_baud = ftdi_8u232am_b9600;
break;
case B19200:
ur->ur_baud = ftdi_8u232am_b19200;
break;
case B38400:
ur->ur_baud = ftdi_8u232am_b38400;
break;
case B57600:
ur->ur_baud = ftdi_8u232am_b57600;
break;
case B115200:
ur->ur_baud = ftdi_8u232am_b115200;
break;
case B230400:
ur->ur_baud = ftdi_8u232am_b230400;
break;
case B460800:
ur->ur_baud = ftdi_8u232am_b460800;
break;
case B921600:
ur->ur_baud = ftdi_8u232am_b921600;
break;
default:
USB_DPRINTF_L3(DPRINT_CTLOP, uf->uf_lh,
"uftdi_param2regs: bad baud %d",
pe->val.ui);
return (USB_FAILURE);
}
break;
case DS_PARAM_PARITY:
if (pe->val.ui & PARENB) {
if (pe->val.ui & PARODD)
ur->ur_data |=
FTDI_SIO_SET_DATA_PARITY_ODD;
else
ur->ur_data |=
FTDI_SIO_SET_DATA_PARITY_EVEN;
} else {
/* LINTED [E_EXPR_NULL_EFFECT] */
ur->ur_data |= FTDI_SIO_SET_DATA_PARITY_NONE;
}
break;
case DS_PARAM_STOPB:
if (pe->val.ui & CSTOPB)
ur->ur_data |= FTDI_SIO_SET_DATA_STOP_BITS_2;
else {
/* LINTED [E_EXPR_NULL_EFFECT] */
ur->ur_data |= FTDI_SIO_SET_DATA_STOP_BITS_1;
}
break;
case DS_PARAM_CHARSZ:
switch (pe->val.ui) {
case CS5:
ur->ur_data |= FTDI_SIO_SET_DATA_BITS(5);
break;
case CS6:
ur->ur_data |= FTDI_SIO_SET_DATA_BITS(6);
break;
case CS7:
ur->ur_data |= FTDI_SIO_SET_DATA_BITS(7);
break;
case CS8:
default:
ur->ur_data |= FTDI_SIO_SET_DATA_BITS(8);
break;
}
break;
case DS_PARAM_XON_XOFF: /* Software flow control */
if ((pe->val.ui & IXON) || (pe->val.ui & IXOFF)) {
uint8_t xonc = pe->val.uc[0];
uint8_t xoffc = pe->val.uc[1];
ur->ur_flowval = (xoffc << 8) | xonc;
ur->ur_flowidx = FTDI_SIO_XON_XOFF_HS << 8;
}
break;
case DS_PARAM_FLOW_CTL: /* Hardware flow control */
if (pe->val.ui & (RTSXOFF | CTSXON)) {
ur->ur_flowval = 0;
ur->ur_flowidx = FTDI_SIO_RTS_CTS_HS << 8;
}
if (pe->val.ui & DTRXOFF) {
ur->ur_flowval = 0;
ur->ur_flowidx = FTDI_SIO_DTR_DSR_HS << 8;
}
break;
default:
USB_DPRINTF_L2(DPRINT_CTLOP, uf->uf_lh,
"uftdi_param2regs: bad param %d", pe->param);
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
uftdi_setregs(uftdi_state_t *uf, uint_t portno, uftdi_regs_t *ur)
{
int rval;
uftdi_regs_t uold;
mutex_enter(&uf->uf_lock);
uold = uf->uf_softr;
mutex_exit(&uf->uf_lock);
if (ur == NULL)
ur = &uold; /* NULL => restore previous values */
rval = uftdi_cmd_vendor_write0(uf, FTDI_SIO_SET_BAUD_RATE,
ur->ur_baud, portno);
if (rval != USB_SUCCESS) {
(void) uftdi_cmd_vendor_write0(uf, FTDI_SIO_SET_BAUD_RATE,
uold.ur_baud, portno);
goto out;
} else {
mutex_enter(&uf->uf_lock);
uf->uf_softr.ur_baud = ur->ur_baud;
mutex_exit(&uf->uf_lock);
}
rval = uftdi_cmd_vendor_write0(uf, FTDI_SIO_SET_DATA,
ur->ur_data, portno);
if (rval != USB_SUCCESS) {
(void) uftdi_cmd_vendor_write0(uf, FTDI_SIO_SET_DATA,
uold.ur_data, portno);
goto out;
} else {
mutex_enter(&uf->uf_lock);
uf->uf_softr.ur_data = ur->ur_data;
mutex_exit(&uf->uf_lock);
}
rval = uftdi_cmd_vendor_write0(uf, FTDI_SIO_SET_FLOW_CTRL,
ur->ur_flowval, ur->ur_flowidx | portno);
if (rval != USB_SUCCESS) {
(void) uftdi_cmd_vendor_write0(uf, FTDI_SIO_SET_FLOW_CTRL,
uold.ur_flowval, uold.ur_flowidx | portno);
goto out;
} else {
mutex_enter(&uf->uf_lock);
uf->uf_softr.ur_flowval = ur->ur_flowval;
uf->uf_softr.ur_flowidx = ur->ur_flowidx;
mutex_exit(&uf->uf_lock);
}
out:
return (rval);
}
/*
* ds_set_port_params
*/
static int
uftdi_set_port_params(ds_hdl_t hdl, uint_t portno, ds_port_params_t *tp)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
int rval;
uftdi_regs_t uregs;
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh, "uftdi_set_port_params");
rval = uftdi_param2regs(uf, tp, &uregs);
if (rval == USB_SUCCESS)
rval = uftdi_setregs(uf, portno, &uregs);
return (rval);
}
/*
* ds_set_modem_ctl
*/
static int
uftdi_set_modem_ctl(ds_hdl_t hdl, uint_t portno, int mask, int val)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
int rval;
uint16_t mctl;
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh, "uftdi_set_modem_ctl");
/*
* Note that we cannot set DTR and RTS simultaneously, so
* we do separate operations for each bit.
*/
if (mask & TIOCM_DTR) {
mctl = (val & TIOCM_DTR) ?
FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW;
rval = uftdi_cmd_vendor_write0(uf,
FTDI_SIO_MODEM_CTRL, mctl, portno);
if (rval == USB_SUCCESS) {
mutex_enter(&uf->uf_lock);
uf->uf_mctl &= ~FTDI_SIO_SET_DTR_HIGH;
uf->uf_mctl |= mctl & FTDI_SIO_SET_DTR_HIGH;
mutex_exit(&uf->uf_lock);
} else
return (rval);
}
if (mask & TIOCM_RTS) {
mctl = (val & TIOCM_RTS) ?
FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW;
rval = uftdi_cmd_vendor_write0(uf,
FTDI_SIO_MODEM_CTRL, mctl, portno);
if (rval == USB_SUCCESS) {
mutex_enter(&uf->uf_lock);
uf->uf_mctl &= ~FTDI_SIO_SET_RTS_HIGH;
uf->uf_mctl |= mctl & FTDI_SIO_SET_RTS_HIGH;
mutex_exit(&uf->uf_lock);
}
}
return (rval);
}
/*
* ds_get_modem_ctl
*/
static int
uftdi_get_modem_ctl(ds_hdl_t hdl, uint_t portno, int mask, int *valp)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
uint_t val = 0;
ASSERT(portno == 0);
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh, "uftdi_get_modem_ctl");
mutex_enter(&uf->uf_lock);
/*
* This status info is delivered to us at least every 40ms
* while the receive pipe is active
*/
if (uf->uf_msr & FTDI_MSR_STATUS_CTS)
val |= TIOCM_CTS;
if (uf->uf_msr & FTDI_MSR_STATUS_DSR)
val |= TIOCM_DSR;
if (uf->uf_msr & FTDI_MSR_STATUS_RI)
val |= TIOCM_RI;
if (uf->uf_msr & FTDI_MSR_STATUS_RLSD)
val |= TIOCM_CD;
/*
* 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.
*/
if ((uf->uf_mctl & FTDI_SIO_SET_RTS_HIGH) == FTDI_SIO_SET_RTS_HIGH)
val |= TIOCM_RTS;
if ((uf->uf_mctl & FTDI_SIO_SET_DTR_HIGH) == FTDI_SIO_SET_DTR_HIGH)
val |= TIOCM_DTR;
mutex_exit(&uf->uf_lock);
*valp = val & mask;
return (USB_SUCCESS);
}
/*
* ds_break_ctl
*/
static int
uftdi_break_ctl(ds_hdl_t hdl, uint_t portno, int ctl)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
uftdi_regs_t *ur = &uf->uf_softr;
uint16_t data;
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh, "uftdi_break_ctl");
mutex_enter(&uf->uf_lock);
data = ur->ur_data | (ctl == DS_ON) ? FTDI_SIO_SET_BREAK : 0;
mutex_exit(&uf->uf_lock);
return (uftdi_cmd_vendor_write0(uf, FTDI_SIO_SET_DATA,
data, portno));
}
/*
* ds_tx
*/
/*ARGSUSED*/
static int
uftdi_tx(ds_hdl_t hdl, uint_t portno, mblk_t *mp)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh, "uftdi_tx");
ASSERT(mp != NULL && MBLKL(mp) >= 1);
mutex_enter(&uf->uf_lock);
uftdi_put_tail(&uf->uf_tx_mp, mp); /* add to the chain */
uftdi_tx_start(uf, NULL);
mutex_exit(&uf->uf_lock);
return (USB_SUCCESS);
}
/*
* ds_rx
*/
/*ARGSUSED*/
static mblk_t *
uftdi_rx(ds_hdl_t hdl, uint_t portno)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
mblk_t *mp;
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh, "uftdi_rx");
mutex_enter(&uf->uf_lock);
mp = uf->uf_rx_mp;
uf->uf_rx_mp = NULL;
mutex_exit(&uf->uf_lock);
return (mp);
}
/*
* ds_stop
*/
/*ARGSUSED*/
static void
uftdi_stop(ds_hdl_t hdl, uint_t portno, int dir)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh, "uftdi_stop");
if (dir & DS_TX) {
mutex_enter(&uf->uf_lock);
uf->uf_port_flags |= UFTDI_PORT_TX_STOPPED;
mutex_exit(&uf->uf_lock);
}
}
/*
* ds_start
*/
/*ARGSUSED*/
static void
uftdi_start(ds_hdl_t hdl, uint_t portno, int dir)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh, "uftdi_start");
if (dir & DS_TX) {
mutex_enter(&uf->uf_lock);
if (uf->uf_port_flags & UFTDI_PORT_TX_STOPPED) {
uf->uf_port_flags &= ~UFTDI_PORT_TX_STOPPED;
uftdi_tx_start(uf, NULL);
}
mutex_exit(&uf->uf_lock);
}
}
/*
* ds_fifo_flush
*/
/*ARGSUSED*/
static int
uftdi_fifo_flush(ds_hdl_t hdl, uint_t portno, int dir)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh,
"uftdi_fifo_flush: dir=0x%x", dir);
mutex_enter(&uf->uf_lock);
ASSERT(uf->uf_port_state == UFTDI_PORT_OPEN);
if (dir & DS_TX) {
freemsg(uf->uf_tx_mp);
uf->uf_tx_mp = NULL;
}
if (dir & DS_RX) {
freemsg(uf->uf_rx_mp);
uf->uf_rx_mp = NULL;
}
mutex_exit(&uf->uf_lock);
if (dir & DS_TX)
(void) uftdi_cmd_vendor_write0(uf,
FTDI_SIO_RESET, FTDI_SIO_RESET_PURGE_TX, portno);
if (dir & DS_RX)
(void) uftdi_cmd_vendor_write0(uf,
FTDI_SIO_RESET, FTDI_SIO_RESET_PURGE_RX, portno);
return (USB_SUCCESS);
}
/*
* ds_fifo_drain
*/
/*ARGSUSED*/
static int
uftdi_fifo_drain(ds_hdl_t hdl, uint_t portno, int timeout)
{
uftdi_state_t *uf = (uftdi_state_t *)hdl;
int rval = USB_SUCCESS;
USB_DPRINTF_L4(DPRINT_CTLOP, uf->uf_lh, "uftdi_fifo_drain");
mutex_enter(&uf->uf_lock);
ASSERT(uf->uf_port_state == UFTDI_PORT_OPEN);
if (uftdi_wait_tx_drain(uf, 0) != USB_SUCCESS) {
mutex_exit(&uf->uf_lock);
return (USB_FAILURE);
}
mutex_exit(&uf->uf_lock);
/* wait 500 ms until hw fifo drains */
delay(drv_usectohz(500*1000));
return (rval);
}
/*
* configuration clean up
*/
static void
uftdi_cleanup(uftdi_state_t *uf, int level)
{
ASSERT(level > 0 && level <= UFTDI_CLEANUP_LEVEL_MAX);
switch (level) {
default:
case 6:
uftdi_close_pipes(uf);
/*FALLTHROUGH*/
case 5:
usb_unregister_event_cbs(uf->uf_dip, uf->uf_usb_events);
/*FALLTHROUGH*/
case 4:
uftdi_destroy_pm_components(uf);
/*FALLTHROUGH*/
case 3:
mutex_destroy(&uf->uf_lock);
cv_destroy(&uf->uf_tx_cv);
usb_free_log_hdl(uf->uf_lh);
uf->uf_lh = NULL;
usb_free_descr_tree(uf->uf_dip, uf->uf_dev_data);
uf->uf_def_ph = NULL;
/*FALLTHROUGH*/
case 2:
usb_client_detach(uf->uf_dip, uf->uf_dev_data);
/*FALLTHROUGH*/
case 1:
kmem_free(uf, sizeof (*uf));
break;
}
}
/*
* device specific attach
*/
static int
uftdi_dev_attach(uftdi_state_t *uf)
{
return (uftdi_open_pipes(uf));
}
/*
* restore device state after CPR resume or reconnect
*/
static int
uftdi_restore_device_state(uftdi_state_t *uf)
{
int state;
mutex_enter(&uf->uf_lock);
state = uf->uf_dev_state;
mutex_exit(&uf->uf_lock);
if (state != USB_DEV_DISCONNECTED && state != USB_DEV_SUSPENDED)
return (state);
if (usb_check_same_device(uf->uf_dip, uf->uf_lh, USB_LOG_L0,
DPRINT_MASK_ALL, USB_CHK_ALL, NULL) != USB_SUCCESS) {
mutex_enter(&uf->uf_lock);
state = uf->uf_dev_state = USB_DEV_DISCONNECTED;
mutex_exit(&uf->uf_lock);
return (state);
}
if (state == USB_DEV_DISCONNECTED) {
USB_DPRINTF_L0(DPRINT_HOTPLUG, uf->uf_lh,
"Device has been reconnected but data may have been lost");
}
if (uftdi_reconnect_pipes(uf) != USB_SUCCESS)
return (state);
/*
* init device state
*/
mutex_enter(&uf->uf_lock);
state = uf->uf_dev_state = USB_DEV_ONLINE;
mutex_exit(&uf->uf_lock);
if ((uftdi_restore_port_state(uf, 0) != USB_SUCCESS)) {
USB_DPRINTF_L2(DPRINT_HOTPLUG, uf->uf_lh,
"uftdi_restore_device_state: failed");
}
return (state);
}
/*
* restore ports state after CPR resume or reconnect
*/
static int
uftdi_restore_port_state(uftdi_state_t *uf, int portno)
{
int rval;
mutex_enter(&uf->uf_lock);
if (uf->uf_port_state != UFTDI_PORT_OPEN) {
mutex_exit(&uf->uf_lock);
return (USB_SUCCESS);
}
mutex_exit(&uf->uf_lock);
/* open hardware serial port, restoring old settings */
if ((rval = uftdi_open_hw_port(uf, portno, 1)) != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_HOTPLUG, uf->uf_lh,
"uftdi_restore_port_state: failed");
}
return (rval);
}
/*
* create PM components
*/
static int
uftdi_create_pm_components(uftdi_state_t *uf)
{
dev_info_t *dip = uf->uf_dip;
uftdi_pm_t *pm;
uint_t pwr_states;
if (usb_create_pm_components(dip, &pwr_states) != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_PM, uf->uf_lh,
"uftdi_create_pm_components: failed");
return (USB_SUCCESS);
}
pm = uf->uf_pm = kmem_zalloc(sizeof (*pm), KM_SLEEP);
pm->pm_pwr_states = (uint8_t)pwr_states;
pm->pm_cur_power = USB_DEV_OS_FULL_PWR;
pm->pm_wakeup_enabled = usb_handle_remote_wakeup(dip,
USB_REMOTE_WAKEUP_ENABLE) == USB_SUCCESS;
(void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
return (USB_SUCCESS);
}
/*
* destroy PM components
*/
static void
uftdi_destroy_pm_components(uftdi_state_t *uf)
{
uftdi_pm_t *pm = uf->uf_pm;
dev_info_t *dip = uf->uf_dip;
int rval;
if (!pm)
return;
if (uf->uf_dev_state != USB_DEV_DISCONNECTED) {
if (pm->pm_wakeup_enabled) {
rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
if (rval != DDI_SUCCESS) {
USB_DPRINTF_L2(DPRINT_PM, uf->uf_lh,
"uftdi_destroy_pm_components: "
"raising power failed, rval=%d", rval);
}
rval = usb_handle_remote_wakeup(dip,
USB_REMOTE_WAKEUP_DISABLE);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_PM, uf->uf_lh,
"uftdi_destroy_pm_components: disable "
"remote wakeup failed, rval=%d", rval);
}
}
(void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF);
}
kmem_free(pm, sizeof (*pm));
uf->uf_pm = NULL;
}
/*
* mark device busy and raise power
*/
static int
uftdi_pm_set_busy(uftdi_state_t *uf)
{
uftdi_pm_t *pm = uf->uf_pm;
dev_info_t *dip = uf->uf_dip;
int rval;
USB_DPRINTF_L4(DPRINT_PM, uf->uf_lh, "uftdi_pm_set_busy");
if (!pm)
return (USB_SUCCESS);
mutex_enter(&uf->uf_lock);
/* if already marked busy, just increment the counter */
if (pm->pm_busy_cnt++ > 0) {
mutex_exit(&uf->uf_lock);
return (USB_SUCCESS);
}
rval = pm_busy_component(dip, 0);
ASSERT(rval == DDI_SUCCESS);
if (pm->pm_cur_power == USB_DEV_OS_FULL_PWR) {
mutex_exit(&uf->uf_lock);
return (USB_SUCCESS);
}
/* need to raise power */
pm->pm_raise_power = B_TRUE;
mutex_exit(&uf->uf_lock);
rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
if (rval != DDI_SUCCESS) {
USB_DPRINTF_L2(DPRINT_PM, uf->uf_lh, "raising power failed");
}
mutex_enter(&uf->uf_lock);
pm->pm_raise_power = B_FALSE;
mutex_exit(&uf->uf_lock);
return (USB_SUCCESS);
}
/*
* mark device idle
*/
static void
uftdi_pm_set_idle(uftdi_state_t *uf)
{
uftdi_pm_t *pm = uf->uf_pm;
dev_info_t *dip = uf->uf_dip;
USB_DPRINTF_L4(DPRINT_PM, uf->uf_lh, "uftdi_pm_set_idle");
if (!pm)
return;
/*
* if more ports use the device, do not mark as yet
*/
mutex_enter(&uf->uf_lock);
if (--pm->pm_busy_cnt > 0) {
mutex_exit(&uf->uf_lock);
return;
}
(void) pm_idle_component(dip, 0);
mutex_exit(&uf->uf_lock);
}
/*
* Functions to handle power transition for OS levels 0 -> 3
* The same level as OS state, different from USB state
*/
static int
uftdi_pwrlvl0(uftdi_state_t *uf)
{
int rval;
USB_DPRINTF_L4(DPRINT_PM, uf->uf_lh, "uftdi_pwrlvl0");
switch (uf->uf_dev_state) {
case USB_DEV_ONLINE:
/* issue USB D3 command to the device */
rval = usb_set_device_pwrlvl3(uf->uf_dip);
ASSERT(rval == USB_SUCCESS);
uf->uf_dev_state = USB_DEV_PWRED_DOWN;
uf->uf_pm->pm_cur_power = USB_DEV_OS_PWR_OFF;
/*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:
USB_DPRINTF_L2(DPRINT_PM, uf->uf_lh,
"uftdi_pwrlvl0: illegal device state");
return (USB_FAILURE);
}
}
static int
uftdi_pwrlvl1(uftdi_state_t *uf)
{
USB_DPRINTF_L4(DPRINT_PM, uf->uf_lh, "uftdi_pwrlvl1");
/* issue USB D2 command to the device */
(void) usb_set_device_pwrlvl2(uf->uf_dip);
return (USB_FAILURE);
}
static int
uftdi_pwrlvl2(uftdi_state_t *uf)
{
USB_DPRINTF_L4(DPRINT_PM, uf->uf_lh, "uftdi_pwrlvl2");
/* issue USB D1 command to the device */
(void) usb_set_device_pwrlvl1(uf->uf_dip);
return (USB_FAILURE);
}
static int
uftdi_pwrlvl3(uftdi_state_t *uf)
{
int rval;
USB_DPRINTF_L4(DPRINT_PM, uf->uf_lh, "uftdi_pwrlvl3");
switch (uf->uf_dev_state) {
case USB_DEV_PWRED_DOWN:
/* Issue USB D0 command to the device here */
rval = usb_set_device_pwrlvl0(uf->uf_dip);
ASSERT(rval == USB_SUCCESS);
uf->uf_dev_state = USB_DEV_ONLINE;
uf->uf_pm->pm_cur_power = USB_DEV_OS_FULL_PWR;
/*FALLTHROUGH*/
case USB_DEV_ONLINE:
/* we are already in full power */
/*FALLTHROUGH*/
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
return (USB_SUCCESS);
default:
USB_DPRINTF_L2(DPRINT_PM, uf->uf_lh,
"uftdi_pwrlvl3: illegal device state");
return (USB_FAILURE);
}
}
/*
* pipe operations
*/
static int
uftdi_open_pipes(uftdi_state_t *uf)
{
int ifc, alt;
usb_pipe_policy_t policy;
usb_ep_data_t *in_data, *out_data;
/* get ep data */
ifc = uf->uf_dev_data->dev_curr_if;
alt = 0;
in_data = usb_lookup_ep_data(uf->uf_dip, uf->uf_dev_data, ifc, alt,
0, USB_EP_ATTR_BULK, USB_EP_DIR_IN);
out_data = usb_lookup_ep_data(uf->uf_dip, uf->uf_dev_data, ifc, alt,
0, USB_EP_ATTR_BULK, USB_EP_DIR_OUT);
if (in_data == NULL || out_data == NULL) {
USB_DPRINTF_L2(DPRINT_ATTACH, uf->uf_lh,
"uftdi_open_pipes: can't get ep data");
return (USB_FAILURE);
}
/* open pipes */
policy.pp_max_async_reqs = 2;
if (usb_pipe_open(uf->uf_dip, &in_data->ep_descr, &policy,
USB_FLAGS_SLEEP, &uf->uf_bulkin_ph) != USB_SUCCESS)
return (USB_FAILURE);
if (usb_pipe_open(uf->uf_dip, &out_data->ep_descr, &policy,
USB_FLAGS_SLEEP, &uf->uf_bulkout_ph) != USB_SUCCESS) {
usb_pipe_close(uf->uf_dip, uf->uf_bulkin_ph, USB_FLAGS_SLEEP,
NULL, NULL);
return (USB_FAILURE);
}
mutex_enter(&uf->uf_lock);
uf->uf_bulkin_state = UFTDI_PIPE_IDLE;
uf->uf_bulkout_state = UFTDI_PIPE_IDLE;
mutex_exit(&uf->uf_lock);
return (USB_SUCCESS);
}
static void
uftdi_close_pipes(uftdi_state_t *uf)
{
if (uf->uf_bulkin_ph)
usb_pipe_close(uf->uf_dip, uf->uf_bulkin_ph,
USB_FLAGS_SLEEP, 0, 0);
if (uf->uf_bulkout_ph)
usb_pipe_close(uf->uf_dip, uf->uf_bulkout_ph,
USB_FLAGS_SLEEP, 0, 0);
mutex_enter(&uf->uf_lock);
uf->uf_bulkin_state = UFTDI_PIPE_CLOSED;
uf->uf_bulkout_state = UFTDI_PIPE_CLOSED;
mutex_exit(&uf->uf_lock);
}
static void
uftdi_disconnect_pipes(uftdi_state_t *uf)
{
uftdi_close_pipes(uf);
}
static int
uftdi_reconnect_pipes(uftdi_state_t *uf)
{
return (uftdi_open_pipes(uf));
}
/*
* bulk in pipe normal and exception callback handler
*/
/*ARGSUSED*/
static void
uftdi_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
uftdi_state_t *uf = (uftdi_state_t *)req->bulk_client_private;
mblk_t *data;
int data_len;
int notify = 0;
data = req->bulk_data;
data_len = data ? MBLKL(data) : 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 ..
*/
if (req->bulk_completion_reason == USB_CR_OK && data_len >= 2) {
uint8_t msr = FTDI_GET_MSR(data->b_rptr);
uint8_t lsr = FTDI_GET_LSR(data->b_rptr);
mutex_enter(&uf->uf_lock);
if (uf->uf_msr != msr ||
(uf->uf_lsr & FTDI_LSR_MASK) != (lsr & FTDI_LSR_MASK)) {
USB_DPRINTF_L3(DPRINT_IN_PIPE, uf->uf_lh,
"uftdi_bulkin_cb: status change "
"0x%02x.0x%02x, was 0x%02x.0x%02x",
msr, lsr, uf->uf_msr, uf->uf_lsr);
uf->uf_msr = msr;
uf->uf_lsr = lsr;
/*
* If we're waiting for a modem status change,
* sending an empty message will cause us to
* reexamine the modem flags.
*/
notify = 1;
}
mutex_exit(&uf->uf_lock);
data_len -= 2;
data->b_rptr += 2;
}
notify |= (data_len > 0);
USB_DPRINTF_L4(DPRINT_IN_PIPE, uf->uf_lh, "uftdi_bulkin_cb: "
"cr=%d len=%d", req->bulk_completion_reason, data_len);
/* save data and notify GSD */
if (notify && uf->uf_port_state == UFTDI_PORT_OPEN &&
req->bulk_completion_reason == USB_CR_OK) {
req->bulk_data = NULL;
uftdi_put_tail(&uf->uf_rx_mp, data);
if (uf->uf_cb.cb_rx)
uf->uf_cb.cb_rx(uf->uf_cb.cb_arg);
}
usb_free_bulk_req(req);
/* receive more */
mutex_enter(&uf->uf_lock);
uf->uf_bulkin_state = UFTDI_PIPE_IDLE;
if (uf->uf_port_state == UFTDI_PORT_OPEN &&
uf->uf_dev_state == USB_DEV_ONLINE) {
if (uftdi_rx_start(uf) != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_IN_PIPE, uf->uf_lh,
"uftdi_bulkin_cb: restart rx fail");
}
}
mutex_exit(&uf->uf_lock);
}
/*
* bulk out common and exception callback
*/
/*ARGSUSED*/
static void
uftdi_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
uftdi_state_t *uf = (uftdi_state_t *)req->bulk_client_private;
int data_len;
mblk_t *data = req->bulk_data;
data_len = data ? MBLKL(data) : 0;
USB_DPRINTF_L4(DPRINT_OUT_PIPE, uf->uf_lh,
"uftdi_bulkout_cb: cr=%d len=%d",
req->bulk_completion_reason, data_len);
if (uf->uf_port_state == UFTDI_PORT_OPEN &&
req->bulk_completion_reason && data_len > 0) {
uftdi_put_head(&uf->uf_tx_mp, data);
req->bulk_data = NULL;
}
usb_free_bulk_req(req);
/* notify GSD */
if (uf->uf_cb.cb_tx)
uf->uf_cb.cb_tx(uf->uf_cb.cb_arg);
/* send more */
mutex_enter(&uf->uf_lock);
uf->uf_bulkout_state = UFTDI_PIPE_IDLE;
if (uf->uf_tx_mp == NULL)
cv_broadcast(&uf->uf_tx_cv);
else
uftdi_tx_start(uf, NULL);
mutex_exit(&uf->uf_lock);
}
/*
* start receiving data
*/
static int
uftdi_rx_start(uftdi_state_t *uf)
{
usb_bulk_req_t *br;
int rval;
USB_DPRINTF_L4(DPRINT_OUT_PIPE, uf->uf_lh, "uftdi_rx_start");
ASSERT(mutex_owned(&uf->uf_lock));
uf->uf_bulkin_state = UFTDI_PIPE_BUSY;
mutex_exit(&uf->uf_lock);
br = usb_alloc_bulk_req(uf->uf_dip, uf->uf_xfer_sz, USB_FLAGS_SLEEP);
br->bulk_len = uf->uf_xfer_sz;
br->bulk_timeout = UFTDI_BULKIN_TIMEOUT;
br->bulk_cb = uftdi_bulkin_cb;
br->bulk_exc_cb = uftdi_bulkin_cb;
br->bulk_client_private = (usb_opaque_t)uf;
br->bulk_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_SHORT_XFER_OK;
rval = usb_pipe_bulk_xfer(uf->uf_bulkin_ph, br, 0);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_IN_PIPE, uf->uf_lh,
"uftdi_rx_start: xfer failed %d", rval);
usb_free_bulk_req(br);
}
mutex_enter(&uf->uf_lock);
if (rval != USB_SUCCESS)
uf->uf_bulkin_state = UFTDI_PIPE_IDLE;
return (rval);
}
/*
* start data transmit
*/
static void
uftdi_tx_start(uftdi_state_t *uf, int *xferd)
{
int len; /* bytes we can transmit */
mblk_t *data; /* data to be transmitted */
int data_len; /* bytes in 'data' */
mblk_t *mp; /* current msgblk */
int copylen; /* bytes copy from 'mp' to 'data' */
int rval;
USB_DPRINTF_L4(DPRINT_OUT_PIPE, uf->uf_lh, "uftdi_tx_start");
ASSERT(mutex_owned(&uf->uf_lock));
ASSERT(uf->uf_port_state != UFTDI_PORT_CLOSED);
if (xferd)
*xferd = 0;
if ((uf->uf_port_flags & UFTDI_PORT_TX_STOPPED) ||
uf->uf_tx_mp == NULL) {
return;
}
if (uf->uf_bulkout_state != UFTDI_PIPE_IDLE) {
USB_DPRINTF_L4(DPRINT_OUT_PIPE, uf->uf_lh,
"uftdi_tx_start: pipe busy");
return;
}
ASSERT(MBLKL(uf->uf_tx_mp) > 0);
/* send as much data as port can receive */
len = min(msgdsize(uf->uf_tx_mp), uf->uf_xfer_sz);
if (len <= 0)
return;
if ((data = allocb(len, BPRI_LO)) == NULL)
return;
/*
* copy no more than 'len' bytes from mblk chain to transmit mblk 'data'
*/
data_len = 0;
while (data_len < len && uf->uf_tx_mp) {
mp = uf->uf_tx_mp;
copylen = min(MBLKL(mp), len - data_len);
bcopy(mp->b_rptr, data->b_wptr, copylen);
mp->b_rptr += copylen;
data->b_wptr += copylen;
data_len += copylen;
if (MBLKL(mp) < 1) {
uf->uf_tx_mp = unlinkb(mp);
freeb(mp);
} else {
ASSERT(data_len == len);
}
}
ASSERT(data_len > 0);
uf->uf_bulkout_state = UFTDI_PIPE_BUSY;
mutex_exit(&uf->uf_lock);
rval = uftdi_send_data(uf, data);
mutex_enter(&uf->uf_lock);
if (rval != USB_SUCCESS) {
uf->uf_bulkout_state = UFTDI_PIPE_IDLE;
uftdi_put_head(&uf->uf_tx_mp, data);
} else {
if (xferd)
*xferd = data_len;
}
}
static int
uftdi_send_data(uftdi_state_t *uf, mblk_t *data)
{
usb_bulk_req_t *br;
int len = MBLKL(data);
int rval;
USB_DPRINTF_L4(DPRINT_OUT_PIPE, uf->uf_lh,
"uftdi_send_data: %d 0x%x 0x%x 0x%x", len, data->b_rptr[0],
(len > 1) ? data->b_rptr[1] : 0, (len > 2) ? data->b_rptr[2] : 0);
ASSERT(!mutex_owned(&uf->uf_lock));
br = usb_alloc_bulk_req(uf->uf_dip, 0, USB_FLAGS_SLEEP);
br->bulk_data = data;
br->bulk_len = len;
br->bulk_timeout = UFTDI_BULKOUT_TIMEOUT;
br->bulk_cb = uftdi_bulkout_cb;
br->bulk_exc_cb = uftdi_bulkout_cb;
br->bulk_client_private = (usb_opaque_t)uf;
br->bulk_attributes = USB_ATTRS_AUTOCLEARING;
rval = usb_pipe_bulk_xfer(uf->uf_bulkout_ph, br, 0);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_OUT_PIPE, uf->uf_lh,
"uftdi_send_data: xfer failed %d", rval);
br->bulk_data = NULL;
usb_free_bulk_req(br);
}
return (rval);
}
/*
* wait until local tx buffer drains.
* 'timeout' is in seconds, zero means wait forever
*/
static int
uftdi_wait_tx_drain(uftdi_state_t *uf, int timeout)
{
clock_t until;
int over = 0;
until = ddi_get_lbolt() + drv_usectohz(1000 * 1000 * timeout);
while (uf->uf_tx_mp && !over) {
if (timeout > 0) {
/* whether timedout or signal pending */
over = cv_timedwait_sig(&uf->uf_tx_cv,
&uf->uf_lock, until) <= 0;
} else {
/* whether a signal is pending */
over = cv_wait_sig(&uf->uf_tx_cv,
&uf->uf_lock) == 0;
}
}
return (uf->uf_tx_mp == NULL ? USB_SUCCESS : USB_FAILURE);
}
/*
* initialize hardware serial port
*/
static int
uftdi_open_hw_port(uftdi_state_t *uf, int portno, int dorestore)
{
int rval;
/*
* Perform a full reset on the device
*/
rval = uftdi_cmd_vendor_write0(uf,
FTDI_SIO_RESET, FTDI_SIO_RESET_SIO, portno);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_DEF_PIPE, uf->uf_lh,
"uftdi_open_hw_port: failed to reset!");
return (rval);
}
if (dorestore) {
/*
* Restore settings from our soft copy of HW registers
*/
(void) uftdi_setregs(uf, portno, NULL);
} 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
{ DS_PARAM_BAUD, .val.ui = B9600 },
{ DS_PARAM_STOPB, .val.ui = CSTOPB },
{ DS_PARAM_PARITY, .val.ui = 0 },
{ DS_PARAM_CHARSZ, .val.ui = CS8 },
{ DS_PARAM_FLOW_CTL, .val.ui = CTSXON }
#endif
};
static ds_port_params_t params = {
ents,
sizeof (ents) / sizeof (ents[0])
};
rval = uftdi_set_port_params(uf, portno, &params);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_DEF_PIPE, uf->uf_lh,
"uftdi_open_hw_port: failed 9600/2/n/8 rval %d",
rval);
}
}
return (rval);
}
static int
uftdi_cmd_vendor_write0(uftdi_state_t *uf,
uint16_t reqno, uint16_t val, uint16_t idx)
{
usb_ctrl_setup_t req;
usb_cb_flags_t cb_flags;
usb_cr_t cr;
int rval;
ASSERT(!mutex_owned(&uf->uf_lock));
req.bmRequestType =
USB_DEV_REQ_TYPE_VENDOR | USB_DEV_REQ_HOST_TO_DEV;
req.bRequest = (uchar_t)reqno;
req.wValue = val;
req.wIndex = idx;
req.wLength = 0;
req.attrs = USB_ATTRS_NONE;
if ((rval = usb_pipe_ctrl_xfer_wait(uf->uf_def_ph,
&req, NULL, &cr, &cb_flags, 0)) != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_DEF_PIPE, uf->uf_lh,
"uftdi_cmd_vendor_write0: 0x%x 0x%x 0x%x failed %d %d 0x%x",
reqno, val, idx, rval, cr, cb_flags);
}
return (rval);
}
/*
* misc routines
*/
/*
* link a message block to tail of message
* account for the case when message is null
*/
static void
uftdi_put_tail(mblk_t **mpp, mblk_t *bp)
{
if (*mpp)
linkb(*mpp, bp);
else
*mpp = bp;
}
/*
* put a message block at the head of the message
* account for the case when message is null
*/
static void
uftdi_put_head(mblk_t **mpp, mblk_t *bp)
{
if (*mpp)
linkb(bp, *mpp);
*mpp = bp;
}
/*ARGSUSED*/
static usb_pipe_handle_t
uftdi_out_pipe(ds_hdl_t hdl, uint_t portno)
{
return (((uftdi_state_t *)hdl)->uf_bulkout_ph);
}
/*ARGSUSED*/
static usb_pipe_handle_t
uftdi_in_pipe(ds_hdl_t hdl, uint_t portno)
{
return (((uftdi_state_t *)hdl)->uf_bulkin_ph);
}