diff --git a/usr/src/uts/common/io/usb/clients/usbser/uchcom/uchcom_dsd.c b/usr/src/uts/common/io/usb/clients/usbser/uchcom/uchcom_dsd.c index 8f1f7f69cf..73d3ee9a0c 100644 --- a/usr/src/uts/common/io/usb/clients/usbser/uchcom/uchcom_dsd.c +++ b/usr/src/uts/common/io/usb/clients/usbser/uchcom/uchcom_dsd.c @@ -150,7 +150,7 @@ static void uchcom_cmd_vendor_convert_status(uchcom_state_t *, uint8_t); static int uchcom_cmd_vendor_update_status(uchcom_state_t *); static int uchcom_cmd_vendor_set_dtr_rts(uchcom_state_t *); -static int uchcom_cmd_vendor_calc_divider_settings(uchcom_state_t *, +static int uchcom_cmd_vendor_calc_divider_settings(uchcom_divider_t *, uint32_t); static int uchcom_cmd_vendor_set_baudrate(uchcom_state_t *, uint32_t); @@ -196,6 +196,44 @@ static uint_t uchcom_errlevel = USB_LOG_L4; static uint_t uchcom_errmask = DPRINT_MASK_ALL; static uint_t uchcom_instance_debug = (uint_t)-1; +/* divider record */ +#define nitems(x) (sizeof((x)) / sizeof((x)[0])) +static const struct uchcom_divider_record dividers[] = +{ + {307200, 307200, UCHCOM_BASE_UNKNOWN, {7, 0xD9, 0}}, + {921600, 921600, UCHCOM_BASE_UNKNOWN, {7, 0xF3, 0}}, + {2999999, 23530, 6000000, {3, 0, 0}}, + {23529, 2942, 750000, {2, 0, 0}}, + {2941, 368, 93750, {1, 0, 0}}, + {367, 1, 11719, {0, 0, 0}}, +}; + +static int uchcom_speedtab[] = { + 0, /* B0 */ + 50, /* B50 */ + 75, /* B75 */ + 110, /* B110 */ + 134, /* B134 */ + 150, /* B150 */ + 200, /* B200 */ + 300, /* B300 */ + 600, /* B600 */ + 1200, /* B1200 */ + 1800, /* B1800 */ + 2400, /* B2400 */ + 4800, /* B4800 */ + 9600, /* B9600 */ + 19200, /* B19200 */ + 38400, /* B38400 */ + 57600, /* B57600 */ + 76800, /* B76800 */ + 115200, /* B115200 */ + 153600, /* B153600 */ + 230400, /* B230400 */ + 307200, /* B307200 */ + 460800 /* B460800 */ +}; + /* * ds_attach */ @@ -564,8 +602,9 @@ static int uchcom_set_port_params(ds_hdl_t hdl, uint_t portno, ds_port_params_t *tp) { uchcom_state_t *uch = (uchcom_state_t *)hdl; - int rval; + int rval = USB_SUCCESS; int i; + uint_t baud; ds_port_param_entry_t *pe; if (tp == NULL) @@ -573,122 +612,131 @@ uchcom_set_port_params(ds_hdl_t hdl, uint_t portno, ds_port_params_t *tp) USB_DPRINTF_L4(DPRINT_CTLOP, uch->uch_lh, "uchcom_set_port_params"); - // clear configuration - rval = uchcom_cmd_vendor_ctrl_write(uch, UCHCOM_REQ_RESET, 0, 0); - if (rval != USB_SUCCESS) { - USB_DPRINTF_L2(DPRINT_DEF_PIPE, uch->uch_lh, - "uchcom_set_port_params: failed to reset!"); - return (rval); - } + /* + * configure + * ---------------------------------------------- + * the chip doesn't support very much it seems + * both freebsd and openbsd seem to default to + * 8n1 mode and support nothing else. + * + * we do the same here. + */ + for (i = 0, pe = tp->tp_entries; i < tp->tp_cnt; i++, pe++) { + switch (pe->param) { + case DS_PARAM_BAUD: + if (pe->val.ui >= NELEM(uchcom_speedtab)) { + cmn_err(CE_WARN, "XXX: baud range oops"); + USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh, + "uchcom_set_port_params: unsupported baudrate"); + rval = USB_FAILURE; + } else { + baud = uchcom_speedtab[pe->val.ui]; + } + break; + + case DS_PARAM_PARITY: + // only supports 8n1 + if (pe->val.ui & PARENB) { + cmn_err(CE_WARN, "XXX: parity oops"); + USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh, + "uchcom_set_port_params: unsupported parity"); + rval = USB_FAILURE; + } + break; + + case DS_PARAM_STOPB: + // only supports 8n1 + if (pe->val.ui & CSTOPB) { + cmn_err(CE_WARN, "XXX: stopb oops"); + USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh, + "uchcom_set_port_params: unsupported cstopb"); + rval = USB_FAILURE; + } + break; + + case DS_PARAM_CHARSZ: + // only supports 8n1 + if (pe->val.ui != CS8) { + cmn_err(CE_WARN, "XXX: charsz oops"); + USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh, + "uchcom_set_port_params: unsupported charsz"); + rval = USB_FAILURE; + } + break; + + case DS_PARAM_XON_XOFF: /* Software flow control */ + break; + + case DS_PARAM_FLOW_CTL: /* Hardware flow control */ + break; + + default: + USB_DPRINTF_L2(DPRINT_CTLOP, uch->uch_lh, + "uchcom_set_port_params: bad param %d", pe->param); + rval = USB_FAILURE; + break; + } + } - // configure - 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: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 300"); - break; - case B600: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 600"); - break; - case B1200: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 1200"); - break; - case B2400: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 2400"); - break; - case B4800: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 4800"); - break; - case B9600: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 9400"); - break; - case B19200: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 19200"); - break; - case B38400: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 38400"); - break; - case B57600: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 57600"); - break; - case B115200: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 115200"); - break; - case B230400: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 230400"); - break; - case B460800: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 460800"); - break; - case B921600: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params br 921600"); - break; - default: - USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh, - "uchcom_set_port_params: bad baud %d", - pe->val.ui); - return (USB_FAILURE); - } - break; - - case DS_PARAM_PARITY: - if (pe->val.ui & PARENB) { - if (pe->val.ui & PARODD) - cmn_err(CE_WARN, "XXX: uchcom_set_port_params par odd"); - else - cmn_err(CE_WARN, "XXX: uchcom_set_port_params par even"); - } else { - cmn_err(CE_WARN, "XXX: uchcom_set_port_params par none"); - } - break; - - case DS_PARAM_STOPB: - if (pe->val.ui & CSTOPB) - cmn_err(CE_WARN, "XXX: uchcom_set_port_params stop bits2"); - else { - cmn_err(CE_WARN, "XXX: uchcom_set_port_params stop bits1"); - } - break; - - case DS_PARAM_CHARSZ: - switch (pe->val.ui) { - case CS8: - cmn_err(CE_WARN, "XXX: uchcom_set_port_params csz cs8"); - break; - default: - USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh, - "uchcom_set_port_params: bad charsz"); - return (USB_FAILURE); - } - 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]; - - cmn_err(CE_WARN, "XXX: uchcom_set_port_params soft flow ???"); - } - break; - - case DS_PARAM_FLOW_CTL: /* Hardware flow control */ - if (pe->val.ui & (RTSXOFF | CTSXON)) { - cmn_err(CE_WARN, "XXX: uchcom_set_port_params hard flow RTS_CTS_HS"); - } - if (pe->val.ui & DTRXOFF) { - cmn_err(CE_WARN, "XXX: uchcom_set_port_params hard flow DTR_DSR_HS"); - } - break; - default: - USB_DPRINTF_L2(DPRINT_CTLOP, uch->uch_lh, - "uchcom_set_port_params: bad param %d", pe->param); - break; - } - } - return (USB_SUCCESS); + if (rval == USB_SUCCESS) { + // reset + rval = uchcom_cmd_vendor_ctrl_write(uch, UCHCOM_REQ_RESET, 0, 0); + if (rval != USB_SUCCESS) { + cmn_err(CE_WARN, "XXX: reset oops"); + USB_DPRINTF_L2(DPRINT_DEF_PIPE, uch->uch_lh, + "uchcom_set_port_params: failed to reset!"); + return (rval); + } + rval = uchcom_cmd_vendor_set_baudrate(uch, baud); + if (rval != USB_SUCCESS) { + cmn_err(CE_WARN, "XXX: first baud"); + USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh, + "uchcom_set_port_params: failed to set baudrate to %d", + baud); + return (rval); + } + + // configure + if (uch->uch_chip_version < UCHCOM_VER_30) { + rval = uchcom_cmd_vendor_reg_read(uch, + UCHCOM_REG_LCR1, NULL, + UCHCOM_REG_LCR2, NULL); + rval = uchcom_cmd_vendor_reg_write(uch, + UCHCOM_REG_LCR1, 0x50, + UCHCOM_REG_LCR2, 0x00); + } else { + /* + * Set up line control: + * - enable transmit and receive + * - set 8n1 mode + * To do: support other sizes, parity, stop bits. + */ + rval = uchcom_cmd_vendor_reg_write(uch, + UCHCOM_REG_LCR1, + UCHCOM_LCR1_RX | UCHCOM_LCR1_TX | UCHCOM_LCR1_CS8, + UCHCOM_REG_LCR2, 0x00); + } + if (rval != USB_SUCCESS) { + cmn_err(CE_WARN, "XXX: init config oops"); + USB_DPRINTF_L3(DPRINT_CTLOP, uch->uch_lh, + "uchcom_set_port_params: initial configuration failed"); + return (rval); + } + + rval = uchcom_cmd_vendor_update_status(uch); + if (rval != USB_SUCCESS) cmn_err(CE_WARN, "XXX: update fail"); + rval = uchcom_cmd_vendor_ctrl_write(uch, UCHCOM_REQ_RESET, 0x501f, 0xd90a); + if (rval != USB_SUCCESS) cmn_err(CE_WARN, "XXX: reset fail"); + rval = uchcom_cmd_vendor_set_baudrate(uch, uchcom_speedtab[pe->val.ui]); + if (rval != USB_SUCCESS) cmn_err(CE_WARN, "XXX: rate fail"); + rval = uchcom_cmd_vendor_set_dtr_rts(uch); + if (rval != USB_SUCCESS) cmn_err(CE_WARN, "XXX: dtr_rts fail"); + rval = uchcom_cmd_vendor_update_status(uch); + if (rval != USB_SUCCESS) cmn_err(CE_WARN, "XXX: update fail"); + } + + return (rval); } /* @@ -1835,17 +1883,68 @@ uchcom_cmd_vendor_set_dtr_rts(uchcom_state_t *uch) } static int -uchcom_cmd_vendor_calc_divider_settings(uchcom_state_t *uch, uint32_t rate) +uchcom_cmd_vendor_calc_divider_settings(uchcom_divider_t *dp, uint32_t rate) { - // XXX: implement me - return (-1); + const struct uchcom_divider_record *rp; + uint32_t div; + uint32_t rem; + uint32_t mod; + uint8_t i; + + /* find record */ + for (i = 0; i != nitems(dividers); i++) { + if (dividers[i].dvr_high >= rate && + dividers[i].dvr_low <= rate) { + rp = ÷rs[i]; + goto found; + } + } + return (USB_FAILURE); + +found: + dp->dv_prescaler = rp->dvr_divider.dv_prescaler; + if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN) + dp->dv_div = rp->dvr_divider.dv_div; + else { + div = rp->dvr_base_clock / rate; + rem = rp->dvr_base_clock % rate; + if (div == 0 || div >= 0xFF) + return (-1); + if ((rem << 1) >= rate) + div += 1; + dp->dv_div = (uint8_t)-div; + } + + mod = (UCHCOM_BPS_MOD_BASE / rate) + UCHCOM_BPS_MOD_BASE_OFS; + mod = mod + (mod / 2); + + dp->dv_mod = (mod + 0xFF) / 0x100; + + return (USB_SUCCESS); } static int uchcom_cmd_vendor_set_baudrate(uchcom_state_t *uch, uint32_t rate) { - // XXX: implement me - return (USB_FAILURE); + int rval; + struct uchcom_divider dv; + + if (uchcom_cmd_vendor_calc_divider_settings(&dv, rate)) + return (USB_FAILURE); + + /* + * According to linux code we need to set bit 7 of UCHCOM_REG_BPS_PRE, + * otherwise the chip will buffer data. + */ + rval = uchcom_cmd_vendor_reg_write(uch, + UCHCOM_REG_BPS_PRE, dv.dv_prescaler | 0x80, + UCHCOM_REG_BPS_DIV, dv.dv_div); + if (rval == USB_SUCCESS) + rval = uchcom_cmd_vendor_reg_write(uch, + UCHCOM_REG_BPS_MOD, dv.dv_mod, + UCHCOM_REG_BPS_PAD, 0); + + return (rval); } /* diff --git a/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_reg.h b/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_reg.h index 8476c26fbf..632ae98efb 100644 --- a/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_reg.h +++ b/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_reg.h @@ -48,11 +48,32 @@ extern "C" { #define UCHCOM_REG_STAT1 0x06 #define UCHCOM_REG_STAT2 0x07 +#define UCHCOM_REG_BPS_PRE 0x12 +#define UCHCOM_REG_BPS_DIV 0x13 +#define UCHCOM_REG_BPS_MOD 0x14 +#define UCHCOM_REG_BPS_PAD 0x0F +#define UCHCOM_REG_BREAK1 0x05 +#define UCHCOM_REG_LCR1 0x18 +#define UCHCOM_REG_LCR2 0x25 +#define UCHCOM_BASE_UNKNOWN 0 +#define UCHCOM_BPS_MOD_BASE 20000000 +#define UCHCOM_BPS_MOD_BASE_OFS 1100 #define UCHCOM_DTR_MASK 0x20 #define UCHCOM_RTS_MASK 0x40 +#define UCHCOM_LCR1_MASK 0xAF +#define UCHCOM_LCR2_MASK 0x07 +#define UCHCOM_LCR1_RX 0x80 +#define UCHCOM_LCR1_TX 0x40 +#define UCHCOM_LCR1_PARENB 0x08 +#define UCHCOM_LCR1_CS8 0x03 +#define UCHCOM_LCR2_PAREVEN 0x07 +#define UCHCOM_LCR2_PARODD 0x06 +#define UCHCOM_LCR2_PARMARK 0x05 +#define UCHCOM_LCR2_PARSPACE 0x04 + /* * XXX - these magic numbers come from Linux (drivers/usb/serial/ch341.c). * The manufacturer was unresponsive when asked for documentation. diff --git a/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_var.h b/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_var.h index e947132e5e..a2349ea66f 100644 --- a/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_var.h +++ b/usr/src/uts/common/sys/usb/clients/usbser/uchcom/uchcom_var.h @@ -112,6 +112,21 @@ _NOTE(DATA_READABLE_WITHOUT_LOCK(uchcom_state::{ uch_bulkout_ph })) +typedef struct uchcom_divider +{ + uint8_t dv_prescaler; + uint8_t dv_div; + uint8_t dv_mod; +} uchcom_divider_t; + +typedef struct uchcom_divider_record +{ + uint32_t dvr_high; + uint32_t dvr_low; + uint32_t dvr_base_clock; + struct uchcom_divider dvr_divider; +} uchcom_devider_record_t; + /* port state */ enum { UCHCOM_PORT_CLOSED, /* port is closed */ @@ -155,6 +170,11 @@ enum { #define DPRINT_PM 0x00004000 #define DPRINT_MASK_ALL 0xFFFFFFFF +/* + * misc macros + */ +#define NELEM(a) (sizeof (a) / sizeof (*(a))) + #ifdef __cplusplus } #endif