pl2303_dsd.c revision d291d9f21e8c0417aec99de243dd48bc400002d0
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
*
* USB Prolific PL2303 device-specific driver (DSD)
*
*/
#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/usbsprl/pl2303_var.h>
#include <sys/usb/clients/usbser/usbsprl/pl2303_vendor.h>
/*
* DSD operations
*/
static int pl2303_attach(ds_attach_info_t *);
static void pl2303_detach(ds_hdl_t);
static int pl2303_register_cb(ds_hdl_t, uint_t, ds_cb_t *);
static void pl2303_unregister_cb(ds_hdl_t, uint_t);
static int pl2303_open_port(ds_hdl_t, uint_t);
static int pl2303_close_port(ds_hdl_t, uint_t);
/* power management */
static int pl2303_usb_power(ds_hdl_t, int, int, int *);
static int pl2303_suspend(ds_hdl_t);
static int pl2303_resume(ds_hdl_t);
static int pl2303_disconnect(ds_hdl_t);
static int pl2303_reconnect(ds_hdl_t);
/* standard UART operations */
static int pl2303_set_port_params(ds_hdl_t, uint_t, ds_port_params_t *);
static int pl2303_set_modem_ctl(ds_hdl_t, uint_t, int, int);
static int pl2303_get_modem_ctl(ds_hdl_t, uint_t, int, int *);
static int pl2303_break_ctl(ds_hdl_t, uint_t, int);
/* data xfer */
static int pl2303_tx(ds_hdl_t, uint_t, mblk_t *);
static mblk_t *pl2303_rx(ds_hdl_t, uint_t);
static void pl2303_stop(ds_hdl_t, uint_t, int);
static void pl2303_start(ds_hdl_t, uint_t, int);
static int pl2303_fifo_flush(ds_hdl_t, uint_t, int);
static int pl2303_fifo_drain(ds_hdl_t, uint_t, int);
/*
* Sub-routines
*/
/* configuration routines */
static void pl2303_cleanup(pl2303_state_t *, int);
static int pl2303_dev_attach(pl2303_state_t *);
static int pl2303_open_hw_port(pl2303_state_t *);
/* hotplug */
static int pl2303_restore_device_state(pl2303_state_t *);
static int pl2303_restore_port_state(pl2303_state_t *);
/* power management */
static int pl2303_create_pm_components(pl2303_state_t *);
static void pl2303_destroy_pm_components(pl2303_state_t *);
static int pl2303_pm_set_busy(pl2303_state_t *);
static void pl2303_pm_set_idle(pl2303_state_t *);
static int pl2303_pwrlvl0(pl2303_state_t *);
static int pl2303_pwrlvl1(pl2303_state_t *);
static int pl2303_pwrlvl2(pl2303_state_t *);
static int pl2303_pwrlvl3(pl2303_state_t *);
/* pipe operations */
static int pl2303_open_pipes(pl2303_state_t *);
static void pl2303_close_pipes(pl2303_state_t *);
static void pl2303_disconnect_pipes(pl2303_state_t *);
static int pl2303_reconnect_pipes(pl2303_state_t *);
/* pipe callbacks */
void pl2303_bulkin_cb(usb_pipe_handle_t, usb_bulk_req_t *);
void pl2303_bulkout_cb(usb_pipe_handle_t, usb_bulk_req_t *);
/* data transfer routines */
static int pl2303_rx_start(pl2303_state_t *);
static void pl2303_tx_start(pl2303_state_t *, int *);
static int pl2303_send_data(pl2303_state_t *, mblk_t *);
static int pl2303_wait_tx_drain(pl2303_state_t *, int);
/* vendor-specific commands */
static int pl2303_cmd_get_line(pl2303_state_t *, mblk_t **);
static int pl2303_cmd_set_line(pl2303_state_t *, mblk_t *);
static int pl2303_cmd_set_ctl(pl2303_state_t *, uint8_t);
static int pl2303_cmd_vendor_write0(pl2303_state_t *, uint16_t, int16_t);
static int pl2303_cmd_set_rtscts(pl2303_state_t *);
static int pl2303_cmd_break(pl2303_state_t *, int);
static void pl2303_mctl2reg(int mask, int val, uint8_t *);
static int pl2303_reg2mctl(uint8_t);
/* misc */
static void pl2303_put_tail(mblk_t **, mblk_t *);
static void pl2303_put_head(mblk_t **, mblk_t *);
/*
* DSD ops structure
*/
ds_ops_t ds_ops = {
DS_OPS_VERSION,
pl2303_attach,
pl2303_detach,
pl2303_register_cb,
pl2303_unregister_cb,
pl2303_open_port,
pl2303_close_port,
pl2303_usb_power,
pl2303_suspend,
pl2303_resume,
pl2303_disconnect,
pl2303_reconnect,
pl2303_set_port_params,
pl2303_set_modem_ctl,
pl2303_get_modem_ctl,
pl2303_break_ctl,
NULL, /* HW don't support loopback */
pl2303_tx,
pl2303_rx,
pl2303_stop,
pl2303_start,
pl2303_fifo_flush,
pl2303_fifo_drain
};
/*
* baud code into baud rate
* value 0 means not supported in hardware
*
*/
static int pl2303_speedtab[] = {
0, /* B0 */
0, /* B50 */
75, /* B75 */
0, /* B110 */
0, /* B134 */
150, /* B150 */
0, /* B200 */
300, /* B300 */
600, /* B600 */
1200, /* B1200 */
1800, /* B1800 */
2400, /* B2400 */
4800, /* B4800 */
9600, /* B9600 */
19200, /* B19200 */
38400, /* B38400 */
57600, /* B57600 */
0, /* B76800 */
115200, /* B115200 */
0, /* B153600 */
230400, /* B230400 */
0, /* B307200 */
460800 /* B460800 */
};
/* debug support */
static uint_t pl2303_errlevel = USB_LOG_L4;
static uint_t pl2303_errmask = DPRINT_MASK_ALL;
static uint_t pl2303_instance_debug = (uint_t)-1;
/*
* ds_attach
*/
static int
pl2303_attach(ds_attach_info_t *aip)
{
pl2303_state_t *plp;
plp = (pl2303_state_t *)kmem_zalloc(sizeof (pl2303_state_t), KM_SLEEP);
plp->pl_dip = aip->ai_dip;
plp->pl_usb_events = aip->ai_usb_events;
*aip->ai_hdl = (ds_hdl_t)plp;
/* only one port */
*aip->ai_port_cnt = 1;
if (usb_client_attach(plp->pl_dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
pl2303_cleanup(plp, 1);
return (USB_FAILURE);
}
if (usb_get_dev_data(plp->pl_dip, &plp->pl_dev_data, USB_PARSE_LVL_IF,
0) != USB_SUCCESS) {
pl2303_cleanup(plp, 2);
return (USB_FAILURE);
}
/*
* check the chip type: pl2303_H, pl2303_X (or pl2303_HX)
* pl2303_UNKNOWN means not supported chip type
*/
if (plp->pl_dev_data->dev_descr->bcdDevice == PROLIFIC_REV_H) {
mutex_enter(&plp->pl_mutex);
plp->pl_chiptype = pl2303_H;
mutex_exit(&plp->pl_mutex);
USB_DPRINTF_L4(DPRINT_ATTACH, plp->pl_lh,
"Chip Type: pl2303_H");
} else if (plp->pl_dev_data->dev_descr->bcdDevice == PROLIFIC_REV_X) {
/*
* pl2303_HX and pl2303_X devices have different hardware,
* but from the view of device driver, they have the same
* software interface.
*
* so "pl2303_X" will stand for both pl2303_HX and pl2303_X
* devices in this driver
*/
mutex_enter(&plp->pl_mutex);
plp->pl_chiptype = pl2303_X;
mutex_exit(&plp->pl_mutex);
USB_DPRINTF_L4(DPRINT_ATTACH, plp->pl_lh,
"Chip Type: pl2303_HX or pl2303_X");
} else {
mutex_enter(&plp->pl_mutex);
plp->pl_chiptype = pl2303_UNKNOWN;
mutex_exit(&plp->pl_mutex);
USB_DPRINTF_L4(DPRINT_ATTACH, plp->pl_lh,
"Chip Type: Unknown");
}
plp->pl_lh = usb_alloc_log_hdl(plp->pl_dip, "pl2303",
&pl2303_errlevel, &pl2303_errmask, &pl2303_instance_debug, 0);
plp->pl_def_ph = plp->pl_dev_data->dev_default_ph;
mutex_init(&plp->pl_mutex, NULL, MUTEX_DRIVER,
plp->pl_dev_data->dev_iblock_cookie);
cv_init(&plp->pl_tx_cv, NULL, CV_DRIVER, NULL);
cv_init(&plp->pl_rx_cv, NULL, CV_DRIVER, NULL);
mutex_enter(&plp->pl_mutex);
plp->pl_dev_state = USB_DEV_ONLINE;
plp->pl_port_state = PL2303_PORT_CLOSED;
mutex_exit(&plp->pl_mutex);
if (pl2303_create_pm_components(plp) != USB_SUCCESS) {
pl2303_cleanup(plp, 2);
return (USB_FAILURE);
}
if (usb_register_event_cbs(plp->pl_dip, plp->pl_usb_events, 0)
!= USB_SUCCESS) {
pl2303_cleanup(plp, 3);
return (USB_FAILURE);
}
if (usb_pipe_get_max_bulk_transfer_size(plp->pl_dip,
&plp->pl_xfer_sz) != USB_SUCCESS) {
pl2303_cleanup(plp, 4);
return (USB_FAILURE);
}
if (plp->pl_xfer_sz > PL2303_XFER_SZ_MAX) {
plp->pl_xfer_sz = PL2303_XFER_SZ_MAX;
}
if (pl2303_dev_attach(plp) != USB_SUCCESS) {
pl2303_cleanup(plp, 4);
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* ds_detach
*/
static void
pl2303_detach(ds_hdl_t hdl)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
pl2303_cleanup(plp, PL2303_CLEANUP_LEVEL_MAX);
}
/*
* ds_register_cb
*/
/*ARGSUSED*/
static int
pl2303_register_cb(ds_hdl_t hdl, uint_t port_num, ds_cb_t *cb)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
plp->pl_cb = *cb;
return (USB_SUCCESS);
}
/*
* ds_unregister_cb
*/
/*ARGSUSED*/
static void
pl2303_unregister_cb(ds_hdl_t hdl, uint_t port_num)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
bzero(&plp->pl_cb, sizeof (plp->pl_cb));
}
/*
* ds_open_port
*/
/*ARGSUSED*/
static int
pl2303_open_port(ds_hdl_t hdl, uint_t port_num)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
int rval = USB_FAILURE;
USB_DPRINTF_L4(DPRINT_OPEN, plp->pl_lh, "pl2303_open_port");
mutex_enter(&plp->pl_mutex);
if ((plp->pl_dev_state == USB_DEV_DISCONNECTED) ||
(plp->pl_port_state != PL2303_PORT_CLOSED)) {
mutex_exit(&plp->pl_mutex);
return (rval);
}
mutex_exit(&plp->pl_mutex);
if ((rval = pl2303_pm_set_busy(plp)) != USB_SUCCESS) {
return (rval);
}
/* initialize hardware serial port */
rval = pl2303_open_hw_port(plp);
if (rval == USB_SUCCESS) {
mutex_enter(&plp->pl_mutex);
plp->pl_port_state = PL2303_PORT_OPEN;
/* start to receive data */
(void) pl2303_rx_start(plp);
mutex_exit(&plp->pl_mutex);
} else {
pl2303_pm_set_idle(plp);
}
return (rval);
}
/*
* ds_close_port
*/
/*ARGSUSED*/
static int
pl2303_close_port(ds_hdl_t hdl, uint_t port_num)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CLOSE, plp->pl_lh, "pl2303_close_port");
mutex_enter(&plp->pl_mutex);
/* free resources and finalize state */
if (plp->pl_rx_mp) {
freemsg(plp->pl_rx_mp);
plp->pl_rx_mp = NULL;
}
if (plp->pl_tx_mp) {
freemsg(plp->pl_tx_mp);
plp->pl_tx_mp = NULL;
}
plp->pl_port_state = PL2303_PORT_CLOSED;
mutex_exit(&plp->pl_mutex);
pl2303_pm_set_idle(plp);
return (USB_SUCCESS);
}
/*
* power management
* ----------------
*
* ds_usb_power
*/
/*ARGSUSED*/
static int
pl2303_usb_power(ds_hdl_t hdl, int comp, int level, int *new_state)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
pl2303_pm_t *pm = plp->pl_pm;
int rval;
USB_DPRINTF_L4(DPRINT_PM, plp->pl_lh, "pl2303_usb_power");
if (!pm) {
return (USB_FAILURE);
}
mutex_enter(&plp->pl_mutex);
/*
* 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, plp->pl_lh, "pl2303_usb_power: "
"illegal power level %d, pwr_states=%x",
level, pm->pm_pwr_states);
mutex_exit(&plp->pl_mutex);
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(&plp->pl_mutex);
return (USB_FAILURE);
}
switch (level) {
case USB_DEV_OS_PWR_OFF:
rval = pl2303_pwrlvl0(plp);
break;
case USB_DEV_OS_PWR_1:
rval = pl2303_pwrlvl1(plp);
break;
case USB_DEV_OS_PWR_2:
rval = pl2303_pwrlvl2(plp);
break;
case USB_DEV_OS_FULL_PWR:
rval = pl2303_pwrlvl3(plp);
break;
default:
ASSERT(0); /* cannot happen */
}
*new_state = plp->pl_dev_state;
mutex_exit(&plp->pl_mutex);
return (rval);
}
/*
* ds_suspend
*/
static int
pl2303_suspend(ds_hdl_t hdl)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
int state;
USB_DPRINTF_L4(DPRINT_PM, plp->pl_lh, "pl2303_suspend");
mutex_enter(&plp->pl_mutex);
state = plp->pl_dev_state = USB_DEV_SUSPENDED;
mutex_exit(&plp->pl_mutex);
pl2303_disconnect_pipes(plp);
return (state);
}
/*
* ds_resume
*/
static int
pl2303_resume(ds_hdl_t hdl)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
int current_state;
int rval;
USB_DPRINTF_L4(DPRINT_PM, plp->pl_lh, "pl2303_resume");
(void) pm_busy_component(plp->pl_dip, 0);
(void) pm_raise_power(plp->pl_dip, 0, USB_DEV_OS_FULL_PWR);
mutex_enter(&plp->pl_mutex);
current_state = plp->pl_dev_state;
mutex_exit(&plp->pl_mutex);
if (current_state != USB_DEV_ONLINE) {
rval = pl2303_restore_device_state(plp);
} else {
rval = USB_SUCCESS;
}
(void) pm_idle_component(plp->pl_dip, 0);
return (rval);
}
/*
* ds_disconnect
*/
static int
pl2303_disconnect(ds_hdl_t hdl)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
int state;
USB_DPRINTF_L4(DPRINT_HOTPLUG, plp->pl_lh, "pl2303_disconnect");
mutex_enter(&plp->pl_mutex);
state = plp->pl_dev_state = USB_DEV_DISCONNECTED;
mutex_exit(&plp->pl_mutex);
pl2303_disconnect_pipes(plp);
return (state);
}
/*
* ds_reconnect
*/
static int
pl2303_reconnect(ds_hdl_t hdl)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_HOTPLUG, plp->pl_lh, "pl2303_reconnect");
return (pl2303_restore_device_state(plp));
}
/*
* standard UART operations
* ------------------------
*
*
* ds_set_port_params
*/
/*ARGSUSED*/
static int
pl2303_set_port_params(ds_hdl_t hdl, uint_t port_num, ds_port_params_t *tp)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
int rval = USB_FAILURE;
mblk_t *bp;
int i;
uint_t ui;
int baud;
int cnt;
ds_port_param_entry_t *pe;
uint16_t xonxoff_symbol;
uint8_t xon_char;
uint8_t xoff_char;
if (tp == NULL) {
return (rval);
}
cnt = tp->tp_cnt;
pe = tp->tp_entries;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_set_port_params");
/*
* get Line Coding Structure Request
* including: baud rate, stop bit, parity type and data bit
*/
if ((rval = pl2303_cmd_get_line(plp, &bp)) != USB_SUCCESS) {
return (rval);
}
/* translate parameters into device-specific bits */
for (i = 0; i < cnt; i++, pe++) {
switch (pe->param) {
case DS_PARAM_BAUD:
ui = pe->val.ui;
/* if we don't support this speed, return USB_FAILURE */
if ((ui >= NELEM(pl2303_speedtab)) ||
((ui > 0) && (pl2303_speedtab[ui] == 0))) {
USB_DPRINTF_L3(DPRINT_CTLOP, plp->pl_lh,
"pl2303_set_port_params: bad baud %d", ui);
return (USB_FAILURE);
}
baud = pl2303_speedtab[ui];
bp->b_rptr[0] = baud & 0xff;
bp->b_rptr[1] = (baud >> 8) & 0xff;
bp->b_rptr[2] = (baud >> 16) & 0xff;
bp->b_rptr[3] = (baud >> 24) & 0xff;
break;
case DS_PARAM_PARITY:
if (pe->val.ui & PARENB) {
if (pe->val.ui & PARODD) {
bp->b_rptr[5] = 1;
} else {
bp->b_rptr[5] = 2;
}
} else {
bp->b_rptr[5] = 0;
}
break;
case DS_PARAM_STOPB:
if (pe->val.ui & CSTOPB) {
bp->b_rptr[4] = 2;
} else {
bp->b_rptr[4] = 0;
}
break;
case DS_PARAM_CHARSZ:
switch (pe->val.ui) {
case CS5:
bp->b_rptr[6] = 5;
break;
case CS6:
bp->b_rptr[6] = 6;
break;
case CS7:
bp->b_rptr[6] = 7;
break;
case CS8:
default:
bp->b_rptr[6] = 8;
break;
}
break;
case DS_PARAM_XON_XOFF:
/*
* Software flow control: XON/XOFF
* not supported by PL-2303H, HX chips
*/
if (pe->val.ui & IXON || pe->val.ui & IXOFF) {
/* not supported by PL-2303H chip */
switch (plp->pl_chiptype) {
case pl2303_H:
break;
case pl2303_X:
xon_char = pe->val.uc[0];
xoff_char = pe->val.uc[1];
xonxoff_symbol = (xoff_char << 8)
| xon_char;
rval = pl2303_cmd_vendor_write0(
plp, SET_XONXOFF,
xonxoff_symbol);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L3(DPRINT_CTLOP,
plp->pl_lh,
"pl2303_set_port_params: "
"set XonXoff failed");
}
break;
case pl2303_UNKNOWN:
default:
break;
}
}
break;
case DS_PARAM_FLOW_CTL:
/* Hardware flow control */
if (pe->val.ui & CTSXON) {
if ((rval = pl2303_cmd_set_rtscts(plp))
!= USB_SUCCESS) {
USB_DPRINTF_L3(DPRINT_CTLOP,
plp->pl_lh,
"pl2303_set_port_params: "
"pl2303_cmd_set_rtscts failed");
}
}
break;
default:
USB_DPRINTF_L2(DPRINT_CTLOP, plp->pl_lh,
"pl2303_set_port_params: bad param %d", pe->param);
break;
}
}
/* set new values for Line Coding Structure */
if ((rval = pl2303_cmd_set_line(plp, bp)) != USB_SUCCESS) {
return (rval);
}
freeb(bp);
bp = NULL;
/* hardware need to get Line Coding Structure again */
if ((rval = pl2303_cmd_get_line(plp, &bp)) != USB_SUCCESS) {
if (bp != NULL) {
freeb(bp);
}
return (rval);
}
if (bp != NULL) {
freeb(bp);
}
return (USB_SUCCESS);
}
/*
* ds_set_modem_ctl
*/
/*ARGSUSED*/
static int
pl2303_set_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int val)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
int rval = USB_FAILURE;
uint8_t new_mctl;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_set_modem_ctl");
mutex_enter(&plp->pl_mutex);
new_mctl = plp->pl_mctl;
mutex_exit(&plp->pl_mutex);
/* set RTS and DTR */
pl2303_mctl2reg(mask, val, &new_mctl);
if ((rval = pl2303_cmd_set_ctl(plp, new_mctl)) == USB_SUCCESS) {
mutex_enter(&plp->pl_mutex);
plp->pl_mctl = new_mctl;
mutex_exit(&plp->pl_mutex);
}
return (rval);
}
/*
* ds_get_modem_ctl
*/
/*ARGSUSED*/
static int
pl2303_get_modem_ctl(ds_hdl_t hdl, uint_t port_num, int mask, int *valp)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_get_modem_ctl");
mutex_enter(&plp->pl_mutex);
/* get RTS and DTR */
*valp = pl2303_reg2mctl(plp->pl_mctl) & mask;
*valp |= (mask & (TIOCM_CD | TIOCM_CTS | TIOCM_DSR | TIOCM_RI));
mutex_exit(&plp->pl_mutex);
return (USB_SUCCESS);
}
/*
* ds_break_ctl
*/
/*ARGSUSED*/
static int
pl2303_break_ctl(ds_hdl_t hdl, uint_t port_num, int ctl)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_break_ctl");
return (pl2303_cmd_break(plp, ctl));
}
/*
* ds_tx
*/
/*ARGSUSED*/
static int
pl2303_tx(ds_hdl_t hdl, uint_t port_num, mblk_t *mp)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
int xferd;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_tx");
/*
* sanity checks
*/
if (mp == NULL) {
USB_DPRINTF_L3(DPRINT_CTLOP, plp->pl_lh, "pl2303_tx: mp=NULL");
return (USB_SUCCESS);
}
if (MBLKL(mp) <= 0) {
USB_DPRINTF_L3(DPRINT_CTLOP, plp->pl_lh, "pl2303_tx: len<=0");
freemsg(mp);
return (USB_SUCCESS);
}
mutex_enter(&plp->pl_mutex);
pl2303_put_tail(&plp->pl_tx_mp, mp); /* add to the chain */
pl2303_tx_start(plp, &xferd);
mutex_exit(&plp->pl_mutex);
return (USB_SUCCESS);
}
/*
* ds_rx
* the real data receiving is in pl2303_open_port
*/
/*ARGSUSED*/
static mblk_t *
pl2303_rx(ds_hdl_t hdl, uint_t port_num)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
mblk_t *mp;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_rx");
mutex_enter(&plp->pl_mutex);
mp = plp->pl_rx_mp;
plp->pl_rx_mp = NULL;
mutex_exit(&plp->pl_mutex);
return (mp);
}
/*
* ds_stop
*/
/*ARGSUSED*/
static void
pl2303_stop(ds_hdl_t hdl, uint_t port_num, int dir)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_stop");
if (dir & DS_TX) {
mutex_enter(&plp->pl_mutex);
plp->pl_port_flags |= PL2303_PORT_TX_STOPPED;
mutex_exit(&plp->pl_mutex);
}
}
/*
* ds_start
*/
/*ARGSUSED*/
static void
pl2303_start(ds_hdl_t hdl, uint_t port_num, int dir)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_start");
if (dir & DS_TX) {
mutex_enter(&plp->pl_mutex);
if (plp->pl_port_flags & PL2303_PORT_TX_STOPPED) {
plp->pl_port_flags &= ~PL2303_PORT_TX_STOPPED;
pl2303_tx_start(plp, NULL);
}
mutex_exit(&plp->pl_mutex);
}
}
/*
* ds_fifo_flush
*/
/*ARGSUSED*/
static int
pl2303_fifo_flush(ds_hdl_t hdl, uint_t port_num, int dir)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_fifo_flush: dir=%x",
dir);
mutex_enter(&plp->pl_mutex);
ASSERT(plp->pl_port_state == PL2303_PORT_OPEN);
if ((dir & DS_TX) && plp->pl_tx_mp) {
freemsg(plp->pl_tx_mp);
plp->pl_tx_mp = NULL;
}
if ((dir & DS_RX) && plp->pl_rx_mp) {
freemsg(plp->pl_rx_mp);
plp->pl_rx_mp = NULL;
}
mutex_exit(&plp->pl_mutex);
return (USB_SUCCESS);
}
/*
* ds_fifo_drain
*/
/*ARGSUSED*/
static int
pl2303_fifo_drain(ds_hdl_t hdl, uint_t port_num, int timeout)
{
pl2303_state_t *plp = (pl2303_state_t *)hdl;
int rval = USB_SUCCESS;
USB_DPRINTF_L4(DPRINT_CTLOP, plp->pl_lh, "pl2303_fifo_drain");
mutex_enter(&plp->pl_mutex);
ASSERT(plp->pl_port_state == PL2303_PORT_OPEN);
/*
* for the reason of hardware, set timeout 0
*/
if (pl2303_wait_tx_drain(plp, 0) != USB_SUCCESS) {
mutex_exit(&plp->pl_mutex);
return (USB_FAILURE);
}
mutex_exit(&plp->pl_mutex);
/* wait 500 ms until hw fifo drains */
delay(drv_usectohz(500*1000));
return (rval);
}
/*
* configuration routines
* ----------------------
*
* clean up routine
*/
static void
pl2303_cleanup(pl2303_state_t *plp, int level)
{
ASSERT((level > 0) && (level <= PL2303_CLEANUP_LEVEL_MAX));
switch (level) {
default:
pl2303_close_pipes(plp);
/* FALLTHRU */
case 4:
usb_unregister_event_cbs(plp->pl_dip, plp->pl_usb_events);
/* FALLTHRU */
case 3:
pl2303_destroy_pm_components(plp);
mutex_destroy(&plp->pl_mutex);
cv_destroy(&plp->pl_tx_cv);
cv_destroy(&plp->pl_rx_cv);
usb_free_log_hdl(plp->pl_lh);
plp->pl_lh = NULL;
usb_free_descr_tree(plp->pl_dip, plp->pl_dev_data);
plp->pl_def_ph = NULL;
/* FALLTHRU */
case 2:
usb_client_detach(plp->pl_dip, plp->pl_dev_data);
/* FALLTHRU */
case 1:
kmem_free(plp, sizeof (pl2303_state_t));
}
}
/*
* device specific attach
*/
static int
pl2303_dev_attach(pl2303_state_t *plp)
{
if (pl2303_open_pipes(plp) != USB_SUCCESS) {
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* hotplug
* -------
*
*
* restore device state after CPR resume or reconnect
*/
static int
pl2303_restore_device_state(pl2303_state_t *plp)
{
int state;
mutex_enter(&plp->pl_mutex);
state = plp->pl_dev_state;
mutex_exit(&plp->pl_mutex);
if ((state != USB_DEV_DISCONNECTED) && (state != USB_DEV_SUSPENDED)) {
return (state);
}
if (usb_check_same_device(plp->pl_dip, plp->pl_lh, USB_LOG_L0,
DPRINT_MASK_ALL, USB_CHK_ALL, NULL) != USB_SUCCESS) {
mutex_enter(&plp->pl_mutex);
state = plp->pl_dev_state = USB_DEV_DISCONNECTED;
mutex_exit(&plp->pl_mutex);
return (state);
}
if (state == USB_DEV_DISCONNECTED) {
USB_DPRINTF_L0(DPRINT_HOTPLUG, plp->pl_lh,
"Device has been reconnected but data may have been lost");
}
if (pl2303_reconnect_pipes(plp) != USB_SUCCESS) {
return (state);
}
/*
* init device state
*/
mutex_enter(&plp->pl_mutex);
state = plp->pl_dev_state = USB_DEV_ONLINE;
mutex_exit(&plp->pl_mutex);
if ((pl2303_restore_port_state(plp) != USB_SUCCESS)) {
USB_DPRINTF_L2(DPRINT_HOTPLUG, plp->pl_lh,
"pl2303_restore_device_state: failed");
}
return (state);
}
/*
* restore ports state after CPR resume or reconnect
*/
static int
pl2303_restore_port_state(pl2303_state_t *plp)
{
int rval;
mutex_enter(&plp->pl_mutex);
if (plp->pl_port_state != PL2303_PORT_OPEN) {
mutex_exit(&plp->pl_mutex);
return (USB_SUCCESS);
}
mutex_exit(&plp->pl_mutex);
/* open hardware serial port */
if ((rval = pl2303_open_hw_port(plp)) != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_HOTPLUG, plp->pl_lh,
"pl2303_restore_ports_state: failed");
}
return (rval);
}
/*
* power management
* ----------------
*
*
* create PM components
*/
static int
pl2303_create_pm_components(pl2303_state_t *plp)
{
dev_info_t *dip = plp->pl_dip;
pl2303_pm_t *pm;
uint_t pwr_states;
if (usb_create_pm_components(dip, &pwr_states) != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_PM, plp->pl_lh,
"pl2303_create_pm_components: failed");
return (USB_SUCCESS);
}
pm = plp->pl_pm = kmem_zalloc(sizeof (pl2303_pm_t), 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
pl2303_destroy_pm_components(pl2303_state_t *plp)
{
pl2303_pm_t *pm = plp->pl_pm;
dev_info_t *dip = plp->pl_dip;
int rval;
if (!pm)
return;
if (plp->pl_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, plp->pl_lh,
"pl2303_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, plp->pl_lh,
"pl2303_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 (pl2303_pm_t));
plp->pl_pm = NULL;
}
/*
* mark device busy and raise power
*/
static int
pl2303_pm_set_busy(pl2303_state_t *plp)
{
pl2303_pm_t *pm = plp->pl_pm;
dev_info_t *dip = plp->pl_dip;
int rval;
USB_DPRINTF_L4(DPRINT_PM, plp->pl_lh, "pl2303_pm_set_busy");
if (!pm) {
return (USB_SUCCESS);
}
mutex_enter(&plp->pl_mutex);
/* if already marked busy, just increment the counter */
if (pm->pm_busy_cnt++ > 0) {
mutex_exit(&plp->pl_mutex);
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(&plp->pl_mutex);
return (USB_SUCCESS);
}
/* need to raise power */
pm->pm_raise_power = B_TRUE;
mutex_exit(&plp->pl_mutex);
rval = pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
if (rval != DDI_SUCCESS) {
USB_DPRINTF_L2(DPRINT_PM, plp->pl_lh, "raising power failed");
}
mutex_enter(&plp->pl_mutex);
pm->pm_raise_power = B_FALSE;
mutex_exit(&plp->pl_mutex);
return (USB_SUCCESS);
}
/*
* mark device idle
*/
static void
pl2303_pm_set_idle(pl2303_state_t *plp)
{
pl2303_pm_t *pm = plp->pl_pm;
dev_info_t *dip = plp->pl_dip;
USB_DPRINTF_L4(DPRINT_PM, plp->pl_lh, "pl2303_pm_set_idle");
if (!pm) {
return;
}
/*
* if more ports use the device, do not mark as yet
*/
mutex_enter(&plp->pl_mutex);
if (--pm->pm_busy_cnt > 0) {
mutex_exit(&plp->pl_mutex);
return;
}
if (pm) {
(void) pm_idle_component(dip, 0);
}
mutex_exit(&plp->pl_mutex);
}
/*
* Functions to handle power transition for OS levels 0 -> 3
* The same level as OS state, different from USB state
*/
static int
pl2303_pwrlvl0(pl2303_state_t *plp)
{
int rval;
USB_DPRINTF_L4(DPRINT_PM, plp->pl_lh, "pl2303_pwrlvl0");
switch (plp->pl_dev_state) {
case USB_DEV_ONLINE:
/* issue USB D3 command to the device */
rval = usb_set_device_pwrlvl3(plp->pl_dip);
ASSERT(rval == USB_SUCCESS);
plp->pl_dev_state = USB_DEV_PWRED_DOWN;
plp->pl_pm->pm_cur_power = USB_DEV_OS_PWR_OFF;
/* FALLTHRU */
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, plp->pl_lh,
"pl2303_pwrlvl0: illegal device state");
return (USB_FAILURE);
}
}
static int
pl2303_pwrlvl1(pl2303_state_t *plp)
{
USB_DPRINTF_L4(DPRINT_PM, plp->pl_lh, "pl2303_pwrlvl1");
/* issue USB D2 command to the device */
(void) usb_set_device_pwrlvl2(plp->pl_dip);
return (USB_FAILURE);
}
static int
pl2303_pwrlvl2(pl2303_state_t *plp)
{
USB_DPRINTF_L4(DPRINT_PM, plp->pl_lh, "pl2303_pwrlvl2");
/* issue USB D1 command to the device */
(void) usb_set_device_pwrlvl1(plp->pl_dip);
return (USB_FAILURE);
}
static int
pl2303_pwrlvl3(pl2303_state_t *plp)
{
int rval;
USB_DPRINTF_L4(DPRINT_PM, plp->pl_lh, "pl2303_pwrlvl3");
switch (plp->pl_dev_state) {
case USB_DEV_PWRED_DOWN:
/* Issue USB D0 command to the device here */
rval = usb_set_device_pwrlvl0(plp->pl_dip);
ASSERT(rval == USB_SUCCESS);
plp->pl_dev_state = USB_DEV_ONLINE;
plp->pl_pm->pm_cur_power = USB_DEV_OS_FULL_PWR;
/* FALLTHRU */
case USB_DEV_ONLINE:
/* we are already in full power */
/* FALLTHRU */
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
return (USB_SUCCESS);
default:
USB_DPRINTF_L2(DPRINT_PM, plp->pl_lh,
"pl2303_pwrlvl3: illegal device state");
return (USB_FAILURE);
}
}
/*
* pipe operations
* ---------------
*
*
*/
static int
pl2303_open_pipes(pl2303_state_t *plp)
{
int ifc, alt;
usb_pipe_policy_t policy;
usb_ep_data_t *in_data, *out_data;
/* get ep data */
ifc = plp->pl_dev_data->dev_curr_if;
alt = 0;
in_data = usb_lookup_ep_data(plp->pl_dip, plp->pl_dev_data, ifc, alt,
0, USB_EP_ATTR_BULK, USB_EP_DIR_IN);
out_data = usb_lookup_ep_data(plp->pl_dip, plp->pl_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, plp->pl_lh,
"pl2303_open_pipes: can't get ep data");
return (USB_FAILURE);
}
/* open pipes */
policy.pp_max_async_reqs = 2;
if (usb_pipe_open(plp->pl_dip, &in_data->ep_descr, &policy,
USB_FLAGS_SLEEP, &plp->pl_bulkin_ph) != USB_SUCCESS) {
return (USB_FAILURE);
}
if (usb_pipe_open(plp->pl_dip, &out_data->ep_descr, &policy,
USB_FLAGS_SLEEP, &plp->pl_bulkout_ph) != USB_SUCCESS) {
usb_pipe_close(plp->pl_dip, plp->pl_bulkin_ph, USB_FLAGS_SLEEP,
NULL, NULL);
return (USB_FAILURE);
}
mutex_enter(&plp->pl_mutex);
plp->pl_bulkin_state = PL2303_PIPE_IDLE;
plp->pl_bulkout_state = PL2303_PIPE_IDLE;
mutex_exit(&plp->pl_mutex);
return (USB_SUCCESS);
}
static void
pl2303_close_pipes(pl2303_state_t *plp)
{
mutex_enter(&plp->pl_mutex);
if (plp->pl_dev_state == USB_DEV_ONLINE) {
USB_DPRINTF_L4(DPRINT_IN_PIPE, plp->pl_lh,
"pl2303_close_pipes: waiting for idle bulkin\n");
while (plp->pl_bulkin_state != PL2303_PIPE_IDLE) {
cv_wait(&plp->pl_rx_cv, &plp->pl_mutex);
}
}
mutex_exit(&plp->pl_mutex);
usb_pipe_close(plp->pl_dip, plp->pl_bulkin_ph, USB_FLAGS_SLEEP, 0, 0);
usb_pipe_close(plp->pl_dip, plp->pl_bulkout_ph, USB_FLAGS_SLEEP, 0, 0);
mutex_enter(&plp->pl_mutex);
plp->pl_bulkin_state = PL2303_PIPE_CLOSED;
plp->pl_bulkout_state = PL2303_PIPE_CLOSED;
mutex_exit(&plp->pl_mutex);
}
static void
pl2303_disconnect_pipes(pl2303_state_t *plp)
{
pl2303_close_pipes(plp);
}
static int
pl2303_reconnect_pipes(pl2303_state_t *plp)
{
if ((pl2303_open_pipes(plp) != USB_SUCCESS)) {
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* pipe callbacks
* --------------
*
*
* bulk in common and exeception callback
*
*/
/*ARGSUSED*/
void
pl2303_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
pl2303_state_t *plp = (pl2303_state_t *)req->bulk_client_private;
mblk_t *data;
int data_len;
data = req->bulk_data;
data_len = (data) ? MBLKL(data) : 0;
USB_DPRINTF_L4(DPRINT_IN_PIPE, plp->pl_lh, "pl2303_bulkin_cb: "
"cr=%d len=%d",
req->bulk_completion_reason,
data_len);
/* save data and notify GSD */
if ((plp->pl_port_state == PL2303_PORT_OPEN) && (data_len) &&
(req->bulk_completion_reason == USB_CR_OK)) {
req->bulk_data = NULL;
pl2303_put_tail(&plp->pl_rx_mp, data);
if (plp->pl_cb.cb_rx) {
plp->pl_cb.cb_rx(plp->pl_cb.cb_arg);
}
}
usb_free_bulk_req(req);
/* receive more */
mutex_enter(&plp->pl_mutex);
plp->pl_bulkin_state = PL2303_PIPE_IDLE;
if (plp->pl_port_state == PL2303_PORT_OPEN) {
(void) pl2303_rx_start(plp);
} else {
plp->pl_bulkin_state = PL2303_PIPE_IDLE;
cv_broadcast(&plp->pl_rx_cv);
}
mutex_exit(&plp->pl_mutex);
}
/*
* bulk out common and exeception callback
*/
/*ARGSUSED*/
void
pl2303_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req)
{
pl2303_state_t *plp = (pl2303_state_t *)req->bulk_client_private;
int data_len;
mblk_t *data = req->bulk_data;
data_len = (req->bulk_data) ? MBLKL(req->bulk_data) : 0;
USB_DPRINTF_L4(DPRINT_OUT_PIPE, plp->pl_lh,
"pl2303_bulkout_cb: cr=%d len=%d",
req->bulk_completion_reason,
data_len);
if (req->bulk_completion_reason && (data_len > 0)) {
pl2303_put_head(&plp->pl_tx_mp, data);
req->bulk_data = NULL;
}
usb_free_bulk_req(req);
/* notify GSD */
if (plp->pl_cb.cb_tx) {
plp->pl_cb.cb_tx(plp->pl_cb.cb_arg);
}
/* send more */
mutex_enter(&plp->pl_mutex);
plp->pl_bulkout_state = PL2303_PIPE_IDLE;
if (plp->pl_tx_mp == NULL) {
cv_broadcast(&plp->pl_tx_cv);
} else {
pl2303_tx_start(plp, NULL);
}
mutex_exit(&plp->pl_mutex);
}
/*
* data transfer routines
* ----------------------
*
*
* start data receipt
*/
static int
pl2303_rx_start(pl2303_state_t *plp)
{
usb_bulk_req_t *br;
int rval = USB_FAILURE;
USB_DPRINTF_L4(DPRINT_OUT_PIPE, plp->pl_lh, "pl2303_rx_start");
ASSERT(mutex_owned(&plp->pl_mutex));
plp->pl_bulkin_state = PL2303_PIPE_BUSY;
mutex_exit(&plp->pl_mutex);
br = usb_alloc_bulk_req(plp->pl_dip, plp->pl_xfer_sz, USB_FLAGS_SLEEP);
br->bulk_len = plp->pl_xfer_sz;
br->bulk_timeout = PL2303_BULKIN_TIMEOUT;
br->bulk_cb = pl2303_bulkin_cb;
br->bulk_exc_cb = pl2303_bulkin_cb;
br->bulk_client_private = (usb_opaque_t)plp;
br->bulk_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_SHORT_XFER_OK;
rval = usb_pipe_bulk_xfer(plp->pl_bulkin_ph, br, 0);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_IN_PIPE, plp->pl_lh,
"pl2303_rx_start: xfer failed %d", rval);
usb_free_bulk_req(br);
}
mutex_enter(&plp->pl_mutex);
if (rval != USB_SUCCESS) {
plp->pl_bulkin_state = PL2303_PIPE_IDLE;
}
return (rval);
}
/*
* start data transmit
*/
static void
pl2303_tx_start(pl2303_state_t *plp, 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, plp->pl_lh, "pl2303_tx_start");
ASSERT(mutex_owned(&plp->pl_mutex));
ASSERT(plp->pl_port_state != PL2303_PORT_CLOSED);
if (xferd) {
*xferd = 0;
}
if ((plp->pl_port_flags & PL2303_PORT_TX_STOPPED) ||
(plp->pl_tx_mp == NULL)) {
return;
}
if (plp->pl_bulkout_state != PL2303_PIPE_IDLE) {
USB_DPRINTF_L4(DPRINT_OUT_PIPE, plp->pl_lh,
"pl2303_tx_start: pipe busy");
return;
}
ASSERT(MBLKL(plp->pl_tx_mp) > 0);
/* send as much data as port can receive */
len = min(msgdsize(plp->pl_tx_mp), plp->pl_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) && plp->pl_tx_mp) {
mp = plp->pl_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) <= 0) {
plp->pl_tx_mp = unlinkb(mp);
freeb(mp);
} else {
ASSERT(data_len == len);
}
}
if (data_len <= 0) {
USB_DPRINTF_L3(DPRINT_OUT_PIPE, plp->pl_lh,
"pl2303_tx_start: copied zero bytes");
freeb(data);
return;
}
plp->pl_bulkout_state = PL2303_PIPE_BUSY;
mutex_exit(&plp->pl_mutex);
rval = pl2303_send_data(plp, data);
mutex_enter(&plp->pl_mutex);
if (rval != USB_SUCCESS) {
plp->pl_bulkout_state = PL2303_PIPE_IDLE;
if (plp->pl_tx_mp == NULL) {
plp->pl_tx_mp = data;
}
} else {
if (xferd) {
*xferd = data_len;
}
}
}
static int
pl2303_send_data(pl2303_state_t *plp, mblk_t *data)
{
usb_bulk_req_t *br;
int len = MBLKL(data);
int rval;
USB_DPRINTF_L4(DPRINT_OUT_PIPE, plp->pl_lh, "pl2303_send_data: %d "
"%x %x %x", len, data->b_rptr[0],
(len > 1) ? data->b_rptr[1] : 0,
(len > 2) ? data->b_rptr[2] : 0);
ASSERT(!mutex_owned(&plp->pl_mutex));
br = usb_alloc_bulk_req(plp->pl_dip, 0, USB_FLAGS_SLEEP);
br->bulk_data = data;
br->bulk_len = len;
br->bulk_timeout = PL2303_BULKOUT_TIMEOUT;
br->bulk_cb = pl2303_bulkout_cb;
br->bulk_exc_cb = pl2303_bulkout_cb;
br->bulk_client_private = (usb_opaque_t)plp;
br->bulk_attributes = USB_ATTRS_AUTOCLEARING;
rval = usb_pipe_bulk_xfer(plp->pl_bulkout_ph, br, 0);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_OUT_PIPE, plp->pl_lh,
"pl2303_send_data: xfer failed %d", rval);
usb_free_bulk_req(br);
}
return (rval);
}
/*
* wait until local tx buffer drains.
* 'timeout' is in seconds, zero means wait forever
*/
static int
pl2303_wait_tx_drain(pl2303_state_t *plp, int timeout)
{
clock_t until;
int over = 0;
until = ddi_get_lbolt() + drv_usectohz(1000 * 1000 * timeout);
while (plp->pl_tx_mp && !over) {
if (timeout > 0) {
/* whether timedout or signal pending */
over = (cv_timedwait_sig(&plp->pl_tx_cv,
&plp->pl_mutex, until) <= 0);
} else {
/* whether a signal is pending */
over = (cv_wait_sig(&plp->pl_tx_cv,
&plp->pl_mutex) == 0);
}
}
return ((plp->pl_tx_mp == NULL) ? USB_SUCCESS : USB_FAILURE);
}
/*
* device operations
* -----------------
*
*
* initialize hardware serial port
*/
static int
pl2303_open_hw_port(pl2303_state_t *plp)
{
int rval = USB_SUCCESS;
/*
* initialize three Device Configuration Registers (DCR):
* DCR0, DCR1, and DCR2
*/
switch (plp->pl_chiptype) {
case (pl2303_H):
/* Set DCR0 */
if ((rval = pl2303_cmd_vendor_write0(plp, SET_DCR0,
DCR0_INIT_H)) != USB_SUCCESS) {
return (rval);
}
/* Set DCR1 */
if ((rval = pl2303_cmd_vendor_write0(plp, SET_DCR1,
DCR1_INIT_H)) != USB_SUCCESS) {
return (rval);
}
/* Set DCR2 */
if ((rval = pl2303_cmd_vendor_write0(plp, SET_DCR2,
DCR2_INIT_H)) != USB_SUCCESS) {
return (rval);
}
break;
case (pl2303_X):
/* Set DCR0 */
if ((rval = pl2303_cmd_vendor_write0(plp, SET_DCR0,
DCR0_INIT)) != USB_SUCCESS) {
return (rval);
}
/* Set DCR1 */
if ((rval = pl2303_cmd_vendor_write0(plp, SET_DCR1,
DCR1_INIT_X)) != USB_SUCCESS) {
return (rval);
}
/* Set DCR2 */
if ((rval = pl2303_cmd_vendor_write0(plp, SET_DCR2,
DCR2_INIT_X)) != USB_SUCCESS) {
return (rval);
}
/* reset Downstream data pipes */
if ((rval = pl2303_cmd_vendor_write0(plp,
RESET_DOWNSTREAM_DATA_PIPE, 0)) != USB_SUCCESS) {
return (rval);
}
/* reset Upstream data pipes */
if ((rval = pl2303_cmd_vendor_write0(plp,
RESET_UPSTREAM_DATA_PIPE, 0)) != USB_SUCCESS) {
return (rval);
}
break;
case (pl2303_UNKNOWN):
default:
USB_DPRINTF_L2(DPRINT_OPEN, plp->pl_lh,
"pl2303_open_hw_port: unknown chiptype");
rval = USB_FAILURE;
}
return (rval);
}
/*
* vendor-specific commands
* ------------------------
*
*
* Get_Line_Coding Request
*/
static int
pl2303_cmd_get_line(pl2303_state_t *plp, mblk_t **data)
{
usb_ctrl_setup_t setup = { PL2303_GET_LINE_CODING_REQUEST_TYPE,
PL2303_GET_LINE_CODING_REQUEST, 0, 0,
PL2303_GET_LINE_CODING_LENGTH, 0 };
usb_cb_flags_t cb_flags;
usb_cr_t cr;
int rval;
*data = NULL;
rval = usb_pipe_ctrl_xfer_wait(plp->pl_def_ph, &setup, data,
&cr, &cb_flags, 0);
if ((rval == USB_SUCCESS) && (*data != NULL)) {
USB_DPRINTF_L4(DPRINT_DEF_PIPE, plp->pl_lh,
"pl2303_cmd_get_line: %x %x %x %x %x %x %x",
(*data)->b_rptr[0], (*data)->b_rptr[1], (*data)->b_rptr[2],
(*data)->b_rptr[3], (*data)->b_rptr[4], (*data)->b_rptr[5],
(*data)->b_rptr[6]);
} else {
USB_DPRINTF_L2(DPRINT_DEF_PIPE, plp->pl_lh,
"pl2303_cmd_get_line: failed %d %d %x",
rval, cr, cb_flags);
}
return (rval);
}
/*
* Set_Line_Coding Request
*/
static int
pl2303_cmd_set_line(pl2303_state_t *plp, mblk_t *data)
{
usb_ctrl_setup_t setup = { PL2303_SET_LINE_CODING_REQUEST_TYPE,
PL2303_SET_LINE_CODING_REQUEST, 0, 0,
PL2303_SET_LINE_CODING_LENGTH, 0 };
usb_cb_flags_t cb_flags;
usb_cr_t cr;
int rval;
USB_DPRINTF_L4(DPRINT_DEF_PIPE, plp->pl_lh,
"pl2303_cmd_set_line: %x %x %x %x %x %x %x",
data->b_rptr[0], data->b_rptr[1], data->b_rptr[2],
data->b_rptr[3], data->b_rptr[4], data->b_rptr[5], data->b_rptr[6]);
rval = usb_pipe_ctrl_xfer_wait(plp->pl_def_ph, &setup, &data,
&cr, &cb_flags, 0);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_DEF_PIPE, plp->pl_lh,
"pl2303_cmd_set_line: failed %d %d %x",
rval, cr, cb_flags);
}
return (rval);
}
/*
* Set_Control_Line_State Request to RTS and DTR
*/
static int
pl2303_cmd_set_ctl(pl2303_state_t *plp, uint8_t val)
{
usb_ctrl_setup_t setup = { PL2303_SET_CONTROL_REQUEST_TYPE,
PL2303_SET_CONTROL_REQUEST, 0, 0,
PL2303_SET_CONTROL_LENGTH, 0 };
usb_cb_flags_t cb_flags;
usb_cr_t cr;
int rval;
setup.wValue = val;
rval = usb_pipe_ctrl_xfer_wait(plp->pl_def_ph, &setup, NULL,
&cr, &cb_flags, 0);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_DEF_PIPE, plp->pl_lh,
"pl2303_cmd_set_ctl: failed %d %d %x",
rval, cr, cb_flags);
}
return (rval);
}
/*
* Vendor_Specific_Write Request
* wLength: 0
*/
static int
pl2303_cmd_vendor_write0(pl2303_state_t *plp, uint16_t value, int16_t index)
{
usb_ctrl_setup_t setup = { PL2303_VENDOR_WRITE_REQUEST_TYPE,
PL2303_VENDOR_WRITE_REQUEST, 0, 0,
PL2303_VENDOR_WRITE_LENGTH, 0 };
usb_cb_flags_t cb_flags;
usb_cr_t cr;
int rval;
setup.wValue = value;
setup.wIndex = index;
rval = usb_pipe_ctrl_xfer_wait(plp->pl_def_ph, &setup, NULL,
&cr, &cb_flags, 0);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_DEF_PIPE, plp->pl_lh,
"pl2303_cmd_vendor_write0: %x %x failed %d %d %x",
value, index, rval, cr, cb_flags);
}
return (rval);
}
/*
* For Hardware flow control
*/
static int
pl2303_cmd_set_rtscts(pl2303_state_t *plp)
{
/* Set DCR0 */
switch (plp->pl_chiptype) {
case pl2303_H:
return (pl2303_cmd_vendor_write0(plp, SET_DCR0, DCR0_INIT_H));
case pl2303_X:
return (pl2303_cmd_vendor_write0(plp, SET_DCR0, DCR0_INIT_X));
case pl2303_UNKNOWN:
default:
return (USB_FAILURE);
}
}
/*
* Set TxD BREAK_ON or BREAK_OFF
*/
static int
pl2303_cmd_break(pl2303_state_t *plp, int ctl)
{
usb_ctrl_setup_t setup = { PL2303_BREAK_REQUEST_TYPE,
PL2303_BREAK_REQUEST, 0, 0,
PL2303_BREAK_LENGTH, 0 };
usb_cb_flags_t cb_flags;
usb_cr_t cr;
int rval;
setup.wValue = (ctl == DS_ON) ? PL2303_BREAK_ON : PL2303_BREAK_OFF;
rval = usb_pipe_ctrl_xfer_wait(plp->pl_def_ph, &setup, NULL,
&cr, &cb_flags, 0);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(DPRINT_DEF_PIPE, plp->pl_lh,
"pl2303_cmd_break: failed rval=%d,cr=%d,cb_flags=0x%x",
rval, cr, cb_flags);
}
return (rval);
}
/*
* for set_mod_ctl
*/
static void
pl2303_mctl2reg(int mask, int val, uint8_t *line_ctl)
{
if (mask & TIOCM_RTS) {
if (val & TIOCM_RTS) {
*line_ctl |= PL2303_CONTROL_RTS;
} else {
*line_ctl &= ~PL2303_CONTROL_RTS;
}
}
if (mask & TIOCM_DTR) {
if (val & TIOCM_DTR) {
*line_ctl |= PL2303_CONTROL_DTR;
} else {
*line_ctl &= ~PL2303_CONTROL_DTR;
}
}
}
/*
* for get_mod_ctl
*/
static int
pl2303_reg2mctl(uint8_t line_ctl)
{
int val = 0;
if (line_ctl & PL2303_CONTROL_RTS) {
val |= TIOCM_RTS;
}
if (line_ctl & PL2303_CONTROL_DTR) {
val |= TIOCM_DTR;
}
return (val);
}
/*
* misc routines
* -------------
*
*/
/*
* link a message block to tail of message
* account for the case when message is null
*/
static void
pl2303_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
pl2303_put_head(mblk_t **mpp, mblk_t *bp)
{
if (*mpp) {
linkb(bp, *mpp);
}
*mpp = bp;
}