From owner-svn-src-head@FreeBSD.ORG Sun Feb 15 12:02:19 2015 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 703B56B7; Sun, 15 Feb 2015 12:02:19 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 58068F65; Sun, 15 Feb 2015 12:02:19 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.9/8.14.9) with ESMTP id t1FC2JTN099309; Sun, 15 Feb 2015 12:02:19 GMT (envelope-from hselasky@FreeBSD.org) Received: (from hselasky@localhost) by svn.freebsd.org (8.14.9/8.14.9/Submit) id t1FC2IcA099300; Sun, 15 Feb 2015 12:02:18 GMT (envelope-from hselasky@FreeBSD.org) Message-Id: <201502151202.t1FC2IcA099300@svn.freebsd.org> X-Authentication-Warning: svn.freebsd.org: hselasky set sender to hselasky@FreeBSD.org using -f From: Hans Petter Selasky Date: Sun, 15 Feb 2015 12:02:18 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r278799 - in head: share/man/man4 sys/conf sys/dev/usb sys/dev/usb/video sys/modules/usb/udl X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 15 Feb 2015 12:02:19 -0000 Author: hselasky Date: Sun Feb 15 12:02:17 2015 New Revision: 278799 URL: https://svnweb.freebsd.org/changeset/base/278799 Log: Import USB display link driver from OpenBSD. Support for compression has been removed and the driver has been greatly simplified and optimised for FreeBSD. The driver is currently not built by default. Requested by: Bruce Simpson Added: head/share/man/man4/udl.4 (contents, props changed) head/sys/dev/usb/video/ head/sys/dev/usb/video/udl.c (contents, props changed) head/sys/dev/usb/video/udl.h (contents, props changed) head/sys/modules/usb/udl/ head/sys/modules/usb/udl/Makefile (contents, props changed) Modified: head/share/man/man4/Makefile head/sys/conf/files head/sys/dev/usb/usbdevs Modified: head/share/man/man4/Makefile ============================================================================== --- head/share/man/man4/Makefile Sun Feb 15 11:37:40 2015 (r278798) +++ head/share/man/man4/Makefile Sun Feb 15 12:02:17 2015 (r278799) @@ -843,6 +843,7 @@ MAN+= \ udbp.4 \ udp.4 \ udplite.4 \ + udl.4 \ uep.4 \ ufm.4 \ ufoma.4 \ Added: head/share/man/man4/udl.4 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/share/man/man4/udl.4 Sun Feb 15 12:02:17 2015 (r278799) @@ -0,0 +1,67 @@ +.\" $OpenBSD: udl.4,v 1.20 2012/09/18 17:11:41 jasper Exp $ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2009 Marcus Glocker +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd February 15, 2015 +.Dt UDL 4 +.Os +.Sh NAME +.Nm udl +.Nd DisplayLink DL-120 / DL-160 USB display devices +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device udl" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +udl_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver supports USB display devices based on the DisplayLink DL-120 / DL-160 +graphic chip. +.Sh HARDWARE +The following devices should work: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Century Corp. Japan Plus One LCD-8000U +.It Century Corp. Japan Plus One LCD-4300U +.It DisplayLink USB to DVI +.It ForwardVideo EasyCAP008 USB to DVI +.It HP USB 2.0 Docking Station (FQ834) +.It HP USB Graphics Adapter (NL571) +.It IOGEAR USB 2.0 External DVI (GUC2020) +.It Koenig CMP-USBVGA10 and CMP-USBVGA11 +.It Lenovo 45K5296 USB to DVI +.It Lenovo ThinkVision LT1421 +.It Lilliput UM-70 +.It Nanovision MiMo UM-710 and UM-740 +.It Rextron VCUD60 USB to DVI +.It Samsung LD220 +.It StarTech CONV-USB2DVI +.It Sunweit USB to DVI +.It Unitek Y-2240 USB to DVI +.It VideoHome NBdock1920 +.El +.Sh SEE ALSO +.Xr usb 4 Modified: head/sys/conf/files ============================================================================== --- head/sys/conf/files Sun Feb 15 11:37:40 2015 (r278798) +++ head/sys/conf/files Sun Feb 15 12:02:17 2015 (r278799) @@ -2563,6 +2563,10 @@ dev/usb/template/usb_template_mtp.c opti dev/usb/template/usb_template_phone.c optional usb_template dev/usb/template/usb_template_serialnet.c optional usb_template # +# USB video drivers +# +dev/usb/video/udl.c optional udl +# # USB END # dev/videomode/videomode.c optional videomode Modified: head/sys/dev/usb/usbdevs ============================================================================== --- head/sys/dev/usb/usbdevs Sun Feb 15 11:37:40 2015 (r278798) +++ head/sys/dev/usb/usbdevs Sun Feb 15 12:02:17 2015 (r278799) @@ -686,6 +686,7 @@ vendor ASUS2 0x1761 ASUS vendor SWEEX2 0x177f Sweex vendor METAGEEK 0x1781 MetaGeek vendor KAMSTRUP 0x17a8 Kamstrup A/S +vendor DISPLAYLINK 0x17e9 DisplayLink vendor LENOVO 0x17ef Lenovo vendor WAVESENSE 0x17f4 WaveSense vendor VAISALA 0x1843 Vaisala @@ -1655,6 +1656,27 @@ product DLINK2 RT3070_4 0x3c15 RT3070 product DLINK2 RT3070_5 0x3c16 RT3070 product DLINK3 DWM652 0x3e04 DWM-652 +/* DisplayLink products */ +product DISPLAYLINK LCD4300U 0x01ba LCD-4300U +product DISPLAYLINK LCD8000U 0x01bb LCD-8000U +product DISPLAYLINK LD220 0x0100 Samsung LD220 +product DISPLAYLINK GUC2020 0x0059 IOGEAR DVI GUC2020 +product DISPLAYLINK VCUD60 0x0136 Rextron DVI +product DISPLAYLINK CONV 0x0138 StarTech CONV-USB2DVI +product DISPLAYLINK DLDVI 0x0141 DisplayLink DVI +product DISPLAYLINK VGA10 0x015a CMP-USBVGA10 +product DISPLAYLINK WSDVI 0x0198 WS Tech DVI +product DISPLAYLINK EC008 0x019b EasyCAP008 DVI +product DISPLAYLINK HPDOCK 0x01d4 HP USB Docking +product DISPLAYLINK NL571 0x01d7 HP USB DVI +product DISPLAYLINK M01061 0x01e2 Lenovo DVI +product DISPLAYLINK SWDVI 0x024c SUNWEIT DVI +product DISPLAYLINK NBDOCK 0x0215 VideoHome NBdock1920 +product DISPLAYLINK LUM70 0x02a9 Lilliput UM-70 +product DISPLAYLINK UM7X0 0x401a nanovision MiMo +product DISPLAYLINK LT1421 0x03e0 Lenovo ThinkVision LT1421 +product DISPLAYLINK POLARIS2 0x0117 Polaris2 USB dock + /* DMI products */ product DMI CFSM_RW 0xa109 CF/SM Reader/Writer product DMI DISK 0x2bcf Generic Disk Added: head/sys/dev/usb/video/udl.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/dev/usb/video/udl.c Sun Feb 15 12:02:17 2015 (r278799) @@ -0,0 +1,1068 @@ +/* $OpenBSD: udl.c,v 1.81 2014/12/09 07:05:06 doug Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2015 Hans Petter Selasky + * Copyright (c) 2009 Marcus Glocker + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Driver for the "DisplayLink DL-120 / DL-160" graphic chips based on + * the reversed engineered specifications of Florian Echtler + * : + * + * http://floe.butterbrot.org/displaylink/doku.php + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include "usbdevs.h" + +#include + +#include "fb_if.h" + +#undef DPRINTF +#undef DPRINTFN +#define USB_DEBUG_VAR udl_debug +#include + +#ifdef USB_DEBUG +static int udl_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, udl, CTLFLAG_RW, 0, "USB UDL"); + +SYSCTL_INT(_hw_usb_udl, OID_AUTO, debug, CTLFLAG_RWTUN, + &udl_debug, 0, "Debug level"); +#endif + +/* + * Prototypes. + */ +static usb_callback_t udl_bulk_write_callback; + +static device_probe_t udl_probe; +static device_attach_t udl_attach; +static device_detach_t udl_detach; +static fb_getinfo_t udl_fb_getinfo; +#if 0 +static fb_blank_display_t udl_fb_blank_display; +#endif + +static void udl_select_chip(struct udl_softc *, struct usb_attach_arg *); +static int udl_init_chip(struct udl_softc *); +static void udl_select_mode(struct udl_softc *); +static int udl_init_resolution(struct udl_softc *); +static void udl_fbmem_alloc(struct udl_softc *); +static int udl_cmd_write_buf_le16(struct udl_softc *, const uint8_t *, uint32_t, uint8_t, int); +static int udl_cmd_buf_copy_le16(struct udl_softc *, uint32_t, uint32_t, uint8_t, int); +static void udl_cmd_insert_int_1(struct udl_cmd_buf *, uint8_t); +static void udl_cmd_insert_int_3(struct udl_cmd_buf *, uint32_t); +static void udl_cmd_insert_buf_le16(struct udl_cmd_buf *, const uint8_t *, uint32_t); +static void udl_cmd_write_reg_1(struct udl_cmd_buf *, uint8_t, uint8_t); +static void udl_cmd_write_reg_3(struct udl_cmd_buf *, uint8_t, uint32_t); +#if 0 +static int udl_power_save(struct udl_softc *, int, int); +#endif + +static const struct usb_config udl_config[UDL_N_TRANSFER] = { + [UDL_BULK_WRITE_0] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_TX, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,}, + .bufsize = UDL_CMD_MAX_DATA_SIZE * UDL_CMD_MAX_FRAMES, + .callback = &udl_bulk_write_callback, + .frames = UDL_CMD_MAX_FRAMES, + .timeout = 5000, /* 5 seconds */ + }, + [UDL_BULK_WRITE_1] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_TX, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,}, + .bufsize = UDL_CMD_MAX_DATA_SIZE * UDL_CMD_MAX_FRAMES, + .callback = &udl_bulk_write_callback, + .frames = UDL_CMD_MAX_FRAMES, + .timeout = 5000, /* 5 seconds */ + }, +}; + +/* + * Driver glue. + */ +static devclass_t udl_devclass; + +static device_method_t udl_methods[] = { + DEVMETHOD(device_probe, udl_probe), + DEVMETHOD(device_attach, udl_attach), + DEVMETHOD(device_detach, udl_detach), + DEVMETHOD(fb_getinfo, udl_fb_getinfo), +#if 0 + DEVMETHOD(fb_blank_display, udl_fb_blank_display), +#endif + DEVMETHOD_END +}; + +static driver_t udl_driver = { + .name = "udl", + .methods = udl_methods, + .size = sizeof(struct udl_softc), +}; + +DRIVER_MODULE(udl, uhub, udl_driver, udl_devclass, NULL, NULL); +MODULE_DEPEND(udl, usb, 1, 1, 1); +MODULE_DEPEND(udl, fbd, 1, 1, 1); +MODULE_DEPEND(udl, videomode, 1, 1, 1); +MODULE_VERSION(udl, 1); + +/* + * Matching devices. + */ +static const STRUCT_USB_HOST_ID udl_devs[] = { + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LCD4300U, DL120)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LCD8000U, DL120)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_GUC2020, DL160)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LD220, DL165)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_VCUD60, DL160)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_DLDVI, DL160)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_VGA10, DL120)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_WSDVI, DLUNK)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_EC008, DL160)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_HPDOCK, DL160)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_NL571, DL160)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_M01061, DL195)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_NBDOCK, DL165)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_SWDVI, DLUNK)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_UM7X0, DL120)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_CONV, DL160)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LUM70, DL125)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_POLARIS2, DLUNK)}, + {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LT1421, DLUNK)} +}; + +static uint32_t +udl_get_fb_size(struct udl_softc *sc) +{ + unsigned i = sc->sc_cur_mode; + + return ((uint32_t)udl_modes[i].hdisplay * + (uint32_t)udl_modes[i].vdisplay * 2); +} + +static uint32_t +udl_get_fb_width(struct udl_softc *sc) +{ + unsigned i = sc->sc_cur_mode; + + return (udl_modes[i].vdisplay); +} + +static uint32_t +udl_get_fb_height(struct udl_softc *sc) +{ + unsigned i = sc->sc_cur_mode; + + return (udl_modes[i].hdisplay); +} + +static uint32_t +udl_get_fb_hz(struct udl_softc *sc) +{ + unsigned i = sc->sc_cur_mode; + + return (udl_modes[i].hz); +} + +static void +udl_callout(void *arg) +{ + struct udl_softc *sc = arg; + const uint32_t max = udl_get_fb_size(sc); + + if (sc->sc_power_save == 0) { + if (sc->sc_sync_off >= max) + sc->sc_sync_off = 0; + usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_0]); + usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_1]); + } + callout_reset(&sc->sc_callout, hz / 5, &udl_callout, sc); +} + +static int +udl_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if (uaa->info.bConfigIndex != 0) + return (ENXIO); + if (uaa->info.bIfaceIndex != 0) + return (ENXIO); + + return (usbd_lookup_id_by_uaa(udl_devs, sizeof(udl_devs), uaa)); +} + +static int +udl_attach(device_t dev) +{ + struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); + struct sysctl_oid *tree = device_get_sysctl_tree(dev); + struct udl_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + int error; + int i; + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, "UDL lock", NULL, MTX_DEF); + cv_init(&sc->sc_cv, "UDLCV"); + callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); + sc->sc_udev = uaa->device; + + error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, + sc->sc_xfer, udl_config, UDL_N_TRANSFER, sc, &sc->sc_mtx); + + if (error) { + DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error)); + goto detach; + } + usbd_xfer_set_priv(sc->sc_xfer[UDL_BULK_WRITE_0], &sc->sc_xfer_head[0]); + usbd_xfer_set_priv(sc->sc_xfer[UDL_BULK_WRITE_1], &sc->sc_xfer_head[1]); + + TAILQ_INIT(&sc->sc_xfer_head[0]); + TAILQ_INIT(&sc->sc_xfer_head[1]); + TAILQ_INIT(&sc->sc_cmd_buf_free); + TAILQ_INIT(&sc->sc_cmd_buf_pending); + + sc->sc_def_chip = -1; + sc->sc_chip = USB_GET_DRIVER_INFO(uaa); + sc->sc_def_mode = -1; + sc->sc_cur_mode = UDL_MAX_MODES; + + /* Allow chip ID to be overwritten */ + SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "chipid_force", + CTLFLAG_RWTUN, &sc->sc_def_chip, 0, "chip ID"); + + /* Export current chip ID */ + SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "chipid", + CTLFLAG_RD, &sc->sc_chip, 0, "chip ID"); + + if (sc->sc_def_chip > -1 && sc->sc_def_chip <= DLMAX) { + device_printf(dev, "Forcing chip ID to 0x%04x\n", sc->sc_def_chip); + sc->sc_chip = sc->sc_def_chip; + } + /* + * The product might have more than one chip + */ + if (sc->sc_chip == DLUNK) + udl_select_chip(sc, uaa); + + for (i = 0; i != UDL_CMD_MAX_BUFFERS; i++) { + struct udl_cmd_buf *cb = &sc->sc_cmd_buf_temp[i]; + + TAILQ_INSERT_TAIL(&sc->sc_cmd_buf_free, cb, entry); + } + + /* + * Initialize chip. + */ + error = udl_init_chip(sc); + if (error != USB_ERR_NORMAL_COMPLETION) + goto detach; + + /* + * Select edid mode. + */ + udl_select_mode(sc); + + /* Allow default mode to be overwritten */ + SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mode_force", + CTLFLAG_RWTUN, &sc->sc_def_mode, 0, "mode"); + + /* Export current mode */ + SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mode", + CTLFLAG_RD, &sc->sc_cur_mode, 0, "mode"); + + i = sc->sc_def_mode; + if (i > -1 && i < UDL_MAX_MODES) { + if (udl_modes[i].chip <= sc->sc_chip) { + device_printf(dev, "Forcing mode to %d\n", i); + sc->sc_cur_mode = i; + } + } + /* Printout current mode */ + device_printf(dev, "Mode selected %dx%d @ %dHz\n", + (int)udl_get_fb_width(sc), + (int)udl_get_fb_height(sc), + (int)udl_get_fb_hz(sc)); + + udl_init_resolution(sc); + + /* Allocate frame buffer */ + udl_fbmem_alloc(sc); + + UDL_LOCK(sc); + udl_callout(sc); + UDL_UNLOCK(sc); + + sc->sc_fb_info.fb_name = device_get_nameunit(dev); + sc->sc_fb_info.fb_size = sc->sc_fb_size; + sc->sc_fb_info.fb_bpp = 16; + sc->sc_fb_info.fb_depth = 16; + sc->sc_fb_info.fb_width = udl_get_fb_width(sc); + sc->sc_fb_info.fb_height = udl_get_fb_height(sc); + sc->sc_fb_info.fb_stride = sc->sc_fb_info.fb_width * 2; + sc->sc_fb_info.fb_pbase = (uintptr_t)sc->sc_fb_addr; + sc->sc_fb_info.fb_vbase = (uintptr_t)sc->sc_fb_addr; + + sc->sc_fbdev = device_add_child(dev, "fbd", -1); + if (sc->sc_fbdev == NULL) + goto detach; + if (device_probe_and_attach(sc->sc_fbdev) != 0) + goto detach; + + return (0); + +detach: + udl_detach(dev); + + return (ENXIO); +} + +static int +udl_detach(device_t dev) +{ + struct udl_softc *sc = device_get_softc(dev); + + if (sc->sc_fbdev != NULL) { + device_t bdev; + + bdev = sc->sc_fbdev; + sc->sc_fbdev = NULL; + device_detach(bdev); + device_delete_child(dev, bdev); + } + UDL_LOCK(sc); + sc->sc_gone = 1; + callout_stop(&sc->sc_callout); + UDL_UNLOCK(sc); + + usbd_transfer_unsetup(sc->sc_xfer, UDL_N_TRANSFER); + + callout_drain(&sc->sc_callout); + + mtx_destroy(&sc->sc_mtx); + cv_destroy(&sc->sc_cv); + + /* + * Free framebuffer memory, if any. + */ + free(sc->sc_fb_addr, M_DEVBUF); + free(sc->sc_fb_copy, M_DEVBUF); + + return (0); +} + +static struct fb_info * +udl_fb_getinfo(device_t dev) +{ + struct udl_softc *sc = device_get_softc(dev); + + return (&sc->sc_fb_info); +} + +#if 0 +static int +udl_fb_blank_display(device_t dev, int mode) +{ + struct udl_softc *sc = device_get_softc(dev); + + switch (mode) { + case V_DISPLAY_ON: + udl_power_save(sc, 1, M_WAITOK); + break; + case V_DISPLAY_BLANK: + udl_power_save(sc, 1, M_WAITOK); + if (sc->sc_fb_addr != 0) { + const uint32_t max = udl_get_fb_size(sc); + + memset((void *)sc->sc_fb_addr, 0, max); + } + break; + case V_DISPLAY_STAND_BY: + case V_DISPLAY_SUSPEND: + udl_power_save(sc, 0, M_WAITOK); + break; + } + return (0); +} +#endif + +static struct udl_cmd_buf * +udl_cmd_buf_alloc(struct udl_softc *sc, int flags) +{ + struct udl_cmd_buf *cb; + + UDL_LOCK(sc); + while ((cb = TAILQ_FIRST(&sc->sc_cmd_buf_free)) == NULL) { + if (flags != M_WAITOK) + break; + cv_wait(&sc->sc_cv, &sc->sc_mtx); + } + if (cb != NULL) { + TAILQ_REMOVE(&sc->sc_cmd_buf_free, cb, entry); + cb->off = 0; + } + UDL_UNLOCK(sc); + return (cb); +} + +static void +udl_cmd_buf_send(struct udl_softc *sc, struct udl_cmd_buf *cb) +{ + UDL_LOCK(sc); + if (sc->sc_gone) { + TAILQ_INSERT_TAIL(&sc->sc_cmd_buf_free, cb, entry); + } else { + /* mark end of command stack */ + udl_cmd_insert_int_1(cb, UDL_BULK_SOC); + udl_cmd_insert_int_1(cb, UDL_BULK_CMD_EOC); + + TAILQ_INSERT_TAIL(&sc->sc_cmd_buf_pending, cb, entry); + usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_0]); + usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_1]); + } + UDL_UNLOCK(sc); +} + +static struct udl_cmd_buf * +udl_fb_synchronize(struct udl_softc *sc) +{ + const uint32_t max = udl_get_fb_size(sc); + + while (sc->sc_sync_off < max) { + uint32_t delta = max - sc->sc_sync_off; + + if (delta > UDL_CMD_MAX_PIXEL_COUNT * 2) + delta = UDL_CMD_MAX_PIXEL_COUNT * 2; + if (bcmp(sc->sc_fb_addr + sc->sc_sync_off, sc->sc_fb_copy + sc->sc_sync_off, delta) != 0) { + struct udl_cmd_buf *cb; + + cb = udl_cmd_buf_alloc(sc, M_NOWAIT); + if (cb == NULL) + goto done; + memcpy(sc->sc_fb_copy + sc->sc_sync_off, + sc->sc_fb_addr + sc->sc_sync_off, delta); + udl_cmd_insert_int_1(cb, UDL_BULK_SOC); + udl_cmd_insert_int_1(cb, UDL_BULK_CMD_FB_WRITE | UDL_BULK_CMD_FB_WORD); + udl_cmd_insert_int_3(cb, sc->sc_sync_off); + udl_cmd_insert_int_1(cb, delta / 2); + udl_cmd_insert_buf_le16(cb, sc->sc_fb_copy + sc->sc_sync_off, delta); + sc->sc_sync_off += delta; + return (cb); + } else { + sc->sc_sync_off += delta; + } + } +done: + return (NULL); +} + +static void +udl_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct udl_softc *sc = usbd_xfer_softc(xfer); + struct udl_cmd_head *phead = usbd_xfer_get_priv(xfer); + struct udl_cmd_buf *cb; + unsigned i; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + TAILQ_CONCAT(&sc->sc_cmd_buf_free, phead, entry); + case USB_ST_SETUP: +tr_setup: + for (i = 0; i != UDL_CMD_MAX_FRAMES; i++) { + cb = TAILQ_FIRST(&sc->sc_cmd_buf_pending); + if (cb == NULL) { + cb = udl_fb_synchronize(sc); + if (cb == NULL) + break; + } + TAILQ_REMOVE(&sc->sc_cmd_buf_pending, cb, entry); + TAILQ_INSERT_TAIL(phead, cb, entry); + usbd_xfer_set_frame_data(xfer, i, cb->buf, cb->off); + } + if (i != 0) { + usbd_xfer_set_frames(xfer, i); + usbd_transfer_submit(xfer); + } + break; + default: + TAILQ_CONCAT(&sc->sc_cmd_buf_free, phead, entry); + if (error != USB_ERR_CANCELLED) { + /* try clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } + /* wakeup any waiters */ + cv_signal(&sc->sc_cv); +} + +#if 0 +static int +udl_power_save(struct udl_softc *sc, int on, int flags) +{ + struct udl_cmd_buf *cb; + + /* get new buffer */ + cb = udl_cmd_buf_alloc(sc, flags); + if (cb == NULL) + return (EAGAIN); + + DPRINTF("screen %s\n", on ? "ON" : "OFF"); + + sc->sc_power_save = on ? 0 : 1; + + if (on) + udl_cmd_write_reg_1(cb, UDL_REG_SCREEN, UDL_REG_SCREEN_ON); + else + udl_cmd_write_reg_1(cb, UDL_REG_SCREEN, UDL_REG_SCREEN_OFF); + + udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff); + udl_cmd_buf_send(sc, cb); + return (0); +} +#endif + +static int +udl_ctrl_msg(struct udl_softc *sc, uint8_t rt, uint8_t r, + uint16_t index, uint16_t value, uint8_t *buf, size_t len) +{ + usb_device_request_t req; + int error; + + req.bmRequestType = rt; + req.bRequest = r; + USETW(req.wIndex, index); + USETW(req.wValue, value); + USETW(req.wLength, len); + + error = usbd_do_request_flags(sc->sc_udev, NULL, + &req, buf, 0, NULL, USB_DEFAULT_TIMEOUT); + + DPRINTF("%s\n", usbd_errstr(error)); + + return (error); +} + +static int +udl_poll(struct udl_softc *sc, uint32_t *buf) +{ + uint32_t lbuf; + int error; + + error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, + UDL_CTRL_CMD_POLL, 0x0000, 0x0000, (uint8_t *)&lbuf, sizeof(lbuf)); + if (error == USB_ERR_NORMAL_COMPLETION) + *buf = le32toh(lbuf); + return (error); +} + +static int +udl_read_1(struct udl_softc *sc, uint16_t addr, uint8_t *buf) +{ + uint8_t lbuf[1]; + int error; + + error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, + UDL_CTRL_CMD_READ_1, addr, 0x0000, lbuf, 1); + if (error == USB_ERR_NORMAL_COMPLETION) + *buf = *(uint8_t *)lbuf; + return (error); +} + +static int +udl_write_1(struct udl_softc *sc, uint16_t addr, uint8_t buf) +{ + int error; + + error = udl_ctrl_msg(sc, UT_WRITE_VENDOR_DEVICE, + UDL_CTRL_CMD_WRITE_1, addr, 0x0000, &buf, 1); + return (error); +} + +static int +udl_read_edid(struct udl_softc *sc, uint8_t *buf) +{ + uint8_t lbuf[64]; + uint16_t offset; + int error; + + offset = 0; + + error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, + UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 64); + if (error != USB_ERR_NORMAL_COMPLETION) + goto fail; + bcopy(lbuf + 1, buf + offset, 63); + offset += 63; + + error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, + UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 64); + if (error != USB_ERR_NORMAL_COMPLETION) + goto fail; + bcopy(lbuf + 1, buf + offset, 63); + offset += 63; + + error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE, + UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 3); + if (error != USB_ERR_NORMAL_COMPLETION) + goto fail; + bcopy(lbuf + 1, buf + offset, 2); +fail: + return (error); +} + +static uint8_t +udl_lookup_mode(uint16_t hdisplay, uint16_t vdisplay, uint8_t hz, + uint16_t chip, uint32_t clock) +{ + uint8_t idx; + + /* + * Check first if we have a matching mode with pixelclock + */ + for (idx = 0; idx != UDL_MAX_MODES; idx++) { + if ((udl_modes[idx].hdisplay == hdisplay) && + (udl_modes[idx].vdisplay == vdisplay) && + (udl_modes[idx].clock == clock) && + (udl_modes[idx].chip <= chip)) { + return (idx); + } + } + + /* + * If not, check for matching mode with update frequency + */ + for (idx = 0; idx != UDL_MAX_MODES; idx++) { + if ((udl_modes[idx].hdisplay == hdisplay) && + (udl_modes[idx].vdisplay == vdisplay) && + (udl_modes[idx].hz == hz) && + (udl_modes[idx].chip <= chip)) { + return (idx); + } + } + return (idx); +} + +static void +udl_select_chip(struct udl_softc *sc, struct usb_attach_arg *uaa) +{ + const char *pserial; + + pserial = usb_get_serial(uaa->device); + + sc->sc_chip = DL120; + + if ((uaa->info.idVendor == USB_VENDOR_DISPLAYLINK) && + (uaa->info.idProduct == USB_PRODUCT_DISPLAYLINK_WSDVI)) { + + /* + * WS Tech DVI is DL120 or DL160. All deviced uses the + * same revision (0.04) so iSerialNumber must be used + * to determin which chip it is. + */ + + if (strlen(pserial) > 7) { + if (strncmp(pserial, "0198-13", 7) == 0) + sc->sc_chip = DL160; + } + DPRINTF("iSerialNumber (%s) used to select chip (%d)\n", + pserial, sc->sc_chip); + } + if ((uaa->info.idVendor == USB_VENDOR_DISPLAYLINK) && + (uaa->info.idProduct == USB_PRODUCT_DISPLAYLINK_SWDVI)) { + + /* + * SUNWEIT DVI is DL160, DL125, DL165 or DL195. Major revision + * can be used to differ between DL1x0 and DL1x5. Minor to + * differ between DL1x5. iSerialNumber seems not to be uniqe. + */ + + sc->sc_chip = DL160; + + if (uaa->info.bcdDevice >= 0x100) { + sc->sc_chip = DL165; + if (uaa->info.bcdDevice == 0x104) + sc->sc_chip = DL195; + if (uaa->info.bcdDevice == 0x108) + sc->sc_chip = DL125; + } + DPRINTF("bcdDevice (%02x) used to select chip (%d)\n", + uaa->info.bcdDevice, sc->sc_chip); + } +} + +static int +udl_set_enc_key(struct udl_softc *sc, uint8_t *buf, uint8_t len) +{ + int error; + + error = udl_ctrl_msg(sc, UT_WRITE_VENDOR_DEVICE, + UDL_CTRL_CMD_SET_KEY, 0x0000, 0x0000, buf, len); + return (error); +} + +static void +udl_fbmem_alloc(struct udl_softc *sc) +{ + uint32_t size; + + size = udl_get_fb_size(sc); + size = round_page(size); + + sc->sc_fb_addr = malloc(size, M_DEVBUF, M_WAITOK | M_ZERO); + sc->sc_fb_copy = malloc(size, M_DEVBUF, M_WAITOK | M_ZERO); + sc->sc_fb_size = size; +} + +static void +udl_cmd_insert_int_1(struct udl_cmd_buf *cb, uint8_t value) +{ + + cb->buf[cb->off] = value; + cb->off += 1; +} + +#if 0 +static void +udl_cmd_insert_int_2(struct udl_cmd_buf *cb, uint16_t value) +{ + uint16_t lvalue; + + lvalue = htobe16(value); + bcopy(&lvalue, cb->buf + cb->off, 2); + + cb->off += 2; +} + +#endif + +static void +udl_cmd_insert_int_3(struct udl_cmd_buf *cb, uint32_t value) +{ + uint32_t lvalue; + +#if BYTE_ORDER == BIG_ENDIAN + lvalue = htobe32(value) << 8; +#else + lvalue = htobe32(value) >> 8; +#endif + bcopy(&lvalue, cb->buf + cb->off, 3); + + cb->off += 3; +} + +#if 0 +static void +udl_cmd_insert_int_4(struct udl_cmd_buf *cb, uint32_t value) +{ + uint32_t lvalue; + + lvalue = htobe32(value); + bcopy(&lvalue, cb->buf + cb->off, 4); + + cb->off += 4; +} + +#endif + +static void +udl_cmd_insert_buf_le16(struct udl_cmd_buf *cb, const uint8_t *buf, uint32_t len) +{ + uint32_t x; + + for (x = 0; x != len; x += 2) { + /* byte swap from little endian to big endian */ + cb->buf[cb->off + x + 0] = buf[x + 1]; + cb->buf[cb->off + x + 1] = buf[x + 0]; + } + cb->off += len; +} + +static void +udl_cmd_write_reg_1(struct udl_cmd_buf *cb, uint8_t reg, uint8_t val) +{ + + udl_cmd_insert_int_1(cb, UDL_BULK_SOC); + udl_cmd_insert_int_1(cb, UDL_BULK_CMD_REG_WRITE_1); + udl_cmd_insert_int_1(cb, reg); + udl_cmd_insert_int_1(cb, val); +} + +static void +udl_cmd_write_reg_3(struct udl_cmd_buf *cb, uint8_t reg, uint32_t val) +{ + + udl_cmd_write_reg_1(cb, reg + 0, (val >> 16) & 0xff); + udl_cmd_write_reg_1(cb, reg + 1, (val >> 8) & 0xff); + udl_cmd_write_reg_1(cb, reg + 2, (val >> 0) & 0xff); +} + +static int +udl_init_chip(struct udl_softc *sc) +{ + uint32_t ui32; + uint8_t ui8; + int error; + + error = udl_poll(sc, &ui32); + if (error != USB_ERR_NORMAL_COMPLETION) + return (error); + DPRINTF("poll=0x%08x\n", ui32); + + /* Some products may use later chip too */ + switch (ui32 & 0xff) { + case 0xf1: /* DL1x5 */ + switch (sc->sc_chip) { + case DL120: + sc->sc_chip = DL125; *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***