diff --git a/sys/arm/freescale/vybrid/vf_ehci.c b/sys/arm/freescale/vybrid/vf_ehci.c index a3477c743997..3a8b48008449 100644 --- a/sys/arm/freescale/vybrid/vf_ehci.c +++ b/sys/arm/freescale/vybrid/vf_ehci.c @@ -1,424 +1,424 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Ruslan Bukin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Vybrid Family Universal Serial Bus (USB) Controller * Chapter 44-45, Vybrid Reference Manual, Rev. 5, 07/2013 */ #include #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "opt_platform.h" #define ENUTMILEVEL3 (1 << 15) #define ENUTMILEVEL2 (1 << 14) #define GPIO_USB_PWR 134 #define USB_ID 0x000 /* Identification register */ #define USB_HWGENERAL 0x004 /* Hardware General */ #define USB_HWHOST 0x008 /* Host Hardware Parameters */ #define USB_HWDEVICE 0x00C /* Device Hardware Parameters */ #define USB_HWTXBUF 0x010 /* TX Buffer Hardware Parameters */ #define USB_HWRXBUF 0x014 /* RX Buffer Hardware Parameters */ #define USB_HCSPARAMS 0x104 /* Host Controller Structural Parameters */ #define USBPHY_PWD 0x00 /* PHY Power-Down Register */ #define USBPHY_PWD_SET 0x04 /* PHY Power-Down Register */ #define USBPHY_PWD_CLR 0x08 /* PHY Power-Down Register */ #define USBPHY_PWD_TOG 0x0C /* PHY Power-Down Register */ #define USBPHY_TX 0x10 /* PHY Transmitter Control Register */ #define USBPHY_RX 0x20 /* PHY Receiver Control Register */ #define USBPHY_RX_SET 0x24 /* PHY Receiver Control Register */ #define USBPHY_RX_CLR 0x28 /* PHY Receiver Control Register */ #define USBPHY_RX_TOG 0x2C /* PHY Receiver Control Register */ #define USBPHY_CTRL 0x30 /* PHY General Control Register */ #define USBPHY_CTRL_SET 0x34 /* PHY General Control Register */ #define USBPHY_CTRL_CLR 0x38 /* PHY General Control Register */ #define USBPHY_CTRL_TOG 0x3C /* PHY General Control Register */ #define USBPHY_STATUS 0x40 /* PHY Status Register */ #define USBPHY_DEBUG 0x50 /* PHY Debug Register */ #define USBPHY_DEBUG_SET 0x54 /* PHY Debug Register */ #define USBPHY_DEBUG_CLR 0x58 /* PHY Debug Register */ #define USBPHY_DEBUG_TOG 0x5C /* PHY Debug Register */ #define USBPHY_DEBUG0_STATUS 0x60 /* UTMI Debug Status Register 0 */ #define USBPHY_DEBUG1 0x70 /* UTMI Debug Status Register 1 */ #define USBPHY_DEBUG1_SET 0x74 /* UTMI Debug Status Register 1 */ #define USBPHY_DEBUG1_CLR 0x78 /* UTMI Debug Status Register 1 */ #define USBPHY_DEBUG1_TOG 0x7C /* UTMI Debug Status Register 1 */ #define USBPHY_VERSION 0x80 /* UTMI RTL Version */ #define USBPHY_IP 0x90 /* PHY IP Block Register */ #define USBPHY_IP_SET 0x94 /* PHY IP Block Register */ #define USBPHY_IP_CLR 0x98 /* PHY IP Block Register */ #define USBPHY_IP_TOG 0x9C /* PHY IP Block Register */ #define USBPHY_CTRL_SFTRST (1U << 31) #define USBPHY_CTRL_CLKGATE (1 << 30) #define USBPHY_DEBUG_CLKGATE (1 << 30) #define PHY_READ4(_sc, _reg) \ bus_space_read_4(_sc->bst_phy, _sc->bsh_phy, _reg) #define PHY_WRITE4(_sc, _reg, _val) \ bus_space_write_4(_sc->bst_phy, _sc->bsh_phy, _reg, _val) #define USBC_READ4(_sc, _reg) \ bus_space_read_4(_sc->bst_usbc, _sc->bsh_usbc, _reg) #define USBC_WRITE4(_sc, _reg, _val) \ bus_space_write_4(_sc->bst_usbc, _sc->bsh_usbc, _reg, _val) /* Forward declarations */ static int vybrid_ehci_attach(device_t dev); static int vybrid_ehci_detach(device_t dev); static int vybrid_ehci_probe(device_t dev); struct vybrid_ehci_softc { ehci_softc_t base; device_t dev; struct resource *res[6]; bus_space_tag_t bst_phy; bus_space_handle_t bsh_phy; bus_space_tag_t bst_usbc; bus_space_handle_t bsh_usbc; }; static struct resource_spec vybrid_ehci_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_MEMORY, 1, RF_ACTIVE }, { SYS_RES_MEMORY, 2, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, vybrid_ehci_probe), DEVMETHOD(device_attach, vybrid_ehci_attach), DEVMETHOD(device_detach, vybrid_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), { 0, 0 } }; /* kobj_class definition */ static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(ehci_softc_t) }; DRIVER_MODULE(vybrid_ehci, simplebus, ehci_driver, 0, 0); MODULE_DEPEND(vybrid_ehci, usb, 1, 1, 1); static void vybrid_ehci_post_reset(struct ehci_softc *ehci_softc) { uint32_t usbmode; /* Force HOST mode */ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM); usbmode &= ~EHCI_UM_CM; usbmode |= EHCI_UM_CM_HOST; EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode); } /* * Public methods */ static int vybrid_ehci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_is_compatible(dev, "fsl,mvf600-usb-ehci") == 0) return (ENXIO); device_set_desc(dev, "Vybrid Family integrated USB controller"); return (BUS_PROBE_DEFAULT); } static int phy_init(struct vybrid_ehci_softc *esc) { device_t sc_gpio_dev; int reg; /* Reset phy */ reg = PHY_READ4(esc, USBPHY_CTRL); reg |= (USBPHY_CTRL_SFTRST); PHY_WRITE4(esc, USBPHY_CTRL, reg); /* Minimum reset time */ DELAY(10000); reg &= ~(USBPHY_CTRL_SFTRST | USBPHY_CTRL_CLKGATE); PHY_WRITE4(esc, USBPHY_CTRL, reg); reg = (ENUTMILEVEL2 | ENUTMILEVEL3); PHY_WRITE4(esc, USBPHY_CTRL_SET, reg); /* Get the GPIO device, we need this to give power to USB */ sc_gpio_dev = devclass_get_device(devclass_find("gpio"), 0); if (sc_gpio_dev == NULL) { device_printf(esc->dev, "Error: failed to get the GPIO dev\n"); return (1); } /* Give power to USB */ GPIO_PIN_SETFLAGS(sc_gpio_dev, GPIO_USB_PWR, GPIO_PIN_OUTPUT); GPIO_PIN_SET(sc_gpio_dev, GPIO_USB_PWR, GPIO_PIN_HIGH); /* Power up PHY */ PHY_WRITE4(esc, USBPHY_PWD, 0x00); /* Ungate clocks */ reg = PHY_READ4(esc, USBPHY_DEBUG); reg &= ~(USBPHY_DEBUG_CLKGATE); PHY_WRITE4(esc, USBPHY_DEBUG, reg); #if 0 printf("USBPHY_CTRL == 0x%08x\n", PHY_READ4(esc, USBPHY_CTRL)); printf("USBPHY_IP == 0x%08x\n", PHY_READ4(esc, USBPHY_IP)); printf("USBPHY_STATUS == 0x%08x\n", PHY_READ4(esc, USBPHY_STATUS)); printf("USBPHY_DEBUG == 0x%08x\n", PHY_READ4(esc, USBPHY_DEBUG)); printf("USBPHY_DEBUG0_STATUS == 0x%08x\n", PHY_READ4(esc, USBPHY_DEBUG0_STATUS)); printf("USBPHY_DEBUG1 == 0x%08x\n", PHY_READ4(esc, USBPHY_DEBUG1)); #endif return (0); } static int vybrid_ehci_attach(device_t dev) { struct vybrid_ehci_softc *esc; ehci_softc_t *sc; bus_space_handle_t bsh; int err; int reg; esc = device_get_softc(dev); esc->dev = dev; sc = &esc->base; sc->sc_bus.parent = dev; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; if (bus_alloc_resources(dev, vybrid_ehci_spec, esc->res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } /* EHCI registers */ sc->sc_io_tag = rman_get_bustag(esc->res[0]); bsh = rman_get_bushandle(esc->res[0]); sc->sc_io_size = rman_get_size(esc->res[0]); esc->bst_usbc = rman_get_bustag(esc->res[1]); esc->bsh_usbc = rman_get_bushandle(esc->res[1]); esc->bst_phy = rman_get_bustag(esc->res[2]); esc->bsh_phy = rman_get_bushandle(esc->res[2]); /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc)) return (ENXIO); #if 0 printf("USBx_HCSPARAMS is 0x%08x\n", bus_space_read_4(sc->sc_io_tag, bsh, USB_HCSPARAMS)); printf("USB_ID == 0x%08x\n", bus_space_read_4(sc->sc_io_tag, bsh, USB_ID)); printf("USB_HWGENERAL == 0x%08x\n", bus_space_read_4(sc->sc_io_tag, bsh, USB_HWGENERAL)); printf("USB_HWHOST == 0x%08x\n", bus_space_read_4(sc->sc_io_tag, bsh, USB_HWHOST)); printf("USB_HWDEVICE == 0x%08x\n", bus_space_read_4(sc->sc_io_tag, bsh, USB_HWDEVICE)); printf("USB_HWTXBUF == 0x%08x\n", bus_space_read_4(sc->sc_io_tag, bsh, USB_HWTXBUF)); printf("USB_HWRXBUF == 0x%08x\n", bus_space_read_4(sc->sc_io_tag, bsh, USB_HWRXBUF)); #endif if (phy_init(esc)) { device_printf(dev, "Could not setup PHY\n"); return (1); } /* * Set handle to USB related registers subregion used by * generic EHCI driver. */ err = bus_space_subregion(sc->sc_io_tag, bsh, 0x100, sc->sc_io_size, &sc->sc_io_hdl); if (err != 0) return (ENXIO); /* Setup interrupt handler */ err = bus_setup_intr(dev, esc->res[3], INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(dev, "Could not setup irq, " "%d\n", err); return (1); } /* Add USB device */ sc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(dev, "Could not add USB device\n"); err = bus_teardown_intr(dev, esc->res[5], sc->sc_intr_hdl); if (err) device_printf(dev, "Could not tear down irq," " %d\n", err); return (1); } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); strlcpy(sc->sc_vendor, "Freescale", sizeof(sc->sc_vendor)); /* Set host mode */ reg = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, 0xA8); reg |= 0x3; bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, 0xA8, reg); /* Set flags and callbacks*/ sc->sc_flags |= EHCI_SCFLG_TT | EHCI_SCFLG_NORESTERM; sc->sc_vendor_post_reset = vybrid_ehci_post_reset; sc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc; err = ehci_init(sc); if (!err) { sc->sc_flags |= EHCI_SCFLG_DONEINIT; err = device_probe_and_attach(sc->sc_bus.bdev); } else { device_printf(dev, "USB init failed err=%d\n", err); device_delete_child(dev, sc->sc_bus.bdev); sc->sc_bus.bdev = NULL; err = bus_teardown_intr(dev, esc->res[5], sc->sc_intr_hdl); if (err) device_printf(dev, "Could not tear down irq," " %d\n", err); return (1); } return (0); } static int vybrid_ehci_detach(device_t dev) { struct vybrid_ehci_softc *esc; ehci_softc_t *sc; int err; esc = device_get_softc(dev); sc = &esc->base; /* First detach all children; we can't detach if that fails. */ - if ((err = device_delete_children(dev)) != 0) + if ((err = bus_generic_detach(dev)) != 0) return (err); /* * only call ehci_detach() after ehci_init() */ if (sc->sc_flags & EHCI_SCFLG_DONEINIT) { ehci_detach(sc); sc->sc_flags &= ~EHCI_SCFLG_DONEINIT; } /* * Disable interrupts that might have been switched on in * ehci_init. */ if (sc->sc_io_tag && sc->sc_io_hdl) bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, EHCI_USBINTR, 0); if (esc->res[5] && sc->sc_intr_hdl) { err = bus_teardown_intr(dev, esc->res[5], sc->sc_intr_hdl); if (err) { device_printf(dev, "Could not tear down irq," " %d\n", err); return (err); } sc->sc_intr_hdl = NULL; } usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); bus_release_resources(dev, vybrid_ehci_spec, esc->res); return (0); } diff --git a/sys/arm/mv/a37x0_spi.c b/sys/arm/mv/a37x0_spi.c index bc47da2f3e6e..027dd57677a3 100644 --- a/sys/arm/mv/a37x0_spi.c +++ b/sys/arm/mv/a37x0_spi.c @@ -1,490 +1,490 @@ /*- * Copyright (c) 2018, 2019 Rubicon Communications, LLC (Netgate) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spibus_if.h" struct a37x0_spi_softc { device_t sc_dev; struct mtx sc_mtx; struct resource *sc_mem_res; struct resource *sc_irq_res; struct spi_command *sc_cmd; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; uint32_t sc_len; uint32_t sc_maxfreq; uint32_t sc_read; uint32_t sc_flags; uint32_t sc_written; void *sc_intrhand; }; #define A37X0_SPI_WRITE(_sc, _off, _val) \ bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off), (_val)) #define A37X0_SPI_READ(_sc, _off) \ bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off)) #define A37X0_SPI_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define A37X0_SPI_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define A37X0_SPI_BUSY (1 << 0) /* * While the A3700 utils from Marvell usually sets the QSF clock to 200MHz, * there is no guarantee that it is correct without the proper clock framework * to retrieve the actual TBG and PLL settings. */ #define A37X0_SPI_CLOCK 200000000 /* QSF Clock 200MHz */ #define A37X0_SPI_CONTROL 0x0 #define A37X0_SPI_CS_SHIFT 16 #define A37X0_SPI_CS_MASK (0xf << A37X0_SPI_CS_SHIFT) #define A37X0_SPI_CONF 0x4 #define A37X0_SPI_WFIFO_THRS_SHIFT 28 #define A37X0_SPI_RFIFO_THRS_SHIFT 24 #define A37X0_SPI_AUTO_CS_EN (1 << 20) #define A37X0_SPI_DMA_WR_EN (1 << 19) #define A37X0_SPI_DMA_RD_EN (1 << 18) #define A37X0_SPI_FIFO_MODE (1 << 17) #define A37X0_SPI_SRST (1 << 16) #define A37X0_SPI_XFER_START (1 << 15) #define A37X0_SPI_XFER_STOP (1 << 14) #define A37X0_SPI_INSTR_PIN (1 << 13) #define A37X0_SPI_ADDR_PIN (1 << 12) #define A37X0_SPI_DATA_PIN_MASK 0x3 #define A37X0_SPI_DATA_PIN_SHIFT 10 #define A37X0_SPI_FIFO_FLUSH (1 << 9) #define A37X0_SPI_RW_EN (1 << 8) #define A37X0_SPI_CLK_POL (1 << 7) #define A37X0_SPI_CLK_PHASE (1 << 6) #define A37X0_SPI_BYTE_LEN (1 << 5) #define A37X0_SPI_PSC_MASK 0x1f #define A37X0_SPI_DATA_OUT 0x8 #define A37X0_SPI_DATA_IN 0xc #define A37X0_SPI_INTR_STAT 0x28 #define A37X0_SPI_INTR_MASK 0x2c #define A37X0_SPI_RDY (1 << 1) #define A37X0_SPI_XFER_DONE (1 << 0) static struct ofw_compat_data compat_data[] = { { "marvell,armada-3700-spi", 1 }, { NULL, 0 } }; static void a37x0_spi_intr(void *); static int a37x0_spi_wait(struct a37x0_spi_softc *sc, int timeout, uint32_t reg, uint32_t mask) { int i; for (i = 0; i < timeout; i++) { if ((A37X0_SPI_READ(sc, reg) & mask) == 0) return (0); DELAY(100); } return (ETIMEDOUT); } static int a37x0_spi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Armada 37x0 SPI controller"); return (BUS_PROBE_DEFAULT); } static int a37x0_spi_attach(device_t dev) { int err, rid; pcell_t maxfreq; struct a37x0_spi_softc *sc; uint32_t reg; sc = device_get_softc(dev); sc->sc_dev = dev; rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_mem_res) { device_printf(dev, "cannot allocate memory window\n"); return (ENXIO); } sc->sc_bst = rman_get_bustag(sc->sc_mem_res); sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (!sc->sc_irq_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); device_printf(dev, "cannot allocate interrupt\n"); return (ENXIO); } /* Make sure that no CS is asserted. */ reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL); A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, reg & ~A37X0_SPI_CS_MASK); /* Reset FIFO. */ reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF); A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg | A37X0_SPI_FIFO_FLUSH); err = a37x0_spi_wait(sc, 20, A37X0_SPI_CONF, A37X0_SPI_FIFO_FLUSH); if (err != 0) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); device_printf(dev, "cannot flush the controller fifo.\n"); return (ENXIO); } /* Reset the Controller. */ reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF); A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg | A37X0_SPI_SRST); DELAY(1000); /* Enable the single byte IO, disable FIFO. */ reg &= ~(A37X0_SPI_FIFO_MODE | A37X0_SPI_BYTE_LEN); A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg); /* Disable and clear interrupts. */ A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_MASK, 0); reg = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT); A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, reg); /* Hook up our interrupt handler. */ if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, a37x0_spi_intr, sc, &sc->sc_intrhand)) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); device_printf(dev, "cannot setup the interrupt handler\n"); return (ENXIO); } mtx_init(&sc->sc_mtx, "a37x0_spi", NULL, MTX_DEF); /* Read the controller max-frequency. */ if (OF_getencprop(ofw_bus_get_node(dev), "spi-max-frequency", &maxfreq, sizeof(maxfreq)) == -1) maxfreq = 0; sc->sc_maxfreq = maxfreq; device_add_child(dev, "spibus", DEVICE_UNIT_ANY); /* Probe and attach the spibus when interrupts are available. */ bus_delayed_attach_children(dev); return (0); } static int a37x0_spi_detach(device_t dev) { int err; struct a37x0_spi_softc *sc; - if ((err = device_delete_children(dev)) != 0) + if ((err = bus_generic_detach(dev)) != 0) return (err); sc = device_get_softc(dev); mtx_destroy(&sc->sc_mtx); if (sc->sc_intrhand) bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intrhand); if (sc->sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); return (0); } static __inline void a37x0_spi_rx_byte(struct a37x0_spi_softc *sc) { struct spi_command *cmd; uint32_t read; uint8_t *p; if (sc->sc_read == sc->sc_len) return; cmd = sc->sc_cmd; p = (uint8_t *)cmd->rx_cmd; read = sc->sc_read++; if (read >= cmd->rx_cmd_sz) { p = (uint8_t *)cmd->rx_data; read -= cmd->rx_cmd_sz; } p[read] = A37X0_SPI_READ(sc, A37X0_SPI_DATA_IN) & 0xff; } static __inline void a37x0_spi_tx_byte(struct a37x0_spi_softc *sc) { struct spi_command *cmd; uint32_t written; uint8_t *p; if (sc->sc_written == sc->sc_len) return; cmd = sc->sc_cmd; p = (uint8_t *)cmd->tx_cmd; written = sc->sc_written++; if (written >= cmd->tx_cmd_sz) { p = (uint8_t *)cmd->tx_data; written -= cmd->tx_cmd_sz; } A37X0_SPI_WRITE(sc, A37X0_SPI_DATA_OUT, p[written]); } static __inline void a37x0_spi_set_clock(struct a37x0_spi_softc *sc, uint32_t clock) { uint32_t psc, reg; if (sc->sc_maxfreq > 0 && clock > sc->sc_maxfreq) clock = sc->sc_maxfreq; psc = A37X0_SPI_CLOCK / clock; if ((A37X0_SPI_CLOCK % clock) > 0) psc++; reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF); reg &= ~A37X0_SPI_PSC_MASK; reg |= psc & A37X0_SPI_PSC_MASK; A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg); } static __inline void a37x0_spi_set_pins(struct a37x0_spi_softc *sc, uint32_t npins) { uint32_t reg; /* Sets single, dual or quad SPI mode. */ reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF); reg &= ~(A37X0_SPI_DATA_PIN_MASK << A37X0_SPI_DATA_PIN_SHIFT); reg |= (npins / 2) << A37X0_SPI_DATA_PIN_SHIFT; reg |= A37X0_SPI_INSTR_PIN | A37X0_SPI_ADDR_PIN; A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg); } static __inline void a37x0_spi_set_mode(struct a37x0_spi_softc *sc, uint32_t mode) { uint32_t reg; reg = A37X0_SPI_READ(sc, A37X0_SPI_CONF); switch (mode) { case 0: reg &= ~(A37X0_SPI_CLK_PHASE | A37X0_SPI_CLK_POL); break; case 1: reg &= ~A37X0_SPI_CLK_POL; reg |= A37X0_SPI_CLK_PHASE; break; case 2: reg &= ~A37X0_SPI_CLK_PHASE; reg |= A37X0_SPI_CLK_POL; break; case 3: reg |= (A37X0_SPI_CLK_PHASE | A37X0_SPI_CLK_POL); break; } A37X0_SPI_WRITE(sc, A37X0_SPI_CONF, reg); } static void a37x0_spi_intr(void *arg) { struct a37x0_spi_softc *sc; uint32_t status; sc = (struct a37x0_spi_softc *)arg; A37X0_SPI_LOCK(sc); /* Filter stray interrupts. */ if ((sc->sc_flags & A37X0_SPI_BUSY) == 0) { A37X0_SPI_UNLOCK(sc); return; } status = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT); if (status & A37X0_SPI_XFER_DONE) a37x0_spi_rx_byte(sc); /* Clear the interrupt status. */ A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, status); /* Check for end of transfer. */ if (sc->sc_written == sc->sc_len && sc->sc_read == sc->sc_len) wakeup(sc->sc_dev); else a37x0_spi_tx_byte(sc); A37X0_SPI_UNLOCK(sc); } static int a37x0_spi_transfer(device_t dev, device_t child, struct spi_command *cmd) { int timeout; struct a37x0_spi_softc *sc; uint32_t clock, cs, mode, reg; KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz, ("TX/RX command sizes should be equal")); KASSERT(cmd->tx_data_sz == cmd->rx_data_sz, ("TX/RX data sizes should be equal")); /* Get the proper data for this child. */ spibus_get_cs(child, &cs); cs &= ~SPIBUS_CS_HIGH; if (cs > 3) { device_printf(dev, "Invalid CS %d requested by %s\n", cs, device_get_nameunit(child)); return (EINVAL); } spibus_get_clock(child, &clock); if (clock == 0) { device_printf(dev, "Invalid clock %uHz requested by %s\n", clock, device_get_nameunit(child)); return (EINVAL); } spibus_get_mode(child, &mode); if (mode > 3) { device_printf(dev, "Invalid mode %u requested by %s\n", mode, device_get_nameunit(child)); return (EINVAL); } sc = device_get_softc(dev); A37X0_SPI_LOCK(sc); /* Wait until the controller is free. */ while (sc->sc_flags & A37X0_SPI_BUSY) mtx_sleep(dev, &sc->sc_mtx, 0, "a37x0_spi", 0); /* Now we have control over SPI controller. */ sc->sc_flags = A37X0_SPI_BUSY; /* Set transfer mode and clock. */ a37x0_spi_set_mode(sc, mode); a37x0_spi_set_pins(sc, 1); a37x0_spi_set_clock(sc, clock); /* Set CS. */ A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, 1 << (A37X0_SPI_CS_SHIFT + cs)); /* Save a pointer to the SPI command. */ sc->sc_cmd = cmd; sc->sc_read = 0; sc->sc_written = 0; sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz; /* Clear interrupts. */ reg = A37X0_SPI_READ(sc, A37X0_SPI_INTR_STAT); A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_STAT, reg); while ((sc->sc_len - sc->sc_written) > 0) { /* * Write to start the transmission and read the byte * back when ready. */ a37x0_spi_tx_byte(sc); timeout = 1000; while (--timeout > 0) { reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL); if (reg & A37X0_SPI_XFER_DONE) break; DELAY(1); } if (timeout == 0) break; a37x0_spi_rx_byte(sc); } /* Stop the controller. */ reg = A37X0_SPI_READ(sc, A37X0_SPI_CONTROL); A37X0_SPI_WRITE(sc, A37X0_SPI_CONTROL, reg & ~A37X0_SPI_CS_MASK); A37X0_SPI_WRITE(sc, A37X0_SPI_INTR_MASK, 0); /* Release the controller and wakeup the next thread waiting for it. */ sc->sc_flags = 0; wakeup_one(dev); A37X0_SPI_UNLOCK(sc); return ((timeout == 0) ? EIO : 0); } static phandle_t a37x0_spi_get_node(device_t bus, device_t dev) { return (ofw_bus_get_node(bus)); } static device_method_t a37x0_spi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, a37x0_spi_probe), DEVMETHOD(device_attach, a37x0_spi_attach), DEVMETHOD(device_detach, a37x0_spi_detach), /* SPI interface */ DEVMETHOD(spibus_transfer, a37x0_spi_transfer), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, a37x0_spi_get_node), DEVMETHOD_END }; static driver_t a37x0_spi_driver = { "spi", a37x0_spi_methods, sizeof(struct a37x0_spi_softc), }; DRIVER_MODULE(a37x0_spi, simplebus, a37x0_spi_driver, 0, 0); diff --git a/sys/arm/nvidia/tegra_ehci.c b/sys/arm/nvidia/tegra_ehci.c index 15f086a6c3c0..59bf8646385a 100644 --- a/sys/arm/nvidia/tegra_ehci.c +++ b/sys/arm/nvidia/tegra_ehci.c @@ -1,315 +1,315 @@ /*- * Copyright (c) 2016 Michal Meloun * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include /* * EHCI driver for Tegra SoCs. */ #include "opt_bus.h" #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define TEGRA_EHCI_REG_OFF 0x100 #define TEGRA_EHCI_REG_SIZE 0x100 /* Compatible devices. */ #define TEGRA124_EHCI 1 #define TEGRA210_EHCI 2 static struct ofw_compat_data compat_data[] = { {"nvidia,tegra124-ehci", (uintptr_t)TEGRA124_EHCI}, {"nvidia,tegra210-ehci", (uintptr_t)TEGRA210_EHCI}, {NULL, 0}, }; struct tegra_ehci_softc { ehci_softc_t ehci_softc; device_t dev; struct resource *ehci_mem_res; /* EHCI core regs. */ struct resource *ehci_irq_res; /* EHCI core IRQ. */ int usb_alloc_called; clk_t clk; phy_t phy; hwreset_t reset; }; static void tegra_ehci_post_reset(struct ehci_softc *ehci_softc) { uint32_t usbmode; /* Force HOST mode. */ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_LPM); usbmode &= ~EHCI_UM_CM; usbmode |= EHCI_UM_CM_HOST; device_printf(ehci_softc->sc_bus.bdev, "set host controller mode\n"); EOWRITE4(ehci_softc, EHCI_USBMODE_LPM, usbmode); } static int tegra_ehci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { device_set_desc(dev, "Nvidia Tegra EHCI controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int tegra_ehci_detach(device_t dev) { struct tegra_ehci_softc *sc; ehci_softc_t *esc; + int error; + + error = bus_generic_detach(dev); + if (error != 0) + return (error); sc = device_get_softc(dev); esc = &sc->ehci_softc; if (sc->clk != NULL) clk_release(sc->clk); - if (esc->sc_bus.bdev != NULL) - device_delete_child(dev, esc->sc_bus.bdev); if (esc->sc_flags & EHCI_SCFLG_DONEINIT) ehci_detach(esc); if (esc->sc_intr_hdl != NULL) bus_teardown_intr(dev, esc->sc_irq_res, esc->sc_intr_hdl); if (sc->ehci_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->ehci_irq_res); if (sc->ehci_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->ehci_mem_res); if (sc->usb_alloc_called) usb_bus_mem_free_all(&esc->sc_bus, &ehci_iterate_hw_softc); - /* During module unload there are lots of children leftover. */ - device_delete_children(dev); - return (0); } static int tegra_ehci_attach(device_t dev) { struct tegra_ehci_softc *sc; ehci_softc_t *esc; int rv, rid; uint64_t freq; sc = device_get_softc(dev); sc->dev = dev; esc = &sc->ehci_softc; /* Allocate resources. */ rid = 0; sc->ehci_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE | RF_SHAREABLE); if (sc->ehci_mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); rv = ENXIO; goto out; } rid = 0; sc->ehci_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->ehci_irq_res == NULL) { device_printf(dev, "Cannot allocate IRQ resources\n"); rv = ENXIO; goto out; } rv = hwreset_get_by_ofw_name(dev, 0, "usb", &sc->reset); if (rv != 0) { device_printf(dev, "Cannot get reset\n"); rv = ENXIO; goto out; } rv = phy_get_by_ofw_property(sc->dev, 0, "nvidia,phy", &sc->phy); if (rv != 0) { device_printf(sc->dev, "Cannot get 'nvidia,phy' phy\n"); rv = ENXIO; goto out; } rv = clk_get_by_ofw_index(sc->dev, 0, 0, &sc->clk); if (rv != 0) { device_printf(dev, "Cannot get clock\n"); goto out; } rv = clk_enable(sc->clk); if (rv != 0) { device_printf(dev, "Cannot enable clock\n"); goto out; } freq = 0; rv = clk_get_freq(sc->clk, &freq); if (rv != 0) { device_printf(dev, "Cannot get clock frequency\n"); goto out; } rv = hwreset_deassert(sc->reset); if (rv != 0) { device_printf(dev, "Cannot clear reset: %d\n", rv); rv = ENXIO; goto out; } rv = phy_enable(sc->phy); if (rv != 0) { device_printf(dev, "Cannot enable phy: %d\n", rv); goto out; } /* Fill data for EHCI driver. */ esc->sc_vendor_get_port_speed = ehci_get_port_speed_hostc; esc->sc_vendor_post_reset = tegra_ehci_post_reset; esc->sc_io_tag = rman_get_bustag(sc->ehci_mem_res); esc->sc_bus.parent = dev; esc->sc_bus.devices = esc->sc_devices; esc->sc_bus.devices_max = EHCI_MAX_DEVICES; esc->sc_bus.dma_bits = 32; /* Allocate all DMA memory. */ rv = usb_bus_mem_alloc_all(&esc->sc_bus, USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc); sc->usb_alloc_called = 1; if (rv != 0) { device_printf(dev, "usb_bus_mem_alloc_all() failed\n"); rv = ENOMEM; goto out; } /* * Set handle to USB related registers subregion used by * generic EHCI driver. */ rv = bus_space_subregion(esc->sc_io_tag, rman_get_bushandle(sc->ehci_mem_res), TEGRA_EHCI_REG_OFF, TEGRA_EHCI_REG_SIZE, &esc->sc_io_hdl); if (rv != 0) { device_printf(dev, "Could not create USB memory subregion\n"); rv = ENXIO; goto out; } /* Setup interrupt handler. */ rv = bus_setup_intr(dev, sc->ehci_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, esc, &esc->sc_intr_hdl); if (rv != 0) { device_printf(dev, "Could not setup IRQ\n"); goto out; } /* Add USB bus device. */ esc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (esc->sc_bus.bdev == NULL) { device_printf(dev, "Could not add USB device\n"); goto out; } device_set_ivars(esc->sc_bus.bdev, &esc->sc_bus); esc->sc_id_vendor = USB_VENDOR_FREESCALE; strlcpy(esc->sc_vendor, "Nvidia", sizeof(esc->sc_vendor)); /* Set flags that affect ehci_init() behavior. */ esc->sc_flags |= EHCI_SCFLG_TT; esc->sc_flags |= EHCI_SCFLG_NORESTERM; rv = ehci_init(esc); if (rv != 0) { device_printf(dev, "USB init failed: %d\n", rv); goto out; } esc->sc_flags |= EHCI_SCFLG_DONEINIT; /* Probe the bus. */ rv = device_probe_and_attach(esc->sc_bus.bdev); if (rv != 0) { device_printf(dev, "device_probe_and_attach() failed\n"); goto out; } return (0); out: tegra_ehci_detach(dev); return (rv); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, tegra_ehci_probe), DEVMETHOD(device_attach, tegra_ehci_attach), DEVMETHOD(device_detach, tegra_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD_END }; static DEFINE_CLASS_0(ehci, ehci_driver, ehci_methods, sizeof(struct tegra_ehci_softc)); DRIVER_MODULE(tegra_ehci, simplebus, ehci_driver, NULL, NULL); MODULE_DEPEND(tegra_ehci, usb, 1, 1, 1); diff --git a/sys/arm/nvidia/tegra_xhci.c b/sys/arm/nvidia/tegra_xhci.c index e3b4dd483189..474e31981770 100644 --- a/sys/arm/nvidia/tegra_xhci.c +++ b/sys/arm/nvidia/tegra_xhci.c @@ -1,1122 +1,1126 @@ /*- * Copyright (c) 2016 Michal Meloun * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include /* * XHCI driver for Tegra SoCs. */ #include "opt_bus.h" #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" /* FPCI address space */ #define T_XUSB_CFG_0 0x000 #define T_XUSB_CFG_1 0x004 #define CFG_1_BUS_MASTER (1 << 2) #define CFG_1_MEMORY_SPACE (1 << 1) #define CFG_1_IO_SPACE (1 << 0) #define T_XUSB_CFG_2 0x008 #define T_XUSB_CFG_3 0x00C #define T_XUSB_CFG_4 0x010 #define CFG_4_BASE_ADDRESS(x) (((x) & 0x1FFFF) << 15) #define T_XUSB_CFG_5 0x014 #define T_XUSB_CFG_ARU_MAILBOX_CMD 0x0E4 #define ARU_MAILBOX_CMD_INT_EN (1U << 31) #define ARU_MAILBOX_CMD_DEST_XHCI (1 << 30) #define ARU_MAILBOX_CMD_DEST_SMI (1 << 29) #define ARU_MAILBOX_CMD_DEST_PME (1 << 28) #define ARU_MAILBOX_CMD_DEST_FALC (1 << 27) #define T_XUSB_CFG_ARU_MAILBOX_DATA_IN 0x0E8 #define ARU_MAILBOX_DATA_IN_DATA(x) (((x) & 0xFFFFFF) << 0) #define ARU_MAILBOX_DATA_IN_TYPE(x) (((x) & 0x0000FF) << 24) #define T_XUSB_CFG_ARU_MAILBOX_DATA_OUT 0x0EC #define ARU_MAILBOX_DATA_OUT_DATA(x) (((x) >> 0) & 0xFFFFFF) #define ARU_MAILBOX_DATA_OUT_TYPE(x) (((x) >> 24) & 0x0000FF) #define T_XUSB_CFG_ARU_MAILBOX_OWNER 0x0F0 #define ARU_MAILBOX_OWNER_SW 2 #define ARU_MAILBOX_OWNER_FW 1 #define ARU_MAILBOX_OWNER_NONE 0 #define XUSB_CFG_ARU_C11_CSBRANGE 0x41C /* ! UNDOCUMENTED ! */ #define ARU_C11_CSBRANGE_PAGE(x) ((x) >> 9) #define ARU_C11_CSBRANGE_ADDR(x) (0x800 + ((x) & 0x1FF)) #define XUSB_CFG_ARU_SMI_INTR 0x428 /* ! UNDOCUMENTED ! */ #define ARU_SMI_INTR_EN (1 << 3) #define ARU_SMI_INTR_FW_HANG (1 << 1) #define XUSB_CFG_ARU_RST 0x42C /* ! UNDOCUMENTED ! */ #define ARU_RST_RESET (1 << 0) #define XUSB_HOST_CONFIGURATION 0x180 #define CONFIGURATION_CLKEN_OVERRIDE (1U<< 31) #define CONFIGURATION_PW_NO_DEVSEL_ERR_CYA (1 << 19) #define CONFIGURATION_INITIATOR_READ_IDLE (1 << 18) #define CONFIGURATION_INITIATOR_WRITE_IDLE (1 << 17) #define CONFIGURATION_WDATA_LEAD_CYA (1 << 15) #define CONFIGURATION_WR_INTRLV_CYA (1 << 14) #define CONFIGURATION_TARGET_READ_IDLE (1 << 11) #define CONFIGURATION_TARGET_WRITE_IDLE (1 << 10) #define CONFIGURATION_MSI_VEC_EMPTY (1 << 9) #define CONFIGURATION_UFPCI_MSIAW (1 << 7) #define CONFIGURATION_UFPCI_PWPASSPW (1 << 6) #define CONFIGURATION_UFPCI_PASSPW (1 << 5) #define CONFIGURATION_UFPCI_PWPASSNPW (1 << 4) #define CONFIGURATION_DFPCI_PWPASSNPW (1 << 3) #define CONFIGURATION_DFPCI_RSPPASSPW (1 << 2) #define CONFIGURATION_DFPCI_PASSPW (1 << 1) #define CONFIGURATION_EN_FPCI (1 << 0) /* IPFS address space */ #define XUSB_HOST_FPCI_ERROR_MASKS 0x184 #define FPCI_ERROR_MASTER_ABORT (1 << 2) #define FPCI_ERRORI_DATA_ERROR (1 << 1) #define FPCI_ERROR_TARGET_ABORT (1 << 0) #define XUSB_HOST_INTR_MASK 0x188 #define INTR_IP_INT_MASK (1 << 16) #define INTR_MSI_MASK (1 << 8) #define INTR_INT_MASK (1 << 0) #define XUSB_HOST_CLKGATE_HYSTERESIS 0x1BC /* CSB Falcon CPU */ #define XUSB_FALCON_CPUCTL 0x100 #define CPUCTL_STOPPED (1 << 5) #define CPUCTL_HALTED (1 << 4) #define CPUCTL_HRESET (1 << 3) #define CPUCTL_SRESET (1 << 2) #define CPUCTL_STARTCPU (1 << 1) #define CPUCTL_IINVAL (1 << 0) #define XUSB_FALCON_BOOTVEC 0x104 #define XUSB_FALCON_DMACTL 0x10C #define XUSB_FALCON_IMFILLRNG1 0x154 #define IMFILLRNG1_TAG_HI(x) (((x) & 0xFFF) << 16) #define IMFILLRNG1_TAG_LO(x) (((x) & 0xFFF) << 0) #define XUSB_FALCON_IMFILLCTL 0x158 /* CSB mempool */ #define XUSB_CSB_MEMPOOL_APMAP 0x10181C #define APMAP_BOOTPATH (1U << 31) #define XUSB_CSB_MEMPOOL_ILOAD_ATTR 0x101A00 #define XUSB_CSB_MEMPOOL_ILOAD_BASE_LO 0x101A04 #define XUSB_CSB_MEMPOOL_ILOAD_BASE_HI 0x101A08 #define XUSB_CSB_MEMPOOL_L2IMEMOP_SIZE 0x101A10 #define L2IMEMOP_SIZE_OFFSET(x) (((x) & 0x3FF) << 8) #define L2IMEMOP_SIZE_SIZE(x) (((x) & 0x0FF) << 24) #define XUSB_CSB_MEMPOOL_L2IMEMOP_TRIG 0x101A14 #define L2IMEMOP_INVALIDATE_ALL (0x40 << 24) #define L2IMEMOP_LOAD_LOCKED_RESULT (0x11 << 24) #define XUSB_CSB_MEMPOOL_L2IMEMOP_RESULT 0x101A18 #define L2IMEMOP_RESULT_VLD (1U << 31) #define XUSB_CSB_IMEM_BLOCK_SIZE 256 #define TEGRA_XHCI_SS_HIGH_SPEED 120000000 #define TEGRA_XHCI_SS_LOW_SPEED 12000000 /* MBOX commands. */ #define MBOX_CMD_MSG_ENABLED 1 #define MBOX_CMD_INC_FALC_CLOCK 2 #define MBOX_CMD_DEC_FALC_CLOCK 3 #define MBOX_CMD_INC_SSPI_CLOCK 4 #define MBOX_CMD_DEC_SSPI_CLOCK 5 #define MBOX_CMD_SET_BW 6 #define MBOX_CMD_SET_SS_PWR_GATING 7 #define MBOX_CMD_SET_SS_PWR_UNGATING 8 #define MBOX_CMD_SAVE_DFE_CTLE_CTX 9 #define MBOX_CMD_AIRPLANE_MODE_ENABLED 10 #define MBOX_CMD_AIRPLANE_MODE_DISABLED 11 #define MBOX_CMD_START_HSIC_IDLE 12 #define MBOX_CMD_STOP_HSIC_IDLE 13 #define MBOX_CMD_DBC_WAKE_STACK 14 #define MBOX_CMD_HSIC_PRETEND_CONNECT 15 #define MBOX_CMD_RESET_SSPI 16 #define MBOX_CMD_DISABLE_SS_LFPS_DETECTION 17 #define MBOX_CMD_ENABLE_SS_LFPS_DETECTION 18 /* MBOX responses. */ #define MBOX_CMD_ACK (0x80 + 0) #define MBOX_CMD_NAK (0x80 + 1) #define IPFS_WR4(_sc, _r, _v) bus_write_4((_sc)->mem_res_ipfs, (_r), (_v)) #define IPFS_RD4(_sc, _r) bus_read_4((_sc)->mem_res_ipfs, (_r)) #define FPCI_WR4(_sc, _r, _v) bus_write_4((_sc)->mem_res_fpci, (_r), (_v)) #define FPCI_RD4(_sc, _r) bus_read_4((_sc)->mem_res_fpci, (_r)) #define LOCK(_sc) mtx_lock(&(_sc)->mtx) #define UNLOCK(_sc) mtx_unlock(&(_sc)->mtx) #define SLEEP(_sc, timeout) \ mtx_sleep(sc, &sc->mtx, 0, "tegra_xhci", timeout); #define LOCK_INIT(_sc) \ mtx_init(&_sc->mtx, device_get_nameunit(_sc->dev), "tegra_xhci", MTX_DEF) #define LOCK_DESTROY(_sc) mtx_destroy(&_sc->mtx) #define ASSERT_LOCKED(_sc) mtx_assert(&_sc->mtx, MA_OWNED) #define ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->mtx, MA_NOTOWNED) struct tegra_xusb_fw_hdr { uint32_t boot_loadaddr_in_imem; uint32_t boot_codedfi_offset; uint32_t boot_codetag; uint32_t boot_codesize; uint32_t phys_memaddr; uint16_t reqphys_memsize; uint16_t alloc_phys_memsize; uint32_t rodata_img_offset; uint32_t rodata_section_start; uint32_t rodata_section_end; uint32_t main_fnaddr; uint32_t fwimg_cksum; uint32_t fwimg_created_time; uint32_t imem_resident_start; uint32_t imem_resident_end; uint32_t idirect_start; uint32_t idirect_end; uint32_t l2_imem_start; uint32_t l2_imem_end; uint32_t version_id; uint8_t init_ddirect; uint8_t reserved[3]; uint32_t phys_addr_log_buffer; uint32_t total_log_entries; uint32_t dequeue_ptr; uint32_t dummy[2]; uint32_t fwimg_len; uint8_t magic[8]; uint32_t ss_low_power_entry_timeout; uint8_t num_hsic_port; uint8_t ss_portmap; uint8_t build; uint8_t padding[137]; /* Pad to 256 bytes */ }; struct xhci_soc; struct tegra_xhci_softc { struct xhci_softc xhci_softc; device_t dev; struct xhci_soc *soc; struct mtx mtx; struct resource *mem_res_fpci; struct resource *mem_res_ipfs; struct resource *irq_res_mbox; void *irq_hdl_mbox; clk_t clk_xusb_host; clk_t clk_xusb_gate; clk_t clk_xusb_falcon_src; clk_t clk_xusb_ss; clk_t clk_xusb_hs_src; clk_t clk_xusb_fs_src; hwreset_t hwreset_xusb_host; hwreset_t hwreset_xusb_ss; regulator_t regulators[16]; /* Safe maximum */ phy_t phys[8]; /* Safe maximum */ struct intr_config_hook irq_hook; bool xhci_inited; void *fw_vaddr; vm_size_t fw_size; }; struct xhci_soc { char *fw_name; char **regulator_names; char **phy_names; }; /* Tegra 124 config */ static char *tegra124_reg_names[] = { "avddio-pex-supply", "dvddio-pex-supply", "avdd-usb-supply", "avdd-pll-utmip-supply", "avdd-pll-erefe-supply", "avdd-usb-ss-pll-supply", "hvdd-usb-ss-supply", "hvdd-usb-ss-pll-e-supply", NULL }; static char *tegra124_phy_names[] = { "usb2-0", "usb2-1", "usb2-2", "usb3-0", NULL }; static struct xhci_soc tegra124_soc = { .fw_name = "tegra124_xusb_fw", .regulator_names = tegra124_reg_names, .phy_names = tegra124_phy_names, }; /* Tegra 210 config */ static char *tegra210_reg_names[] = { "dvddio-pex-supply", "hvddio-pex-supply", "avdd-usb-supply", "avdd-pll-utmip-supply", "avdd-pll-uerefe-supply", "dvdd-usb-ss-pll-supply", "hvdd-usb-ss-pll-e-supply", NULL }; static char *tegra210_phy_names[] = { "usb2-0", "usb2-1", "usb2-2", "usb2-3", "usb3-0", "usb3-1", NULL }; static struct xhci_soc tegra210_soc = { .fw_name = "tegra210_xusb_fw", .regulator_names = tegra210_reg_names, .phy_names = tegra210_phy_names, }; /* Compatible devices. */ static struct ofw_compat_data compat_data[] = { {"nvidia,tegra124-xusb", (uintptr_t)&tegra124_soc}, {"nvidia,tegra210-xusb", (uintptr_t)&tegra210_soc}, {NULL, 0} }; static uint32_t CSB_RD4(struct tegra_xhci_softc *sc, uint32_t addr) { FPCI_WR4(sc, XUSB_CFG_ARU_C11_CSBRANGE, ARU_C11_CSBRANGE_PAGE(addr)); return (FPCI_RD4(sc, ARU_C11_CSBRANGE_ADDR(addr))); } static void CSB_WR4(struct tegra_xhci_softc *sc, uint32_t addr, uint32_t val) { FPCI_WR4(sc, XUSB_CFG_ARU_C11_CSBRANGE, ARU_C11_CSBRANGE_PAGE(addr)); FPCI_WR4(sc, ARU_C11_CSBRANGE_ADDR(addr), val); } static int get_fdt_resources(struct tegra_xhci_softc *sc, phandle_t node) { int i, rv; /* Regulators. */ for (i = 0; sc->soc->regulator_names[i] != NULL; i++) { if (i >= nitems(sc->regulators)) { device_printf(sc->dev, "Too many regulators present in DT.\n"); return (EOVERFLOW); } rv = regulator_get_by_ofw_property(sc->dev, 0, sc->soc->regulator_names[i], sc->regulators + i); if (rv != 0) { device_printf(sc->dev, "Cannot get '%s' regulator\n", sc->soc->regulator_names[i]); return (ENXIO); } } rv = hwreset_get_by_ofw_name(sc->dev, 0, "xusb_host", &sc->hwreset_xusb_host); if (rv != 0) { device_printf(sc->dev, "Cannot get 'xusb_host' reset\n"); return (ENXIO); } rv = hwreset_get_by_ofw_name(sc->dev, 0, "xusb_ss", &sc->hwreset_xusb_ss); if (rv != 0) { device_printf(sc->dev, "Cannot get 'xusb_ss' reset\n"); return (ENXIO); } /* Phys. */ for (i = 0; sc->soc->phy_names[i] != NULL; i++) { if (i >= nitems(sc->phys)) { device_printf(sc->dev, "Too many phys present in DT.\n"); return (EOVERFLOW); } rv = phy_get_by_ofw_name(sc->dev, 0, sc->soc->phy_names[i], sc->phys + i); if (rv != 0 && rv != ENOENT) { device_printf(sc->dev, "Cannot get '%s' phy.\n", sc->soc->phy_names[i]); return (ENXIO); } } rv = clk_get_by_ofw_name(sc->dev, 0, "xusb_host", &sc->clk_xusb_host); if (rv != 0) { device_printf(sc->dev, "Cannot get 'xusb_host' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(sc->dev, 0, "xusb_falcon_src", &sc->clk_xusb_falcon_src); if (rv != 0) { device_printf(sc->dev, "Cannot get 'xusb_falcon_src' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(sc->dev, 0, "xusb_ss", &sc->clk_xusb_ss); if (rv != 0) { device_printf(sc->dev, "Cannot get 'xusb_ss' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(sc->dev, 0, "xusb_hs_src", &sc->clk_xusb_hs_src); if (rv != 0) { device_printf(sc->dev, "Cannot get 'xusb_hs_src' clock\n"); return (ENXIO); } rv = clk_get_by_ofw_name(sc->dev, 0, "xusb_fs_src", &sc->clk_xusb_fs_src); if (rv != 0) { device_printf(sc->dev, "Cannot get 'xusb_fs_src' clock\n"); return (ENXIO); } /* Clock xusb_gate is missing in mainstream DT */ rv = clk_get_by_name(sc->dev, "xusb_gate", &sc->clk_xusb_gate); if (rv != 0) { device_printf(sc->dev, "Cannot get 'xusb_gate' clock\n"); return (ENXIO); } return (0); } static int enable_fdt_resources(struct tegra_xhci_softc *sc) { int i, rv; rv = hwreset_assert(sc->hwreset_xusb_host); if (rv != 0) { device_printf(sc->dev, "Cannot reset 'xusb_host' reset\n"); return (rv); } rv = hwreset_assert(sc->hwreset_xusb_ss); if (rv != 0) { device_printf(sc->dev, "Cannot reset 'xusb_ss' reset\n"); return (rv); } /* Regulators. */ for (i = 0; i < nitems(sc->regulators); i++) { if (sc->regulators[i] == NULL) continue; rv = regulator_enable(sc->regulators[i]); if (rv != 0) { device_printf(sc->dev, "Cannot enable '%s' regulator\n", sc->soc->regulator_names[i]); return (rv); } } /* Power off XUSB host and XUSB SS domains. */ rv = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); if (rv != 0) { device_printf(sc->dev, "Cannot powerdown 'xusba' domain\n"); return (rv); } rv = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); if (rv != 0) { device_printf(sc->dev, "Cannot powerdown 'xusbc' domain\n"); return (rv); } /* Setup XUSB ss_src clock first */ clk_set_freq(sc->clk_xusb_ss, TEGRA_XHCI_SS_HIGH_SPEED, 0); if (rv != 0) return (rv); /* The XUSB gate clock must be enabled before XUSBA can be powered. */ rv = clk_enable(sc->clk_xusb_gate); if (rv != 0) { device_printf(sc->dev, "Cannot enable 'xusb_gate' clock\n"); return (rv); } /* Power on XUSB host and XUSB SS domains. */ rv = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC, sc->clk_xusb_host, sc->hwreset_xusb_host); if (rv != 0) { device_printf(sc->dev, "Cannot powerup 'xusbc' domain\n"); return (rv); } rv = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA, sc->clk_xusb_ss, sc->hwreset_xusb_ss); if (rv != 0) { device_printf(sc->dev, "Cannot powerup 'xusba' domain\n"); return (rv); } /* Enable rest of clocks */ rv = clk_enable(sc->clk_xusb_falcon_src); if (rv != 0) { device_printf(sc->dev, "Cannot enable 'xusb_falcon_src' clock\n"); return (rv); } rv = clk_enable(sc->clk_xusb_fs_src); if (rv != 0) { device_printf(sc->dev, "Cannot enable 'xusb_fs_src' clock\n"); return (rv); } rv = clk_enable(sc->clk_xusb_hs_src); if (rv != 0) { device_printf(sc->dev, "Cannot enable 'xusb_hs_src' clock\n"); return (rv); } /* Phys. */ for (i = 0; i < nitems(sc->phys); i++) { if (sc->phys[i] == NULL) continue; rv = phy_enable(sc->phys[i]); if (rv != 0) { device_printf(sc->dev, "Cannot enable '%s' phy\n", sc->soc->phy_names[i]); return (rv); } } return (0); } /* Respond by ACK/NAK back to FW */ static void mbox_send_ack(struct tegra_xhci_softc *sc, uint32_t cmd, uint32_t data) { uint32_t reg; reg = ARU_MAILBOX_DATA_IN_TYPE(cmd) | ARU_MAILBOX_DATA_IN_DATA(data); FPCI_WR4(sc, T_XUSB_CFG_ARU_MAILBOX_DATA_IN, reg); reg = FPCI_RD4(sc, T_XUSB_CFG_ARU_MAILBOX_CMD); reg |= ARU_MAILBOX_CMD_DEST_FALC | ARU_MAILBOX_CMD_INT_EN; FPCI_WR4(sc, T_XUSB_CFG_ARU_MAILBOX_CMD, reg); } /* Sent command to FW */ static int mbox_send_cmd(struct tegra_xhci_softc *sc, uint32_t cmd, uint32_t data) { uint32_t reg; int i; reg = FPCI_RD4(sc, T_XUSB_CFG_ARU_MAILBOX_OWNER); if (reg != ARU_MAILBOX_OWNER_NONE) { device_printf(sc->dev, "CPU mailbox is busy: 0x%08X\n", reg); return (EBUSY); } /* XXX Is this right? Retry loop? Wait before send? */ FPCI_WR4(sc, T_XUSB_CFG_ARU_MAILBOX_OWNER, ARU_MAILBOX_OWNER_SW); reg = FPCI_RD4(sc, T_XUSB_CFG_ARU_MAILBOX_OWNER); if (reg != ARU_MAILBOX_OWNER_SW) { device_printf(sc->dev, "Cannot acquire CPU mailbox: 0x%08X\n", reg); return (EBUSY); } reg = ARU_MAILBOX_DATA_IN_TYPE(cmd) | ARU_MAILBOX_DATA_IN_DATA(data); FPCI_WR4(sc, T_XUSB_CFG_ARU_MAILBOX_DATA_IN, reg); reg = FPCI_RD4(sc, T_XUSB_CFG_ARU_MAILBOX_CMD); reg |= ARU_MAILBOX_CMD_DEST_FALC | ARU_MAILBOX_CMD_INT_EN; FPCI_WR4(sc, T_XUSB_CFG_ARU_MAILBOX_CMD, reg); for (i = 250; i > 0; i--) { reg = FPCI_RD4(sc, T_XUSB_CFG_ARU_MAILBOX_OWNER); if (reg == ARU_MAILBOX_OWNER_NONE) break; DELAY(100); } if (i <= 0) { device_printf(sc->dev, "Command response timeout: 0x%08X\n", reg); return (ETIMEDOUT); } return(0); } static void process_msg(struct tegra_xhci_softc *sc, uint32_t req_cmd, uint32_t req_data, uint32_t *resp_cmd, uint32_t *resp_data) { uint64_t freq; int rv; /* In most cases, data are echoed back. */ *resp_data = req_data; switch (req_cmd) { case MBOX_CMD_INC_FALC_CLOCK: case MBOX_CMD_DEC_FALC_CLOCK: rv = clk_set_freq(sc->clk_xusb_falcon_src, req_data * 1000ULL, 0); if (rv == 0) { rv = clk_get_freq(sc->clk_xusb_falcon_src, &freq); *resp_data = (uint32_t)(freq / 1000); } *resp_cmd = rv == 0 ? MBOX_CMD_ACK: MBOX_CMD_NAK; break; case MBOX_CMD_INC_SSPI_CLOCK: case MBOX_CMD_DEC_SSPI_CLOCK: rv = clk_set_freq(sc->clk_xusb_ss, req_data * 1000ULL, 0); if (rv == 0) { rv = clk_get_freq(sc->clk_xusb_ss, &freq); *resp_data = (uint32_t)(freq / 1000); } *resp_cmd = rv == 0 ? MBOX_CMD_ACK: MBOX_CMD_NAK; break; case MBOX_CMD_SET_BW: /* No respense is expected. */ *resp_cmd = 0; break; case MBOX_CMD_SET_SS_PWR_GATING: case MBOX_CMD_SET_SS_PWR_UNGATING: *resp_cmd = MBOX_CMD_NAK; break; case MBOX_CMD_SAVE_DFE_CTLE_CTX: /* Not implemented yet. */ *resp_cmd = MBOX_CMD_ACK; break; case MBOX_CMD_START_HSIC_IDLE: case MBOX_CMD_STOP_HSIC_IDLE: /* Not implemented yet. */ *resp_cmd = MBOX_CMD_NAK; break; case MBOX_CMD_DISABLE_SS_LFPS_DETECTION: case MBOX_CMD_ENABLE_SS_LFPS_DETECTION: /* Not implemented yet. */ *resp_cmd = MBOX_CMD_NAK; break; case MBOX_CMD_AIRPLANE_MODE_ENABLED: case MBOX_CMD_AIRPLANE_MODE_DISABLED: case MBOX_CMD_DBC_WAKE_STACK: case MBOX_CMD_HSIC_PRETEND_CONNECT: case MBOX_CMD_RESET_SSPI: device_printf(sc->dev, "Received unused/unexpected command: %u\n", req_cmd); *resp_cmd = 0; break; default: device_printf(sc->dev, "Received unknown command: %u\n", req_cmd); } } static void intr_mbox(void *arg) { struct tegra_xhci_softc *sc; uint32_t reg, msg, resp_cmd, resp_data; sc = (struct tegra_xhci_softc *)arg; /* Clear interrupt first */ reg = FPCI_RD4(sc, XUSB_CFG_ARU_SMI_INTR); FPCI_WR4(sc, XUSB_CFG_ARU_SMI_INTR, reg); if (reg & ARU_SMI_INTR_FW_HANG) { device_printf(sc->dev, "XUSB CPU firmware hang!!! CPUCTL: 0x%08X\n", CSB_RD4(sc, XUSB_FALCON_CPUCTL)); } msg = FPCI_RD4(sc, T_XUSB_CFG_ARU_MAILBOX_DATA_OUT); resp_cmd = 0; process_msg(sc, ARU_MAILBOX_DATA_OUT_TYPE(msg), ARU_MAILBOX_DATA_OUT_DATA(msg), &resp_cmd, &resp_data); if (resp_cmd != 0) mbox_send_ack(sc, resp_cmd, resp_data); else FPCI_WR4(sc, T_XUSB_CFG_ARU_MAILBOX_OWNER, ARU_MAILBOX_OWNER_NONE); reg = FPCI_RD4(sc, T_XUSB_CFG_ARU_MAILBOX_CMD); reg &= ~ARU_MAILBOX_CMD_DEST_SMI; FPCI_WR4(sc, T_XUSB_CFG_ARU_MAILBOX_CMD, reg); } static int load_fw(struct tegra_xhci_softc *sc) { const struct firmware *fw; const struct tegra_xusb_fw_hdr *fw_hdr; vm_paddr_t fw_paddr, fw_base; void *fw_vaddr; vm_size_t fw_size; uint32_t code_tags, code_size; struct clocktime fw_clock; struct timespec fw_timespec; int i; /* Reset ARU */ FPCI_WR4(sc, XUSB_CFG_ARU_RST, ARU_RST_RESET); DELAY(3000); /* Check if FALCON already runs */ if (CSB_RD4(sc, XUSB_CSB_MEMPOOL_ILOAD_BASE_LO) != 0) { device_printf(sc->dev, "XUSB CPU is already loaded, CPUCTL: 0x%08X\n", CSB_RD4(sc, XUSB_FALCON_CPUCTL)); return (0); } fw = firmware_get(sc->soc->fw_name); if (fw == NULL) { device_printf(sc->dev, "Cannot read xusb firmware\n"); return (ENOENT); } /* Allocate uncached memory and copy firmware into. */ fw_hdr = (const struct tegra_xusb_fw_hdr *)fw->data; fw_size = fw_hdr->fwimg_len; fw_vaddr = kmem_alloc_contig(fw_size, M_WAITOK, 0, -1UL, PAGE_SIZE, 0, VM_MEMATTR_UNCACHEABLE); fw_paddr = vtophys((uintptr_t)fw_vaddr); fw_hdr = (const struct tegra_xusb_fw_hdr *)fw_vaddr; memcpy(fw_vaddr, fw->data, fw_size); firmware_put(fw, FIRMWARE_UNLOAD); sc->fw_vaddr = fw_vaddr; sc->fw_size = fw_size; /* Setup firmware physical address and size. */ fw_base = fw_paddr + sizeof(*fw_hdr); CSB_WR4(sc, XUSB_CSB_MEMPOOL_ILOAD_ATTR, fw_size); CSB_WR4(sc, XUSB_CSB_MEMPOOL_ILOAD_BASE_LO, fw_base & 0xFFFFFFFF); CSB_WR4(sc, XUSB_CSB_MEMPOOL_ILOAD_BASE_HI, (uint64_t)fw_base >> 32); CSB_WR4(sc, XUSB_CSB_MEMPOOL_APMAP, APMAP_BOOTPATH); /* Invalidate full L2IMEM context. */ CSB_WR4(sc, XUSB_CSB_MEMPOOL_L2IMEMOP_TRIG, L2IMEMOP_INVALIDATE_ALL); /* Program load of L2IMEM by boot code. */ code_tags = howmany(fw_hdr->boot_codetag, XUSB_CSB_IMEM_BLOCK_SIZE); code_size = howmany(fw_hdr->boot_codesize, XUSB_CSB_IMEM_BLOCK_SIZE); CSB_WR4(sc, XUSB_CSB_MEMPOOL_L2IMEMOP_SIZE, L2IMEMOP_SIZE_OFFSET(code_tags) | L2IMEMOP_SIZE_SIZE(code_size)); /* Execute L2IMEM boot code fetch. */ CSB_WR4(sc, XUSB_CSB_MEMPOOL_L2IMEMOP_TRIG, L2IMEMOP_LOAD_LOCKED_RESULT); /* Program FALCON auto-fill range and block count */ CSB_WR4(sc, XUSB_FALCON_IMFILLCTL, code_size); CSB_WR4(sc, XUSB_FALCON_IMFILLRNG1, IMFILLRNG1_TAG_LO(code_tags) | IMFILLRNG1_TAG_HI(code_tags + code_size)); CSB_WR4(sc, XUSB_FALCON_DMACTL, 0); /* Wait for CPU */ for (i = 500; i > 0; i--) { if (CSB_RD4(sc, XUSB_CSB_MEMPOOL_L2IMEMOP_RESULT) & L2IMEMOP_RESULT_VLD) break; DELAY(100); } if (i <= 0) { device_printf(sc->dev, "Timedout while wating for DMA, " "state: 0x%08X\n", CSB_RD4(sc, XUSB_CSB_MEMPOOL_L2IMEMOP_RESULT)); return (ETIMEDOUT); } /* Boot FALCON cpu */ CSB_WR4(sc, XUSB_FALCON_BOOTVEC, fw_hdr->boot_codetag); CSB_WR4(sc, XUSB_FALCON_CPUCTL, CPUCTL_STARTCPU); /* Wait for CPU */ for (i = 50; i > 0; i--) { if (CSB_RD4(sc, XUSB_FALCON_CPUCTL) == CPUCTL_STOPPED) break; DELAY(100); } if (i <= 0) { device_printf(sc->dev, "Timedout while wating for FALCON cpu, " "state: 0x%08X\n", CSB_RD4(sc, XUSB_FALCON_CPUCTL)); return (ETIMEDOUT); } fw_timespec.tv_sec = fw_hdr->fwimg_created_time; fw_timespec.tv_nsec = 0; clock_ts_to_ct(&fw_timespec, &fw_clock); device_printf(sc->dev, " Falcon firmware version: %02X.%02X.%04X," " (%d/%d/%d %d:%02d:%02d UTC)\n", (fw_hdr->version_id >> 24) & 0xFF,(fw_hdr->version_id >> 15) & 0xFF, fw_hdr->version_id & 0xFFFF, fw_clock.day, fw_clock.mon, fw_clock.year, fw_clock.hour, fw_clock.min, fw_clock.sec); return (0); } static int init_hw(struct tegra_xhci_softc *sc) { int rv; uint32_t reg; rman_res_t base_addr; base_addr = rman_get_start(sc->xhci_softc.sc_io_res); /* Enable FPCI access */ reg = IPFS_RD4(sc, XUSB_HOST_CONFIGURATION); reg |= CONFIGURATION_EN_FPCI; IPFS_WR4(sc, XUSB_HOST_CONFIGURATION, reg); IPFS_RD4(sc, XUSB_HOST_CONFIGURATION); /* Program bar for XHCI base address */ reg = FPCI_RD4(sc, T_XUSB_CFG_4); reg &= ~CFG_4_BASE_ADDRESS(~0); reg |= CFG_4_BASE_ADDRESS((uint32_t)base_addr >> 15); FPCI_WR4(sc, T_XUSB_CFG_4, reg); FPCI_WR4(sc, T_XUSB_CFG_5, (uint32_t)((uint64_t)(base_addr) >> 32)); /* Enable bus master */ reg = FPCI_RD4(sc, T_XUSB_CFG_1); reg |= CFG_1_IO_SPACE; reg |= CFG_1_MEMORY_SPACE; reg |= CFG_1_BUS_MASTER; FPCI_WR4(sc, T_XUSB_CFG_1, reg); /* Enable Interrupts */ reg = IPFS_RD4(sc, XUSB_HOST_INTR_MASK); reg |= INTR_IP_INT_MASK; IPFS_WR4(sc, XUSB_HOST_INTR_MASK, reg); /* Set hysteresis */ IPFS_WR4(sc, XUSB_HOST_CLKGATE_HYSTERESIS, 128); rv = load_fw(sc); if (rv != 0) return rv; return (0); } static int tegra_xhci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { device_set_desc(dev, "Nvidia Tegra XHCI controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int tegra_xhci_detach(device_t dev) { struct tegra_xhci_softc *sc; struct xhci_softc *xsc; + int error; sc = device_get_softc(dev); xsc = &sc->xhci_softc; /* during module unload there are lots of children leftover */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); + if (sc->xhci_inited) { usb_callout_drain(&xsc->sc_callout); xhci_halt_controller(xsc); } if (xsc->sc_irq_res && xsc->sc_intr_hdl) { bus_teardown_intr(dev, xsc->sc_irq_res, xsc->sc_intr_hdl); xsc->sc_intr_hdl = NULL; } if (xsc->sc_irq_res) { bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(xsc->sc_irq_res), xsc->sc_irq_res); xsc->sc_irq_res = NULL; } if (xsc->sc_io_res != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(xsc->sc_io_res), xsc->sc_io_res); xsc->sc_io_res = NULL; } if (sc->xhci_inited) xhci_uninit(xsc); if (sc->irq_hdl_mbox != NULL) bus_teardown_intr(dev, sc->irq_res_mbox, sc->irq_hdl_mbox); if (sc->fw_vaddr != NULL) kmem_free(sc->fw_vaddr, sc->fw_size); LOCK_DESTROY(sc); return (0); } static int tegra_xhci_attach(device_t dev) { struct tegra_xhci_softc *sc; struct xhci_softc *xsc; int rv, rid; phandle_t node; sc = device_get_softc(dev); sc->dev = dev; sc->soc = (struct xhci_soc *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; node = ofw_bus_get_node(dev); xsc = &sc->xhci_softc; LOCK_INIT(sc); rv = get_fdt_resources(sc, node); if (rv != 0) { rv = ENXIO; goto error; } rv = enable_fdt_resources(sc); if (rv != 0) { rv = ENXIO; goto error; } /* Allocate resources. */ rid = 0; xsc->sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (xsc->sc_io_res == NULL) { device_printf(dev, "Could not allocate HCD memory resources\n"); rv = ENXIO; goto error; } rid = 1; sc->mem_res_fpci = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mem_res_fpci == NULL) { device_printf(dev, "Could not allocate FPCI memory resources\n"); rv = ENXIO; goto error; } rid = 2; sc->mem_res_ipfs = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mem_res_ipfs == NULL) { device_printf(dev, "Could not allocate IPFS memory resources\n"); rv = ENXIO; goto error; } rid = 0; xsc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (xsc->sc_irq_res == NULL) { device_printf(dev, "Could not allocate HCD IRQ resources\n"); rv = ENXIO; goto error; } rid = 1; sc->irq_res_mbox = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->irq_res_mbox == NULL) { device_printf(dev, "Could not allocate MBOX IRQ resources\n"); rv = ENXIO; goto error; } rv = init_hw(sc); if (rv != 0) { device_printf(dev, "Could not initialize XUSB hardware\n"); goto error; } /* Wakeup and enable firmaware */ rv = mbox_send_cmd(sc, MBOX_CMD_MSG_ENABLED, 0); if (rv != 0) { device_printf(sc->dev, "Could not enable XUSB firmware\n"); goto error; } /* Fill data for XHCI driver. */ xsc->sc_bus.parent = dev; xsc->sc_bus.devices = xsc->sc_devices; xsc->sc_bus.devices_max = XHCI_MAX_DEVICES; xsc->sc_io_tag = rman_get_bustag(xsc->sc_io_res); xsc->sc_io_hdl = rman_get_bushandle(xsc->sc_io_res); xsc->sc_io_size = rman_get_size(xsc->sc_io_res); strlcpy(xsc->sc_vendor, "Nvidia", sizeof(xsc->sc_vendor)); /* Add USB bus device. */ xsc->sc_bus.bdev = device_add_child(sc->dev, "usbus", DEVICE_UNIT_ANY); if (xsc->sc_bus.bdev == NULL) { device_printf(sc->dev, "Could not add USB device\n"); rv = ENXIO; goto error; } device_set_ivars(xsc->sc_bus.bdev, &xsc->sc_bus); device_set_desc(xsc->sc_bus.bdev, "Nvidia USB 3.0 controller"); rv = xhci_init(xsc, sc->dev, 1); if (rv != 0) { device_printf(sc->dev, "USB init failed: %d\n", rv); goto error; } sc->xhci_inited = true; rv = xhci_start_controller(xsc); if (rv != 0) { device_printf(sc->dev, "Could not start XHCI controller: %d\n", rv); goto error; } rv = bus_setup_intr(dev, sc->irq_res_mbox, INTR_TYPE_MISC | INTR_MPSAFE, NULL, intr_mbox, sc, &sc->irq_hdl_mbox); if (rv != 0) { device_printf(dev, "Could not setup error IRQ: %d\n",rv); xsc->sc_intr_hdl = NULL; goto error; } rv = bus_setup_intr(dev, xsc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)xhci_interrupt, xsc, &xsc->sc_intr_hdl); if (rv != 0) { device_printf(dev, "Could not setup error IRQ: %d\n",rv); xsc->sc_intr_hdl = NULL; goto error; } /* Probe the bus. */ rv = device_probe_and_attach(xsc->sc_bus.bdev); if (rv != 0) { device_printf(sc->dev, "Could not initialize USB: %d\n", rv); goto error; } return (0); error: panic("XXXXX"); tegra_xhci_detach(dev); return (rv); } static device_method_t xhci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, tegra_xhci_probe), DEVMETHOD(device_attach, tegra_xhci_attach), DEVMETHOD(device_detach, tegra_xhci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD_END }; static DEFINE_CLASS_0(xhci, xhci_driver, xhci_methods, sizeof(struct tegra_xhci_softc)); DRIVER_MODULE(tegra_xhci, simplebus, xhci_driver, NULL, NULL); MODULE_DEPEND(tegra_xhci, usb, 1, 1, 1); diff --git a/sys/arm/ti/am335x/am335x_musb.c b/sys/arm/ti/am335x/am335x_musb.c index 147602c4dbd3..24a204e42c9c 100644 --- a/sys/arm/ti/am335x/am335x_musb.c +++ b/sys/arm/ti/am335x/am335x_musb.c @@ -1,457 +1,460 @@ /*- * Copyright (c) 2013 Oleksandr Tymoshenko * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usbssdebug #include #include #include #include #include #include #include #include #include #include "syscon_if.h" #define USBCTRL_REV 0x00 #define USBCTRL_CTRL 0x14 #define USBCTRL_STAT 0x18 #define USBCTRL_IRQ_STAT0 0x30 #define IRQ_STAT0_RXSHIFT 16 #define IRQ_STAT0_TXSHIFT 0 #define USBCTRL_IRQ_STAT1 0x34 #define IRQ_STAT1_DRVVBUS (1 << 8) #define USBCTRL_INTEN_SET0 0x38 #define USBCTRL_INTEN_SET1 0x3C #define USBCTRL_INTEN_USB_ALL 0x1ff #define USBCTRL_INTEN_USB_SOF (1 << 3) #define USBCTRL_INTEN_CLR0 0x40 #define USBCTRL_INTEN_CLR1 0x44 #define USBCTRL_UTMI 0xE0 #define USBCTRL_UTMI_FSDATAEXT (1 << 1) #define USBCTRL_MODE 0xE8 #define USBCTRL_MODE_IDDIG (1 << 8) #define USBCTRL_MODE_IDDIGMUX (1 << 7) /* USBSS resource + 2 MUSB ports */ #define RES_USBCORE 0 #define RES_USBCTRL 1 #define USB_WRITE4(sc, idx, reg, val) do { \ bus_write_4((sc)->sc_mem_res[idx], (reg), (val)); \ } while (0) #define USB_READ4(sc, idx, reg) bus_read_4((sc)->sc_mem_res[idx], (reg)) #define USBCTRL_WRITE4(sc, reg, val) \ USB_WRITE4((sc), RES_USBCTRL, (reg), (val)) #define USBCTRL_READ4(sc, reg) \ USB_READ4((sc), RES_USBCTRL, (reg)) static struct resource_spec am335x_musbotg_mem_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_MEMORY, 1, RF_ACTIVE }, { -1, 0, 0 } }; #ifdef USB_DEBUG static int usbssdebug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, am335x_usbss, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "AM335x USBSS"); SYSCTL_INT(_hw_usb_am335x_usbss, OID_AUTO, debug, CTLFLAG_RW, &usbssdebug, 0, "Debug level"); #endif static device_probe_t musbotg_probe; static device_attach_t musbotg_attach; static device_detach_t musbotg_detach; struct musbotg_super_softc { struct musbotg_softc sc_otg; struct resource *sc_mem_res[2]; int sc_irq_rid; struct syscon *syscon; }; static void musbotg_vbus_poll(struct musbotg_super_softc *sc) { uint32_t stat; if (sc->sc_otg.sc_mode == MUSB2_DEVICE_MODE) musbotg_vbus_interrupt(&sc->sc_otg, 1); else { stat = USBCTRL_READ4(sc, USBCTRL_STAT); musbotg_vbus_interrupt(&sc->sc_otg, stat & 1); } } /* * Arg to musbotg_clocks_on and musbot_clocks_off is * a uint32_t * pointing to the SCM register offset. */ static uint32_t USB_CTRL[] = {SCM_USB_CTRL0, SCM_USB_CTRL1}; static void musbotg_clocks_on(void *arg) { struct musbotg_softc *sc; struct musbotg_super_softc *ssc; uint32_t reg; sc = arg; ssc = sc->sc_platform_data; reg = SYSCON_READ_4(ssc->syscon, USB_CTRL[sc->sc_id]); reg &= ~3; /* Enable power */ reg |= 1 << 19; /* VBUS detect enable */ reg |= 1 << 20; /* Session end enable */ SYSCON_WRITE_4(ssc->syscon, USB_CTRL[sc->sc_id], reg); } static void musbotg_clocks_off(void *arg) { struct musbotg_softc *sc; struct musbotg_super_softc *ssc; uint32_t reg; sc = arg; ssc = sc->sc_platform_data; /* Disable power to PHY */ reg = SYSCON_READ_4(ssc->syscon, USB_CTRL[sc->sc_id]); SYSCON_WRITE_4(ssc->syscon, USB_CTRL[sc->sc_id], reg | 3); } static void musbotg_ep_int_set(struct musbotg_softc *sc, int ep, int on) { struct musbotg_super_softc *ssc = sc->sc_platform_data; uint32_t epmask; epmask = ((1 << ep) << IRQ_STAT0_RXSHIFT); epmask |= ((1 << ep) << IRQ_STAT0_TXSHIFT); if (on) USBCTRL_WRITE4(ssc, USBCTRL_INTEN_SET0, epmask); else USBCTRL_WRITE4(ssc, USBCTRL_INTEN_CLR0, epmask); } static void musbotg_wrapper_interrupt(void *arg) { struct musbotg_softc *sc = arg; struct musbotg_super_softc *ssc = sc->sc_platform_data; uint32_t stat, stat0, stat1; stat = USBCTRL_READ4(ssc, USBCTRL_STAT); stat0 = USBCTRL_READ4(ssc, USBCTRL_IRQ_STAT0); stat1 = USBCTRL_READ4(ssc, USBCTRL_IRQ_STAT1); if (stat0) USBCTRL_WRITE4(ssc, USBCTRL_IRQ_STAT0, stat0); if (stat1) USBCTRL_WRITE4(ssc, USBCTRL_IRQ_STAT1, stat1); DPRINTFN(4, "port%d: stat0=%08x stat1=%08x, stat=%08x\n", sc->sc_id, stat0, stat1, stat); if (stat1 & IRQ_STAT1_DRVVBUS) musbotg_vbus_interrupt(sc, stat & 1); musbotg_interrupt(arg, ((stat0 >> 16) & 0xffff), stat0 & 0xffff, stat1 & 0xff); } static int musbotg_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "ti,musb-am33xx")) return (ENXIO); device_set_desc(dev, "TI AM33xx integrated USB OTG controller"); return (BUS_PROBE_DEFAULT); } static int musbotg_attach(device_t dev) { struct musbotg_super_softc *sc = device_get_softc(dev); char mode[16]; int err; uint32_t reg; phandle_t opp_table; clk_t clk_usbotg_fck; sc->sc_otg.sc_id = device_get_unit(dev); /* FIXME: The devicetree needs to be updated to get a handle to the gate * usbotg_fck@47c. see TRM 8.1.12.2 CM_WKUP CM_CLKDCOLDO_DPLL_PER. */ err = clk_get_by_name(dev, "usbotg_fck@47c", &clk_usbotg_fck); if (err) { device_printf(dev, "Can not find usbotg_fck@47c\n"); return (ENXIO); } err = clk_enable(clk_usbotg_fck); if (err) { device_printf(dev, "Can not enable usbotg_fck@47c\n"); return (ENXIO); } /* FIXME: For now; Go and kidnap syscon from opp-table */ opp_table = OF_finddevice("/opp-table"); if (opp_table == -1) { device_printf(dev, "Cant find /opp-table\n"); return (ENXIO); } if (!OF_hasprop(opp_table, "syscon")) { device_printf(dev, "/opp-table missing syscon property\n"); return (ENXIO); } err = syscon_get_by_ofw_property(dev, opp_table, "syscon", &sc->syscon); if (err) { device_printf(dev, "Failed to get syscon\n"); return (ENXIO); } /* Request the memory resources */ err = bus_alloc_resources(dev, am335x_musbotg_mem_spec, sc->sc_mem_res); if (err) { device_printf(dev, "Error: could not allocate mem resources\n"); return (ENXIO); } /* Request the IRQ resources */ sc->sc_otg.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE); if (sc->sc_otg.sc_irq_res == NULL) { device_printf(dev, "Error: could not allocate irq resources\n"); return (ENXIO); } /* setup MUSB OTG USB controller interface softc */ sc->sc_otg.sc_clocks_on = &musbotg_clocks_on; sc->sc_otg.sc_clocks_off = &musbotg_clocks_off; sc->sc_otg.sc_clocks_arg = &sc->sc_otg; sc->sc_otg.sc_ep_int_set = musbotg_ep_int_set; /* initialise some bus fields */ sc->sc_otg.sc_bus.parent = dev; sc->sc_otg.sc_bus.devices = sc->sc_otg.sc_devices; sc->sc_otg.sc_bus.devices_max = MUSB2_MAX_DEVICES; sc->sc_otg.sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_otg.sc_bus, USB_GET_DMA_TAG(dev), NULL)) { device_printf(dev, "Failed allocate bus mem for musb\n"); return (ENOMEM); } sc->sc_otg.sc_io_res = sc->sc_mem_res[RES_USBCORE]; sc->sc_otg.sc_io_tag = rman_get_bustag(sc->sc_otg.sc_io_res); sc->sc_otg.sc_io_hdl = rman_get_bushandle(sc->sc_otg.sc_io_res); sc->sc_otg.sc_io_size = rman_get_size(sc->sc_otg.sc_io_res); sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (!(sc->sc_otg.sc_bus.bdev)) { device_printf(dev, "No busdev for musb\n"); goto error; } device_set_ivars(sc->sc_otg.sc_bus.bdev, &sc->sc_otg.sc_bus); err = bus_setup_intr(dev, sc->sc_otg.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)musbotg_wrapper_interrupt, &sc->sc_otg, &sc->sc_otg.sc_intr_hdl); if (err) { sc->sc_otg.sc_intr_hdl = NULL; device_printf(dev, "Failed to setup interrupt for musb\n"); goto error; } sc->sc_otg.sc_platform_data = sc; if (OF_getprop(ofw_bus_get_node(dev), "dr_mode", mode, sizeof(mode)) > 0) { if (strcasecmp(mode, "host") == 0) sc->sc_otg.sc_mode = MUSB2_HOST_MODE; else sc->sc_otg.sc_mode = MUSB2_DEVICE_MODE; } else { /* Beaglebone defaults: USB0 device, USB1 HOST. */ if (sc->sc_otg.sc_id == 0) sc->sc_otg.sc_mode = MUSB2_DEVICE_MODE; else sc->sc_otg.sc_mode = MUSB2_HOST_MODE; } /* * software-controlled function */ if (sc->sc_otg.sc_mode == MUSB2_HOST_MODE) { reg = USBCTRL_READ4(sc, USBCTRL_MODE); reg |= USBCTRL_MODE_IDDIGMUX; reg &= ~USBCTRL_MODE_IDDIG; USBCTRL_WRITE4(sc, USBCTRL_MODE, reg); USBCTRL_WRITE4(sc, USBCTRL_UTMI, USBCTRL_UTMI_FSDATAEXT); } else { reg = USBCTRL_READ4(sc, USBCTRL_MODE); reg |= USBCTRL_MODE_IDDIGMUX; reg |= USBCTRL_MODE_IDDIG; USBCTRL_WRITE4(sc, USBCTRL_MODE, reg); } reg = USBCTRL_INTEN_USB_ALL & ~USBCTRL_INTEN_USB_SOF; USBCTRL_WRITE4(sc, USBCTRL_INTEN_SET1, reg); USBCTRL_WRITE4(sc, USBCTRL_INTEN_CLR0, 0xffffffff); err = musbotg_init(&sc->sc_otg); if (!err) err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev); if (err) goto error; /* poll VBUS one time */ musbotg_vbus_poll(sc); return (0); error: musbotg_detach(dev); return (ENXIO); } static int musbotg_detach(device_t dev) { struct musbotg_super_softc *sc = device_get_softc(dev); + int error; /* during module unload there are lots of children leftover */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) { /* * only call musbotg_uninit() after musbotg_init() */ musbotg_uninit(&sc->sc_otg); bus_teardown_intr(dev, sc->sc_otg.sc_irq_res, sc->sc_otg.sc_intr_hdl); sc->sc_otg.sc_intr_hdl = NULL; } usb_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL); /* Free resources if any */ if (sc->sc_mem_res[0]) bus_release_resources(dev, am335x_musbotg_mem_spec, sc->sc_mem_res); if (sc->sc_otg.sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_otg.sc_irq_res); return (0); } static device_method_t musbotg_methods[] = { /* Device interface */ DEVMETHOD(device_probe, musbotg_probe), DEVMETHOD(device_attach, musbotg_attach), DEVMETHOD(device_detach, musbotg_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t musbotg_driver = { .name = "musbotg", .methods = musbotg_methods, .size = sizeof(struct musbotg_super_softc), }; DRIVER_MODULE(musbotg, ti_sysc, musbotg_driver, 0, 0); MODULE_DEPEND(musbotg, ti_sysc, 1, 1, 1); MODULE_DEPEND(musbotg, ti_am3359_cppi41, 1, 1, 1); MODULE_DEPEND(usbss, usb, 1, 1, 1); diff --git a/sys/arm/ti/usb/omap_ehci.c b/sys/arm/ti/usb/omap_ehci.c index fee5f662963b..224c786bf9fa 100644 --- a/sys/arm/ti/usb/omap_ehci.c +++ b/sys/arm/ti/usb/omap_ehci.c @@ -1,464 +1,466 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 * Ben Gray . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* EHCI */ #define OMAP_USBHOST_HCCAPBASE 0x0000 #define OMAP_USBHOST_HCSPARAMS 0x0004 #define OMAP_USBHOST_HCCPARAMS 0x0008 #define OMAP_USBHOST_USBCMD 0x0010 #define OMAP_USBHOST_USBSTS 0x0014 #define OMAP_USBHOST_USBINTR 0x0018 #define OMAP_USBHOST_FRINDEX 0x001C #define OMAP_USBHOST_CTRLDSSEGMENT 0x0020 #define OMAP_USBHOST_PERIODICLISTBASE 0x0024 #define OMAP_USBHOST_ASYNCLISTADDR 0x0028 #define OMAP_USBHOST_CONFIGFLAG 0x0050 #define OMAP_USBHOST_PORTSC(i) (0x0054 + (0x04 * (i))) #define OMAP_USBHOST_INSNREG00 0x0090 #define OMAP_USBHOST_INSNREG01 0x0094 #define OMAP_USBHOST_INSNREG02 0x0098 #define OMAP_USBHOST_INSNREG03 0x009C #define OMAP_USBHOST_INSNREG04 0x00A0 #define OMAP_USBHOST_INSNREG05_UTMI 0x00A4 #define OMAP_USBHOST_INSNREG05_ULPI 0x00A4 #define OMAP_USBHOST_INSNREG06 0x00A8 #define OMAP_USBHOST_INSNREG07 0x00AC #define OMAP_USBHOST_INSNREG08 0x00B0 #define OMAP_USBHOST_INSNREG04_DISABLE_UNSUSPEND (1 << 5) #define OMAP_USBHOST_INSNREG05_ULPI_CONTROL_SHIFT 31 #define OMAP_USBHOST_INSNREG05_ULPI_PORTSEL_SHIFT 24 #define OMAP_USBHOST_INSNREG05_ULPI_OPSEL_SHIFT 22 #define OMAP_USBHOST_INSNREG05_ULPI_REGADD_SHIFT 16 #define OMAP_USBHOST_INSNREG05_ULPI_EXTREGADD_SHIFT 8 #define OMAP_USBHOST_INSNREG05_ULPI_WRDATA_SHIFT 0 #define ULPI_FUNC_CTRL_RESET (1 << 5) /*-------------------------------------------------------------------------*/ /* * Macros for Set and Clear * See ULPI 1.1 specification to find the registers with Set and Clear offsets */ #define ULPI_SET(a) (a + 1) #define ULPI_CLR(a) (a + 2) /*-------------------------------------------------------------------------*/ /* * Register Map */ #define ULPI_VENDOR_ID_LOW 0x00 #define ULPI_VENDOR_ID_HIGH 0x01 #define ULPI_PRODUCT_ID_LOW 0x02 #define ULPI_PRODUCT_ID_HIGH 0x03 #define ULPI_FUNC_CTRL 0x04 #define ULPI_IFC_CTRL 0x07 #define ULPI_OTG_CTRL 0x0a #define ULPI_USB_INT_EN_RISE 0x0d #define ULPI_USB_INT_EN_FALL 0x10 #define ULPI_USB_INT_STS 0x13 #define ULPI_USB_INT_LATCH 0x14 #define ULPI_DEBUG 0x15 #define ULPI_SCRATCH 0x16 #define OMAP_EHCI_HC_DEVSTR "TI OMAP USB 2.0 controller" struct omap_ehci_softc { ehci_softc_t base; /* storage for EHCI code */ device_t sc_dev; }; static device_attach_t omap_ehci_attach; static device_detach_t omap_ehci_detach; /** * omap_ehci_read_4 - read a 32-bit value from the EHCI registers * omap_ehci_write_4 - write a 32-bit value from the EHCI registers * @sc: omap ehci device context * @off: byte offset within the register set to read from * @val: the value to write into the register * * * LOCKING: * None * * RETURNS: * nothing in case of write function, if read function returns the value read. */ static inline uint32_t omap_ehci_read_4(struct omap_ehci_softc *sc, bus_size_t off) { return (bus_read_4(sc->base.sc_io_res, off)); } static inline void omap_ehci_write_4(struct omap_ehci_softc *sc, bus_size_t off, uint32_t val) { bus_write_4(sc->base.sc_io_res, off, val); } /** * omap_ehci_soft_phy_reset - resets the phy using the reset command * @isc: omap ehci device context * @port: port to send the reset over * * * LOCKING: * none * * RETURNS: * nothing */ static void omap_ehci_soft_phy_reset(struct omap_ehci_softc *isc, unsigned int port) { unsigned long timeout = (hz < 10) ? 1 : ((100 * hz) / 1000); uint32_t reg; reg = ULPI_FUNC_CTRL_RESET /* FUNCTION_CTRL_SET register */ | (ULPI_SET(ULPI_FUNC_CTRL) << OMAP_USBHOST_INSNREG05_ULPI_REGADD_SHIFT) /* Write */ | (2 << OMAP_USBHOST_INSNREG05_ULPI_OPSEL_SHIFT) /* PORTn */ | ((port + 1) << OMAP_USBHOST_INSNREG05_ULPI_PORTSEL_SHIFT) /* start ULPI access*/ | (1 << OMAP_USBHOST_INSNREG05_ULPI_CONTROL_SHIFT); omap_ehci_write_4(isc, OMAP_USBHOST_INSNREG05_ULPI, reg); /* Wait for ULPI access completion */ while ((omap_ehci_read_4(isc, OMAP_USBHOST_INSNREG05_ULPI) & (1 << OMAP_USBHOST_INSNREG05_ULPI_CONTROL_SHIFT))) { /* Sleep for a tick */ pause("USBPHY_RESET", 1); if (timeout-- == 0) { device_printf(isc->sc_dev, "PHY reset operation timed out\n"); break; } } } /** * omap_ehci_init - initialises the USB host EHCI controller * @isc: omap ehci device context * * This initialisation routine is quite heavily based on the work done by the * OMAP Linux team (for which I thank them very much). The init sequence is * almost identical, diverging only for the FreeBSD specifics. * * LOCKING: * none * * RETURNS: * 0 on success, a negative error code on failure. */ static int omap_ehci_init(struct omap_ehci_softc *isc) { uint32_t reg = 0; int i; device_t uhh_dev; uhh_dev = device_get_parent(isc->sc_dev); device_printf(isc->sc_dev, "Starting TI EHCI USB Controller\n"); /* Set the interrupt threshold control, it controls the maximum rate at * which the host controller issues interrupts. We set it to 1 microframe * at startup - the default is 8 mircoframes (equates to 1ms). */ reg = omap_ehci_read_4(isc, OMAP_USBHOST_USBCMD); reg &= 0xff00ffff; reg |= (1 << 16); omap_ehci_write_4(isc, OMAP_USBHOST_USBCMD, reg); /* Soft reset the PHY using PHY reset command over ULPI */ for (i = 0; i < OMAP_HS_USB_PORTS; i++) { if (omap_usb_port_mode(uhh_dev, i) == EHCI_HCD_OMAP_MODE_PHY) omap_ehci_soft_phy_reset(isc, i); } return(0); } /** * omap_ehci_probe - starts the given command * @dev: * * Effectively boilerplate EHCI resume code. * * LOCKING: * Caller should be holding the OMAP3_MMC lock. * * RETURNS: * EH_HANDLED or EH_NOT_HANDLED */ static int omap_ehci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "ti,ehci-omap")) return (ENXIO); device_set_desc(dev, OMAP_EHCI_HC_DEVSTR); return (BUS_PROBE_DEFAULT); } /** * omap_ehci_attach - driver entry point, sets up the ECHI controller/driver * @dev: the new device handle * * Sets up bus spaces, interrupt handles, etc for the EHCI controller. It also * parses the resource hints and calls omap_ehci_init() to initialise the * H/W. * * LOCKING: * none * * RETURNS: * 0 on success or a positive error code on failure. */ static int omap_ehci_attach(device_t dev) { struct omap_ehci_softc *isc = device_get_softc(dev); ehci_softc_t *sc = &isc->base; #ifdef SOC_OMAP4 phandle_t root; #endif int err; int rid; #ifdef SOC_OMAP4 /* * If we're running a Pandaboard, run Pandaboard-specific * init code. */ root = OF_finddevice("/"); if (ofw_bus_node_is_compatible(root, "ti,omap4-panda")) pandaboard_usb_hub_init(); #endif /* initialise some bus fields */ sc->sc_bus.parent = dev; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; sprintf(sc->sc_vendor, "Texas Instruments"); /* save the device */ isc->sc_dev = dev; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc)) { return (ENOMEM); } /* Allocate resource for the EHCI register set */ rid = 0; sc->sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_io_res) { device_printf(dev, "Error: Could not map EHCI memory\n"); goto error; } /* Request an interrupt resource */ rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(dev, "Error: could not allocate irq\n"); goto error; } /* Add this device as a child of the USBus device */ sc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(dev, "Error: could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); device_set_desc(sc->sc_bus.bdev, OMAP_EHCI_HC_DEVSTR); /* Initialise the ECHI registers */ err = omap_ehci_init(isc); if (err) { device_printf(dev, "Error: could not setup OMAP EHCI, %d\n", err); goto error; } /* Set the tag and size of the register set in the EHCI context */ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res); /* Setup the interrupt */ err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(dev, "Error: could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; goto error; } /* Finally we are ready to kick off the ECHI host controller */ err = ehci_init(sc); if (err == 0) { err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(dev, "Error: USB init failed err=%d\n", err); goto error; } return (0); error: omap_ehci_detach(dev); return (ENXIO); } /** * omap_ehci_detach - detach the device and cleanup the driver * @dev: device handle * * Clean-up routine where everything initialised in omap_ehci_attach is * freed and cleaned up. This function calls omap_ehci_fini() to shutdown * the on-chip module. * * LOCKING: * none * * RETURNS: * Always returns 0 (success). */ static int omap_ehci_detach(device_t dev) { struct omap_ehci_softc *isc = device_get_softc(dev); ehci_softc_t *sc = &isc->base; int err; /* during module unload there are lots of children leftover */ - device_delete_children(dev); + err = bus_generic_detach(dev); + if (err != 0) + return (err); /* * disable interrupts that might have been switched on in ehci_init */ if (sc->sc_io_res) { EWRITE4(sc, EHCI_USBINTR, 0); } if (sc->sc_irq_res && sc->sc_intr_hdl) { /* * only call ehci_detach() after ehci_init() */ ehci_detach(sc); err = bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_hdl); if (err) device_printf(dev, "Error: could not tear down irq, %d\n", err); sc->sc_intr_hdl = NULL; } /* Free the resources stored in the base EHCI handler */ if (sc->sc_irq_res) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_io_res); sc->sc_io_res = NULL; } return (0); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, omap_ehci_probe), DEVMETHOD(device_attach, omap_ehci_attach), DEVMETHOD(device_detach, omap_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), {0, 0} }; static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(struct omap_ehci_softc), }; DRIVER_MODULE(omap_ehci, omap_uhh, ehci_driver, 0, 0); diff --git a/sys/arm/ti/usb/omap_host.c b/sys/arm/ti/usb/omap_host.c index b7c387c00601..c336a25eabf3 100644 --- a/sys/arm/ti/usb/omap_host.c +++ b/sys/arm/ti/usb/omap_host.c @@ -1,464 +1,467 @@ /*- * Copyright (c) 2015 Oleksandr Tymoshenko * Copyright (c) 2011 Ben Gray . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include /* * USB Host Module */ /* UHH */ #define OMAP_USBHOST_UHH_REVISION 0x0000 #define OMAP_USBHOST_UHH_SYSCONFIG 0x0010 #define OMAP_USBHOST_UHH_SYSSTATUS 0x0014 #define OMAP_USBHOST_UHH_HOSTCONFIG 0x0040 #define OMAP_USBHOST_UHH_DEBUG_CSR 0x0044 /* UHH Register Set */ #define UHH_SYSCONFIG_MIDLEMODE_MASK (3UL << 12) #define UHH_SYSCONFIG_MIDLEMODE_SMARTSTANDBY (2UL << 12) #define UHH_SYSCONFIG_MIDLEMODE_NOSTANDBY (1UL << 12) #define UHH_SYSCONFIG_MIDLEMODE_FORCESTANDBY (0UL << 12) #define UHH_SYSCONFIG_CLOCKACTIVITY (1UL << 8) #define UHH_SYSCONFIG_SIDLEMODE_MASK (3UL << 3) #define UHH_SYSCONFIG_SIDLEMODE_SMARTIDLE (2UL << 3) #define UHH_SYSCONFIG_SIDLEMODE_NOIDLE (1UL << 3) #define UHH_SYSCONFIG_SIDLEMODE_FORCEIDLE (0UL << 3) #define UHH_SYSCONFIG_ENAWAKEUP (1UL << 2) #define UHH_SYSCONFIG_SOFTRESET (1UL << 1) #define UHH_SYSCONFIG_AUTOIDLE (1UL << 0) #define UHH_HOSTCONFIG_APP_START_CLK (1UL << 31) #define UHH_HOSTCONFIG_P3_CONNECT_STATUS (1UL << 10) #define UHH_HOSTCONFIG_P2_CONNECT_STATUS (1UL << 9) #define UHH_HOSTCONFIG_P1_CONNECT_STATUS (1UL << 8) #define UHH_HOSTCONFIG_ENA_INCR_ALIGN (1UL << 5) #define UHH_HOSTCONFIG_ENA_INCR16 (1UL << 4) #define UHH_HOSTCONFIG_ENA_INCR8 (1UL << 3) #define UHH_HOSTCONFIG_ENA_INCR4 (1UL << 2) #define UHH_HOSTCONFIG_AUTOPPD_ON_OVERCUR_EN (1UL << 1) #define UHH_HOSTCONFIG_P1_ULPI_BYPASS (1UL << 0) /* The following are on rev2 (OMAP44xx) of the EHCI only */ #define UHH_SYSCONFIG_IDLEMODE_MASK (3UL << 2) #define UHH_SYSCONFIG_IDLEMODE_NOIDLE (1UL << 2) #define UHH_SYSCONFIG_STANDBYMODE_MASK (3UL << 4) #define UHH_SYSCONFIG_STANDBYMODE_NOSTDBY (1UL << 4) #define UHH_HOSTCONFIG_P1_MODE_MASK (3UL << 16) #define UHH_HOSTCONFIG_P1_MODE_ULPI_PHY (0UL << 16) #define UHH_HOSTCONFIG_P1_MODE_UTMI_PHY (1UL << 16) #define UHH_HOSTCONFIG_P1_MODE_HSIC (3UL << 16) #define UHH_HOSTCONFIG_P2_MODE_MASK (3UL << 18) #define UHH_HOSTCONFIG_P2_MODE_ULPI_PHY (0UL << 18) #define UHH_HOSTCONFIG_P2_MODE_UTMI_PHY (1UL << 18) #define UHH_HOSTCONFIG_P2_MODE_HSIC (3UL << 18) /* * Values of UHH_REVISION - Note: these are not given in the TRM but taken * from the linux OMAP EHCI driver (thanks guys). It has been verified on * a Panda and Beagle board. */ #define OMAP_UHH_REV1 0x00000010 /* OMAP3 */ #define OMAP_UHH_REV2 0x50700100 /* OMAP4 */ struct omap_uhh_softc { struct simplebus_softc simplebus_sc; device_t sc_dev; /* UHH register set */ struct resource* uhh_mem_res; /* The revision of the HS USB HOST read from UHH_REVISION */ uint32_t uhh_rev; /* The following details are provided by conf hints */ int port_mode[3]; }; static device_attach_t omap_uhh_attach; static device_detach_t omap_uhh_detach; static inline uint32_t omap_uhh_read_4(struct omap_uhh_softc *sc, bus_size_t off) { return bus_read_4(sc->uhh_mem_res, off); } static inline void omap_uhh_write_4(struct omap_uhh_softc *sc, bus_size_t off, uint32_t val) { bus_write_4(sc->uhh_mem_res, off, val); } static int omap_uhh_init(struct omap_uhh_softc *isc) { uint8_t tll_ch_mask; uint32_t reg; int i; /* Enable Clocks for high speed USBHOST */ ti_sysc_clock_enable(device_get_parent(isc->sc_dev)); /* Read the UHH revision */ isc->uhh_rev = omap_uhh_read_4(isc, OMAP_USBHOST_UHH_REVISION); device_printf(isc->sc_dev, "UHH revision 0x%08x\n", isc->uhh_rev); /* FIXME */ #if 0 if (isc->uhh_rev == OMAP_UHH_REV2) { /* For OMAP44xx devices you have to enable the per-port clocks: * PHY_MODE - External ULPI clock * TTL_MODE - Internal UTMI clock * HSIC_MODE - Internal 480Mhz and 60Mhz clocks */ switch(isc->port_mode[0]) { case EHCI_HCD_OMAP_MODE_UNKNOWN: break; case EHCI_HCD_OMAP_MODE_PHY: if (ti_prcm_clk_set_source(USBP1_PHY_CLK, EXT_CLK)) device_printf(isc->sc_dev, "failed to set clock source for port 0\n"); if (ti_prcm_clk_enable(USBP1_PHY_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP1_PHY_CLK source for port 0\n"); break; case EHCI_HCD_OMAP_MODE_TLL: if (ti_prcm_clk_enable(USBP1_UTMI_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP1_PHY_CLK source for port 0\n"); break; case EHCI_HCD_OMAP_MODE_HSIC: if (ti_prcm_clk_enable(USBP1_HSIC_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP1_PHY_CLK source for port 0\n"); break; default: device_printf(isc->sc_dev, "unknown port mode %d for port 0\n", isc->port_mode[0]); } switch(isc->port_mode[1]) { case EHCI_HCD_OMAP_MODE_UNKNOWN: break; case EHCI_HCD_OMAP_MODE_PHY: if (ti_prcm_clk_set_source(USBP2_PHY_CLK, EXT_CLK)) device_printf(isc->sc_dev, "failed to set clock source for port 0\n"); if (ti_prcm_clk_enable(USBP2_PHY_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP2_PHY_CLK source for port 1\n"); break; case EHCI_HCD_OMAP_MODE_TLL: if (ti_prcm_clk_enable(USBP2_UTMI_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP2_UTMI_CLK source for port 1\n"); break; case EHCI_HCD_OMAP_MODE_HSIC: if (ti_prcm_clk_enable(USBP2_HSIC_CLK)) device_printf(isc->sc_dev, "failed to set clock USBP2_HSIC_CLK source for port 1\n"); break; default: device_printf(isc->sc_dev, "unknown port mode %d for port 1\n", isc->port_mode[1]); } } #endif /* Put UHH in SmartIdle/SmartStandby mode */ reg = omap_uhh_read_4(isc, OMAP_USBHOST_UHH_SYSCONFIG); if (isc->uhh_rev == OMAP_UHH_REV1) { reg &= ~(UHH_SYSCONFIG_SIDLEMODE_MASK | UHH_SYSCONFIG_MIDLEMODE_MASK); reg |= (UHH_SYSCONFIG_ENAWAKEUP | UHH_SYSCONFIG_AUTOIDLE | UHH_SYSCONFIG_CLOCKACTIVITY | UHH_SYSCONFIG_SIDLEMODE_SMARTIDLE | UHH_SYSCONFIG_MIDLEMODE_SMARTSTANDBY); } else if (isc->uhh_rev == OMAP_UHH_REV2) { reg &= ~UHH_SYSCONFIG_IDLEMODE_MASK; reg |= UHH_SYSCONFIG_IDLEMODE_NOIDLE; reg &= ~UHH_SYSCONFIG_STANDBYMODE_MASK; reg |= UHH_SYSCONFIG_STANDBYMODE_NOSTDBY; } omap_uhh_write_4(isc, OMAP_USBHOST_UHH_SYSCONFIG, reg); device_printf(isc->sc_dev, "OMAP_UHH_SYSCONFIG: 0x%08x\n", reg); reg = omap_uhh_read_4(isc, OMAP_USBHOST_UHH_HOSTCONFIG); /* Setup ULPI bypass and burst configurations */ reg |= (UHH_HOSTCONFIG_ENA_INCR4 | UHH_HOSTCONFIG_ENA_INCR8 | UHH_HOSTCONFIG_ENA_INCR16); reg &= ~UHH_HOSTCONFIG_ENA_INCR_ALIGN; if (isc->uhh_rev == OMAP_UHH_REV1) { if (isc->port_mode[0] == EHCI_HCD_OMAP_MODE_UNKNOWN) reg &= ~UHH_HOSTCONFIG_P1_CONNECT_STATUS; if (isc->port_mode[1] == EHCI_HCD_OMAP_MODE_UNKNOWN) reg &= ~UHH_HOSTCONFIG_P2_CONNECT_STATUS; if (isc->port_mode[2] == EHCI_HCD_OMAP_MODE_UNKNOWN) reg &= ~UHH_HOSTCONFIG_P3_CONNECT_STATUS; /* Bypass the TLL module for PHY mode operation */ if ((isc->port_mode[0] == EHCI_HCD_OMAP_MODE_PHY) || (isc->port_mode[1] == EHCI_HCD_OMAP_MODE_PHY) || (isc->port_mode[2] == EHCI_HCD_OMAP_MODE_PHY)) reg &= ~UHH_HOSTCONFIG_P1_ULPI_BYPASS; else reg |= UHH_HOSTCONFIG_P1_ULPI_BYPASS; } else if (isc->uhh_rev == OMAP_UHH_REV2) { reg |= UHH_HOSTCONFIG_APP_START_CLK; /* Clear port mode fields for PHY mode*/ reg &= ~UHH_HOSTCONFIG_P1_MODE_MASK; reg &= ~UHH_HOSTCONFIG_P2_MODE_MASK; if (isc->port_mode[0] == EHCI_HCD_OMAP_MODE_TLL) reg |= UHH_HOSTCONFIG_P1_MODE_UTMI_PHY; else if (isc->port_mode[0] == EHCI_HCD_OMAP_MODE_HSIC) reg |= UHH_HOSTCONFIG_P1_MODE_HSIC; if (isc->port_mode[1] == EHCI_HCD_OMAP_MODE_TLL) reg |= UHH_HOSTCONFIG_P2_MODE_UTMI_PHY; else if (isc->port_mode[1] == EHCI_HCD_OMAP_MODE_HSIC) reg |= UHH_HOSTCONFIG_P2_MODE_HSIC; } omap_uhh_write_4(isc, OMAP_USBHOST_UHH_HOSTCONFIG, reg); device_printf(isc->sc_dev, "UHH setup done, uhh_hostconfig=0x%08x\n", reg); /* I found the code and comments in the Linux EHCI driver - thanks guys :) * * "An undocumented "feature" in the OMAP3 EHCI controller, causes suspended * ports to be taken out of suspend when the USBCMD.Run/Stop bit is cleared * (for example when we do omap_uhh_bus_suspend). This breaks suspend-resume if * the root-hub is allowed to suspend. Writing 1 to this undocumented * register bit disables this feature and restores normal behavior." */ #if 0 omap_uhh_write_4(isc, OMAP_USBHOST_INSNREG04, OMAP_USBHOST_INSNREG04_DISABLE_UNSUSPEND); #endif tll_ch_mask = 0; for (i = 0; i < OMAP_HS_USB_PORTS; i++) { if (isc->port_mode[i] == EHCI_HCD_OMAP_MODE_TLL) tll_ch_mask |= (1 << i); } if (tll_ch_mask) omap_tll_utmi_enable(tll_ch_mask); return(0); } /** * omap_uhh_fini - shutdown the EHCI controller * @isc: omap ehci device context * * * * LOCKING: * none * * RETURNS: * 0 on success, a negative error code on failure. */ static void omap_uhh_fini(struct omap_uhh_softc *isc) { unsigned long timeout; device_printf(isc->sc_dev, "Stopping TI EHCI USB Controller\n"); /* Set the timeout */ if (hz < 10) timeout = 1; else timeout = (100 * hz) / 1000; /* Reset the UHH, OHCI and EHCI modules */ omap_uhh_write_4(isc, OMAP_USBHOST_UHH_SYSCONFIG, 0x0002); while ((omap_uhh_read_4(isc, OMAP_USBHOST_UHH_SYSSTATUS) & 0x07) == 0x00) { /* Sleep for a tick */ pause("USBRESET", 1); if (timeout-- == 0) { device_printf(isc->sc_dev, "operation timed out\n"); break; } } /* Disable functional and interface clocks for the TLL and HOST modules */ ti_sysc_clock_disable(device_get_parent(isc->sc_dev)); device_printf(isc->sc_dev, "Clock to USB host has been disabled\n"); } int omap_usb_port_mode(device_t dev, int port) { struct omap_uhh_softc *isc; isc = device_get_softc(dev); if ((port < 0) || (port >= OMAP_HS_USB_PORTS)) return (-1); return isc->port_mode[port]; } static int omap_uhh_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "ti,usbhs-host")) return (ENXIO); device_set_desc(dev, "TI OMAP USB 2.0 Host module"); return (BUS_PROBE_DEFAULT); } static int omap_uhh_attach(device_t dev) { struct omap_uhh_softc *isc = device_get_softc(dev); int err; int rid; int i; phandle_t node; char propname[16]; char *mode; /* save the device */ isc->sc_dev = dev; /* Allocate resource for the UHH register set */ rid = 0; isc->uhh_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!isc->uhh_mem_res) { device_printf(dev, "Error: Could not map UHH memory\n"); goto error; } node = ofw_bus_get_node(dev); if (node == -1) goto error; /* Get port modes from FDT */ for (i = 0; i < OMAP_HS_USB_PORTS; i++) { isc->port_mode[i] = EHCI_HCD_OMAP_MODE_UNKNOWN; snprintf(propname, sizeof(propname), "port%d-mode", i+1); if (OF_getprop_alloc(node, propname, (void**)&mode) <= 0) continue; if (strcmp(mode, "ehci-phy") == 0) isc->port_mode[i] = EHCI_HCD_OMAP_MODE_PHY; else if (strcmp(mode, "ehci-tll") == 0) isc->port_mode[i] = EHCI_HCD_OMAP_MODE_TLL; else if (strcmp(mode, "ehci-hsic") == 0) isc->port_mode[i] = EHCI_HCD_OMAP_MODE_HSIC; } /* Initialise the ECHI registers */ err = omap_uhh_init(isc); if (err) { device_printf(dev, "Error: could not setup OMAP EHCI, %d\n", err); goto error; } simplebus_init(dev, node); /* * Allow devices to identify. */ bus_identify_children(dev); /* * Now walk the OFW tree and attach top-level devices. */ for (node = OF_child(node); node > 0; node = OF_peer(node)) simplebus_add_device(dev, node, 0, NULL, -1, NULL); bus_attach_children(dev); return (0); error: omap_uhh_detach(dev); return (ENXIO); } static int omap_uhh_detach(device_t dev) { struct omap_uhh_softc *isc = device_get_softc(dev); + int error; /* during module unload there are lots of children leftover */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); if (isc->uhh_mem_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, isc->uhh_mem_res); isc->uhh_mem_res = NULL; } omap_uhh_fini(isc); return (0); } static device_method_t omap_uhh_methods[] = { /* Device interface */ DEVMETHOD(device_probe, omap_uhh_probe), DEVMETHOD(device_attach, omap_uhh_attach), DEVMETHOD(device_detach, omap_uhh_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; DEFINE_CLASS_1(omap_uhh, omap_uhh_driver, omap_uhh_methods, sizeof(struct omap_uhh_softc), simplebus_driver); DRIVER_MODULE(omap_uhh, simplebus, omap_uhh_driver, 0, 0); diff --git a/sys/arm/xilinx/zy7_ehci.c b/sys/arm/xilinx/zy7_ehci.c index f2e1d8a9ec2b..545e2a9bce16 100644 --- a/sys/arm/xilinx/zy7_ehci.c +++ b/sys/arm/xilinx/zy7_ehci.c @@ -1,365 +1,368 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2013 Thomas Skibo * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * A host-controller driver for Zynq-7000's USB OTG controller. * * Reference: Zynq-7000 All Programmable SoC Technical Reference Manual. * (v1.4) November 16, 2012. Xilinx doc UG585. Ch. 15 covers the USB * controller and register definitions are in appendix B.34. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Register definitions. */ #define ZY7_USB_ID 0x0000 #define ZY7_USB_HWGENERAL 0x0004 #define ZY7_USB_HWHOST 0x0008 #define ZY7_USB_HWDEVICE 0x000c #define ZY7_USB_HWTXBUF 0x0010 #define ZY7_USB_HWRXBUF 0x0014 #define ZY7_USB_GPTIMER0LD 0x0080 #define ZY7_USB_GPTIMER0CTRL 0x0084 #define ZY7_USB_GPTIMER1LD 0x0088 #define ZY7_USB_GPTIMER1CTRL 0x008c #define ZY7_USB_SBUSCFG 0x0090 #define ZY7_USB_CAPLENGTH_HCIVERSION 0x0100 #define ZY7_USB_HCSPARAMS 0x0104 #define ZY7_USB_HCCPARAMS 0x0108 #define ZY7_USB_DCIVERSION 0x0120 #define ZY7_USB_DCCPARAMS 0x0124 #define ZY7_USB_USBCMD 0x0140 #define ZY7_USB_USBSTS 0x0144 #define ZY7_USB_USBINTR 0x0148 #define ZY7_USB_FRINDEX 0x014c #define ZY7_USB_PERIODICLISTBASE_DEICEADDR 0x0154 #define ZY7_USB_ASYNCLISTADDR_ENDPOINTLISTADDR 0x0158 #define ZY7_USB_TTCTRL 0x015c #define ZY7_USB_BURSTSIZE 0x0160 #define ZY7_USB_TXFILLTUNING 0x0164 #define ZY7_USB_TXFILLTUNING_TXFIFOTHRES_SHFT 16 #define ZY7_USB_TXFILLTUNING_TXFIFOTHRES_MASK (0x3f<<16) #define ZY7_USB_TXTFILLTUNING 0x0168 #define ZY7_USB_IC_USB 0x016c #define ZY7_USB_ULPI_VIEWPORT 0x0170 #define ZY7_USB_ULPI_VIEWPORT_WU (1<<31) #define ZY7_USB_ULPI_VIEWPORT_RUN (1<<30) #define ZY7_USB_ULPI_VIEWPORT_RW (1<<29) #define ZY7_USB_ULPI_VIEWPORT_SS (1<<27) #define ZY7_USB_ULPI_VIEWPORT_PORT_MASK (7<<24) #define ZY7_USB_ULPI_VIEWPORT_PORT_SHIFT 24 #define ZY7_USB_ULPI_VIEWPORT_ADDR_MASK (0xff<<16) #define ZY7_USB_ULPI_VIEWPORT_ADDR_SHIFT 16 #define ZY7_USB_ULPI_VIEWPORT_DATARD_MASK (0xff<<8) #define ZY7_USB_ULPI_VIEWPORT_DATARD_SHIFT 8 #define ZY7_USB_ULPI_VIEWPORT_DATAWR_MASK (0xff<<0) #define ZY7_USB_ULPI_VIEWPORT_DATAWR_SHIFT 0 #define ZY7_USB_ENDPTNAK 0x0178 #define ZY7_USB_ENDPTNAKEN 0x017c #define ZY7_USB_CONFIGFLAG 0x0180 #define ZY7_USB_PORTSC(n) (0x0180+4*(n)) #define ZY7_USB_PORTSC_PTS_MASK (3<<30) #define ZY7_USB_PORTSC_PTS_SHIFT 30 #define ZY7_USB_PORTSC_PTS_UTMI (0<<30) #define ZY7_USB_PORTSC_PTS_ULPI (2<<30) #define ZY7_USB_PORTSC_PTS_SERIAL (3<<30) #define ZY7_USB_PORTSC_PTW (1<<28) #define ZY7_USB_PORTSC_PTS2 (1<<25) #define ZY7_USB_OTGSC 0x01a4 #define ZY7_USB_USBMODE 0x01a8 #define ZY7_USB_ENDPTSETUPSTAT 0x01ac #define ZY7_USB_ENDPTPRIME 0x01b0 #define ZY7_USB_ENDPTFLUSH 0x01b4 #define ZY7_USB_ENDPTSTAT 0x01b8 #define ZY7_USB_ENDPTCOMPLETE 0x01bc #define ZY7_USB_ENDPTCTRL(n) (0x01c0+4*(n)) #define EHCI_REG_OFFSET ZY7_USB_CAPLENGTH_HCIVERSION #define EHCI_REG_SIZE 0x100 static void zy7_ehci_post_reset(struct ehci_softc *ehci_softc) { uint32_t usbmode; /* Force HOST mode */ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM); usbmode &= ~EHCI_UM_CM; usbmode |= EHCI_UM_CM_HOST; EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode); } static int zy7_phy_config(device_t dev, bus_space_tag_t io_tag, bus_space_handle_t bsh) { phandle_t node; char buf[64]; uint32_t portsc; int tries; node = ofw_bus_get_node(dev); if (OF_getprop(node, "phy_type", buf, sizeof(buf)) > 0) { portsc = bus_space_read_4(io_tag, bsh, ZY7_USB_PORTSC(1)); portsc &= ~(ZY7_USB_PORTSC_PTS_MASK | ZY7_USB_PORTSC_PTW | ZY7_USB_PORTSC_PTS2); if (strcmp(buf,"ulpi") == 0) portsc |= ZY7_USB_PORTSC_PTS_ULPI; else if (strcmp(buf,"utmi") == 0) portsc |= ZY7_USB_PORTSC_PTS_UTMI; else if (strcmp(buf,"utmi-wide") == 0) portsc |= (ZY7_USB_PORTSC_PTS_UTMI | ZY7_USB_PORTSC_PTW); else if (strcmp(buf, "serial") == 0) portsc |= ZY7_USB_PORTSC_PTS_SERIAL; bus_space_write_4(io_tag, bsh, ZY7_USB_PORTSC(1), portsc); } if (OF_getprop(node, "phy_vbus_ext", buf, sizeof(buf)) >= 0) { /* Tell PHY that VBUS is supplied externally. */ bus_space_write_4(io_tag, bsh, ZY7_USB_ULPI_VIEWPORT, ZY7_USB_ULPI_VIEWPORT_RUN | ZY7_USB_ULPI_VIEWPORT_RW | (0 << ZY7_USB_ULPI_VIEWPORT_PORT_SHIFT) | (0x0b << ZY7_USB_ULPI_VIEWPORT_ADDR_SHIFT) | (0x60 << ZY7_USB_ULPI_VIEWPORT_DATAWR_SHIFT) ); tries = 100; while ((bus_space_read_4(io_tag, bsh, ZY7_USB_ULPI_VIEWPORT) & ZY7_USB_ULPI_VIEWPORT_RUN) != 0) { if (--tries < 0) return (-1); DELAY(1); } } return (0); } static int zy7_ehci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "xlnx,zy7_ehci")) return (ENXIO); device_set_desc(dev, "Zynq-7000 EHCI USB 2.0 controller"); return (0); } static int zy7_ehci_detach(device_t dev); static int zy7_ehci_attach(device_t dev) { ehci_softc_t *sc = device_get_softc(dev); bus_space_handle_t bsh; int err, rid; /* initialize some bus fields */ sc->sc_bus.parent = dev; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc)) return (ENOMEM); /* Allocate memory. */ rid = 0; sc->sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_io_res == NULL) { device_printf(dev, "Can't allocate memory"); zy7_ehci_detach(dev); return (ENOMEM); } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); bsh = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = EHCI_REG_SIZE; if (bus_space_subregion(sc->sc_io_tag, bsh, EHCI_REG_OFFSET, sc->sc_io_size, &sc->sc_io_hdl) != 0) panic("%s: unable to subregion USB host registers", device_get_name(dev)); /* Allocate IRQ. */ rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(dev, "Can't allocate IRQ\n"); zy7_ehci_detach(dev); return (ENOMEM); } /* Add USB device */ sc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(dev, "Could not add USB device\n"); zy7_ehci_detach(dev); return (ENXIO); } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); device_set_desc(sc->sc_bus.bdev, "Zynq-7000 ehci USB 2.0 controller"); strcpy(sc->sc_vendor, "Xilinx"); /* or IP vendor? */ /* Activate the interrupt */ err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(dev, "Cannot setup IRQ\n"); zy7_ehci_detach(dev); return (err); } /* Customization. */ sc->sc_flags |= EHCI_SCFLG_TT | EHCI_SCFLG_NORESTERM; sc->sc_vendor_post_reset = zy7_ehci_post_reset; sc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc; /* Modify FIFO burst threshold from 2 to 8. */ bus_space_write_4(sc->sc_io_tag, bsh, ZY7_USB_TXFILLTUNING, 8 << ZY7_USB_TXFILLTUNING_TXFIFOTHRES_SHFT); /* Handle PHY options. */ if (zy7_phy_config(dev, sc->sc_io_tag, bsh) < 0) { device_printf(dev, "Cannot config phy!\n"); zy7_ehci_detach(dev); return (EIO); } /* Init ehci. */ err = ehci_init(sc); if (!err) { sc->sc_flags |= EHCI_SCFLG_DONEINIT; err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(dev, "USB init failed err=%d\n", err); zy7_ehci_detach(dev); return (err); } return (0); } static int zy7_ehci_detach(device_t dev) { ehci_softc_t *sc = device_get_softc(dev); + int error; /* during module unload there are lots of children leftover */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); if ((sc->sc_flags & EHCI_SCFLG_DONEINIT) != 0) { ehci_detach(sc); sc->sc_flags &= ~EHCI_SCFLG_DONEINIT; } if (sc->sc_irq_res) { if (sc->sc_intr_hdl != NULL) bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_hdl); bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->sc_irq_res), sc->sc_irq_res); } if (sc->sc_io_res) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->sc_io_res), sc->sc_io_res); usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); return (0); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, zy7_ehci_probe), DEVMETHOD(device_attach, zy7_ehci_attach), DEVMETHOD(device_detach, zy7_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD_END }; static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(struct ehci_softc), }; DRIVER_MODULE(zy7_ehci, simplebus, ehci_driver, NULL, NULL); MODULE_DEPEND(zy7_ehci, usb, 1, 1, 1); diff --git a/sys/dev/ahci/ahci.c b/sys/dev/ahci/ahci.c index d64ec8caa13f..d5ce503f62ee 100644 --- a/sys/dev/ahci/ahci.c +++ b/sys/dev/ahci/ahci.c @@ -1,2909 +1,2911 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009-2012 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ahci.h" #include #include #include #include #include /* local prototypes */ static void ahci_intr(void *data); static void ahci_intr_one(void *data); static void ahci_intr_one_edge(void *data); static int ahci_ch_init(device_t dev); static int ahci_ch_deinit(device_t dev); static int ahci_ch_suspend(device_t dev); static int ahci_ch_resume(device_t dev); static void ahci_ch_pm(void *arg); static void ahci_ch_intr(void *arg); static void ahci_ch_intr_direct(void *arg); static void ahci_ch_intr_main(struct ahci_channel *ch, uint32_t istatus); static void ahci_begin_transaction(struct ahci_channel *ch, union ccb *ccb); static void ahci_dmasetprd(void *arg, bus_dma_segment_t *segs, int nsegs, int error); static void ahci_execute_transaction(struct ahci_slot *slot); static void ahci_timeout(void *arg); static void ahci_end_transaction(struct ahci_slot *slot, enum ahci_err_type et); static int ahci_setup_fis(struct ahci_channel *ch, struct ahci_cmd_tab *ctp, union ccb *ccb, int tag); static void ahci_dmainit(device_t dev); static void ahci_dmasetupc_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error); static void ahci_dmafini(device_t dev); static void ahci_slotsalloc(device_t dev); static void ahci_slotsfree(device_t dev); static void ahci_reset(struct ahci_channel *ch); static void ahci_start(struct ahci_channel *ch, int fbs); static void ahci_stop(struct ahci_channel *ch); static void ahci_clo(struct ahci_channel *ch); static void ahci_start_fr(struct ahci_channel *ch); static void ahci_stop_fr(struct ahci_channel *ch); static int ahci_phy_check_events(struct ahci_channel *ch, u_int32_t serr); static uint32_t ahci_ch_detval(struct ahci_channel *ch, uint32_t val); static int ahci_sata_connect(struct ahci_channel *ch); static int ahci_sata_phy_reset(struct ahci_channel *ch); static int ahci_wait_ready(struct ahci_channel *ch, int t, int t0); static void ahci_issue_recovery(struct ahci_channel *ch); static void ahci_process_read_log(struct ahci_channel *ch, union ccb *ccb); static void ahci_process_request_sense(struct ahci_channel *ch, union ccb *ccb); static void ahciaction(struct cam_sim *sim, union ccb *ccb); static void ahcipoll(struct cam_sim *sim); static MALLOC_DEFINE(M_AHCI, "AHCI driver", "AHCI driver data buffers"); #define recovery_type spriv_field0 #define RECOVERY_NONE 0 #define RECOVERY_READ_LOG 1 #define RECOVERY_REQUEST_SENSE 2 #define recovery_slot spriv_field1 static uint32_t ahci_ch_detval(struct ahci_channel *ch, uint32_t val) { return ch->disablephy ? ATA_SC_DET_DISABLE : val; } int ahci_ctlr_setup(device_t dev) { struct ahci_controller *ctlr = device_get_softc(dev); /* Clear interrupts */ ATA_OUTL(ctlr->r_mem, AHCI_IS, ATA_INL(ctlr->r_mem, AHCI_IS)); /* Configure CCC */ if (ctlr->ccc) { ATA_OUTL(ctlr->r_mem, AHCI_CCCP, ATA_INL(ctlr->r_mem, AHCI_PI)); ATA_OUTL(ctlr->r_mem, AHCI_CCCC, (ctlr->ccc << AHCI_CCCC_TV_SHIFT) | (4 << AHCI_CCCC_CC_SHIFT) | AHCI_CCCC_EN); ctlr->cccv = (ATA_INL(ctlr->r_mem, AHCI_CCCC) & AHCI_CCCC_INT_MASK) >> AHCI_CCCC_INT_SHIFT; if (bootverbose) { device_printf(dev, "CCC with %dms/4cmd enabled on vector %d\n", ctlr->ccc, ctlr->cccv); } } /* Enable AHCI interrupts */ ATA_OUTL(ctlr->r_mem, AHCI_GHC, ATA_INL(ctlr->r_mem, AHCI_GHC) | AHCI_GHC_IE); return (0); } int ahci_ctlr_reset(device_t dev) { struct ahci_controller *ctlr = device_get_softc(dev); uint32_t v; int timeout; /* BIOS/OS Handoff */ if ((ATA_INL(ctlr->r_mem, AHCI_VS) >= 0x00010200) && (ATA_INL(ctlr->r_mem, AHCI_CAP2) & AHCI_CAP2_BOH) && ((v = ATA_INL(ctlr->r_mem, AHCI_BOHC)) & AHCI_BOHC_OOS) == 0) { /* Request OS ownership. */ ATA_OUTL(ctlr->r_mem, AHCI_BOHC, v | AHCI_BOHC_OOS); /* Wait up to 2s for BIOS ownership release. */ for (timeout = 0; timeout < 80; timeout++) { DELAY(25000); v = ATA_INL(ctlr->r_mem, AHCI_BOHC); if ((v & AHCI_BOHC_BOS) == 0) break; if ((v & AHCI_BOHC_BB) == 0) break; } } /* Enable AHCI mode */ ATA_OUTL(ctlr->r_mem, AHCI_GHC, AHCI_GHC_AE); /* Reset AHCI controller */ ATA_OUTL(ctlr->r_mem, AHCI_GHC, AHCI_GHC_AE|AHCI_GHC_HR); for (timeout = 1000; timeout > 0; timeout--) { DELAY(1000); if ((ATA_INL(ctlr->r_mem, AHCI_GHC) & AHCI_GHC_HR) == 0) break; } if (timeout == 0) { device_printf(dev, "AHCI controller reset failure\n"); return (ENXIO); } /* Reenable AHCI mode */ ATA_OUTL(ctlr->r_mem, AHCI_GHC, AHCI_GHC_AE); if (ctlr->quirks & AHCI_Q_RESTORE_CAP) { /* * Restore capability field. * This is write to a read-only register to restore its state. * On fully standard-compliant hardware this is not needed and * this operation shall not take place. See ahci_pci.c for * platforms using this quirk. */ ATA_OUTL(ctlr->r_mem, AHCI_CAP, ctlr->caps); } return (0); } int ahci_attach(device_t dev) { struct ahci_controller *ctlr = device_get_softc(dev); int error, i, speed, unit; uint32_t u, version; device_t child; ctlr->dev = dev; ctlr->ccc = 0; resource_int_value(device_get_name(dev), device_get_unit(dev), "ccc", &ctlr->ccc); mtx_init(&ctlr->ch_mtx, "AHCI channels lock", NULL, MTX_DEF); /* Setup our own memory management for channels. */ ctlr->sc_iomem.rm_start = rman_get_start(ctlr->r_mem); ctlr->sc_iomem.rm_end = rman_get_end(ctlr->r_mem); ctlr->sc_iomem.rm_type = RMAN_ARRAY; ctlr->sc_iomem.rm_descr = "I/O memory addresses"; if ((error = rman_init(&ctlr->sc_iomem)) != 0) { ahci_free_mem(dev); return (error); } if ((error = rman_manage_region(&ctlr->sc_iomem, rman_get_start(ctlr->r_mem), rman_get_end(ctlr->r_mem))) != 0) { ahci_free_mem(dev); rman_fini(&ctlr->sc_iomem); return (error); } /* Get the HW capabilities */ version = ATA_INL(ctlr->r_mem, AHCI_VS); ctlr->caps = ATA_INL(ctlr->r_mem, AHCI_CAP); if (version >= 0x00010200) ctlr->caps2 = ATA_INL(ctlr->r_mem, AHCI_CAP2); if (ctlr->caps & AHCI_CAP_EMS) ctlr->capsem = ATA_INL(ctlr->r_mem, AHCI_EM_CTL); if (ctlr->quirks & AHCI_Q_FORCE_PI) { /* * Enable ports. * The spec says that BIOS sets up bits corresponding to * available ports. On platforms where this information * is missing, the driver can define available ports on its own. */ int nports = (ctlr->caps & AHCI_CAP_NPMASK) + 1; int nmask = (1 << nports) - 1; ATA_OUTL(ctlr->r_mem, AHCI_PI, nmask); device_printf(dev, "Forcing PI to %d ports (mask = %x)\n", nports, nmask); } ctlr->ichannels = ATA_INL(ctlr->r_mem, AHCI_PI); /* Identify and set separate quirks for HBA and RAID f/w Marvells. */ if ((ctlr->quirks & AHCI_Q_ALTSIG) && (ctlr->caps & AHCI_CAP_SPM) == 0) ctlr->quirks |= AHCI_Q_NOBSYRES; if (ctlr->quirks & AHCI_Q_1CH) { ctlr->caps &= ~AHCI_CAP_NPMASK; ctlr->ichannels &= 0x01; } if (ctlr->quirks & AHCI_Q_2CH) { ctlr->caps &= ~AHCI_CAP_NPMASK; ctlr->caps |= 1; ctlr->ichannels &= 0x03; } if (ctlr->quirks & AHCI_Q_4CH) { ctlr->caps &= ~AHCI_CAP_NPMASK; ctlr->caps |= 3; ctlr->ichannels &= 0x0f; } ctlr->channels = MAX(flsl(ctlr->ichannels), (ctlr->caps & AHCI_CAP_NPMASK) + 1); if (ctlr->quirks & AHCI_Q_NOPMP) ctlr->caps &= ~AHCI_CAP_SPM; if (ctlr->quirks & AHCI_Q_NONCQ) ctlr->caps &= ~AHCI_CAP_SNCQ; if ((ctlr->caps & AHCI_CAP_CCCS) == 0) ctlr->ccc = 0; ctlr->emloc = ATA_INL(ctlr->r_mem, AHCI_EM_LOC); /* Create controller-wide DMA tag. */ if (bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0, (ctlr->caps & AHCI_CAP_64BIT) ? BUS_SPACE_MAXADDR : BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_MAXSIZE, BUS_SPACE_UNRESTRICTED, BUS_SPACE_MAXSIZE, ctlr->dma_coherent ? BUS_DMA_COHERENT : 0, NULL, NULL, &ctlr->dma_tag)) { ahci_free_mem(dev); rman_fini(&ctlr->sc_iomem); return (ENXIO); } ahci_ctlr_setup(dev); /* Setup interrupts. */ if ((error = ahci_setup_interrupt(dev)) != 0) { bus_dma_tag_destroy(ctlr->dma_tag); ahci_free_mem(dev); rman_fini(&ctlr->sc_iomem); return (error); } i = 0; for (u = ctlr->ichannels; u != 0; u >>= 1) i += (u & 1); ctlr->direct = (ctlr->msi && (ctlr->numirqs > 1 || i <= 3)); resource_int_value(device_get_name(dev), device_get_unit(dev), "direct", &ctlr->direct); /* Announce HW capabilities. */ speed = (ctlr->caps & AHCI_CAP_ISS) >> AHCI_CAP_ISS_SHIFT; device_printf(dev, "AHCI v%x.%02x with %d %sGbps ports, Port Multiplier %s%s\n", ((version >> 20) & 0xf0) + ((version >> 16) & 0x0f), ((version >> 4) & 0xf0) + (version & 0x0f), (ctlr->caps & AHCI_CAP_NPMASK) + 1, ((speed == 1) ? "1.5":((speed == 2) ? "3": ((speed == 3) ? "6":"?"))), (ctlr->caps & AHCI_CAP_SPM) ? "supported" : "not supported", (ctlr->caps & AHCI_CAP_FBSS) ? " with FBS" : ""); if (ctlr->quirks != 0) { device_printf(dev, "quirks=0x%b\n", ctlr->quirks, AHCI_Q_BIT_STRING); } if (bootverbose) { device_printf(dev, "Caps:%s%s%s%s%s%s%s%s %sGbps", (ctlr->caps & AHCI_CAP_64BIT) ? " 64bit":"", (ctlr->caps & AHCI_CAP_SNCQ) ? " NCQ":"", (ctlr->caps & AHCI_CAP_SSNTF) ? " SNTF":"", (ctlr->caps & AHCI_CAP_SMPS) ? " MPS":"", (ctlr->caps & AHCI_CAP_SSS) ? " SS":"", (ctlr->caps & AHCI_CAP_SALP) ? " ALP":"", (ctlr->caps & AHCI_CAP_SAL) ? " AL":"", (ctlr->caps & AHCI_CAP_SCLO) ? " CLO":"", ((speed == 1) ? "1.5":((speed == 2) ? "3": ((speed == 3) ? "6":"?")))); printf("%s%s%s%s%s%s %dcmd%s%s%s %dports\n", (ctlr->caps & AHCI_CAP_SAM) ? " AM":"", (ctlr->caps & AHCI_CAP_SPM) ? " PM":"", (ctlr->caps & AHCI_CAP_FBSS) ? " FBS":"", (ctlr->caps & AHCI_CAP_PMD) ? " PMD":"", (ctlr->caps & AHCI_CAP_SSC) ? " SSC":"", (ctlr->caps & AHCI_CAP_PSC) ? " PSC":"", ((ctlr->caps & AHCI_CAP_NCS) >> AHCI_CAP_NCS_SHIFT) + 1, (ctlr->caps & AHCI_CAP_CCCS) ? " CCC":"", (ctlr->caps & AHCI_CAP_EMS) ? " EM":"", (ctlr->caps & AHCI_CAP_SXS) ? " eSATA":"", (ctlr->caps & AHCI_CAP_NPMASK) + 1); } if (bootverbose && version >= 0x00010200) { device_printf(dev, "Caps2:%s%s%s%s%s%s\n", (ctlr->caps2 & AHCI_CAP2_DESO) ? " DESO":"", (ctlr->caps2 & AHCI_CAP2_SADM) ? " SADM":"", (ctlr->caps2 & AHCI_CAP2_SDS) ? " SDS":"", (ctlr->caps2 & AHCI_CAP2_APST) ? " APST":"", (ctlr->caps2 & AHCI_CAP2_NVMP) ? " NVMP":"", (ctlr->caps2 & AHCI_CAP2_BOH) ? " BOH":""); } /* Attach all channels on this controller */ for (unit = 0; unit < ctlr->channels; unit++) { child = device_add_child(dev, "ahcich", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(dev, "failed to add channel device\n"); continue; } device_set_ivars(child, (void *)(intptr_t)unit); if ((ctlr->ichannels & (1 << unit)) == 0) device_disable(child); } /* Attach any remapped NVME device */ for (; unit < ctlr->channels + ctlr->remapped_devices; unit++) { child = device_add_child(dev, "nvme", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(dev, "failed to add remapped NVMe device"); continue; } device_set_ivars(child, (void *)(intptr_t)(unit | AHCI_REMAPPED_UNIT)); } int em = (ctlr->caps & AHCI_CAP_EMS) != 0; resource_int_value(device_get_name(dev), device_get_unit(dev), "em", &em); if (em) { child = device_add_child(dev, "ahciem", DEVICE_UNIT_ANY); if (child == NULL) device_printf(dev, "failed to add enclosure device\n"); else device_set_ivars(child, (void *)(intptr_t)AHCI_EM_UNIT); } bus_attach_children(dev); return (0); } int ahci_detach(device_t dev) { struct ahci_controller *ctlr = device_get_softc(dev); - int i; + int error, i; /* Detach & delete all children */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); /* Free interrupts. */ for (i = 0; i < ctlr->numirqs; i++) { if (ctlr->irqs[i].r_irq) { bus_teardown_intr(dev, ctlr->irqs[i].r_irq, ctlr->irqs[i].handle); bus_release_resource(dev, SYS_RES_IRQ, ctlr->irqs[i].r_irq_rid, ctlr->irqs[i].r_irq); } } bus_dma_tag_destroy(ctlr->dma_tag); /* Free memory. */ rman_fini(&ctlr->sc_iomem); ahci_free_mem(dev); mtx_destroy(&ctlr->ch_mtx); return (0); } void ahci_free_mem(device_t dev) { struct ahci_controller *ctlr = device_get_softc(dev); /* Release memory resources */ if (ctlr->r_mem) bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); if (ctlr->r_msix_table) bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_msix_tab_rid, ctlr->r_msix_table); if (ctlr->r_msix_pba) bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_msix_pba_rid, ctlr->r_msix_pba); ctlr->r_msix_pba = ctlr->r_mem = ctlr->r_msix_table = NULL; } int ahci_setup_interrupt(device_t dev) { struct ahci_controller *ctlr = device_get_softc(dev); int i; /* Check for single MSI vector fallback. */ if (ctlr->numirqs > 1 && (ATA_INL(ctlr->r_mem, AHCI_GHC) & AHCI_GHC_MRSM) != 0) { device_printf(dev, "Falling back to one MSI\n"); ctlr->numirqs = 1; } /* Ensure we don't overrun irqs. */ if (ctlr->numirqs > AHCI_MAX_IRQS) { device_printf(dev, "Too many irqs %d > %d (clamping)\n", ctlr->numirqs, AHCI_MAX_IRQS); ctlr->numirqs = AHCI_MAX_IRQS; } /* Allocate all IRQs. */ for (i = 0; i < ctlr->numirqs; i++) { ctlr->irqs[i].ctlr = ctlr; ctlr->irqs[i].r_irq_rid = i + (ctlr->msi ? 1 : 0); if (ctlr->channels == 1 && !ctlr->ccc && ctlr->msi) ctlr->irqs[i].mode = AHCI_IRQ_MODE_ONE; else if (ctlr->numirqs == 1 || i >= ctlr->channels || (ctlr->ccc && i == ctlr->cccv)) ctlr->irqs[i].mode = AHCI_IRQ_MODE_ALL; else if (ctlr->channels > ctlr->numirqs && i == ctlr->numirqs - 1) ctlr->irqs[i].mode = AHCI_IRQ_MODE_AFTER; else ctlr->irqs[i].mode = AHCI_IRQ_MODE_ONE; if (!(ctlr->irqs[i].r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &ctlr->irqs[i].r_irq_rid, RF_SHAREABLE | RF_ACTIVE))) { device_printf(dev, "unable to map interrupt\n"); return (ENXIO); } if ((bus_setup_intr(dev, ctlr->irqs[i].r_irq, ATA_INTR_FLAGS, NULL, (ctlr->irqs[i].mode != AHCI_IRQ_MODE_ONE) ? ahci_intr : ((ctlr->quirks & AHCI_Q_EDGEIS) ? ahci_intr_one_edge : ahci_intr_one), &ctlr->irqs[i], &ctlr->irqs[i].handle))) { /* SOS XXX release r_irq */ device_printf(dev, "unable to setup interrupt\n"); return (ENXIO); } if (ctlr->numirqs > 1) { bus_describe_intr(dev, ctlr->irqs[i].r_irq, ctlr->irqs[i].handle, ctlr->irqs[i].mode == AHCI_IRQ_MODE_ONE ? "ch%d" : "%d", i); } } return (0); } /* * Common case interrupt handler. */ static void ahci_intr(void *data) { struct ahci_controller_irq *irq = data; struct ahci_controller *ctlr = irq->ctlr; u_int32_t is, ise = 0; void *arg; int unit; if (irq->mode == AHCI_IRQ_MODE_ALL) { unit = 0; if (ctlr->ccc) is = ctlr->ichannels; else is = ATA_INL(ctlr->r_mem, AHCI_IS); } else { /* AHCI_IRQ_MODE_AFTER */ unit = irq->r_irq_rid - 1; is = ATA_INL(ctlr->r_mem, AHCI_IS); is &= (0xffffffff << unit); } /* CCC interrupt is edge triggered. */ if (ctlr->ccc) ise = 1 << ctlr->cccv; /* Some controllers have edge triggered IS. */ if (ctlr->quirks & AHCI_Q_EDGEIS) ise |= is; if (ise != 0) ATA_OUTL(ctlr->r_mem, AHCI_IS, ise); for (; unit < ctlr->channels; unit++) { if ((is & (1 << unit)) != 0 && (arg = ctlr->interrupt[unit].argument)) { ctlr->interrupt[unit].function(arg); } } for (; unit < ctlr->channels + ctlr->remapped_devices; unit++) { if ((arg = ctlr->interrupt[unit].argument)) { ctlr->interrupt[unit].function(arg); } } /* AHCI declares level triggered IS. */ if (!(ctlr->quirks & AHCI_Q_EDGEIS)) ATA_OUTL(ctlr->r_mem, AHCI_IS, is); ATA_RBL(ctlr->r_mem, AHCI_IS); } /* * Simplified interrupt handler for multivector MSI mode. */ static void ahci_intr_one(void *data) { struct ahci_controller_irq *irq = data; struct ahci_controller *ctlr = irq->ctlr; void *arg; int unit; unit = irq->r_irq_rid - 1; if ((arg = ctlr->interrupt[unit].argument)) ctlr->interrupt[unit].function(arg); /* AHCI declares level triggered IS. */ ATA_OUTL(ctlr->r_mem, AHCI_IS, 1 << unit); ATA_RBL(ctlr->r_mem, AHCI_IS); } static void ahci_intr_one_edge(void *data) { struct ahci_controller_irq *irq = data; struct ahci_controller *ctlr = irq->ctlr; void *arg; int unit; unit = irq->r_irq_rid - 1; /* Some controllers have edge triggered IS. */ ATA_OUTL(ctlr->r_mem, AHCI_IS, 1 << unit); if ((arg = ctlr->interrupt[unit].argument)) ctlr->interrupt[unit].function(arg); ATA_RBL(ctlr->r_mem, AHCI_IS); } struct resource * ahci_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct ahci_controller *ctlr = device_get_softc(dev); struct resource *res; rman_res_t st; int offset, size, unit; bool is_em, is_remapped; unit = (intptr_t)device_get_ivars(child); is_em = is_remapped = false; if (unit & AHCI_REMAPPED_UNIT) { unit &= AHCI_UNIT; unit -= ctlr->channels; is_remapped = true; } else if (unit & AHCI_EM_UNIT) { unit &= AHCI_UNIT; is_em = true; } res = NULL; switch (type) { case SYS_RES_MEMORY: if (is_remapped) { offset = ctlr->remap_offset + unit * ctlr->remap_size; size = ctlr->remap_size; } else if (!is_em) { offset = AHCI_OFFSET + (unit << 7); size = 128; } else if ((ctlr->caps & AHCI_CAP_EMS) == 0) { break; } else if (*rid == 0) { offset = AHCI_EM_CTL; size = 4; } else { offset = (ctlr->emloc & 0xffff0000) >> 14; size = (ctlr->emloc & 0x0000ffff) << 2; if (*rid != 1) { if (*rid == 2 && (ctlr->capsem & (AHCI_EM_XMT | AHCI_EM_SMB)) == 0) offset += size; else break; } } st = rman_get_start(ctlr->r_mem); res = rman_reserve_resource(&ctlr->sc_iomem, st + offset, st + offset + size - 1, size, RF_ACTIVE, child); if (res) { bus_space_handle_t bsh; bus_space_tag_t bst; bsh = rman_get_bushandle(ctlr->r_mem); bst = rman_get_bustag(ctlr->r_mem); bus_space_subregion(bst, bsh, offset, 128, &bsh); rman_set_bushandle(res, bsh); rman_set_bustag(res, bst); } break; case SYS_RES_IRQ: if (*rid == ATA_IRQ_RID) res = ctlr->irqs[0].r_irq; break; } return (res); } int ahci_release_resource(device_t dev, device_t child, struct resource *r) { switch (rman_get_type(r)) { case SYS_RES_MEMORY: rman_release_resource(r); return (0); case SYS_RES_IRQ: if (rman_get_rid(r) != ATA_IRQ_RID) return (ENOENT); return (0); } return (EINVAL); } int ahci_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *function, void *argument, void **cookiep) { struct ahci_controller *ctlr = device_get_softc(dev); int unit = (intptr_t)device_get_ivars(child) & AHCI_UNIT; if (filter != NULL) { printf("ahci.c: we cannot use a filter here\n"); return (EINVAL); } ctlr->interrupt[unit].function = function; ctlr->interrupt[unit].argument = argument; return (0); } int ahci_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { struct ahci_controller *ctlr = device_get_softc(dev); int unit = (intptr_t)device_get_ivars(child) & AHCI_UNIT; ctlr->interrupt[unit].function = NULL; ctlr->interrupt[unit].argument = NULL; return (0); } int ahci_print_child(device_t dev, device_t child) { intptr_t ivars; int retval; retval = bus_print_child_header(dev, child); ivars = (intptr_t)device_get_ivars(child); if ((ivars & AHCI_EM_UNIT) == 0) retval += printf(" at channel %d", (int)ivars & AHCI_UNIT); retval += bus_print_child_footer(dev, child); return (retval); } int ahci_child_location(device_t dev, device_t child, struct sbuf *sb) { intptr_t ivars; ivars = (intptr_t)device_get_ivars(child); if ((ivars & AHCI_EM_UNIT) == 0) sbuf_printf(sb, "channel=%d", (int)ivars & AHCI_UNIT); return (0); } bus_dma_tag_t ahci_get_dma_tag(device_t dev, device_t child) { struct ahci_controller *ctlr = device_get_softc(dev); return (ctlr->dma_tag); } void ahci_attached(device_t dev, struct ahci_channel *ch) { struct ahci_controller *ctlr = device_get_softc(dev); mtx_lock(&ctlr->ch_mtx); ctlr->ch[ch->unit] = ch; mtx_unlock(&ctlr->ch_mtx); } void ahci_detached(device_t dev, struct ahci_channel *ch) { struct ahci_controller *ctlr = device_get_softc(dev); mtx_lock(&ctlr->ch_mtx); mtx_lock(&ch->mtx); ctlr->ch[ch->unit] = NULL; mtx_unlock(&ch->mtx); mtx_unlock(&ctlr->ch_mtx); } struct ahci_channel * ahci_getch(device_t dev, int n) { struct ahci_controller *ctlr = device_get_softc(dev); struct ahci_channel *ch; KASSERT(n >= 0 && n < AHCI_MAX_PORTS, ("Bad channel number %d", n)); mtx_lock(&ctlr->ch_mtx); ch = ctlr->ch[n]; if (ch != NULL) mtx_lock(&ch->mtx); mtx_unlock(&ctlr->ch_mtx); return (ch); } void ahci_putch(struct ahci_channel *ch) { mtx_unlock(&ch->mtx); } static int ahci_ch_probe(device_t dev) { device_set_desc(dev, "AHCI channel"); return (BUS_PROBE_DEFAULT); } static int ahci_ch_disablephy_proc(SYSCTL_HANDLER_ARGS) { struct ahci_channel *ch; int error, value; ch = arg1; value = ch->disablephy; error = sysctl_handle_int(oidp, &value, 0, req); if (error != 0 || req->newptr == NULL || (value != 0 && value != 1)) return (error); mtx_lock(&ch->mtx); ch->disablephy = value; if (value) { ahci_ch_deinit(ch->dev); } else { ahci_ch_init(ch->dev); ahci_phy_check_events(ch, ATA_SE_PHY_CHANGED | ATA_SE_EXCHANGED); } mtx_unlock(&ch->mtx); return (0); } static int ahci_ch_attach(device_t dev) { struct ahci_controller *ctlr = device_get_softc(device_get_parent(dev)); struct ahci_channel *ch = device_get_softc(dev); struct cam_devq *devq; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; int rid, error, i, sata_rev = 0; u_int32_t version; ch->dev = dev; ch->unit = (intptr_t)device_get_ivars(dev); ch->caps = ctlr->caps; ch->caps2 = ctlr->caps2; ch->start = ctlr->ch_start; ch->quirks = ctlr->quirks; ch->vendorid = ctlr->vendorid; ch->deviceid = ctlr->deviceid; ch->subvendorid = ctlr->subvendorid; ch->subdeviceid = ctlr->subdeviceid; ch->numslots = ((ch->caps & AHCI_CAP_NCS) >> AHCI_CAP_NCS_SHIFT) + 1; mtx_init(&ch->mtx, "AHCI channel lock", NULL, MTX_DEF); ch->pm_level = 0; resource_int_value(device_get_name(dev), device_get_unit(dev), "pm_level", &ch->pm_level); STAILQ_INIT(&ch->doneq); if (ch->pm_level > 3) callout_init_mtx(&ch->pm_timer, &ch->mtx, 0); callout_init_mtx(&ch->reset_timer, &ch->mtx, 0); /* JMicron external ports (0) sometimes limited */ if ((ctlr->quirks & AHCI_Q_SATA1_UNIT0) && ch->unit == 0) sata_rev = 1; if (ch->quirks & AHCI_Q_SATA2) sata_rev = 2; resource_int_value(device_get_name(dev), device_get_unit(dev), "sata_rev", &sata_rev); for (i = 0; i < 16; i++) { ch->user[i].revision = sata_rev; ch->user[i].mode = 0; ch->user[i].bytecount = 8192; ch->user[i].tags = ch->numslots; ch->user[i].caps = 0; ch->curr[i] = ch->user[i]; if (ch->pm_level) { ch->user[i].caps = CTS_SATA_CAPS_H_PMREQ | CTS_SATA_CAPS_H_APST | CTS_SATA_CAPS_D_PMREQ | CTS_SATA_CAPS_D_APST; } ch->user[i].caps |= CTS_SATA_CAPS_H_DMAAA | CTS_SATA_CAPS_H_AN; } rid = 0; if (!(ch->r_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE))) return (ENXIO); ch->chcaps = ATA_INL(ch->r_mem, AHCI_P_CMD); version = ATA_INL(ctlr->r_mem, AHCI_VS); if (version < 0x00010200 && (ctlr->caps & AHCI_CAP_FBSS)) ch->chcaps |= AHCI_P_CMD_FBSCP; if (ch->caps2 & AHCI_CAP2_SDS) ch->chscaps = ATA_INL(ch->r_mem, AHCI_P_DEVSLP); if (bootverbose) { device_printf(dev, "Caps:%s%s%s%s%s%s\n", (ch->chcaps & AHCI_P_CMD_HPCP) ? " HPCP":"", (ch->chcaps & AHCI_P_CMD_MPSP) ? " MPSP":"", (ch->chcaps & AHCI_P_CMD_CPD) ? " CPD":"", (ch->chcaps & AHCI_P_CMD_ESP) ? " ESP":"", (ch->chcaps & AHCI_P_CMD_FBSCP) ? " FBSCP":"", (ch->chscaps & AHCI_P_DEVSLP_DSP) ? " DSP":""); } ahci_dmainit(dev); ahci_slotsalloc(dev); mtx_lock(&ch->mtx); ahci_ch_init(dev); rid = ATA_IRQ_RID; if (!(ch->r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE))) { device_printf(dev, "Unable to map interrupt\n"); error = ENXIO; goto err0; } if ((bus_setup_intr(dev, ch->r_irq, ATA_INTR_FLAGS, NULL, ctlr->direct ? ahci_ch_intr_direct : ahci_ch_intr, ch, &ch->ih))) { device_printf(dev, "Unable to setup interrupt\n"); error = ENXIO; goto err1; } /* Create the device queue for our SIM. */ devq = cam_simq_alloc(ch->numslots); if (devq == NULL) { device_printf(dev, "Unable to allocate simq\n"); error = ENOMEM; goto err1; } /* Construct SIM entry */ ch->sim = cam_sim_alloc(ahciaction, ahcipoll, "ahcich", ch, device_get_unit(dev), (struct mtx *)&ch->mtx, (ch->quirks & AHCI_Q_NOCCS) ? 1 : min(2, ch->numslots), (ch->caps & AHCI_CAP_SNCQ) ? ch->numslots : 0, devq); if (ch->sim == NULL) { cam_simq_free(devq); device_printf(dev, "unable to allocate sim\n"); error = ENOMEM; goto err1; } if (xpt_bus_register(ch->sim, dev, 0) != CAM_SUCCESS) { device_printf(dev, "unable to register xpt bus\n"); error = ENXIO; goto err2; } if (xpt_create_path(&ch->path, /*periph*/NULL, cam_sim_path(ch->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { device_printf(dev, "unable to create path\n"); error = ENXIO; goto err3; } if (ch->pm_level > 3) { callout_reset(&ch->pm_timer, (ch->pm_level == 4) ? hz / 1000 : hz / 8, ahci_ch_pm, ch); } mtx_unlock(&ch->mtx); ahci_attached(device_get_parent(dev), ch); ctx = device_get_sysctl_ctx(dev); tree = device_get_sysctl_tree(dev); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "disable_phy", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, ch, 0, ahci_ch_disablephy_proc, "IU", "Disable PHY"); return (0); err3: xpt_bus_deregister(cam_sim_path(ch->sim)); err2: cam_sim_free(ch->sim, /*free_devq*/TRUE); err1: bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq); err0: bus_release_resource(dev, SYS_RES_MEMORY, ch->unit, ch->r_mem); mtx_unlock(&ch->mtx); mtx_destroy(&ch->mtx); return (error); } static int ahci_ch_detach(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); ahci_detached(device_get_parent(dev), ch); mtx_lock(&ch->mtx); xpt_async(AC_LOST_DEVICE, ch->path, NULL); /* Forget about reset. */ if (ch->resetting) { ch->resetting = 0; xpt_release_simq(ch->sim, TRUE); } xpt_free_path(ch->path); xpt_bus_deregister(cam_sim_path(ch->sim)); cam_sim_free(ch->sim, /*free_devq*/TRUE); mtx_unlock(&ch->mtx); if (ch->pm_level > 3) callout_drain(&ch->pm_timer); callout_drain(&ch->reset_timer); bus_teardown_intr(dev, ch->r_irq, ch->ih); bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq); ahci_ch_deinit(dev); ahci_slotsfree(dev); ahci_dmafini(dev); bus_release_resource(dev, SYS_RES_MEMORY, ch->unit, ch->r_mem); mtx_destroy(&ch->mtx); return (0); } static int ahci_ch_init(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); uint64_t work; /* Disable port interrupts */ ATA_OUTL(ch->r_mem, AHCI_P_IE, 0); /* Setup work areas */ work = ch->dma.work_bus + AHCI_CL_OFFSET; ATA_OUTL(ch->r_mem, AHCI_P_CLB, work & 0xffffffff); ATA_OUTL(ch->r_mem, AHCI_P_CLBU, work >> 32); work = ch->dma.rfis_bus; ATA_OUTL(ch->r_mem, AHCI_P_FB, work & 0xffffffff); ATA_OUTL(ch->r_mem, AHCI_P_FBU, work >> 32); /* Activate the channel and power/spin up device */ ATA_OUTL(ch->r_mem, AHCI_P_CMD, (AHCI_P_CMD_ACTIVE | AHCI_P_CMD_POD | AHCI_P_CMD_SUD | ((ch->pm_level == 2 || ch->pm_level == 3) ? AHCI_P_CMD_ALPE : 0) | ((ch->pm_level > 2) ? AHCI_P_CMD_ASP : 0 ))); ahci_start_fr(ch); ahci_start(ch, 1); return (0); } static int ahci_ch_deinit(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); /* Disable port interrupts. */ ATA_OUTL(ch->r_mem, AHCI_P_IE, 0); /* Reset command register. */ ahci_stop(ch); ahci_stop_fr(ch); ATA_OUTL(ch->r_mem, AHCI_P_CMD, 0); /* Allow everything, including partial and slumber modes. */ ATA_OUTL(ch->r_mem, AHCI_P_SCTL, 0); /* Request slumber mode transition and give some time to get there. */ ATA_OUTL(ch->r_mem, AHCI_P_CMD, AHCI_P_CMD_SLUMBER); DELAY(100); /* Disable PHY. */ ATA_OUTL(ch->r_mem, AHCI_P_SCTL, ATA_SC_DET_DISABLE); return (0); } static int ahci_ch_suspend(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); mtx_lock(&ch->mtx); xpt_freeze_simq(ch->sim, 1); /* Forget about reset. */ if (ch->resetting) { ch->resetting = 0; callout_stop(&ch->reset_timer); xpt_release_simq(ch->sim, TRUE); } while (ch->oslots) msleep(ch, &ch->mtx, PRIBIO, "ahcisusp", hz/100); ahci_ch_deinit(dev); mtx_unlock(&ch->mtx); return (0); } static int ahci_ch_resume(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); mtx_lock(&ch->mtx); ahci_ch_init(dev); ahci_reset(ch); xpt_release_simq(ch->sim, TRUE); mtx_unlock(&ch->mtx); return (0); } static device_method_t ahcich_methods[] = { DEVMETHOD(device_probe, ahci_ch_probe), DEVMETHOD(device_attach, ahci_ch_attach), DEVMETHOD(device_detach, ahci_ch_detach), DEVMETHOD(device_suspend, ahci_ch_suspend), DEVMETHOD(device_resume, ahci_ch_resume), DEVMETHOD_END }; static driver_t ahcich_driver = { "ahcich", ahcich_methods, sizeof(struct ahci_channel) }; DRIVER_MODULE(ahcich, ahci, ahcich_driver, NULL, NULL); struct ahci_dc_cb_args { bus_addr_t maddr; int error; }; static void ahci_dmainit(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); struct ahci_dc_cb_args dcba; size_t rfsize; int error; /* Command area. */ error = bus_dma_tag_create(bus_get_dma_tag(dev), 1024, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, AHCI_WORK_SIZE, 1, AHCI_WORK_SIZE, 0, NULL, NULL, &ch->dma.work_tag); if (error != 0) goto error; error = bus_dmamem_alloc(ch->dma.work_tag, (void **)&ch->dma.work, BUS_DMA_ZERO, &ch->dma.work_map); if (error != 0) goto error; error = bus_dmamap_load(ch->dma.work_tag, ch->dma.work_map, ch->dma.work, AHCI_WORK_SIZE, ahci_dmasetupc_cb, &dcba, BUS_DMA_NOWAIT); if (error != 0 || (error = dcba.error) != 0) { bus_dmamem_free(ch->dma.work_tag, ch->dma.work, ch->dma.work_map); goto error; } ch->dma.work_bus = dcba.maddr; /* FIS receive area. */ if (ch->chcaps & AHCI_P_CMD_FBSCP) rfsize = 4096; else rfsize = 256; error = bus_dma_tag_create(bus_get_dma_tag(dev), rfsize, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, rfsize, 1, rfsize, 0, NULL, NULL, &ch->dma.rfis_tag); if (error != 0) goto error; error = bus_dmamem_alloc(ch->dma.rfis_tag, (void **)&ch->dma.rfis, 0, &ch->dma.rfis_map); if (error != 0) goto error; error = bus_dmamap_load(ch->dma.rfis_tag, ch->dma.rfis_map, ch->dma.rfis, rfsize, ahci_dmasetupc_cb, &dcba, BUS_DMA_NOWAIT); if (error != 0 || (error = dcba.error) != 0) { bus_dmamem_free(ch->dma.rfis_tag, ch->dma.rfis, ch->dma.rfis_map); goto error; } ch->dma.rfis_bus = dcba.maddr; /* Data area. */ error = bus_dma_tag_create(bus_get_dma_tag(dev), 2, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, AHCI_SG_ENTRIES * PAGE_SIZE, AHCI_SG_ENTRIES, AHCI_PRD_MAX, 0, busdma_lock_mutex, &ch->mtx, &ch->dma.data_tag); if (error != 0) goto error; return; error: device_printf(dev, "WARNING - DMA initialization failed, error %d\n", error); ahci_dmafini(dev); } static void ahci_dmasetupc_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error) { struct ahci_dc_cb_args *dcba = (struct ahci_dc_cb_args *)xsc; if (!(dcba->error = error)) dcba->maddr = segs[0].ds_addr; } static void ahci_dmafini(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); if (ch->dma.data_tag) { bus_dma_tag_destroy(ch->dma.data_tag); ch->dma.data_tag = NULL; } if (ch->dma.rfis_bus) { bus_dmamap_unload(ch->dma.rfis_tag, ch->dma.rfis_map); bus_dmamem_free(ch->dma.rfis_tag, ch->dma.rfis, ch->dma.rfis_map); ch->dma.rfis_bus = 0; ch->dma.rfis = NULL; } if (ch->dma.work_bus) { bus_dmamap_unload(ch->dma.work_tag, ch->dma.work_map); bus_dmamem_free(ch->dma.work_tag, ch->dma.work, ch->dma.work_map); ch->dma.work_bus = 0; ch->dma.work = NULL; } if (ch->dma.work_tag) { bus_dma_tag_destroy(ch->dma.work_tag); ch->dma.work_tag = NULL; } } static void ahci_slotsalloc(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); int i; /* Alloc and setup command/dma slots */ bzero(ch->slot, sizeof(ch->slot)); for (i = 0; i < ch->numslots; i++) { struct ahci_slot *slot = &ch->slot[i]; slot->ch = ch; slot->slot = i; slot->state = AHCI_SLOT_EMPTY; slot->ct_offset = AHCI_CT_OFFSET + AHCI_CT_SIZE * i; slot->ccb = NULL; callout_init_mtx(&slot->timeout, &ch->mtx, 0); if (bus_dmamap_create(ch->dma.data_tag, 0, &slot->dma.data_map)) device_printf(ch->dev, "FAILURE - create data_map\n"); } } static void ahci_slotsfree(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); int i; /* Free all dma slots */ for (i = 0; i < ch->numslots; i++) { struct ahci_slot *slot = &ch->slot[i]; callout_drain(&slot->timeout); if (slot->dma.data_map) { bus_dmamap_destroy(ch->dma.data_tag, slot->dma.data_map); slot->dma.data_map = NULL; } } } static int ahci_phy_check_events(struct ahci_channel *ch, u_int32_t serr) { if (((ch->pm_level == 0) && (serr & ATA_SE_PHY_CHANGED)) || ((ch->pm_level != 0 || ch->listening) && (serr & ATA_SE_EXCHANGED))) { u_int32_t status = ATA_INL(ch->r_mem, AHCI_P_SSTS); union ccb *ccb; if (bootverbose) { if ((status & ATA_SS_DET_MASK) != ATA_SS_DET_NO_DEVICE) device_printf(ch->dev, "CONNECT requested\n"); else device_printf(ch->dev, "DISCONNECT requested\n"); } ahci_reset(ch); if ((ccb = xpt_alloc_ccb_nowait()) == NULL) return (0); if (xpt_create_path(&ccb->ccb_h.path, NULL, cam_sim_path(ch->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_free_ccb(ccb); return (0); } xpt_rescan(ccb); return (1); } return (0); } static void ahci_cpd_check_events(struct ahci_channel *ch) { u_int32_t status; union ccb *ccb; device_t dev; if (ch->pm_level == 0) return; status = ATA_INL(ch->r_mem, AHCI_P_CMD); if ((status & AHCI_P_CMD_CPD) == 0) return; if (bootverbose) { dev = ch->dev; if (status & AHCI_P_CMD_CPS) { device_printf(dev, "COLD CONNECT requested\n"); } else device_printf(dev, "COLD DISCONNECT requested\n"); } ahci_reset(ch); if ((ccb = xpt_alloc_ccb_nowait()) == NULL) return; if (xpt_create_path(&ccb->ccb_h.path, NULL, cam_sim_path(ch->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_free_ccb(ccb); return; } xpt_rescan(ccb); } static void ahci_notify_events(struct ahci_channel *ch, u_int32_t status) { struct cam_path *dpath; int i; if (ch->caps & AHCI_CAP_SSNTF) ATA_OUTL(ch->r_mem, AHCI_P_SNTF, status); if (bootverbose) device_printf(ch->dev, "SNTF 0x%04x\n", status); for (i = 0; i < 16; i++) { if ((status & (1 << i)) == 0) continue; if (xpt_create_path(&dpath, NULL, xpt_path_path_id(ch->path), i, 0) == CAM_REQ_CMP) { xpt_async(AC_SCSI_AEN, dpath, NULL); xpt_free_path(dpath); } } } static void ahci_done(struct ahci_channel *ch, union ccb *ccb) { mtx_assert(&ch->mtx, MA_OWNED); if ((ccb->ccb_h.func_code & XPT_FC_QUEUED) == 0 || ch->batch == 0) { xpt_done(ccb); return; } STAILQ_INSERT_TAIL(&ch->doneq, &ccb->ccb_h, sim_links.stqe); } static void ahci_ch_intr(void *arg) { struct ahci_channel *ch = (struct ahci_channel *)arg; uint32_t istatus; /* Read interrupt statuses. */ istatus = ATA_INL(ch->r_mem, AHCI_P_IS); mtx_lock(&ch->mtx); ahci_ch_intr_main(ch, istatus); mtx_unlock(&ch->mtx); } static void ahci_ch_intr_direct(void *arg) { struct ahci_channel *ch = (struct ahci_channel *)arg; struct ccb_hdr *ccb_h; uint32_t istatus; STAILQ_HEAD(, ccb_hdr) tmp_doneq = STAILQ_HEAD_INITIALIZER(tmp_doneq); /* Read interrupt statuses. */ istatus = ATA_INL(ch->r_mem, AHCI_P_IS); mtx_lock(&ch->mtx); ch->batch = 1; ahci_ch_intr_main(ch, istatus); ch->batch = 0; /* * Prevent the possibility of issues caused by processing the queue * while unlocked below by moving the contents to a local queue. */ STAILQ_CONCAT(&tmp_doneq, &ch->doneq); mtx_unlock(&ch->mtx); while ((ccb_h = STAILQ_FIRST(&tmp_doneq)) != NULL) { STAILQ_REMOVE_HEAD(&tmp_doneq, sim_links.stqe); xpt_done_direct((union ccb *)ccb_h); } } static void ahci_ch_pm(void *arg) { struct ahci_channel *ch = (struct ahci_channel *)arg; uint32_t work; if (ch->numrslots != 0) return; work = ATA_INL(ch->r_mem, AHCI_P_CMD); if (ch->pm_level == 4) work |= AHCI_P_CMD_PARTIAL; else work |= AHCI_P_CMD_SLUMBER; ATA_OUTL(ch->r_mem, AHCI_P_CMD, work); } static void ahci_ch_intr_main(struct ahci_channel *ch, uint32_t istatus) { uint32_t cstatus, serr = 0, sntf = 0, ok, err; enum ahci_err_type et; int i, ccs, port, reset = 0; /* Clear interrupt statuses. */ ATA_OUTL(ch->r_mem, AHCI_P_IS, istatus); /* Read command statuses. */ if (ch->numtslots != 0) cstatus = ATA_INL(ch->r_mem, AHCI_P_SACT); else cstatus = 0; if (ch->numrslots != ch->numtslots) cstatus |= ATA_INL(ch->r_mem, AHCI_P_CI); /* Read SNTF in one of possible ways. */ if ((istatus & AHCI_P_IX_SDB) && (ch->pm_present || ch->curr[0].atapi != 0)) { if (ch->caps & AHCI_CAP_SSNTF) sntf = ATA_INL(ch->r_mem, AHCI_P_SNTF); else if (ch->fbs_enabled) { u_int8_t *fis = ch->dma.rfis + 0x58; for (i = 0; i < 16; i++) { if (fis[1] & 0x80) { fis[1] &= 0x7f; sntf |= 1 << i; } fis += 256; } } else { u_int8_t *fis = ch->dma.rfis + 0x58; if (fis[1] & 0x80) sntf = (1 << (fis[1] & 0x0f)); } } /* Process PHY events */ if (istatus & (AHCI_P_IX_PC | AHCI_P_IX_PRC | AHCI_P_IX_OF | AHCI_P_IX_IF | AHCI_P_IX_HBD | AHCI_P_IX_HBF | AHCI_P_IX_TFE)) { serr = ATA_INL(ch->r_mem, AHCI_P_SERR); if (serr) { ATA_OUTL(ch->r_mem, AHCI_P_SERR, serr); reset = ahci_phy_check_events(ch, serr); } } /* Process cold presence detection events */ if ((istatus & AHCI_P_IX_CPD) && !reset) ahci_cpd_check_events(ch); /* Process command errors */ if (istatus & (AHCI_P_IX_OF | AHCI_P_IX_IF | AHCI_P_IX_HBD | AHCI_P_IX_HBF | AHCI_P_IX_TFE)) { if (ch->quirks & AHCI_Q_NOCCS) { /* * ASMedia chips sometimes report failed commands as * completed. Count all running commands as failed. */ cstatus |= ch->rslots; /* They also report wrong CCS, so try to guess one. */ ccs = powerof2(cstatus) ? ffs(cstatus) - 1 : -1; } else { ccs = (ATA_INL(ch->r_mem, AHCI_P_CMD) & AHCI_P_CMD_CCS_MASK) >> AHCI_P_CMD_CCS_SHIFT; } //device_printf(dev, "%s ERROR is %08x cs %08x ss %08x rs %08x tfd %02x serr %08x fbs %08x ccs %d\n", // __func__, istatus, cstatus, sstatus, ch->rslots, ATA_INL(ch->r_mem, AHCI_P_TFD), // serr, ATA_INL(ch->r_mem, AHCI_P_FBS), ccs); port = -1; if (ch->fbs_enabled) { uint32_t fbs = ATA_INL(ch->r_mem, AHCI_P_FBS); if (fbs & AHCI_P_FBS_SDE) { port = (fbs & AHCI_P_FBS_DWE) >> AHCI_P_FBS_DWE_SHIFT; } else { for (i = 0; i < 16; i++) { if (ch->numrslotspd[i] == 0) continue; if (port == -1) port = i; else if (port != i) { port = -2; break; } } } } err = ch->rslots & cstatus; } else { ccs = 0; err = 0; port = -1; } /* Complete all successful commands. */ ok = ch->rslots & ~cstatus; for (i = 0; i < ch->numslots; i++) { if ((ok >> i) & 1) ahci_end_transaction(&ch->slot[i], AHCI_ERR_NONE); } /* On error, complete the rest of commands with error statuses. */ if (err) { if (ch->frozen) { union ccb *fccb = ch->frozen; ch->frozen = NULL; fccb->ccb_h.status = CAM_REQUEUE_REQ | CAM_RELEASE_SIMQ; if (!(fccb->ccb_h.status & CAM_DEV_QFRZN)) { xpt_freeze_devq(fccb->ccb_h.path, 1); fccb->ccb_h.status |= CAM_DEV_QFRZN; } ahci_done(ch, fccb); } for (i = 0; i < ch->numslots; i++) { /* XXX: requests in loading state. */ if (((err >> i) & 1) == 0) continue; if (port >= 0 && ch->slot[i].ccb->ccb_h.target_id != port) continue; if (istatus & AHCI_P_IX_TFE) { if (port != -2) { /* Task File Error */ if (ch->numtslotspd[ ch->slot[i].ccb->ccb_h.target_id] == 0) { /* Untagged operation. */ if (i == ccs) et = AHCI_ERR_TFE; else et = AHCI_ERR_INNOCENT; } else { /* Tagged operation. */ et = AHCI_ERR_NCQ; } } else { et = AHCI_ERR_TFE; ch->fatalerr = 1; } } else if (istatus & AHCI_P_IX_IF) { if (ch->numtslots == 0 && i != ccs && port != -2) et = AHCI_ERR_INNOCENT; else et = AHCI_ERR_SATA; } else et = AHCI_ERR_INVALID; ahci_end_transaction(&ch->slot[i], et); } /* * We can't reinit port if there are some other * commands active, use resume to complete them. */ if (ch->rslots != 0 && !ch->recoverycmd) ATA_OUTL(ch->r_mem, AHCI_P_FBS, AHCI_P_FBS_EN | AHCI_P_FBS_DEC); } /* Process NOTIFY events */ if (sntf) ahci_notify_events(ch, sntf); } /* Must be called with channel locked. */ static int ahci_check_collision(struct ahci_channel *ch, union ccb *ccb) { int t = ccb->ccb_h.target_id; if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) { /* Tagged command while we have no supported tag free. */ if (((~ch->oslots) & (0xffffffff >> (32 - ch->curr[t].tags))) == 0) return (1); /* If we have FBS */ if (ch->fbs_enabled) { /* Tagged command while untagged are active. */ if (ch->numrslotspd[t] != 0 && ch->numtslotspd[t] == 0) return (1); } else { /* Tagged command while untagged are active. */ if (ch->numrslots != 0 && ch->numtslots == 0) return (1); /* Tagged command while tagged to other target is active. */ if (ch->numtslots != 0 && ch->taggedtarget != ccb->ccb_h.target_id) return (1); } } else { /* If we have FBS */ if (ch->fbs_enabled) { /* Untagged command while tagged are active. */ if (ch->numrslotspd[t] != 0 && ch->numtslotspd[t] != 0) return (1); } else { /* Untagged command while tagged are active. */ if (ch->numrslots != 0 && ch->numtslots != 0) return (1); } } if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & (CAM_ATAIO_CONTROL | CAM_ATAIO_NEEDRESULT))) { /* Atomic command while anything active. */ if (ch->numrslots != 0) return (1); } /* We have some atomic command running. */ if (ch->aslots != 0) return (1); return (0); } /* Must be called with channel locked. */ static void ahci_begin_transaction(struct ahci_channel *ch, union ccb *ccb) { struct ahci_slot *slot; int tag, tags; /* Choose empty slot. */ tags = ch->numslots; if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) tags = ch->curr[ccb->ccb_h.target_id].tags; if (ch->lastslot + 1 < tags) tag = ffs(~(ch->oslots >> (ch->lastslot + 1))); else tag = 0; if (tag == 0 || tag + ch->lastslot >= tags) tag = ffs(~ch->oslots) - 1; else tag += ch->lastslot; ch->lastslot = tag; /* Occupy chosen slot. */ slot = &ch->slot[tag]; slot->ccb = ccb; /* Stop PM timer. */ if (ch->numrslots == 0 && ch->pm_level > 3) callout_stop(&ch->pm_timer); /* Update channel stats. */ ch->oslots |= (1 << tag); ch->numrslots++; ch->numrslotspd[ccb->ccb_h.target_id]++; if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) { ch->numtslots++; ch->numtslotspd[ccb->ccb_h.target_id]++; ch->taggedtarget = ccb->ccb_h.target_id; } if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & (CAM_ATAIO_CONTROL | CAM_ATAIO_NEEDRESULT))) ch->aslots |= (1 << tag); if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { slot->state = AHCI_SLOT_LOADING; bus_dmamap_load_ccb(ch->dma.data_tag, slot->dma.data_map, ccb, ahci_dmasetprd, slot, 0); } else { slot->dma.nsegs = 0; ahci_execute_transaction(slot); } } /* Locked by busdma engine. */ static void ahci_dmasetprd(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct ahci_slot *slot = arg; struct ahci_channel *ch = slot->ch; struct ahci_cmd_tab *ctp; struct ahci_dma_prd *prd; int i; if (error) { device_printf(ch->dev, "DMA load error\n"); ahci_end_transaction(slot, AHCI_ERR_INVALID); return; } KASSERT(nsegs <= AHCI_SG_ENTRIES, ("too many DMA segment entries\n")); /* Get a piece of the workspace for this request */ ctp = (struct ahci_cmd_tab *)(ch->dma.work + slot->ct_offset); /* Fill S/G table */ prd = &ctp->prd_tab[0]; for (i = 0; i < nsegs; i++) { prd[i].dba = htole64(segs[i].ds_addr); prd[i].dbc = htole32((segs[i].ds_len - 1) & AHCI_PRD_MASK); } slot->dma.nsegs = nsegs; bus_dmamap_sync(ch->dma.data_tag, slot->dma.data_map, ((slot->ccb->ccb_h.flags & CAM_DIR_IN) ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE)); ahci_execute_transaction(slot); } /* Must be called with channel locked. */ static void ahci_execute_transaction(struct ahci_slot *slot) { struct ahci_channel *ch = slot->ch; struct ahci_cmd_tab *ctp; struct ahci_cmd_list *clp; union ccb *ccb = slot->ccb; int port = ccb->ccb_h.target_id & 0x0f; int fis_size, i, softreset; uint8_t *fis = ch->dma.rfis + 0x40; uint8_t val; uint16_t cmd_flags; /* Get a piece of the workspace for this request */ ctp = (struct ahci_cmd_tab *)(ch->dma.work + slot->ct_offset); /* Setup the FIS for this request */ if (!(fis_size = ahci_setup_fis(ch, ctp, ccb, slot->slot))) { device_printf(ch->dev, "Setting up SATA FIS failed\n"); ahci_end_transaction(slot, AHCI_ERR_INVALID); return; } /* Setup the command list entry */ clp = (struct ahci_cmd_list *) (ch->dma.work + AHCI_CL_OFFSET + (AHCI_CL_SIZE * slot->slot)); cmd_flags = (ccb->ccb_h.flags & CAM_DIR_OUT ? AHCI_CMD_WRITE : 0) | (ccb->ccb_h.func_code == XPT_SCSI_IO ? (AHCI_CMD_ATAPI | AHCI_CMD_PREFETCH) : 0) | (fis_size / sizeof(u_int32_t)) | (port << 12); clp->prd_length = htole16(slot->dma.nsegs); /* Special handling for Soft Reset command. */ if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL)) { if (ccb->ataio.cmd.control & ATA_A_RESET) { softreset = 1; /* Kick controller into sane state */ ahci_stop(ch); ahci_clo(ch); ahci_start(ch, 0); cmd_flags |= AHCI_CMD_RESET | AHCI_CMD_CLR_BUSY; } else { softreset = 2; /* Prepare FIS receive area for check. */ for (i = 0; i < 20; i++) fis[i] = 0xff; } } else softreset = 0; clp->bytecount = 0; clp->cmd_flags = htole16(cmd_flags); clp->cmd_table_phys = htole64(ch->dma.work_bus + slot->ct_offset); bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); bus_dmamap_sync(ch->dma.rfis_tag, ch->dma.rfis_map, BUS_DMASYNC_PREREAD); /* Set ACTIVE bit for NCQ commands. */ if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) { ATA_OUTL(ch->r_mem, AHCI_P_SACT, 1 << slot->slot); } /* If FBS is enabled, set PMP port. */ if (ch->fbs_enabled) { ATA_OUTL(ch->r_mem, AHCI_P_FBS, AHCI_P_FBS_EN | (port << AHCI_P_FBS_DEV_SHIFT)); } /* Issue command to the controller. */ slot->state = AHCI_SLOT_RUNNING; ch->rslots |= (1 << slot->slot); ATA_OUTL(ch->r_mem, AHCI_P_CI, (1 << slot->slot)); /* Device reset commands doesn't interrupt. Poll them. */ if (ccb->ccb_h.func_code == XPT_ATA_IO && (ccb->ataio.cmd.command == ATA_DEVICE_RESET || softreset)) { int count, timeout = ccb->ccb_h.timeout * 100; enum ahci_err_type et = AHCI_ERR_NONE; for (count = 0; count < timeout; count++) { DELAY(10); if (!(ATA_INL(ch->r_mem, AHCI_P_CI) & (1 << slot->slot))) break; if ((ATA_INL(ch->r_mem, AHCI_P_TFD) & ATA_S_ERROR) && softreset != 1) { #if 0 device_printf(ch->dev, "Poll error on slot %d, TFD: %04x\n", slot->slot, ATA_INL(ch->r_mem, AHCI_P_TFD)); #endif et = AHCI_ERR_TFE; break; } /* Workaround for ATI SB600/SB700 chipsets. */ if (ccb->ccb_h.target_id == 15 && (ch->quirks & AHCI_Q_ATI_PMP_BUG) && (ATA_INL(ch->r_mem, AHCI_P_IS) & AHCI_P_IX_IPM)) { et = AHCI_ERR_TIMEOUT; break; } } /* * Some Marvell controllers require additional time * after soft reset to work properly. Setup delay * to 50ms after soft reset. */ if (ch->quirks & AHCI_Q_MRVL_SR_DEL) DELAY(50000); /* * Marvell HBAs with non-RAID firmware do not wait for * readiness after soft reset, so we have to wait here. * Marvell RAIDs do not have this problem, but instead * sometimes forget to update FIS receive area, breaking * this wait. */ if ((ch->quirks & AHCI_Q_NOBSYRES) == 0 && (ch->quirks & AHCI_Q_ATI_PMP_BUG) == 0 && softreset == 2 && et == AHCI_ERR_NONE) { for ( ; count < timeout; count++) { bus_dmamap_sync(ch->dma.rfis_tag, ch->dma.rfis_map, BUS_DMASYNC_POSTREAD); val = fis[2]; bus_dmamap_sync(ch->dma.rfis_tag, ch->dma.rfis_map, BUS_DMASYNC_PREREAD); if ((val & ATA_S_BUSY) == 0) break; DELAY(10); } } if (timeout && (count >= timeout)) { device_printf(ch->dev, "Poll timeout on slot %d port %d\n", slot->slot, port); device_printf(ch->dev, "is %08x cs %08x ss %08x " "rs %08x tfd %02x serr %08x cmd %08x\n", ATA_INL(ch->r_mem, AHCI_P_IS), ATA_INL(ch->r_mem, AHCI_P_CI), ATA_INL(ch->r_mem, AHCI_P_SACT), ch->rslots, ATA_INL(ch->r_mem, AHCI_P_TFD), ATA_INL(ch->r_mem, AHCI_P_SERR), ATA_INL(ch->r_mem, AHCI_P_CMD)); et = AHCI_ERR_TIMEOUT; } /* Kick controller into sane state and enable FBS. */ if (softreset == 2) ch->eslots |= (1 << slot->slot); ahci_end_transaction(slot, et); return; } /* Start command execution timeout */ callout_reset_sbt(&slot->timeout, SBT_1MS * ccb->ccb_h.timeout / 2, 0, ahci_timeout, slot, 0); return; } /* Must be called with channel locked. */ static void ahci_process_timeout(struct ahci_channel *ch) { int i; mtx_assert(&ch->mtx, MA_OWNED); /* Handle the rest of commands. */ for (i = 0; i < ch->numslots; i++) { /* Do we have a running request on slot? */ if (ch->slot[i].state < AHCI_SLOT_RUNNING) continue; ahci_end_transaction(&ch->slot[i], AHCI_ERR_TIMEOUT); } } /* Must be called with channel locked. */ static void ahci_rearm_timeout(struct ahci_channel *ch) { int i; mtx_assert(&ch->mtx, MA_OWNED); for (i = 0; i < ch->numslots; i++) { struct ahci_slot *slot = &ch->slot[i]; /* Do we have a running request on slot? */ if (slot->state < AHCI_SLOT_RUNNING) continue; if ((ch->toslots & (1 << i)) == 0) continue; callout_reset_sbt(&slot->timeout, SBT_1MS * slot->ccb->ccb_h.timeout / 2, 0, ahci_timeout, slot, 0); } } /* Locked by callout mechanism. */ static void ahci_timeout(void *arg) { struct ahci_slot *slot = arg; struct ahci_channel *ch = slot->ch; device_t dev = ch->dev; uint32_t sstatus; int ccs; int i; /* Check for stale timeout. */ if (slot->state < AHCI_SLOT_RUNNING) return; /* Check if slot was not being executed last time we checked. */ if (slot->state < AHCI_SLOT_EXECUTING) { /* Check if slot started executing. */ sstatus = ATA_INL(ch->r_mem, AHCI_P_SACT); ccs = (ATA_INL(ch->r_mem, AHCI_P_CMD) & AHCI_P_CMD_CCS_MASK) >> AHCI_P_CMD_CCS_SHIFT; if ((sstatus & (1 << slot->slot)) != 0 || ccs == slot->slot || ch->fbs_enabled || ch->wrongccs) slot->state = AHCI_SLOT_EXECUTING; else if ((ch->rslots & (1 << ccs)) == 0) { ch->wrongccs = 1; slot->state = AHCI_SLOT_EXECUTING; } callout_reset_sbt(&slot->timeout, SBT_1MS * slot->ccb->ccb_h.timeout / 2, 0, ahci_timeout, slot, 0); return; } device_printf(dev, "Timeout on slot %d port %d\n", slot->slot, slot->ccb->ccb_h.target_id & 0x0f); device_printf(dev, "is %08x cs %08x ss %08x rs %08x tfd %02x " "serr %08x cmd %08x\n", ATA_INL(ch->r_mem, AHCI_P_IS), ATA_INL(ch->r_mem, AHCI_P_CI), ATA_INL(ch->r_mem, AHCI_P_SACT), ch->rslots, ATA_INL(ch->r_mem, AHCI_P_TFD), ATA_INL(ch->r_mem, AHCI_P_SERR), ATA_INL(ch->r_mem, AHCI_P_CMD)); /* Handle frozen command. */ if (ch->frozen) { union ccb *fccb = ch->frozen; ch->frozen = NULL; fccb->ccb_h.status = CAM_REQUEUE_REQ | CAM_RELEASE_SIMQ; if (!(fccb->ccb_h.status & CAM_DEV_QFRZN)) { xpt_freeze_devq(fccb->ccb_h.path, 1); fccb->ccb_h.status |= CAM_DEV_QFRZN; } ahci_done(ch, fccb); } if (!ch->fbs_enabled && !ch->wrongccs) { /* Without FBS we know real timeout source. */ ch->fatalerr = 1; /* Handle command with timeout. */ ahci_end_transaction(&ch->slot[slot->slot], AHCI_ERR_TIMEOUT); /* Handle the rest of commands. */ for (i = 0; i < ch->numslots; i++) { /* Do we have a running request on slot? */ if (ch->slot[i].state < AHCI_SLOT_RUNNING) continue; ahci_end_transaction(&ch->slot[i], AHCI_ERR_INNOCENT); } } else { /* With FBS we wait for other commands timeout and pray. */ if (ch->toslots == 0) xpt_freeze_simq(ch->sim, 1); ch->toslots |= (1 << slot->slot); if ((ch->rslots & ~ch->toslots) == 0) ahci_process_timeout(ch); else device_printf(dev, " ... waiting for slots %08x\n", ch->rslots & ~ch->toslots); } } /* Must be called with channel locked. */ static void ahci_end_transaction(struct ahci_slot *slot, enum ahci_err_type et) { struct ahci_channel *ch = slot->ch; union ccb *ccb = slot->ccb; struct ahci_cmd_list *clp; int lastto; uint32_t sig; bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); clp = (struct ahci_cmd_list *) (ch->dma.work + AHCI_CL_OFFSET + (AHCI_CL_SIZE * slot->slot)); /* Read result registers to the result struct * May be incorrect if several commands finished same time, * so read only when sure or have to. */ if (ccb->ccb_h.func_code == XPT_ATA_IO) { struct ata_res *res = &ccb->ataio.res; if ((et == AHCI_ERR_TFE) || (ccb->ataio.cmd.flags & CAM_ATAIO_NEEDRESULT)) { u_int8_t *fis = ch->dma.rfis + 0x40; bus_dmamap_sync(ch->dma.rfis_tag, ch->dma.rfis_map, BUS_DMASYNC_POSTREAD); if (ch->fbs_enabled) { fis += ccb->ccb_h.target_id * 256; res->status = fis[2]; res->error = fis[3]; } else { uint16_t tfd = ATA_INL(ch->r_mem, AHCI_P_TFD); res->status = tfd; res->error = tfd >> 8; } res->lba_low = fis[4]; res->lba_mid = fis[5]; res->lba_high = fis[6]; res->device = fis[7]; res->lba_low_exp = fis[8]; res->lba_mid_exp = fis[9]; res->lba_high_exp = fis[10]; res->sector_count = fis[12]; res->sector_count_exp = fis[13]; /* * Some weird controllers do not return signature in * FIS receive area. Read it from PxSIG register. */ if ((ch->quirks & AHCI_Q_ALTSIG) && (ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL) && (ccb->ataio.cmd.control & ATA_A_RESET) == 0) { sig = ATA_INL(ch->r_mem, AHCI_P_SIG); res->lba_high = sig >> 24; res->lba_mid = sig >> 16; res->lba_low = sig >> 8; res->sector_count = sig; } } else bzero(res, sizeof(*res)); if ((ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) == 0 && (ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE && (ch->quirks & AHCI_Q_NOCOUNT) == 0) { ccb->ataio.resid = ccb->ataio.dxfer_len - le32toh(clp->bytecount); } } else { if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE && (ch->quirks & AHCI_Q_NOCOUNT) == 0) { ccb->csio.resid = ccb->csio.dxfer_len - le32toh(clp->bytecount); } } if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { bus_dmamap_sync(ch->dma.data_tag, slot->dma.data_map, (ccb->ccb_h.flags & CAM_DIR_IN) ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ch->dma.data_tag, slot->dma.data_map); } if (et != AHCI_ERR_NONE) ch->eslots |= (1 << slot->slot); /* In case of error, freeze device for proper recovery. */ if ((et != AHCI_ERR_NONE) && (!ch->recoverycmd) && !(ccb->ccb_h.status & CAM_DEV_QFRZN)) { xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status |= CAM_DEV_QFRZN; } /* Set proper result status. */ ccb->ccb_h.status &= ~CAM_STATUS_MASK; switch (et) { case AHCI_ERR_NONE: ccb->ccb_h.status |= CAM_REQ_CMP; if (ccb->ccb_h.func_code == XPT_SCSI_IO) ccb->csio.scsi_status = SCSI_STATUS_OK; break; case AHCI_ERR_INVALID: ch->fatalerr = 1; ccb->ccb_h.status |= CAM_REQ_INVALID; break; case AHCI_ERR_INNOCENT: ccb->ccb_h.status |= CAM_REQUEUE_REQ; break; case AHCI_ERR_TFE: case AHCI_ERR_NCQ: if (ccb->ccb_h.func_code == XPT_SCSI_IO) { ccb->ccb_h.status |= CAM_SCSI_STATUS_ERROR; ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; } else { ccb->ccb_h.status |= CAM_ATA_STATUS_ERROR; } break; case AHCI_ERR_SATA: ch->fatalerr = 1; if (!ch->recoverycmd) { xpt_freeze_simq(ch->sim, 1); ccb->ccb_h.status &= ~CAM_STATUS_MASK; ccb->ccb_h.status |= CAM_RELEASE_SIMQ; } ccb->ccb_h.status |= CAM_UNCOR_PARITY; break; case AHCI_ERR_TIMEOUT: if (!ch->recoverycmd) { xpt_freeze_simq(ch->sim, 1); ccb->ccb_h.status &= ~CAM_STATUS_MASK; ccb->ccb_h.status |= CAM_RELEASE_SIMQ; } ccb->ccb_h.status |= CAM_CMD_TIMEOUT; break; default: ch->fatalerr = 1; ccb->ccb_h.status |= CAM_REQ_CMP_ERR; } /* Free slot. */ ch->oslots &= ~(1 << slot->slot); ch->rslots &= ~(1 << slot->slot); ch->aslots &= ~(1 << slot->slot); slot->state = AHCI_SLOT_EMPTY; slot->ccb = NULL; /* Update channel stats. */ ch->numrslots--; ch->numrslotspd[ccb->ccb_h.target_id]--; if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) { ch->numtslots--; ch->numtslotspd[ccb->ccb_h.target_id]--; } /* Cancel timeout state if request completed normally. */ if (et != AHCI_ERR_TIMEOUT) { lastto = (ch->toslots == (1 << slot->slot)); ch->toslots &= ~(1 << slot->slot); if (lastto) xpt_release_simq(ch->sim, TRUE); } /* If it was first request of reset sequence and there is no error, * proceed to second request. */ if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL) && (ccb->ataio.cmd.control & ATA_A_RESET) && et == AHCI_ERR_NONE) { ccb->ataio.cmd.control &= ~ATA_A_RESET; ahci_begin_transaction(ch, ccb); return; } /* If it was our READ LOG command - process it. */ if (ccb->ccb_h.recovery_type == RECOVERY_READ_LOG) { ahci_process_read_log(ch, ccb); /* If it was our REQUEST SENSE command - process it. */ } else if (ccb->ccb_h.recovery_type == RECOVERY_REQUEST_SENSE) { ahci_process_request_sense(ch, ccb); /* If it was NCQ or ATAPI command error, put result on hold. */ } else if (et == AHCI_ERR_NCQ || ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR && (ccb->ccb_h.flags & CAM_DIS_AUTOSENSE) == 0)) { ch->hold[slot->slot] = ccb; ch->numhslots++; } else ahci_done(ch, ccb); /* If we have no other active commands, ... */ if (ch->rslots == 0) { /* if there was fatal error - reset port. */ if (ch->toslots != 0 || ch->fatalerr) { ahci_reset(ch); } else { /* if we have slots in error, we can reinit port. */ if (ch->eslots != 0) { ahci_stop(ch); ahci_clo(ch); ahci_start(ch, 1); } /* if there commands on hold, we can do READ LOG. */ if (!ch->recoverycmd && ch->numhslots) ahci_issue_recovery(ch); } /* If all the rest of commands are in timeout - give them chance. */ } else if ((ch->rslots & ~ch->toslots) == 0 && et != AHCI_ERR_TIMEOUT) ahci_rearm_timeout(ch); /* Unfreeze frozen command. */ if (ch->frozen && !ahci_check_collision(ch, ch->frozen)) { union ccb *fccb = ch->frozen; ch->frozen = NULL; ahci_begin_transaction(ch, fccb); xpt_release_simq(ch->sim, TRUE); } /* Start PM timer. */ if (ch->numrslots == 0 && ch->pm_level > 3 && (ch->curr[ch->pm_present ? 15 : 0].caps & CTS_SATA_CAPS_D_PMREQ)) { callout_schedule(&ch->pm_timer, (ch->pm_level == 4) ? hz / 1000 : hz / 8); } } static void ahci_issue_recovery(struct ahci_channel *ch) { union ccb *ccb; struct ccb_ataio *ataio; struct ccb_scsiio *csio; int i; /* Find some held command. */ for (i = 0; i < ch->numslots; i++) { if (ch->hold[i]) break; } ccb = xpt_alloc_ccb_nowait(); if (ccb == NULL) { device_printf(ch->dev, "Unable to allocate recovery command\n"); completeall: /* We can't do anything -- complete held commands. */ for (i = 0; i < ch->numslots; i++) { if (ch->hold[i] == NULL) continue; ch->hold[i]->ccb_h.status &= ~CAM_STATUS_MASK; ch->hold[i]->ccb_h.status |= CAM_RESRC_UNAVAIL; ahci_done(ch, ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; } ahci_reset(ch); return; } xpt_setup_ccb(&ccb->ccb_h, ch->hold[i]->ccb_h.path, ch->hold[i]->ccb_h.pinfo.priority); if (ch->hold[i]->ccb_h.func_code == XPT_ATA_IO) { /* READ LOG */ ccb->ccb_h.recovery_type = RECOVERY_READ_LOG; ccb->ccb_h.func_code = XPT_ATA_IO; ccb->ccb_h.flags = CAM_DIR_IN; ccb->ccb_h.timeout = 1000; /* 1s should be enough. */ ataio = &ccb->ataio; ataio->data_ptr = malloc(512, M_AHCI, M_NOWAIT); if (ataio->data_ptr == NULL) { xpt_free_ccb(ccb); device_printf(ch->dev, "Unable to allocate memory for READ LOG command\n"); goto completeall; } ataio->dxfer_len = 512; bzero(&ataio->cmd, sizeof(ataio->cmd)); ataio->cmd.flags = CAM_ATAIO_48BIT; ataio->cmd.command = 0x2F; /* READ LOG EXT */ ataio->cmd.sector_count = 1; ataio->cmd.sector_count_exp = 0; ataio->cmd.lba_low = 0x10; ataio->cmd.lba_mid = 0; ataio->cmd.lba_mid_exp = 0; } else { /* REQUEST SENSE */ ccb->ccb_h.recovery_type = RECOVERY_REQUEST_SENSE; ccb->ccb_h.recovery_slot = i; ccb->ccb_h.func_code = XPT_SCSI_IO; ccb->ccb_h.flags = CAM_DIR_IN; ccb->ccb_h.status = 0; ccb->ccb_h.timeout = 1000; /* 1s should be enough. */ csio = &ccb->csio; csio->data_ptr = (void *)&ch->hold[i]->csio.sense_data; csio->dxfer_len = ch->hold[i]->csio.sense_len; csio->cdb_len = 6; bzero(&csio->cdb_io, sizeof(csio->cdb_io)); csio->cdb_io.cdb_bytes[0] = 0x03; csio->cdb_io.cdb_bytes[4] = csio->dxfer_len; } /* Freeze SIM while doing recovery. */ ch->recoverycmd = 1; xpt_freeze_simq(ch->sim, 1); ahci_begin_transaction(ch, ccb); } static void ahci_process_read_log(struct ahci_channel *ch, union ccb *ccb) { uint8_t *data; struct ata_res *res; int i; ch->recoverycmd = 0; data = ccb->ataio.data_ptr; if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP && (data[0] & 0x80) == 0) { for (i = 0; i < ch->numslots; i++) { if (!ch->hold[i]) continue; if (ch->hold[i]->ccb_h.func_code != XPT_ATA_IO) continue; if ((data[0] & 0x1F) == i) { res = &ch->hold[i]->ataio.res; res->status = data[2]; res->error = data[3]; res->lba_low = data[4]; res->lba_mid = data[5]; res->lba_high = data[6]; res->device = data[7]; res->lba_low_exp = data[8]; res->lba_mid_exp = data[9]; res->lba_high_exp = data[10]; res->sector_count = data[12]; res->sector_count_exp = data[13]; } else { ch->hold[i]->ccb_h.status &= ~CAM_STATUS_MASK; ch->hold[i]->ccb_h.status |= CAM_REQUEUE_REQ; } ahci_done(ch, ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; } } else { if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) device_printf(ch->dev, "Error while READ LOG EXT\n"); else if ((data[0] & 0x80) == 0) { device_printf(ch->dev, "Non-queued command error in READ LOG EXT\n"); } for (i = 0; i < ch->numslots; i++) { if (!ch->hold[i]) continue; if (ch->hold[i]->ccb_h.func_code != XPT_ATA_IO) continue; ahci_done(ch, ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; } } free(ccb->ataio.data_ptr, M_AHCI); xpt_free_ccb(ccb); xpt_release_simq(ch->sim, TRUE); } static void ahci_process_request_sense(struct ahci_channel *ch, union ccb *ccb) { int i; ch->recoverycmd = 0; i = ccb->ccb_h.recovery_slot; if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { ch->hold[i]->ccb_h.status |= CAM_AUTOSNS_VALID; } else { ch->hold[i]->ccb_h.status &= ~CAM_STATUS_MASK; ch->hold[i]->ccb_h.status |= CAM_AUTOSENSE_FAIL; } ahci_done(ch, ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; xpt_free_ccb(ccb); xpt_release_simq(ch->sim, TRUE); } static void ahci_start(struct ahci_channel *ch, int fbs) { u_int32_t cmd; /* Run the channel start callback, if any. */ if (ch->start) ch->start(ch); /* Clear SATA error register */ ATA_OUTL(ch->r_mem, AHCI_P_SERR, 0xFFFFFFFF); /* Clear any interrupts pending on this channel */ ATA_OUTL(ch->r_mem, AHCI_P_IS, 0xFFFFFFFF); /* Configure FIS-based switching if supported. */ if (ch->chcaps & AHCI_P_CMD_FBSCP) { ch->fbs_enabled = (fbs && ch->pm_present) ? 1 : 0; ATA_OUTL(ch->r_mem, AHCI_P_FBS, ch->fbs_enabled ? AHCI_P_FBS_EN : 0); } /* Start operations on this channel */ cmd = ATA_INL(ch->r_mem, AHCI_P_CMD); cmd &= ~AHCI_P_CMD_PMA; ATA_OUTL(ch->r_mem, AHCI_P_CMD, cmd | AHCI_P_CMD_ST | (ch->pm_present ? AHCI_P_CMD_PMA : 0)); } static void ahci_stop(struct ahci_channel *ch) { u_int32_t cmd; int timeout; /* Kill all activity on this channel */ cmd = ATA_INL(ch->r_mem, AHCI_P_CMD); ATA_OUTL(ch->r_mem, AHCI_P_CMD, cmd & ~AHCI_P_CMD_ST); /* Wait for activity stop. */ timeout = 0; do { DELAY(10); if (timeout++ > 50000) { device_printf(ch->dev, "stopping AHCI engine failed\n"); break; } } while (ATA_INL(ch->r_mem, AHCI_P_CMD) & AHCI_P_CMD_CR); ch->eslots = 0; } static void ahci_clo(struct ahci_channel *ch) { u_int32_t cmd; int timeout; /* Issue Command List Override if supported */ if (ch->caps & AHCI_CAP_SCLO) { cmd = ATA_INL(ch->r_mem, AHCI_P_CMD); cmd |= AHCI_P_CMD_CLO; ATA_OUTL(ch->r_mem, AHCI_P_CMD, cmd); timeout = 0; do { DELAY(10); if (timeout++ > 50000) { device_printf(ch->dev, "executing CLO failed\n"); break; } } while (ATA_INL(ch->r_mem, AHCI_P_CMD) & AHCI_P_CMD_CLO); } } static void ahci_stop_fr(struct ahci_channel *ch) { u_int32_t cmd; int timeout; /* Kill all FIS reception on this channel */ cmd = ATA_INL(ch->r_mem, AHCI_P_CMD); ATA_OUTL(ch->r_mem, AHCI_P_CMD, cmd & ~AHCI_P_CMD_FRE); /* Wait for FIS reception stop. */ timeout = 0; do { DELAY(10); if (timeout++ > 50000) { device_printf(ch->dev, "stopping AHCI FR engine failed\n"); break; } } while (ATA_INL(ch->r_mem, AHCI_P_CMD) & AHCI_P_CMD_FR); } static void ahci_start_fr(struct ahci_channel *ch) { u_int32_t cmd; /* Start FIS reception on this channel */ cmd = ATA_INL(ch->r_mem, AHCI_P_CMD); ATA_OUTL(ch->r_mem, AHCI_P_CMD, cmd | AHCI_P_CMD_FRE); } static int ahci_wait_ready(struct ahci_channel *ch, int t, int t0) { int timeout = 0; uint32_t val; while ((val = ATA_INL(ch->r_mem, AHCI_P_TFD)) & (ATA_S_BUSY | ATA_S_DRQ)) { if (timeout > t) { if (t != 0) { device_printf(ch->dev, "AHCI reset: device not ready after %dms " "(tfd = %08x)\n", MAX(t, 0) + t0, val); } return (EBUSY); } DELAY(1000); timeout++; } if (bootverbose) device_printf(ch->dev, "AHCI reset: device ready after %dms\n", timeout + t0); return (0); } static void ahci_reset_to(void *arg) { struct ahci_channel *ch = arg; if (ch->resetting == 0) return; ch->resetting--; if (ahci_wait_ready(ch, ch->resetting == 0 ? -1 : 0, (310 - ch->resetting) * 100) == 0) { ch->resetting = 0; ahci_start(ch, 1); xpt_release_simq(ch->sim, TRUE); return; } if (ch->resetting == 0) { ahci_clo(ch); ahci_start(ch, 1); xpt_release_simq(ch->sim, TRUE); return; } callout_schedule(&ch->reset_timer, hz / 10); } static void ahci_reset(struct ahci_channel *ch) { struct ahci_controller *ctlr = device_get_softc(device_get_parent(ch->dev)); int i; xpt_freeze_simq(ch->sim, 1); if (bootverbose) device_printf(ch->dev, "AHCI reset...\n"); /* Forget about previous reset. */ if (ch->resetting) { ch->resetting = 0; callout_stop(&ch->reset_timer); xpt_release_simq(ch->sim, TRUE); } /* Requeue freezed command. */ if (ch->frozen) { union ccb *fccb = ch->frozen; ch->frozen = NULL; fccb->ccb_h.status = CAM_REQUEUE_REQ | CAM_RELEASE_SIMQ; if (!(fccb->ccb_h.status & CAM_DEV_QFRZN)) { xpt_freeze_devq(fccb->ccb_h.path, 1); fccb->ccb_h.status |= CAM_DEV_QFRZN; } ahci_done(ch, fccb); } /* Kill the engine and requeue all running commands. */ ahci_stop(ch); for (i = 0; i < ch->numslots; i++) { /* Do we have a running request on slot? */ if (ch->slot[i].state < AHCI_SLOT_RUNNING) continue; /* XXX; Commands in loading state. */ ahci_end_transaction(&ch->slot[i], AHCI_ERR_INNOCENT); } for (i = 0; i < ch->numslots; i++) { if (!ch->hold[i]) continue; ahci_done(ch, ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; } if (ch->toslots != 0) xpt_release_simq(ch->sim, TRUE); ch->eslots = 0; ch->toslots = 0; ch->wrongccs = 0; ch->fatalerr = 0; /* Tell the XPT about the event */ xpt_async(AC_BUS_RESET, ch->path, NULL); /* Disable port interrupts */ ATA_OUTL(ch->r_mem, AHCI_P_IE, 0); /* Reset and reconnect PHY, */ if (!ahci_sata_phy_reset(ch)) { if (bootverbose) device_printf(ch->dev, "AHCI reset: device not found\n"); ch->devices = 0; /* Enable wanted port interrupts */ ATA_OUTL(ch->r_mem, AHCI_P_IE, (((ch->pm_level != 0) ? AHCI_P_IX_CPD | AHCI_P_IX_MP : 0) | AHCI_P_IX_PRC | AHCI_P_IX_PC)); xpt_release_simq(ch->sim, TRUE); return; } if (bootverbose) device_printf(ch->dev, "AHCI reset: device found\n"); /* Wait for clearing busy status. */ if (ahci_wait_ready(ch, dumping ? 31000 : 0, 0)) { if (dumping) ahci_clo(ch); else ch->resetting = 310; } ch->devices = 1; /* Enable wanted port interrupts */ ATA_OUTL(ch->r_mem, AHCI_P_IE, (((ch->pm_level != 0) ? AHCI_P_IX_CPD | AHCI_P_IX_MP : 0) | AHCI_P_IX_TFE | AHCI_P_IX_HBF | AHCI_P_IX_HBD | AHCI_P_IX_IF | AHCI_P_IX_OF | ((ch->pm_level == 0) ? AHCI_P_IX_PRC : 0) | AHCI_P_IX_PC | AHCI_P_IX_DP | AHCI_P_IX_UF | (ctlr->ccc ? 0 : AHCI_P_IX_SDB) | AHCI_P_IX_DS | AHCI_P_IX_PS | (ctlr->ccc ? 0 : AHCI_P_IX_DHR))); if (ch->resetting) callout_reset(&ch->reset_timer, hz / 10, ahci_reset_to, ch); else { ahci_start(ch, 1); xpt_release_simq(ch->sim, TRUE); } } static int ahci_setup_fis(struct ahci_channel *ch, struct ahci_cmd_tab *ctp, union ccb *ccb, int tag) { u_int8_t *fis = &ctp->cfis[0]; bzero(fis, 20); fis[0] = 0x27; /* host to device */ fis[1] = (ccb->ccb_h.target_id & 0x0f); if (ccb->ccb_h.func_code == XPT_SCSI_IO) { fis[1] |= 0x80; fis[2] = ATA_PACKET_CMD; if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE && ch->curr[ccb->ccb_h.target_id].mode >= ATA_DMA) fis[3] = ATA_F_DMA; else { fis[5] = ccb->csio.dxfer_len; fis[6] = ccb->csio.dxfer_len >> 8; } fis[7] = ATA_D_LBA; fis[15] = ATA_A_4BIT; bcopy((ccb->ccb_h.flags & CAM_CDB_POINTER) ? ccb->csio.cdb_io.cdb_ptr : ccb->csio.cdb_io.cdb_bytes, ctp->acmd, ccb->csio.cdb_len); bzero(ctp->acmd + ccb->csio.cdb_len, 32 - ccb->csio.cdb_len); } else if ((ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL) == 0) { fis[1] |= 0x80; fis[2] = ccb->ataio.cmd.command; fis[3] = ccb->ataio.cmd.features; fis[4] = ccb->ataio.cmd.lba_low; fis[5] = ccb->ataio.cmd.lba_mid; fis[6] = ccb->ataio.cmd.lba_high; fis[7] = ccb->ataio.cmd.device; fis[8] = ccb->ataio.cmd.lba_low_exp; fis[9] = ccb->ataio.cmd.lba_mid_exp; fis[10] = ccb->ataio.cmd.lba_high_exp; fis[11] = ccb->ataio.cmd.features_exp; fis[12] = ccb->ataio.cmd.sector_count; if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) { fis[12] &= 0x07; fis[12] |= tag << 3; } fis[13] = ccb->ataio.cmd.sector_count_exp; if (ccb->ataio.ata_flags & ATA_FLAG_ICC) fis[14] = ccb->ataio.icc; fis[15] = ATA_A_4BIT; if (ccb->ataio.ata_flags & ATA_FLAG_AUX) { fis[16] = ccb->ataio.aux & 0xff; fis[17] = (ccb->ataio.aux >> 8) & 0xff; fis[18] = (ccb->ataio.aux >> 16) & 0xff; fis[19] = (ccb->ataio.aux >> 24) & 0xff; } } else { fis[15] = ccb->ataio.cmd.control; } return (20); } static int ahci_sata_connect(struct ahci_channel *ch) { u_int32_t status; int timeout, timeoutslot, found = 0; /* * Wait for "connect well", up to 100ms by default and * up to 500ms for devices with the SLOWDEV quirk. */ timeoutslot = ((ch->quirks & AHCI_Q_SLOWDEV) ? 5000 : 1000); for (timeout = 0; timeout < timeoutslot; timeout++) { status = ATA_INL(ch->r_mem, AHCI_P_SSTS); if ((status & ATA_SS_DET_MASK) != ATA_SS_DET_NO_DEVICE) found = 1; if (((status & ATA_SS_DET_MASK) == ATA_SS_DET_PHY_ONLINE) && ((status & ATA_SS_SPD_MASK) != ATA_SS_SPD_NO_SPEED) && ((status & ATA_SS_IPM_MASK) == ATA_SS_IPM_ACTIVE)) break; if ((status & ATA_SS_DET_MASK) == ATA_SS_DET_PHY_OFFLINE) { if (bootverbose) { device_printf(ch->dev, "SATA offline status=%08x\n", status); } return (0); } if (found == 0 && timeout >= 100) break; DELAY(100); } if (timeout >= timeoutslot || !found) { if (bootverbose) { device_printf(ch->dev, "SATA connect timeout time=%dus status=%08x\n", timeout * 100, status); } return (0); } if (bootverbose) { device_printf(ch->dev, "SATA connect time=%dus status=%08x\n", timeout * 100, status); } /* Clear SATA error register */ ATA_OUTL(ch->r_mem, AHCI_P_SERR, 0xffffffff); return (1); } static int ahci_sata_phy_reset(struct ahci_channel *ch) { int sata_rev; uint32_t val, detval; if (ch->listening) { val = ATA_INL(ch->r_mem, AHCI_P_CMD); val |= AHCI_P_CMD_SUD; ATA_OUTL(ch->r_mem, AHCI_P_CMD, val); ch->listening = 0; } sata_rev = ch->user[ch->pm_present ? 15 : 0].revision; if (sata_rev == 1) val = ATA_SC_SPD_SPEED_GEN1; else if (sata_rev == 2) val = ATA_SC_SPD_SPEED_GEN2; else if (sata_rev == 3) val = ATA_SC_SPD_SPEED_GEN3; else val = 0; detval = ahci_ch_detval(ch, ATA_SC_DET_RESET); ATA_OUTL(ch->r_mem, AHCI_P_SCTL, detval | val | ATA_SC_IPM_DIS_PARTIAL | ATA_SC_IPM_DIS_SLUMBER); DELAY(1000); detval = ahci_ch_detval(ch, ATA_SC_DET_IDLE); ATA_OUTL(ch->r_mem, AHCI_P_SCTL, detval | val | ((ch->pm_level > 0) ? 0 : (ATA_SC_IPM_DIS_PARTIAL | ATA_SC_IPM_DIS_SLUMBER))); if (!ahci_sata_connect(ch)) { if (ch->caps & AHCI_CAP_SSS) { val = ATA_INL(ch->r_mem, AHCI_P_CMD); val &= ~AHCI_P_CMD_SUD; ATA_OUTL(ch->r_mem, AHCI_P_CMD, val); ch->listening = 1; } else if (ch->pm_level > 0) ATA_OUTL(ch->r_mem, AHCI_P_SCTL, ATA_SC_DET_DISABLE); return (0); } return (1); } static int ahci_check_ids(struct ahci_channel *ch, union ccb *ccb) { if (ccb->ccb_h.target_id > ((ch->caps & AHCI_CAP_SPM) ? 15 : 0)) { ccb->ccb_h.status = CAM_TID_INVALID; ahci_done(ch, ccb); return (-1); } if (ccb->ccb_h.target_lun != 0) { ccb->ccb_h.status = CAM_LUN_INVALID; ahci_done(ch, ccb); return (-1); } return (0); } static void ahciaction(struct cam_sim *sim, union ccb *ccb) { struct ahci_channel *ch; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("ahciaction func_code=%x\n", ccb->ccb_h.func_code)); ch = (struct ahci_channel *)cam_sim_softc(sim); switch (ccb->ccb_h.func_code) { /* Common cases first */ case XPT_ATA_IO: /* Execute the requested I/O operation */ case XPT_SCSI_IO: if (ahci_check_ids(ch, ccb)) return; if (ch->devices == 0 || (ch->pm_present == 0 && ccb->ccb_h.target_id > 0 && ccb->ccb_h.target_id < 15)) { ccb->ccb_h.status = CAM_SEL_TIMEOUT; break; } ccb->ccb_h.recovery_type = RECOVERY_NONE; /* Check for command collision. */ if (ahci_check_collision(ch, ccb)) { /* Freeze command. */ ch->frozen = ccb; /* We have only one frozen slot, so freeze simq also. */ xpt_freeze_simq(ch->sim, 1); return; } ahci_begin_transaction(ch, ccb); return; case XPT_ABORT: /* Abort the specified CCB */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; break; case XPT_SET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; struct ahci_device *d; if (ahci_check_ids(ch, ccb)) return; if (cts->type == CTS_TYPE_CURRENT_SETTINGS) d = &ch->curr[ccb->ccb_h.target_id]; else d = &ch->user[ccb->ccb_h.target_id]; if (cts->xport_specific.sata.valid & CTS_SATA_VALID_REVISION) d->revision = cts->xport_specific.sata.revision; if (cts->xport_specific.sata.valid & CTS_SATA_VALID_MODE) d->mode = cts->xport_specific.sata.mode; if (cts->xport_specific.sata.valid & CTS_SATA_VALID_BYTECOUNT) d->bytecount = min(8192, cts->xport_specific.sata.bytecount); if (cts->xport_specific.sata.valid & CTS_SATA_VALID_TAGS) d->tags = min(ch->numslots, cts->xport_specific.sata.tags); if (cts->xport_specific.sata.valid & CTS_SATA_VALID_PM) ch->pm_present = cts->xport_specific.sata.pm_present; if (cts->xport_specific.sata.valid & CTS_SATA_VALID_ATAPI) d->atapi = cts->xport_specific.sata.atapi; if (cts->xport_specific.sata.valid & CTS_SATA_VALID_CAPS) d->caps = cts->xport_specific.sata.caps; ccb->ccb_h.status = CAM_REQ_CMP; break; } case XPT_GET_TRAN_SETTINGS: /* Get default/user set transfer settings for the target */ { struct ccb_trans_settings *cts = &ccb->cts; struct ahci_device *d; uint32_t status; if (ahci_check_ids(ch, ccb)) return; if (cts->type == CTS_TYPE_CURRENT_SETTINGS) d = &ch->curr[ccb->ccb_h.target_id]; else d = &ch->user[ccb->ccb_h.target_id]; cts->protocol = PROTO_UNSPECIFIED; cts->protocol_version = PROTO_VERSION_UNSPECIFIED; cts->transport = XPORT_SATA; cts->transport_version = XPORT_VERSION_UNSPECIFIED; cts->proto_specific.valid = 0; cts->xport_specific.sata.valid = 0; if (cts->type == CTS_TYPE_CURRENT_SETTINGS && (ccb->ccb_h.target_id == 15 || (ccb->ccb_h.target_id == 0 && !ch->pm_present))) { status = ATA_INL(ch->r_mem, AHCI_P_SSTS) & ATA_SS_SPD_MASK; if (status & 0x0f0) { cts->xport_specific.sata.revision = (status & 0x0f0) >> 4; cts->xport_specific.sata.valid |= CTS_SATA_VALID_REVISION; } cts->xport_specific.sata.caps = d->caps & CTS_SATA_CAPS_D; if (ch->pm_level) { if (ch->caps & (AHCI_CAP_PSC | AHCI_CAP_SSC)) cts->xport_specific.sata.caps |= CTS_SATA_CAPS_H_PMREQ; if (ch->caps2 & AHCI_CAP2_APST) cts->xport_specific.sata.caps |= CTS_SATA_CAPS_H_APST; } if ((ch->caps & AHCI_CAP_SNCQ) && (ch->quirks & AHCI_Q_NOAA) == 0) cts->xport_specific.sata.caps |= CTS_SATA_CAPS_H_DMAAA; cts->xport_specific.sata.caps |= CTS_SATA_CAPS_H_AN; cts->xport_specific.sata.caps &= ch->user[ccb->ccb_h.target_id].caps; cts->xport_specific.sata.valid |= CTS_SATA_VALID_CAPS; } else { cts->xport_specific.sata.revision = d->revision; cts->xport_specific.sata.valid |= CTS_SATA_VALID_REVISION; cts->xport_specific.sata.caps = d->caps; cts->xport_specific.sata.valid |= CTS_SATA_VALID_CAPS; } cts->xport_specific.sata.mode = d->mode; cts->xport_specific.sata.valid |= CTS_SATA_VALID_MODE; cts->xport_specific.sata.bytecount = d->bytecount; cts->xport_specific.sata.valid |= CTS_SATA_VALID_BYTECOUNT; cts->xport_specific.sata.pm_present = ch->pm_present; cts->xport_specific.sata.valid |= CTS_SATA_VALID_PM; cts->xport_specific.sata.tags = d->tags; cts->xport_specific.sata.valid |= CTS_SATA_VALID_TAGS; cts->xport_specific.sata.atapi = d->atapi; cts->xport_specific.sata.valid |= CTS_SATA_VALID_ATAPI; ccb->ccb_h.status = CAM_REQ_CMP; break; } case XPT_RESET_BUS: /* Reset the specified SCSI bus */ case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ ahci_reset(ch); ccb->ccb_h.status = CAM_REQ_CMP; break; case XPT_TERM_IO: /* Terminate the I/O process */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; break; case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; /* XXX??? */ cpi->hba_inquiry = PI_SDTR_ABLE; if (ch->caps & AHCI_CAP_SNCQ) cpi->hba_inquiry |= PI_TAG_ABLE; if (ch->caps & AHCI_CAP_SPM) cpi->hba_inquiry |= PI_SATAPM; cpi->target_sprt = 0; cpi->hba_misc = PIM_SEQSCAN | PIM_UNMAPPED; if ((ch->quirks & AHCI_Q_NOAUX) == 0) cpi->hba_misc |= PIM_ATA_EXT; cpi->hba_eng_cnt = 0; if (ch->caps & AHCI_CAP_SPM) cpi->max_target = 15; else cpi->max_target = 0; cpi->max_lun = 0; cpi->initiator_id = 0; cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 150000; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "AHCI", HBA_IDLEN); strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->transport = XPORT_SATA; cpi->transport_version = XPORT_VERSION_UNSPECIFIED; cpi->protocol = PROTO_ATA; cpi->protocol_version = PROTO_VERSION_UNSPECIFIED; cpi->maxio = ctob(AHCI_SG_ENTRIES - 1); /* ATI SB600 can't handle 256 sectors with FPDMA (NCQ). */ if (ch->quirks & AHCI_Q_MAXIO_64K) cpi->maxio = min(cpi->maxio, 128 * 512); cpi->hba_vendor = ch->vendorid; cpi->hba_device = ch->deviceid; cpi->hba_subvendor = ch->subvendorid; cpi->hba_subdevice = ch->subdeviceid; cpi->ccb_h.status = CAM_REQ_CMP; break; } default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } ahci_done(ch, ccb); } static void ahcipoll(struct cam_sim *sim) { struct ahci_channel *ch = (struct ahci_channel *)cam_sim_softc(sim); uint32_t istatus; /* Read interrupt statuses and process if any. */ istatus = ATA_INL(ch->r_mem, AHCI_P_IS); if (istatus != 0) ahci_ch_intr_main(ch, istatus); if (ch->resetting != 0 && (--ch->resetpolldiv <= 0 || !callout_pending(&ch->reset_timer))) { ch->resetpolldiv = 1000; ahci_reset_to(ch); } } MODULE_VERSION(ahci, 1); MODULE_DEPEND(ahci, cam, 1, 1, 1); diff --git a/sys/dev/ata/ata-pci.c b/sys/dev/ata/ata-pci.c index 8c22bb6ff427..6d8b8fb3aad1 100644 --- a/sys/dev/ata/ata-pci.c +++ b/sys/dev/ata/ata-pci.c @@ -1,918 +1,921 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 - 2008 Søren Schmidt * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DEFINE(M_ATAPCI, "ata_pci", "ATA driver PCI"); /* misc defines */ #define IOMASK 0xfffffffc /* * generic PCI ATA device probe */ int ata_pci_probe(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(dev); /* is this a storage class device ? */ if (pci_get_class(dev) != PCIC_STORAGE) return (ENXIO); /* is this an IDE/ATA type device ? */ if (pci_get_subclass(dev) != PCIS_STORAGE_IDE) return (ENXIO); device_set_descf(dev, "%s ATA controller", ata_pcivendor2str(dev)); ctlr->chipinit = ata_generic_chipinit; /* we are a low priority handler */ return (BUS_PROBE_GENERIC); } int ata_pci_attach(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(dev); device_t child; u_int32_t cmd; int unit; /* do chipset specific setups only needed once */ ctlr->legacy = ata_legacy(dev); if (ctlr->legacy || pci_read_config(dev, PCIR_BAR(2), 4) & IOMASK) ctlr->channels = 2; else ctlr->channels = 1; ctlr->ichannels = -1; ctlr->ch_attach = ata_pci_ch_attach; ctlr->ch_detach = ata_pci_ch_detach; ctlr->dev = dev; /* if needed try to enable busmastering */ pci_enable_busmaster(dev); cmd = pci_read_config(dev, PCIR_COMMAND, 2); /* if busmastering mode "stuck" use it */ if ((cmd & PCIM_CMD_BUSMASTEREN) == PCIM_CMD_BUSMASTEREN) { ctlr->r_type1 = SYS_RES_IOPORT; ctlr->r_rid1 = ATA_BMADDR_RID; ctlr->r_res1 = bus_alloc_resource_any(dev, ctlr->r_type1, &ctlr->r_rid1, RF_ACTIVE); } if (ctlr->chipinit(dev)) { if (ctlr->r_res1) bus_release_resource(dev, ctlr->r_type1, ctlr->r_rid1, ctlr->r_res1); return ENXIO; } /* attach all channels on this controller */ for (unit = 0; unit < ctlr->channels; unit++) { if ((ctlr->ichannels & (1 << unit)) == 0) continue; child = device_add_child(dev, "ata", ((unit == 0 || unit == 1) && ctlr->legacy) ? unit : devclass_find_free_unit(ata_devclass, 2)); if (child == NULL) device_printf(dev, "failed to add ata child device\n"); else device_set_ivars(child, (void *)(intptr_t)unit); } bus_attach_children(dev); return 0; } int ata_pci_detach(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(dev); + int error; /* detach & delete all children */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); if (ctlr->r_irq) { bus_teardown_intr(dev, ctlr->r_irq, ctlr->handle); bus_release_resource(dev, SYS_RES_IRQ, ctlr->r_irq_rid, ctlr->r_irq); if (ctlr->r_irq_rid != ATA_IRQ_RID) pci_release_msi(dev); } if (ctlr->chipdeinit != NULL) ctlr->chipdeinit(dev); if (ctlr->r_res2) { bus_release_resource(dev, ctlr->r_type2, ctlr->r_rid2, ctlr->r_res2); } if (ctlr->r_res1) { bus_release_resource(dev, ctlr->r_type1, ctlr->r_rid1, ctlr->r_res1); } return 0; } int ata_pci_suspend(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(dev); int error = 0; bus_generic_suspend(dev); if (ctlr->suspend) error = ctlr->suspend(dev); return error; } int ata_pci_resume(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(dev); int error = 0; if (ctlr->resume) error = ctlr->resume(dev); bus_generic_resume(dev); return error; } int ata_pci_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { return (BUS_READ_IVAR(device_get_parent(dev), dev, which, result)); } int ata_pci_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { return (BUS_WRITE_IVAR(device_get_parent(dev), dev, which, value)); } uint32_t ata_pci_read_config(device_t dev, device_t child, int reg, int width) { return (pci_read_config(dev, reg, width)); } void ata_pci_write_config(device_t dev, device_t child, int reg, uint32_t val, int width) { pci_write_config(dev, reg, val, width); } struct resource * ata_pci_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct ata_pci_controller *controller = device_get_softc(dev); struct resource *res = NULL; if (device_get_devclass(child) == ata_devclass) { int unit = ((struct ata_channel *)device_get_softc(child))->unit; int myrid; if (type == SYS_RES_IOPORT) { switch (*rid) { case ATA_IOADDR_RID: if (controller->legacy) { start = (unit ? ATA_SECONDARY : ATA_PRIMARY); count = ATA_IOSIZE; end = start + count - 1; } myrid = PCIR_BAR(0) + (unit << 3); res = BUS_ALLOC_RESOURCE(device_get_parent(dev), dev, SYS_RES_IOPORT, &myrid, start, end, count, flags); break; case ATA_CTLADDR_RID: if (controller->legacy) { start = (unit ? ATA_SECONDARY : ATA_PRIMARY) + ATA_CTLOFFSET; count = ATA_CTLIOSIZE; end = start + count - 1; } myrid = PCIR_BAR(1) + (unit << 3); res = BUS_ALLOC_RESOURCE(device_get_parent(dev), dev, SYS_RES_IOPORT, &myrid, start, end, count, flags); break; } } if (type == SYS_RES_IRQ && *rid == ATA_IRQ_RID) { if (controller->legacy) { int irq = (unit == 0 ? 14 : 15); res = BUS_ALLOC_RESOURCE(device_get_parent(dev), child, SYS_RES_IRQ, rid, irq, irq, 1, flags); } else res = controller->r_irq; } } else { if (type == SYS_RES_IRQ) { if (*rid != ATA_IRQ_RID) return (NULL); res = controller->r_irq; } else { res = BUS_ALLOC_RESOURCE(device_get_parent(dev), dev, type, rid, start, end, count, flags); } } return (res); } int ata_pci_release_resource(device_t dev, device_t child, struct resource *r) { int rid = rman_get_rid(r); int type = rman_get_type(r); if (device_get_devclass(child) == ata_devclass) { struct ata_pci_controller *controller = device_get_softc(dev); if (type == SYS_RES_IOPORT) { switch (rid) { case ATA_IOADDR_RID: case ATA_CTLADDR_RID: return BUS_RELEASE_RESOURCE(device_get_parent(dev), dev, r); default: return ENOENT; } } if (type == SYS_RES_IRQ) { if (rid != ATA_IRQ_RID) return ENOENT; if (controller->legacy) { return BUS_RELEASE_RESOURCE(device_get_parent(dev), child, r); } else return 0; } } else { if (type == SYS_RES_IRQ) { if (rid != ATA_IRQ_RID) return (ENOENT); return (0); } else { return (BUS_RELEASE_RESOURCE(device_get_parent(dev), child, r)); } } return (EINVAL); } int ata_pci_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *function, void *argument, void **cookiep) { struct ata_pci_controller *controller = device_get_softc(dev); if (controller->legacy) { return BUS_SETUP_INTR(device_get_parent(dev), child, irq, flags, filter, function, argument, cookiep); } else { struct ata_pci_controller *controller = device_get_softc(dev); int unit; if (filter != NULL) { printf("ata-pci.c: we cannot use a filter here\n"); return (EINVAL); } if (device_get_devclass(child) == ata_devclass) unit = ((struct ata_channel *)device_get_softc(child))->unit; else unit = ATA_PCI_MAX_CH - 1; controller->interrupt[unit].function = function; controller->interrupt[unit].argument = argument; *cookiep = controller; return 0; } } int ata_pci_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { struct ata_pci_controller *controller = device_get_softc(dev); if (controller->legacy) { return BUS_TEARDOWN_INTR(device_get_parent(dev), child, irq, cookie); } else { struct ata_pci_controller *controller = device_get_softc(dev); int unit; if (device_get_devclass(child) == ata_devclass) unit = ((struct ata_channel *)device_get_softc(child))->unit; else unit = ATA_PCI_MAX_CH - 1; controller->interrupt[unit].function = NULL; controller->interrupt[unit].argument = NULL; return 0; } } int ata_generic_setmode(device_t dev, int target, int mode) { return (min(mode, ATA_UDMA2)); } int ata_generic_chipinit(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(dev); if (ata_setup_interrupt(dev, ata_generic_intr)) return ENXIO; ctlr->setmode = ata_generic_setmode; return 0; } int ata_pci_ch_attach(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(device_get_parent(dev)); struct ata_channel *ch = device_get_softc(dev); struct resource *io = NULL, *ctlio = NULL; int i, rid; rid = ATA_IOADDR_RID; if (!(io = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE))) return ENXIO; rid = ATA_CTLADDR_RID; if (!(ctlio = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid,RF_ACTIVE))){ bus_release_resource(dev, SYS_RES_IOPORT, ATA_IOADDR_RID, io); return ENXIO; } ata_pci_dmainit(dev); for (i = ATA_DATA; i <= ATA_COMMAND; i ++) { ch->r_io[i].res = io; ch->r_io[i].offset = i; } ch->r_io[ATA_CONTROL].res = ctlio; ch->r_io[ATA_CONTROL].offset = ctlr->legacy ? 0 : 2; ch->r_io[ATA_IDX_ADDR].res = io; ata_default_registers(dev); if (ctlr->r_res1) { for (i = ATA_BMCMD_PORT; i <= ATA_BMDTP_PORT; i++) { ch->r_io[i].res = ctlr->r_res1; ch->r_io[i].offset = (i - ATA_BMCMD_PORT) + (ch->unit*ATA_BMIOSIZE); } } ata_pci_hw(dev); return 0; } int ata_pci_ch_detach(device_t dev) { struct ata_channel *ch = device_get_softc(dev); ata_pci_dmafini(dev); bus_release_resource(dev, SYS_RES_IOPORT, ATA_CTLADDR_RID, ch->r_io[ATA_CONTROL].res); bus_release_resource(dev, SYS_RES_IOPORT, ATA_IOADDR_RID, ch->r_io[ATA_IDX_ADDR].res); return (0); } int ata_pci_status(device_t dev) { struct ata_pci_controller *controller = device_get_softc(device_get_parent(dev)); struct ata_channel *ch = device_get_softc(dev); if ((dumping || !controller->legacy) && ((ch->flags & ATA_ALWAYS_DMASTAT) || (ch->dma.flags & ATA_DMA_ACTIVE))) { int bmstat = ATA_IDX_INB(ch, ATA_BMSTAT_PORT) & ATA_BMSTAT_MASK; if ((bmstat & ATA_BMSTAT_INTERRUPT) == 0) return 0; ATA_IDX_OUTB(ch, ATA_BMSTAT_PORT, bmstat & ~ATA_BMSTAT_ERROR); DELAY(1); } if (ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_BUSY) { DELAY(100); if (ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_BUSY) return 0; } return 1; } void ata_pci_hw(device_t dev) { struct ata_channel *ch = device_get_softc(dev); ata_generic_hw(dev); ch->hw.status = ata_pci_status; } static int ata_pci_dmastart(struct ata_request *request) { struct ata_channel *ch = device_get_softc(request->parent); ATA_DEBUG_RQ(request, "dmastart"); ATA_IDX_OUTB(ch, ATA_BMSTAT_PORT, (ATA_IDX_INB(ch, ATA_BMSTAT_PORT) | (ATA_BMSTAT_INTERRUPT | ATA_BMSTAT_ERROR))); ATA_IDX_OUTL(ch, ATA_BMDTP_PORT, request->dma->sg_bus); ch->dma.flags |= ATA_DMA_ACTIVE; ATA_IDX_OUTB(ch, ATA_BMCMD_PORT, (ATA_IDX_INB(ch, ATA_BMCMD_PORT) & ~ATA_BMCMD_WRITE_READ) | ((request->flags & ATA_R_READ) ? ATA_BMCMD_WRITE_READ : 0)| ATA_BMCMD_START_STOP); return 0; } static int ata_pci_dmastop(struct ata_request *request) { struct ata_channel *ch = device_get_softc(request->parent); int error; ATA_DEBUG_RQ(request, "dmastop"); ATA_IDX_OUTB(ch, ATA_BMCMD_PORT, ATA_IDX_INB(ch, ATA_BMCMD_PORT) & ~ATA_BMCMD_START_STOP); ch->dma.flags &= ~ATA_DMA_ACTIVE; error = ATA_IDX_INB(ch, ATA_BMSTAT_PORT) & ATA_BMSTAT_MASK; ATA_IDX_OUTB(ch, ATA_BMSTAT_PORT, ATA_BMSTAT_INTERRUPT | ATA_BMSTAT_ERROR); return error; } static void ata_pci_dmareset(device_t dev) { struct ata_channel *ch = device_get_softc(dev); struct ata_request *request; ATA_IDX_OUTB(ch, ATA_BMCMD_PORT, ATA_IDX_INB(ch, ATA_BMCMD_PORT) & ~ATA_BMCMD_START_STOP); ch->dma.flags &= ~ATA_DMA_ACTIVE; ATA_IDX_OUTB(ch, ATA_BMSTAT_PORT, ATA_BMSTAT_INTERRUPT | ATA_BMSTAT_ERROR); if ((request = ch->running)) { device_printf(dev, "DMA reset calling unload\n"); ch->dma.unload(request); } } void ata_pci_dmainit(device_t dev) { struct ata_channel *ch = device_get_softc(dev); ata_dmainit(dev); ch->dma.start = ata_pci_dmastart; ch->dma.stop = ata_pci_dmastop; ch->dma.reset = ata_pci_dmareset; } void ata_pci_dmafini(device_t dev) { ata_dmafini(dev); } int ata_pci_print_child(device_t dev, device_t child) { int retval; retval = bus_print_child_header(dev, child); retval += printf(" at channel %d", (int)(intptr_t)device_get_ivars(child)); retval += bus_print_child_footer(dev, child); return (retval); } int ata_pci_child_location(device_t dev, device_t child, struct sbuf *sb) { sbuf_printf(sb, "channel=%d", (int)(intptr_t)device_get_ivars(child)); return (0); } static bus_dma_tag_t ata_pci_get_dma_tag(device_t bus, device_t child) { return (bus_get_dma_tag(bus)); } static device_method_t ata_pci_methods[] = { /* device interface */ DEVMETHOD(device_probe, ata_pci_probe), DEVMETHOD(device_attach, ata_pci_attach), DEVMETHOD(device_detach, ata_pci_detach), DEVMETHOD(device_suspend, ata_pci_suspend), DEVMETHOD(device_resume, ata_pci_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* bus methods */ DEVMETHOD(bus_read_ivar, ata_pci_read_ivar), DEVMETHOD(bus_write_ivar, ata_pci_write_ivar), DEVMETHOD(bus_alloc_resource, ata_pci_alloc_resource), DEVMETHOD(bus_release_resource, ata_pci_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, ata_pci_setup_intr), DEVMETHOD(bus_teardown_intr, ata_pci_teardown_intr), DEVMETHOD(pci_read_config, ata_pci_read_config), DEVMETHOD(pci_write_config, ata_pci_write_config), DEVMETHOD(bus_print_child, ata_pci_print_child), DEVMETHOD(bus_child_location, ata_pci_child_location), DEVMETHOD(bus_get_dma_tag, ata_pci_get_dma_tag), DEVMETHOD_END }; static driver_t ata_pci_driver = { "atapci", ata_pci_methods, sizeof(struct ata_pci_controller), }; DRIVER_MODULE(atapci, pci, ata_pci_driver, NULL, NULL); MODULE_VERSION(atapci, 1); MODULE_DEPEND(atapci, ata, 1, 1, 1); static int ata_pcichannel_probe(device_t dev) { if ((intptr_t)device_get_ivars(dev) < 0) return (ENXIO); device_set_desc(dev, "ATA channel"); return ata_probe(dev); } static int ata_pcichannel_attach(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(device_get_parent(dev)); struct ata_channel *ch = device_get_softc(dev); int error; if (ch->attached) return (0); ch->attached = 1; ch->dev = dev; ch->unit = (intptr_t)device_get_ivars(dev); resource_int_value(device_get_name(dev), device_get_unit(dev), "pm_level", &ch->pm_level); if ((error = ctlr->ch_attach(dev))) return error; return ata_attach(dev); } static int ata_pcichannel_detach(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(device_get_parent(dev)); struct ata_channel *ch = device_get_softc(dev); int error; if (!ch->attached) return (0); ch->attached = 0; if ((error = ata_detach(dev))) return error; if (ctlr->ch_detach) return (ctlr->ch_detach(dev)); return (0); } static int ata_pcichannel_suspend(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(device_get_parent(dev)); struct ata_channel *ch = device_get_softc(dev); int error; if (!ch->attached) return (0); if ((error = ata_suspend(dev))) return (error); if (ctlr->ch_suspend != NULL && (error = ctlr->ch_suspend(dev))) return (error); return (0); } static int ata_pcichannel_resume(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(device_get_parent(dev)); struct ata_channel *ch = device_get_softc(dev); int error; if (!ch->attached) return (0); if (ctlr->ch_resume != NULL && (error = ctlr->ch_resume(dev))) return (error); return ata_resume(dev); } static void ata_pcichannel_reset(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(device_get_parent(dev)); struct ata_channel *ch = device_get_softc(dev); /* if DMA engine present reset it */ if (ch->dma.reset) ch->dma.reset(dev); /* reset the controller HW */ if (ctlr->reset) ctlr->reset(dev); else ata_generic_reset(dev); } static int ata_pcichannel_setmode(device_t dev, int target, int mode) { struct ata_pci_controller *ctlr = device_get_softc(device_get_parent(dev)); if (ctlr->setmode) return (ctlr->setmode(dev, target, mode)); else return (ata_generic_setmode(dev, target, mode)); } static int ata_pcichannel_getrev(device_t dev, int target) { struct ata_pci_controller *ctlr = device_get_softc(device_get_parent(dev)); struct ata_channel *ch = device_get_softc(dev); if (ch->flags & ATA_SATA) { if (ctlr->getrev) return (ctlr->getrev(dev, target)); else return (0xff); } else return (0); } static device_method_t ata_pcichannel_methods[] = { /* device interface */ DEVMETHOD(device_probe, ata_pcichannel_probe), DEVMETHOD(device_attach, ata_pcichannel_attach), DEVMETHOD(device_detach, ata_pcichannel_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, ata_pcichannel_suspend), DEVMETHOD(device_resume, ata_pcichannel_resume), /* ATA methods */ DEVMETHOD(ata_setmode, ata_pcichannel_setmode), DEVMETHOD(ata_getrev, ata_pcichannel_getrev), DEVMETHOD(ata_reset, ata_pcichannel_reset), DEVMETHOD_END }; driver_t ata_pcichannel_driver = { "ata", ata_pcichannel_methods, sizeof(struct ata_channel), }; DRIVER_MODULE(ata, atapci, ata_pcichannel_driver, NULL, NULL); /* * misc support functions */ int ata_legacy(device_t dev) { return (((pci_read_config(dev, PCIR_SUBCLASS, 1) == PCIS_STORAGE_IDE) && (pci_read_config(dev, PCIR_PROGIF, 1)&PCIP_STORAGE_IDE_MASTERDEV)&& ((pci_read_config(dev, PCIR_PROGIF, 1) & (PCIP_STORAGE_IDE_MODEPRIM | PCIP_STORAGE_IDE_MODESEC)) != (PCIP_STORAGE_IDE_MODEPRIM | PCIP_STORAGE_IDE_MODESEC))) || (!pci_read_config(dev, PCIR_BAR(0), 4) && !pci_read_config(dev, PCIR_BAR(1), 4) && !pci_read_config(dev, PCIR_BAR(2), 4) && !pci_read_config(dev, PCIR_BAR(3), 4) && !pci_read_config(dev, PCIR_BAR(5), 4))); } void ata_generic_intr(void *data) { struct ata_pci_controller *ctlr = data; struct ata_channel *ch; int unit; for (unit = 0; unit < ATA_PCI_MAX_CH; unit++) { if ((ch = ctlr->interrupt[unit].argument)) ctlr->interrupt[unit].function(ch); } } int ata_setup_interrupt(device_t dev, void *intr_func) { struct ata_pci_controller *ctlr = device_get_softc(dev); int i, msi = 0; if (!ctlr->legacy) { if (resource_int_value(device_get_name(dev), device_get_unit(dev), "msi", &i) == 0 && i != 0) msi = 1; if (msi && pci_msi_count(dev) > 0 && pci_alloc_msi(dev, &msi) == 0) { ctlr->r_irq_rid = 0x1; } else { msi = 0; ctlr->r_irq_rid = ATA_IRQ_RID; } if (!(ctlr->r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &ctlr->r_irq_rid, RF_SHAREABLE | RF_ACTIVE))) { device_printf(dev, "unable to map interrupt\n"); if (msi) pci_release_msi(dev); return ENXIO; } if ((bus_setup_intr(dev, ctlr->r_irq, ATA_INTR_FLAGS, NULL, intr_func, ctlr, &ctlr->handle))) { device_printf(dev, "unable to setup interrupt\n"); bus_release_resource(dev, SYS_RES_IRQ, ctlr->r_irq_rid, ctlr->r_irq); if (msi) pci_release_msi(dev); return ENXIO; } } return 0; } void ata_set_desc(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(dev); device_set_descf(dev, "%s %s %s controller", ata_pcivendor2str(dev), ctlr->chip->text, ata_mode2str(ctlr->chip->max_dma)); } const struct ata_chip_id * ata_match_chip(device_t dev, const struct ata_chip_id *index) { uint32_t devid; uint8_t revid; devid = pci_get_devid(dev); revid = pci_get_revid(dev); while (index->chipid != 0) { if (devid == index->chipid && revid >= index->chiprev) return (index); index++; } return (NULL); } const struct ata_chip_id * ata_find_chip(device_t dev, const struct ata_chip_id *index, int slot) { const struct ata_chip_id *idx; device_t *children; int nchildren, i; uint8_t s; if (device_get_children(device_get_parent(dev), &children, &nchildren)) return (NULL); for (i = 0; i < nchildren; i++) { s = pci_get_slot(children[i]); if ((slot >= 0 && s == slot) || (slot < 0 && s <= -slot)) { idx = ata_match_chip(children[i], index); if (idx != NULL) { free(children, M_TEMP); return (idx); } } } free(children, M_TEMP); return (NULL); } const char * ata_pcivendor2str(device_t dev) { switch (pci_get_vendor(dev)) { case ATA_ACARD_ID: return "Acard"; case ATA_ACER_LABS_ID: return "AcerLabs"; case ATA_AMD_ID: return "AMD"; case ATA_ADAPTEC_ID: return "Adaptec"; case ATA_ATI_ID: return "ATI"; case ATA_CYRIX_ID: return "Cyrix"; case ATA_CYPRESS_ID: return "Cypress"; case ATA_HIGHPOINT_ID: return "HighPoint"; case ATA_INTEL_ID: return "Intel"; case ATA_ITE_ID: return "ITE"; case ATA_JMICRON_ID: return "JMicron"; case ATA_MARVELL_ID: return "Marvell"; case ATA_MARVELL2_ID: return "Marvell"; case ATA_NATIONAL_ID: return "National"; case ATA_NETCELL_ID: return "Netcell"; case ATA_NVIDIA_ID: return "nVidia"; case ATA_PROMISE_ID: return "Promise"; case ATA_SERVERWORKS_ID: return "ServerWorks"; case ATA_SILICON_IMAGE_ID: return "SiI"; case ATA_SIS_ID: return "SiS"; case ATA_VIA_ID: return "VIA"; case ATA_CENATEK_ID: return "Cenatek"; case ATA_MICRON_ID: return "Micron"; default: return "Generic"; } } int ata_mode2idx(int mode) { if ((mode & ATA_DMA_MASK) == ATA_UDMA0) return (mode & ATA_MODE_MASK) + 8; if ((mode & ATA_DMA_MASK) == ATA_WDMA0) return (mode & ATA_MODE_MASK) + 5; return (mode & ATA_MODE_MASK) - ATA_PIO0; } diff --git a/sys/dev/atopcase/atopcase.c b/sys/dev/atopcase/atopcase.c index e4e248f7ce0a..9e64b389c9e3 100644 --- a/sys/dev/atopcase/atopcase.c +++ b/sys/dev/atopcase/atopcase.c @@ -1,723 +1,723 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021-2023 Val Packett * Copyright (c) 2023 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "opt_hid.h" #include "opt_spi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR atopcase_debug #include #include #include #include #include "spibus_if.h" #include "atopcase_reg.h" #include "atopcase_var.h" #define ATOPCASE_IN_KDB() (SCHEDULER_STOPPED() || kdb_active) #define ATOPCASE_IN_POLLING_MODE(sc) \ (((sc)->sc_gpe_bit == 0 && ((sc)->sc_irq_ih == NULL)) || cold ||\ ATOPCASE_IN_KDB()) #define ATOPCASE_WAKEUP(sc, chan) do { \ if (!ATOPCASE_IN_POLLING_MODE(sc)) { \ DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wakeup: %p\n", chan); \ wakeup(chan); \ } \ } while (0) #define ATOPCASE_SPI_PAUSE() DELAY(100) #define ATOPCASE_SPI_NO_SLEEP_FLAG(sc) \ ((sc)->sc_irq_ih != NULL ? SPI_FLAG_NO_SLEEP : 0) /* Tunables */ static SYSCTL_NODE(_hw_hid, OID_AUTO, atopcase, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "Apple MacBook Topcase HID driver"); #ifdef HID_DEBUG enum atopcase_log_level atopcase_debug = ATOPCASE_LLEVEL_DISABLED; SYSCTL_INT(_hw_hid_atopcase, OID_AUTO, debug, CTLFLAG_RWTUN, &atopcase_debug, ATOPCASE_LLEVEL_DISABLED, "atopcase log level"); #endif /* !HID_DEBUG */ static const uint8_t booted[] = { 0xa0, 0x80, 0x00, 0x00 }; static const uint8_t status_ok[] = { 0xac, 0x27, 0x68, 0xd5 }; static inline struct atopcase_child * atopcase_get_child_by_device(struct atopcase_softc *sc, uint8_t device) { switch (device) { case ATOPCASE_DEV_KBRD: return (&sc->sc_kb); case ATOPCASE_DEV_TPAD: return (&sc->sc_tp); default: return (NULL); } } static int atopcase_receive_status(struct atopcase_softc *sc) { struct spi_command cmd = SPI_COMMAND_INITIALIZER; uint8_t dummy_buffer[4] = { 0 }; uint8_t status_buffer[4] = { 0 }; int err; cmd.tx_cmd = dummy_buffer; cmd.tx_cmd_sz = sizeof(dummy_buffer); cmd.rx_cmd = status_buffer; cmd.rx_cmd_sz = sizeof(status_buffer); cmd.flags = ATOPCASE_SPI_NO_SLEEP_FLAG(sc); err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd); ATOPCASE_SPI_PAUSE(); if (err) { device_printf(sc->sc_dev, "SPI error: %d\n", err); return (err); } DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Status: %*D\n", 4, status_buffer, " "); if (memcmp(status_buffer, status_ok, sizeof(status_ok)) == 0) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Wrote command\n"); ATOPCASE_WAKEUP(sc, sc->sc_dev); } else { device_printf(sc->sc_dev, "Failed to write command\n"); return (EIO); } return (0); } static int atopcase_process_message(struct atopcase_softc *sc, uint8_t device, void *msg, uint16_t msg_len) { struct atopcase_header *hdr = msg; struct atopcase_child *ac; void *payload; uint16_t pl_len, crc; payload = (uint8_t *)msg + sizeof(*hdr); pl_len = le16toh(hdr->len); if (pl_len + sizeof(*hdr) + sizeof(crc) != msg_len) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "message with length overflow\n"); return (EIO); } crc = le16toh(*(uint16_t *)((uint8_t *)payload + pl_len)); if (crc != crc16(0, msg, msg_len - sizeof(crc))) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "message with failed checksum\n"); return (EIO); } #define CPOFF(dst, len, off) do { \ unsigned _len = le16toh(len); \ unsigned _off = le16toh(off); \ if (pl_len >= _len + _off) { \ memcpy(dst, (uint8_t*)payload + _off, MIN(_len, sizeof(dst)));\ (dst)[MIN(_len, sizeof(dst) - 1)] = '\0'; \ }} while (0); if ((ac = atopcase_get_child_by_device(sc, device)) != NULL && hdr->type == ATOPCASE_MSG_TYPE_REPORT(device)) { if (ac->open) ac->intr_handler(ac->intr_ctx, payload, pl_len); } else if (device == ATOPCASE_DEV_INFO && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_IFACE) && (ac = atopcase_get_child_by_device(sc, hdr->type_arg)) != NULL) { struct atopcase_iface_info_payload *iface = payload; CPOFF(ac->name, iface->name_len, iface->name_off); DPRINTF("Interface #%d name: %s\n", ac->device, ac->name); } else if (device == ATOPCASE_DEV_INFO && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DESCRIPTOR) && (ac = atopcase_get_child_by_device(sc, hdr->type_arg)) != NULL) { memcpy(ac->rdesc, payload, pl_len); ac->rdesc_len = ac->hw.rdescsize = pl_len; DPRINTF("%s HID report descriptor: %*D\n", ac->name, (int) ac->hw.rdescsize, ac->rdesc, " "); } else if (device == ATOPCASE_DEV_INFO && hdr->type == ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DEVICE) && hdr->type_arg == ATOPCASE_INFO_DEVICE) { struct atopcase_device_info_payload *dev = payload; sc->sc_vid = le16toh(dev->vid); sc->sc_pid = le16toh(dev->pid); sc->sc_ver = le16toh(dev->ver); CPOFF(sc->sc_vendor, dev->vendor_len, dev->vendor_off); CPOFF(sc->sc_product, dev->product_len, dev->product_off); CPOFF(sc->sc_serial, dev->serial_len, dev->serial_off); if (bootverbose) { device_printf(sc->sc_dev, "Device info descriptor:\n"); printf(" Vendor: %s\n", sc->sc_vendor); printf(" Product: %s\n", sc->sc_product); printf(" Serial: %s\n", sc->sc_serial); } } return (0); } int atopcase_receive_packet(struct atopcase_softc *sc) { struct atopcase_packet pkt = { 0 }; struct spi_command cmd = SPI_COMMAND_INITIALIZER; void *msg; int err; uint16_t length, remaining, offset, msg_len; bzero(&sc->sc_junk, sizeof(struct atopcase_packet)); cmd.tx_cmd = &sc->sc_junk; cmd.tx_cmd_sz = sizeof(struct atopcase_packet); cmd.rx_cmd = &pkt; cmd.rx_cmd_sz = sizeof(struct atopcase_packet); cmd.flags = ATOPCASE_SPI_NO_SLEEP_FLAG(sc); err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd); ATOPCASE_SPI_PAUSE(); if (err) { device_printf(sc->sc_dev, "SPI error: %d\n", err); return (err); } DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Response: %*D\n", 256, &pkt, " "); if (le16toh(pkt.checksum) != crc16(0, &pkt, sizeof(pkt) - 2)) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "packet with failed checksum\n"); return (EIO); } /* * When we poll and nothing has arrived we get a particular packet * starting with '80 11 00 01' */ if (pkt.direction == ATOPCASE_DIR_NOTHING) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "'Nothing' packet: %*D\n", 4, &pkt, " "); return (EAGAIN); } if (pkt.direction != ATOPCASE_DIR_READ && pkt.direction != ATOPCASE_DIR_WRITE) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "unknown message direction 0x%x\n", pkt.direction); return (EIO); } length = le16toh(pkt.length); remaining = le16toh(pkt.remaining); offset = le16toh(pkt.offset); if (length > sizeof(pkt.data)) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "packet with length overflow: %u\n", length); return (EIO); } if (pkt.direction == ATOPCASE_DIR_READ && pkt.device == ATOPCASE_DEV_INFO && length == sizeof(booted) && memcmp(pkt.data, booted, length) == 0) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "GPE boot packet\n"); sc->sc_booted = true; ATOPCASE_WAKEUP(sc, sc); return (0); } /* handle multi-packet messages */ if (remaining != 0 || offset != 0) { if (offset != sc->sc_msg_len) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Unexpected offset (got %u, expected %u)\n", offset, sc->sc_msg_len); sc->sc_msg_len = 0; return (EIO); } if ((size_t)remaining + length + offset > sizeof(sc->sc_msg)) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Message with length overflow: %zu\n", (size_t)remaining + length + offset); sc->sc_msg_len = 0; return (EIO); } memcpy(sc->sc_msg + offset, &pkt.data, length); sc->sc_msg_len += length; if (remaining != 0) return (0); msg = sc->sc_msg; msg_len = sc->sc_msg_len; } else { msg = pkt.data; msg_len = length; } sc->sc_msg_len = 0; err = atopcase_process_message(sc, pkt.device, msg, msg_len); if (err == 0 && pkt.direction == ATOPCASE_DIR_WRITE) { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Write ack\n"); ATOPCASE_WAKEUP(sc, sc); } return (err); } static int atopcase_send(struct atopcase_softc *sc, struct atopcase_packet *pkt) { struct spi_command cmd = SPI_COMMAND_INITIALIZER; int err, retries; cmd.tx_cmd = pkt; cmd.tx_cmd_sz = sizeof(struct atopcase_packet); cmd.rx_cmd = &sc->sc_junk; cmd.rx_cmd_sz = sizeof(struct atopcase_packet); cmd.flags = SPI_FLAG_KEEP_CS | ATOPCASE_SPI_NO_SLEEP_FLAG(sc); DPRINTFN(ATOPCASE_LLEVEL_TRACE, "Request: %*D\n", (int)sizeof(struct atopcase_packet), cmd.tx_cmd, " "); if (!ATOPCASE_IN_POLLING_MODE(sc)) { if (sc->sc_irq_ih != NULL) mtx_lock(&sc->sc_mtx); else sx_xlock(&sc->sc_sx); } sc->sc_wait_for_status = true; err = SPIBUS_TRANSFER(device_get_parent(sc->sc_dev), sc->sc_dev, &cmd); ATOPCASE_SPI_PAUSE(); if (!ATOPCASE_IN_POLLING_MODE(sc)) { if (sc->sc_irq_ih != NULL) mtx_unlock(&sc->sc_mtx); else sx_xunlock(&sc->sc_sx); } if (err != 0) { device_printf(sc->sc_dev, "SPI error: %d\n", err); goto exit; } if (ATOPCASE_IN_POLLING_MODE(sc)) { err = atopcase_receive_status(sc); } else { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wait for: %p\n", sc->sc_dev); err = tsleep(sc->sc_dev, 0, "atcstat", hz / 10); } sc->sc_wait_for_status = false; if (err != 0) { DPRINTF("Write status read failed: %d\n", err); goto exit; } if (ATOPCASE_IN_POLLING_MODE(sc)) { /* Backlight setting may require a lot of time */ retries = 20; while ((err = atopcase_receive_packet(sc)) == EAGAIN && --retries != 0) DELAY(1000); } else { DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "wait for: %p\n", sc); err = tsleep(sc, 0, "atcack", hz / 10); } if (err != 0) DPRINTF("Write ack read failed: %d\n", err); exit: if (err == EWOULDBLOCK) err = EIO; return (err); } static void atopcase_create_message(struct atopcase_packet *pkt, uint8_t device, uint16_t type, uint8_t type_arg, const void *payload, uint8_t len, uint16_t resp_len) { struct atopcase_header *hdr = (struct atopcase_header *)pkt->data; uint16_t msg_checksum; static uint8_t seq_no; KASSERT(len <= ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header), ("outgoing msg must be 1 packet")); bzero(pkt, sizeof(struct atopcase_packet)); pkt->direction = ATOPCASE_DIR_WRITE; pkt->device = device; pkt->length = htole16(sizeof(*hdr) + len + 2); hdr->type = htole16(type); hdr->type_arg = type_arg; hdr->seq_no = seq_no++; hdr->resp_len = htole16((resp_len == 0) ? len : resp_len); hdr->len = htole16(len); memcpy(pkt->data + sizeof(*hdr), payload, len); msg_checksum = htole16(crc16(0, pkt->data, pkt->length - 2)); memcpy(pkt->data + sizeof(*hdr) + len, &msg_checksum, 2); pkt->checksum = htole16(crc16(0, (uint8_t*)pkt, sizeof(*pkt) - 2)); return; } static int atopcase_request_desc(struct atopcase_softc *sc, uint16_t type, uint8_t device) { atopcase_create_message( &sc->sc_buf, ATOPCASE_DEV_INFO, type, device, NULL, 0, 0x200); return (atopcase_send(sc, &sc->sc_buf)); } int atopcase_intr(struct atopcase_softc *sc) { int err; DPRINTFN(ATOPCASE_LLEVEL_DEBUG, "Interrupt event\n"); if (sc->sc_wait_for_status) { err = atopcase_receive_status(sc); sc->sc_wait_for_status = false; } else err = atopcase_receive_packet(sc); return (err); } static int atopcase_add_child(struct atopcase_softc *sc, struct atopcase_child *ac, uint8_t device) { device_t hidbus; int err = 0; ac->device = device; /* fill device info */ strlcpy(ac->hw.name, "Apple MacBook", sizeof(ac->hw.name)); ac->hw.idBus = BUS_SPI; ac->hw.idVendor = sc->sc_vid; ac->hw.idProduct = sc->sc_pid; ac->hw.idVersion = sc->sc_ver; strlcpy(ac->hw.idPnP, sc->sc_hid, sizeof(ac->hw.idPnP)); strlcpy(ac->hw.serial, sc->sc_serial, sizeof(ac->hw.serial)); /* * HID write and set_report methods executed on Apple SPI topcase * hardware do the same request on SPI layer. Set HQ_NOWRITE quirk to * force hidmap to convert writes to set_reports. That makes HID bus * write handler unnecessary and reduces code duplication. */ hid_add_dynamic_quirk(&ac->hw, HQ_NOWRITE); DPRINTF("Get the interface #%d descriptor\n", device); err = atopcase_request_desc(sc, ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_IFACE), device); if (err) { device_printf(sc->sc_dev, "can't receive iface descriptor\n"); goto exit; } DPRINTF("Get the \"%s\" HID report descriptor\n", ac->name); err = atopcase_request_desc(sc, ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DESCRIPTOR), device); if (err) { device_printf(sc->sc_dev, "can't receive report descriptor\n"); goto exit; } hidbus = device_add_child(sc->sc_dev, "hidbus", -1); if (hidbus == NULL) { device_printf(sc->sc_dev, "can't add child\n"); err = ENOMEM; goto exit; } device_set_ivars(hidbus, &ac->hw); ac->hidbus = hidbus; exit: return (err); } int atopcase_init(struct atopcase_softc *sc) { int err; /* Wait until we know we're getting reasonable responses */ if(!sc->sc_booted && tsleep(sc, 0, "atcboot", hz / 20) != 0) { device_printf(sc->sc_dev, "can't establish communication\n"); err = EIO; goto err; } /* * Management device may send a message on first boot after power off. * Let interrupt handler to read and discard it. */ DELAY(2000); DPRINTF("Get the device descriptor\n"); err = atopcase_request_desc(sc, ATOPCASE_MSG_TYPE_INFO(ATOPCASE_INFO_DEVICE), ATOPCASE_INFO_DEVICE); if (err) { device_printf(sc->sc_dev, "can't receive device descriptor\n"); goto err; } err = atopcase_add_child(sc, &sc->sc_kb, ATOPCASE_DEV_KBRD); if (err != 0) goto err; err = atopcase_add_child(sc, &sc->sc_tp, ATOPCASE_DEV_TPAD); if (err != 0) goto err; /* TODO: skip on 2015 models where it's controlled by asmc */ sc->sc_backlight = backlight_register("atopcase", sc->sc_dev); if (!sc->sc_backlight) { device_printf(sc->sc_dev, "can't register backlight\n"); err = ENOMEM; } if (sc->sc_tq != NULL) taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_task, hz / 120); bus_attach_children(sc->sc_dev); return (0); err: return (err); } int atopcase_destroy(struct atopcase_softc *sc) { int err; - err = device_delete_children(sc->sc_dev); + err = bus_generic_detach(sc->sc_dev); if (err) return (err); if (sc->sc_backlight) backlight_destroy(sc->sc_backlight); return (0); } static struct atopcase_child * atopcase_get_child_by_hidbus(device_t child) { device_t parent = device_get_parent(child); struct atopcase_softc *sc = device_get_softc(parent); if (child == sc->sc_kb.hidbus) return (&sc->sc_kb); if (child == sc->sc_tp.hidbus) return (&sc->sc_tp); panic("unknown child"); } void atopcase_intr_setup(device_t dev, device_t child, hid_intr_t intr, void *context, struct hid_rdesc_info *rdesc) { struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); if (intr == NULL) return; rdesc->rdsize = ATOPCASE_MSG_SIZE - sizeof(struct atopcase_header) - 2; rdesc->grsize = 0; rdesc->srsize = ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header) - 2; rdesc->wrsize = 0; ac->intr_handler = intr; ac->intr_ctx = context; } void atopcase_intr_unsetup(device_t dev, device_t child) { } int atopcase_intr_start(device_t dev, device_t child) { struct atopcase_softc *sc = device_get_softc(dev); struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); if (ATOPCASE_IN_POLLING_MODE(sc)) sx_xlock(&sc->sc_write_sx); else if (sc->sc_irq_ih != NULL) mtx_lock(&sc->sc_mtx); else sx_xlock(&sc->sc_sx); ac->open = true; if (ATOPCASE_IN_POLLING_MODE(sc)) sx_xunlock(&sc->sc_write_sx); else if (sc->sc_irq_ih != NULL) mtx_unlock(&sc->sc_mtx); else sx_xunlock(&sc->sc_sx); return (0); } int atopcase_intr_stop(device_t dev, device_t child) { struct atopcase_softc *sc = device_get_softc(dev); struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); if (ATOPCASE_IN_POLLING_MODE(sc)) sx_xlock(&sc->sc_write_sx); else if (sc->sc_irq_ih != NULL) mtx_lock(&sc->sc_mtx); else sx_xlock(&sc->sc_sx); ac->open = false; if (ATOPCASE_IN_POLLING_MODE(sc)) sx_xunlock(&sc->sc_write_sx); else if (sc->sc_irq_ih != NULL) mtx_unlock(&sc->sc_mtx); else sx_xunlock(&sc->sc_sx); return (0); } void atopcase_intr_poll(device_t dev, device_t child) { struct atopcase_softc *sc = device_get_softc(dev); (void)atopcase_receive_packet(sc); } int atopcase_get_rdesc(device_t dev, device_t child, void *buf, hid_size_t len) { struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); if (ac->rdesc_len != len) return (ENXIO); memcpy(buf, ac->rdesc, len); return (0); } int atopcase_set_report(device_t dev, device_t child, const void *buf, hid_size_t len, uint8_t type __unused, uint8_t id) { struct atopcase_softc *sc = device_get_softc(dev); struct atopcase_child *ac = atopcase_get_child_by_hidbus(child); int err; if (len >= ATOPCASE_DATA_SIZE - sizeof(struct atopcase_header) - 2) return (EINVAL); DPRINTF("%s HID command SET_REPORT %d (len %d): %*D\n", ac->name, id, len, len, buf, " "); if (!ATOPCASE_IN_KDB()) sx_xlock(&sc->sc_write_sx); atopcase_create_message(&sc->sc_buf, ac->device, ATOPCASE_MSG_TYPE_SET_REPORT(ac->device, id), 0, buf, len, 0); err = atopcase_send(sc, &sc->sc_buf); if (!ATOPCASE_IN_KDB()) sx_xunlock(&sc->sc_write_sx); return (err); } int atopcase_backlight_update_status(device_t dev, struct backlight_props *props) { struct atopcase_softc *sc = device_get_softc(dev); struct atopcase_bl_payload payload = { 0 }; payload.report_id = ATOPCASE_BKL_REPORT_ID; payload.device = ATOPCASE_DEV_KBRD; /* * Hardware range is 32-255 for visible backlight, * convert from percentages */ payload.level = (props->brightness == 0) ? 0 : (32 + (223 * props->brightness / 100)); payload.status = (payload.level > 0) ? 0x01F4 : 0x1; return (atopcase_set_report(dev, sc->sc_kb.hidbus, &payload, sizeof(payload), HID_OUTPUT_REPORT, ATOPCASE_BKL_REPORT_ID)); } int atopcase_backlight_get_status(device_t dev, struct backlight_props *props) { struct atopcase_softc *sc = device_get_softc(dev); props->brightness = sc->sc_backlight_level; props->nlevels = 0; return (0); } int atopcase_backlight_get_info(device_t dev, struct backlight_info *info) { info->type = BACKLIGHT_TYPE_KEYBOARD; strlcpy(info->name, "Apple MacBook Keyboard", BACKLIGHTMAXNAMELENGTH); return (0); } diff --git a/sys/dev/etherswitch/e6000sw/e6000sw.c b/sys/dev/etherswitch/e6000sw/e6000sw.c index e79082759593..cb71fa5b0e88 100644 --- a/sys/dev/etherswitch/e6000sw/e6000sw.c +++ b/sys/dev/etherswitch/e6000sw/e6000sw.c @@ -1,1782 +1,1784 @@ /*- * Copyright (c) 2015 Semihalf * Copyright (c) 2015 Stormshield * Copyright (c) 2018-2019, Rubicon Communications, LLC (Netgate) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #else #include #endif #include "e6000swreg.h" #include "etherswitch_if.h" #include "miibus_if.h" #include "mdio_if.h" MALLOC_DECLARE(M_E6000SW); MALLOC_DEFINE(M_E6000SW, "e6000sw", "e6000sw switch"); #define E6000SW_LOCK(_sc) sx_xlock(&(_sc)->sx) #define E6000SW_UNLOCK(_sc) sx_unlock(&(_sc)->sx) #define E6000SW_LOCK_ASSERT(_sc, _what) sx_assert(&(_sc)->sx, (_what)) #define E6000SW_TRYLOCK(_sc) sx_tryxlock(&(_sc)->sx) #define E6000SW_LOCKED(_sc) sx_xlocked(&(_sc)->sx) #define E6000SW_WAITREADY(_sc, _reg, _bit) \ e6000sw_waitready((_sc), REG_GLOBAL, (_reg), (_bit)) #define E6000SW_WAITREADY2(_sc, _reg, _bit) \ e6000sw_waitready((_sc), REG_GLOBAL2, (_reg), (_bit)) #define MDIO_READ(dev, addr, reg) \ MDIO_READREG(device_get_parent(dev), (addr), (reg)) #define MDIO_WRITE(dev, addr, reg, val) \ MDIO_WRITEREG(device_get_parent(dev), (addr), (reg), (val)) typedef struct e6000sw_softc { device_t dev; #ifdef FDT phandle_t node; #endif struct sx sx; if_t ifp[E6000SW_MAX_PORTS]; char *ifname[E6000SW_MAX_PORTS]; device_t miibus[E6000SW_MAX_PORTS]; struct taskqueue *sc_tq; struct timeout_task sc_tt; int vlans[E6000SW_NUM_VLANS]; uint32_t swid; uint32_t vlan_mode; uint32_t cpuports_mask; uint32_t fixed_mask; uint32_t fixed25_mask; uint32_t ports_mask; int phy_base; int sw_addr; int num_ports; } e6000sw_softc_t; static etherswitch_info_t etherswitch_info = { .es_nports = 0, .es_nvlangroups = 0, .es_vlan_caps = ETHERSWITCH_VLAN_PORT | ETHERSWITCH_VLAN_DOT1Q, .es_name = "Marvell 6000 series switch" }; static void e6000sw_identify(driver_t *, device_t); static int e6000sw_probe(device_t); #ifdef FDT static int e6000sw_parse_fixed_link(e6000sw_softc_t *, phandle_t, uint32_t); static int e6000sw_parse_ethernet(e6000sw_softc_t *, phandle_t, uint32_t); #endif static int e6000sw_attach(device_t); static int e6000sw_detach(device_t); static int e6000sw_read_xmdio(device_t, int, int, int); static int e6000sw_write_xmdio(device_t, int, int, int, int); static int e6000sw_readphy(device_t, int, int); static int e6000sw_writephy(device_t, int, int, int); static int e6000sw_readphy_locked(device_t, int, int); static int e6000sw_writephy_locked(device_t, int, int, int); static etherswitch_info_t* e6000sw_getinfo(device_t); static int e6000sw_getconf(device_t, etherswitch_conf_t *); static int e6000sw_setconf(device_t, etherswitch_conf_t *); static void e6000sw_lock(device_t); static void e6000sw_unlock(device_t); static int e6000sw_getport(device_t, etherswitch_port_t *); static int e6000sw_setport(device_t, etherswitch_port_t *); static int e6000sw_set_vlan_mode(e6000sw_softc_t *, uint32_t); static int e6000sw_readreg_wrapper(device_t, int); static int e6000sw_writereg_wrapper(device_t, int, int); static int e6000sw_getvgroup_wrapper(device_t, etherswitch_vlangroup_t *); static int e6000sw_setvgroup_wrapper(device_t, etherswitch_vlangroup_t *); static int e6000sw_setvgroup(device_t, etherswitch_vlangroup_t *); static int e6000sw_getvgroup(device_t, etherswitch_vlangroup_t *); static void e6000sw_setup(device_t, e6000sw_softc_t *); static void e6000sw_tick(void *, int); static void e6000sw_set_atustat(device_t, e6000sw_softc_t *, int, int); static int e6000sw_atu_flush(device_t, e6000sw_softc_t *, int); static int e6000sw_vtu_flush(e6000sw_softc_t *); static int e6000sw_vtu_update(e6000sw_softc_t *, int, int, int, int, int); static __inline void e6000sw_writereg(e6000sw_softc_t *, int, int, int); static __inline uint32_t e6000sw_readreg(e6000sw_softc_t *, int, int); static int e6000sw_ifmedia_upd(if_t); static void e6000sw_ifmedia_sts(if_t, struct ifmediareq *); static int e6000sw_atu_mac_table(device_t, e6000sw_softc_t *, struct atu_opt *, int); static int e6000sw_get_pvid(e6000sw_softc_t *, int, int *); static void e6000sw_set_pvid(e6000sw_softc_t *, int, int); static __inline bool e6000sw_is_cpuport(e6000sw_softc_t *, int); static __inline bool e6000sw_is_fixedport(e6000sw_softc_t *, int); static __inline bool e6000sw_is_fixed25port(e6000sw_softc_t *, int); static __inline bool e6000sw_is_phyport(e6000sw_softc_t *, int); static __inline bool e6000sw_is_portenabled(e6000sw_softc_t *, int); static __inline struct mii_data *e6000sw_miiforphy(e6000sw_softc_t *, unsigned int); static device_method_t e6000sw_methods[] = { /* device interface */ DEVMETHOD(device_identify, e6000sw_identify), DEVMETHOD(device_probe, e6000sw_probe), DEVMETHOD(device_attach, e6000sw_attach), DEVMETHOD(device_detach, e6000sw_detach), /* bus interface */ DEVMETHOD(bus_add_child, device_add_child_ordered), /* mii interface */ DEVMETHOD(miibus_readreg, e6000sw_readphy), DEVMETHOD(miibus_writereg, e6000sw_writephy), /* etherswitch interface */ DEVMETHOD(etherswitch_getinfo, e6000sw_getinfo), DEVMETHOD(etherswitch_getconf, e6000sw_getconf), DEVMETHOD(etherswitch_setconf, e6000sw_setconf), DEVMETHOD(etherswitch_lock, e6000sw_lock), DEVMETHOD(etherswitch_unlock, e6000sw_unlock), DEVMETHOD(etherswitch_getport, e6000sw_getport), DEVMETHOD(etherswitch_setport, e6000sw_setport), DEVMETHOD(etherswitch_readreg, e6000sw_readreg_wrapper), DEVMETHOD(etherswitch_writereg, e6000sw_writereg_wrapper), DEVMETHOD(etherswitch_readphyreg, e6000sw_readphy), DEVMETHOD(etherswitch_writephyreg, e6000sw_writephy), DEVMETHOD(etherswitch_setvgroup, e6000sw_setvgroup_wrapper), DEVMETHOD(etherswitch_getvgroup, e6000sw_getvgroup_wrapper), DEVMETHOD_END }; DEFINE_CLASS_0(e6000sw, e6000sw_driver, e6000sw_methods, sizeof(e6000sw_softc_t)); DRIVER_MODULE(e6000sw, mdio, e6000sw_driver, 0, 0); DRIVER_MODULE(etherswitch, e6000sw, etherswitch_driver, 0, 0); DRIVER_MODULE(miibus, e6000sw, miibus_driver, 0, 0); MODULE_DEPEND(e6000sw, mdio, 1, 1, 1); static void e6000sw_identify(driver_t *driver, device_t parent) { if (device_find_child(parent, "e6000sw", -1) == NULL) BUS_ADD_CHILD(parent, 0, "e6000sw", DEVICE_UNIT_ANY); } static int e6000sw_probe(device_t dev) { e6000sw_softc_t *sc; const char *description; #ifdef FDT phandle_t switch_node; #else int is_6190; #endif sc = device_get_softc(dev); sc->dev = dev; #ifdef FDT switch_node = ofw_bus_find_compatible(OF_finddevice("/"), "marvell,mv88e6085"); if (switch_node == 0) { switch_node = ofw_bus_find_compatible(OF_finddevice("/"), "marvell,mv88e6190"); if (switch_node == 0) return (ENXIO); /* * Trust DTS and fix the port register offset for the MV88E6190 * detection bellow. */ sc->swid = MV88E6190; } if (bootverbose) device_printf(dev, "Found switch_node: 0x%x\n", switch_node); sc->node = switch_node; if (OF_getencprop(sc->node, "reg", &sc->sw_addr, sizeof(sc->sw_addr)) < 0) return (ENXIO); #else if (resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "addr", &sc->sw_addr) != 0) return (ENXIO); if (resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "is6190", &is_6190) != 0) /* * Check "is8190" to keep backward compatibility with * older setups. */ resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev), "is8190", &is_6190); if (is_6190 != 0) sc->swid = MV88E6190; #endif if (sc->sw_addr < 0 || sc->sw_addr > 32) return (ENXIO); /* * Create temporary lock, just to satisfy assertions, * when obtaining the switch ID. Destroy immediately afterwards. */ sx_init(&sc->sx, "e6000sw_tmp"); E6000SW_LOCK(sc); sc->swid = e6000sw_readreg(sc, REG_PORT(sc, 0), SWITCH_ID) & 0xfff0; E6000SW_UNLOCK(sc); sx_destroy(&sc->sx); switch (sc->swid) { case MV88E6141: description = "Marvell 88E6141"; sc->phy_base = 0x10; sc->num_ports = 6; break; case MV88E6341: description = "Marvell 88E6341"; sc->phy_base = 0x10; sc->num_ports = 6; break; case MV88E6352: description = "Marvell 88E6352"; sc->num_ports = 7; break; case MV88E6172: description = "Marvell 88E6172"; sc->num_ports = 7; break; case MV88E6176: description = "Marvell 88E6176"; sc->num_ports = 7; break; case MV88E6190: description = "Marvell 88E6190"; sc->num_ports = 11; break; default: device_printf(dev, "Unrecognized device, id 0x%x.\n", sc->swid); return (ENXIO); } device_set_desc(dev, description); return (BUS_PROBE_DEFAULT); } #ifdef FDT static int e6000sw_parse_fixed_link(e6000sw_softc_t *sc, phandle_t node, uint32_t port) { int speed; phandle_t fixed_link; fixed_link = ofw_bus_find_child(node, "fixed-link"); if (fixed_link != 0) { sc->fixed_mask |= (1 << port); if (OF_getencprop(fixed_link, "speed", &speed, sizeof(speed)) < 0) { device_printf(sc->dev, "Port %d has a fixed-link node without a speed " "property\n", port); return (ENXIO); } if (speed == 2500 && (MVSWITCH(sc, MV88E6141) || MVSWITCH(sc, MV88E6341) || MVSWITCH(sc, MV88E6190))) sc->fixed25_mask |= (1 << port); } return (0); } static int e6000sw_parse_ethernet(e6000sw_softc_t *sc, phandle_t port_handle, uint32_t port) { phandle_t switch_eth, switch_eth_handle; if (OF_getencprop(port_handle, "ethernet", (void*)&switch_eth_handle, sizeof(switch_eth_handle)) > 0) { if (switch_eth_handle > 0) { switch_eth = OF_node_from_xref(switch_eth_handle); device_printf(sc->dev, "CPU port at %d\n", port); sc->cpuports_mask |= (1 << port); return (e6000sw_parse_fixed_link(sc, switch_eth, port)); } else device_printf(sc->dev, "Port %d has ethernet property but it points " "to an invalid location\n", port); } return (0); } static int e6000sw_parse_child_fdt(e6000sw_softc_t *sc, phandle_t child, int *pport) { uint32_t port; if (pport == NULL) return (ENXIO); if (OF_getencprop(child, "reg", (void *)&port, sizeof(port)) < 0) return (ENXIO); if (port >= sc->num_ports) return (ENXIO); *pport = port; if (e6000sw_parse_fixed_link(sc, child, port) != 0) return (ENXIO); if (e6000sw_parse_ethernet(sc, child, port) != 0) return (ENXIO); if ((sc->fixed_mask & (1 << port)) != 0) device_printf(sc->dev, "fixed port at %d\n", port); else device_printf(sc->dev, "PHY at port %d\n", port); return (0); } #else static int e6000sw_check_hint_val(device_t dev, int *val, char *fmt, ...) { char *resname; int err, len; va_list ap; len = min(strlen(fmt) * 2, 128); if (len == 0) return (-1); resname = malloc(len, M_E6000SW, M_WAITOK); memset(resname, 0, len); va_start(ap, fmt); vsnprintf(resname, len - 1, fmt, ap); va_end(ap); err = resource_int_value(device_get_name(dev), device_get_unit(dev), resname, val); free(resname, M_E6000SW); return (err); } static int e6000sw_parse_hinted_port(e6000sw_softc_t *sc, int port) { int err, val; err = e6000sw_check_hint_val(sc->dev, &val, "port%ddisabled", port); if (err == 0 && val != 0) return (1); err = e6000sw_check_hint_val(sc->dev, &val, "port%dcpu", port); if (err == 0 && val != 0) { sc->cpuports_mask |= (1 << port); sc->fixed_mask |= (1 << port); if (bootverbose) device_printf(sc->dev, "CPU port at %d\n", port); } err = e6000sw_check_hint_val(sc->dev, &val, "port%dspeed", port); if (err == 0 && val != 0) { sc->fixed_mask |= (1 << port); if (val == 2500) sc->fixed25_mask |= (1 << port); } if (bootverbose) { if ((sc->fixed_mask & (1 << port)) != 0) device_printf(sc->dev, "fixed port at %d\n", port); else device_printf(sc->dev, "PHY at port %d\n", port); } return (0); } #endif static int e6000sw_init_interface(e6000sw_softc_t *sc, int port) { char name[IFNAMSIZ]; snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->dev)); sc->ifp[port] = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp[port], sc); if_setflagbits(sc->ifp[port], IFF_UP | IFF_BROADCAST | IFF_DRV_RUNNING | IFF_SIMPLEX, 0); sc->ifname[port] = malloc(strlen(name) + 1, M_E6000SW, M_NOWAIT); if (sc->ifname[port] == NULL) { if_free(sc->ifp[port]); return (ENOMEM); } memcpy(sc->ifname[port], name, strlen(name) + 1); if_initname(sc->ifp[port], sc->ifname[port], port); return (0); } static int e6000sw_attach_miibus(e6000sw_softc_t *sc, int port) { int err; err = mii_attach(sc->dev, &sc->miibus[port], sc->ifp[port], e6000sw_ifmedia_upd, e6000sw_ifmedia_sts, BMSR_DEFCAPMASK, port + sc->phy_base, MII_OFFSET_ANY, 0); if (err != 0) return (err); return (0); } static void e6000sw_serdes_power(device_t dev, int port, bool sgmii) { uint32_t reg; /* SGMII */ reg = e6000sw_read_xmdio(dev, port, E6000SW_SERDES_DEV, E6000SW_SERDES_SGMII_CTL); if (sgmii) reg &= ~E6000SW_SERDES_PDOWN; else reg |= E6000SW_SERDES_PDOWN; e6000sw_write_xmdio(dev, port, E6000SW_SERDES_DEV, E6000SW_SERDES_SGMII_CTL, reg); /* 10GBASE-R/10GBASE-X4/X2 */ reg = e6000sw_read_xmdio(dev, port, E6000SW_SERDES_DEV, E6000SW_SERDES_PCS_CTL1); if (sgmii) reg |= E6000SW_SERDES_PDOWN; else reg &= ~E6000SW_SERDES_PDOWN; e6000sw_write_xmdio(dev, port, E6000SW_SERDES_DEV, E6000SW_SERDES_PCS_CTL1, reg); } static int e6000sw_attach(device_t dev) { bool sgmii; e6000sw_softc_t *sc; #ifdef FDT phandle_t child, ports; #endif int err, port; uint32_t reg; err = 0; sc = device_get_softc(dev); /* * According to the Linux source code, all of the Switch IDs we support * are multi_chip capable, and should go into multi-chip mode if the * sw_addr != 0. */ if (MVSWITCH_MULTICHIP(sc)) device_printf(dev, "multi-chip addressing mode (%#x)\n", sc->sw_addr); else device_printf(dev, "single-chip addressing mode\n"); sx_init(&sc->sx, "e6000sw"); E6000SW_LOCK(sc); e6000sw_setup(dev, sc); sc->sc_tq = taskqueue_create("e6000sw_taskq", M_NOWAIT, taskqueue_thread_enqueue, &sc->sc_tq); TIMEOUT_TASK_INIT(sc->sc_tq, &sc->sc_tt, 0, e6000sw_tick, sc); taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq", device_get_nameunit(dev)); #ifdef FDT ports = ofw_bus_find_child(sc->node, "ports"); if (ports == 0) { device_printf(dev, "failed to parse DTS: no ports found for " "switch\n"); E6000SW_UNLOCK(sc); return (ENXIO); } for (child = OF_child(ports); child != 0; child = OF_peer(child)) { err = e6000sw_parse_child_fdt(sc, child, &port); if (err != 0) { device_printf(sc->dev, "failed to parse DTS\n"); goto out_fail; } #else for (port = 0; port < sc->num_ports; port++) { err = e6000sw_parse_hinted_port(sc, port); if (err != 0) continue; #endif /* Port is in use. */ sc->ports_mask |= (1 << port); err = e6000sw_init_interface(sc, port); if (err != 0) { device_printf(sc->dev, "failed to init interface\n"); goto out_fail; } if (e6000sw_is_fixedport(sc, port)) { /* Link must be down to change speed force value. */ reg = e6000sw_readreg(sc, REG_PORT(sc, port), PSC_CONTROL); reg &= ~PSC_CONTROL_LINK_UP; reg |= PSC_CONTROL_FORCED_LINK; e6000sw_writereg(sc, REG_PORT(sc, port), PSC_CONTROL, reg); /* * Force speed, full-duplex, EEE off and flow-control * on. */ reg &= ~(PSC_CONTROL_SPD2500 | PSC_CONTROL_ALT_SPD | PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON | PSC_CONTROL_FORCED_EEE); if (e6000sw_is_fixed25port(sc, port)) reg |= PSC_CONTROL_SPD2500; else reg |= PSC_CONTROL_SPD1000; if (MVSWITCH(sc, MV88E6190) && e6000sw_is_fixed25port(sc, port)) reg |= PSC_CONTROL_ALT_SPD; reg |= PSC_CONTROL_FORCED_DPX | PSC_CONTROL_FULLDPX | PSC_CONTROL_FORCED_LINK | PSC_CONTROL_LINK_UP | PSC_CONTROL_FORCED_SPD; if (!MVSWITCH(sc, MV88E6190)) reg |= PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON; if (MVSWITCH(sc, MV88E6141) || MVSWITCH(sc, MV88E6341) || MVSWITCH(sc, MV88E6190)) reg |= PSC_CONTROL_FORCED_EEE; e6000sw_writereg(sc, REG_PORT(sc, port), PSC_CONTROL, reg); /* Power on the SERDES interfaces. */ if (MVSWITCH(sc, MV88E6190) && (port == 9 || port == 10)) { if (e6000sw_is_fixed25port(sc, port)) sgmii = false; else sgmii = true; e6000sw_serdes_power(sc->dev, port, sgmii); } } /* Don't attach miibus at CPU/fixed ports */ if (!e6000sw_is_phyport(sc, port)) continue; err = e6000sw_attach_miibus(sc, port); if (err != 0) { device_printf(sc->dev, "failed to attach miibus\n"); goto out_fail; } } etherswitch_info.es_nports = sc->num_ports; /* Default to port vlan. */ e6000sw_set_vlan_mode(sc, ETHERSWITCH_VLAN_PORT); reg = e6000sw_readreg(sc, REG_GLOBAL, SWITCH_GLOBAL_STATUS); if (reg & SWITCH_GLOBAL_STATUS_IR) device_printf(dev, "switch is ready.\n"); E6000SW_UNLOCK(sc); bus_identify_children(dev); bus_attach_children(dev); taskqueue_enqueue_timeout(sc->sc_tq, &sc->sc_tt, hz); return (0); out_fail: e6000sw_detach(dev); return (err); } static int e6000sw_waitready(e6000sw_softc_t *sc, uint32_t phy, uint32_t reg, uint32_t busybit) { int i; for (i = 0; i < E6000SW_RETRIES; i++) { if ((e6000sw_readreg(sc, phy, reg) & busybit) == 0) return (0); DELAY(1); } return (1); } /* XMDIO/Clause 45 access. */ static int e6000sw_read_xmdio(device_t dev, int phy, int devaddr, int devreg) { e6000sw_softc_t *sc; uint32_t reg; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } reg = devaddr & SMI_CMD_REG_ADDR_MASK; reg |= (phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK; /* Load C45 register address. */ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg); e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, reg | SMI_CMD_OP_C45_ADDR); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } /* Start C45 read operation. */ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, reg | SMI_CMD_OP_C45_READ); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } /* Read C45 data. */ reg = e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG); return (reg & PHY_DATA_MASK); } static int e6000sw_write_xmdio(device_t dev, int phy, int devaddr, int devreg, int val) { e6000sw_softc_t *sc; uint32_t reg; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } reg = devaddr & SMI_CMD_REG_ADDR_MASK; reg |= (phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK; /* Load C45 register address. */ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg); e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, reg | SMI_CMD_OP_C45_ADDR); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } /* Load data and start the C45 write operation. */ e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg); e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, reg | SMI_CMD_OP_C45_WRITE); return (0); } static int e6000sw_readphy(device_t dev, int phy, int reg) { e6000sw_softc_t *sc; int locked, ret; sc = device_get_softc(dev); locked = E6000SW_LOCKED(sc); if (!locked) E6000SW_LOCK(sc); ret = e6000sw_readphy_locked(dev, phy, reg); if (!locked) E6000SW_UNLOCK(sc); return (ret); } /* * PHY registers are paged. Put page index in reg 22 (accessible from every * page), then access specific register. */ static int e6000sw_readphy_locked(device_t dev, int phy, int reg) { e6000sw_softc_t *sc; uint32_t val; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!e6000sw_is_phyport(sc, phy) || reg >= E6000SW_NUM_PHY_REGS) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, SMI_CMD_OP_C22_READ | (reg & SMI_CMD_REG_ADDR_MASK) | ((phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK)); if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } val = e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG); return (val & PHY_DATA_MASK); } static int e6000sw_writephy(device_t dev, int phy, int reg, int data) { e6000sw_softc_t *sc; int locked, ret; sc = device_get_softc(dev); locked = E6000SW_LOCKED(sc); if (!locked) E6000SW_LOCK(sc); ret = e6000sw_writephy_locked(dev, phy, reg, data); if (!locked) E6000SW_UNLOCK(sc); return (ret); } static int e6000sw_writephy_locked(device_t dev, int phy, int reg, int data) { e6000sw_softc_t *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!e6000sw_is_phyport(sc, phy) || reg >= E6000SW_NUM_PHY_REGS) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) { device_printf(dev, "Timeout while waiting for switch\n"); return (ETIMEDOUT); } e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, data & PHY_DATA_MASK); e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG, SMI_CMD_OP_C22_WRITE | (reg & SMI_CMD_REG_ADDR_MASK) | ((phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK)); return (0); } static int e6000sw_detach(device_t dev) { - int phy; + int error, phy; e6000sw_softc_t *sc; sc = device_get_softc(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); + if (device_is_attached(dev)) taskqueue_drain_timeout(sc->sc_tq, &sc->sc_tt); if (sc->sc_tq != NULL) taskqueue_free(sc->sc_tq); - device_delete_children(dev); - sx_destroy(&sc->sx); for (phy = 0; phy < sc->num_ports; phy++) { if (sc->ifp[phy] != NULL) if_free(sc->ifp[phy]); if (sc->ifname[phy] != NULL) free(sc->ifname[phy], M_E6000SW); } return (0); } static etherswitch_info_t* e6000sw_getinfo(device_t dev) { return (ðerswitch_info); } static int e6000sw_getconf(device_t dev, etherswitch_conf_t *conf) { struct e6000sw_softc *sc; /* Return the VLAN mode. */ sc = device_get_softc(dev); conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; conf->vlan_mode = sc->vlan_mode; return (0); } static int e6000sw_setconf(device_t dev, etherswitch_conf_t *conf) { struct e6000sw_softc *sc; /* Set the VLAN mode. */ sc = device_get_softc(dev); if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) { E6000SW_LOCK(sc); e6000sw_set_vlan_mode(sc, conf->vlan_mode); E6000SW_UNLOCK(sc); } return (0); } static void e6000sw_lock(device_t dev) { struct e6000sw_softc *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); } static void e6000sw_unlock(device_t dev) { struct e6000sw_softc *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); E6000SW_UNLOCK(sc); } static int e6000sw_getport(device_t dev, etherswitch_port_t *p) { struct mii_data *mii; int err; struct ifmediareq *ifmr; uint32_t reg; e6000sw_softc_t *sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); if (p->es_port >= sc->num_ports || p->es_port < 0) return (EINVAL); if (!e6000sw_is_portenabled(sc, p->es_port)) return (0); E6000SW_LOCK(sc); e6000sw_get_pvid(sc, p->es_port, &p->es_pvid); /* Port flags. */ reg = e6000sw_readreg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2); if (reg & PORT_CONTROL2_DISC_TAGGED) p->es_flags |= ETHERSWITCH_PORT_DROPTAGGED; if (reg & PORT_CONTROL2_DISC_UNTAGGED) p->es_flags |= ETHERSWITCH_PORT_DROPUNTAGGED; err = 0; if (e6000sw_is_fixedport(sc, p->es_port)) { if (e6000sw_is_cpuport(sc, p->es_port)) p->es_flags |= ETHERSWITCH_PORT_CPU; ifmr = &p->es_ifmr; ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; ifmr->ifm_count = 0; if (e6000sw_is_fixed25port(sc, p->es_port)) ifmr->ifm_active = IFM_2500_T; else ifmr->ifm_active = IFM_1000_T; ifmr->ifm_active |= IFM_ETHER | IFM_FDX; ifmr->ifm_current = ifmr->ifm_active; ifmr->ifm_mask = 0; } else { mii = e6000sw_miiforphy(sc, p->es_port); err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCGIFMEDIA); } E6000SW_UNLOCK(sc); return (err); } static int e6000sw_setport(device_t dev, etherswitch_port_t *p) { e6000sw_softc_t *sc; int err; struct mii_data *mii; uint32_t reg; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); if (p->es_port >= sc->num_ports || p->es_port < 0) return (EINVAL); if (!e6000sw_is_portenabled(sc, p->es_port)) return (0); E6000SW_LOCK(sc); /* Port flags. */ reg = e6000sw_readreg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2); if (p->es_flags & ETHERSWITCH_PORT_DROPTAGGED) reg |= PORT_CONTROL2_DISC_TAGGED; else reg &= ~PORT_CONTROL2_DISC_TAGGED; if (p->es_flags & ETHERSWITCH_PORT_DROPUNTAGGED) reg |= PORT_CONTROL2_DISC_UNTAGGED; else reg &= ~PORT_CONTROL2_DISC_UNTAGGED; e6000sw_writereg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2, reg); err = 0; if (p->es_pvid != 0) e6000sw_set_pvid(sc, p->es_port, p->es_pvid); if (e6000sw_is_phyport(sc, p->es_port)) { mii = e6000sw_miiforphy(sc, p->es_port); err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, &mii->mii_media, SIOCSIFMEDIA); } E6000SW_UNLOCK(sc); return (err); } static __inline void e6000sw_port_vlan_assign(e6000sw_softc_t *sc, int port, uint32_t fid, uint32_t members) { uint32_t reg; reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VLAN_MAP); reg &= ~(PORT_MASK(sc) | PORT_VLAN_MAP_FID_MASK); reg |= members & PORT_MASK(sc) & ~(1 << port); reg |= (fid << PORT_VLAN_MAP_FID) & PORT_VLAN_MAP_FID_MASK; e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VLAN_MAP, reg); reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL1); reg &= ~PORT_CONTROL1_FID_MASK; reg |= (fid >> 4) & PORT_CONTROL1_FID_MASK; e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL1, reg); } static int e6000sw_init_vlan(struct e6000sw_softc *sc) { int i, port, ret; uint32_t members; /* Disable all ports */ for (port = 0; port < sc->num_ports; port++) { ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL, (ret & ~PORT_CONTROL_ENABLE)); } /* Flush VTU. */ e6000sw_vtu_flush(sc); for (port = 0; port < sc->num_ports; port++) { /* Reset the egress and frame mode. */ ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL); ret &= ~(PORT_CONTROL_EGRESS | PORT_CONTROL_FRAME); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL, ret); /* Set the 802.1q mode. */ ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL2); ret &= ~PORT_CONTROL2_DOT1Q; if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) ret |= PORT_CONTROL2_DOT1Q; e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL2, ret); } for (port = 0; port < sc->num_ports; port++) { if (!e6000sw_is_portenabled(sc, port)) continue; ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID); /* Set port priority */ ret &= ~PORT_VID_PRIORITY_MASK; /* Set VID map */ ret &= ~PORT_VID_DEF_VID_MASK; if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) ret |= 1; else ret |= (port + 1); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VID, ret); } /* Assign the member ports to each origin port. */ for (port = 0; port < sc->num_ports; port++) { members = 0; if (e6000sw_is_portenabled(sc, port)) { for (i = 0; i < sc->num_ports; i++) { if (i == port || !e6000sw_is_portenabled(sc, i)) continue; members |= (1 << i); } } /* Default to FID 0. */ e6000sw_port_vlan_assign(sc, port, 0, members); } /* Reset internal VLAN table. */ for (i = 0; i < nitems(sc->vlans); i++) sc->vlans[i] = 0; /* Create default VLAN (1). */ if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { sc->vlans[0] = 1; e6000sw_vtu_update(sc, 0, sc->vlans[0], 1, 0, sc->ports_mask); } /* Enable all ports */ for (port = 0; port < sc->num_ports; port++) { if (!e6000sw_is_portenabled(sc, port)) continue; ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL, (ret | PORT_CONTROL_ENABLE)); } return (0); } static int e6000sw_set_vlan_mode(struct e6000sw_softc *sc, uint32_t mode) { E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); switch (mode) { case ETHERSWITCH_VLAN_PORT: sc->vlan_mode = ETHERSWITCH_VLAN_PORT; etherswitch_info.es_nvlangroups = sc->num_ports; return (e6000sw_init_vlan(sc)); break; case ETHERSWITCH_VLAN_DOT1Q: sc->vlan_mode = ETHERSWITCH_VLAN_DOT1Q; etherswitch_info.es_nvlangroups = E6000SW_NUM_VLANS; return (e6000sw_init_vlan(sc)); break; default: return (EINVAL); } } /* * Registers in this switch are divided into sections, specified in * documentation. So as to access any of them, section index and reg index * is necessary. etherswitchcfg uses only one variable, so indexes were * compressed into addr_reg: 32 * section_index + reg_index. */ static int e6000sw_readreg_wrapper(device_t dev, int addr_reg) { e6000sw_softc_t *sc; sc = device_get_softc(dev); if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) || (addr_reg < (REG_PORT(sc, 0) * 32))) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } return (e6000sw_readreg(device_get_softc(dev), addr_reg / 32, addr_reg % 32)); } static int e6000sw_writereg_wrapper(device_t dev, int addr_reg, int val) { e6000sw_softc_t *sc; sc = device_get_softc(dev); if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) || (addr_reg < (REG_PORT(sc, 0) * 32))) { device_printf(dev, "Wrong register address.\n"); return (EINVAL); } e6000sw_writereg(device_get_softc(dev), addr_reg / 32, addr_reg % 32, val); return (0); } /* * setvgroup/getvgroup called from etherswitchfcg need to be locked, * while internal calls do not. */ static int e6000sw_setvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; int ret; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); ret = e6000sw_setvgroup(dev, vg); E6000SW_UNLOCK(sc); return (ret); } static int e6000sw_getvgroup_wrapper(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; int ret; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); ret = e6000sw_getvgroup(dev, vg); E6000SW_UNLOCK(sc); return (ret); } static int e6000sw_set_port_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { uint32_t port; port = vg->es_vlangroup; if (port > sc->num_ports) return (EINVAL); if (vg->es_member_ports != vg->es_untagged_ports) { device_printf(sc->dev, "Tagged ports not supported.\n"); return (EINVAL); } e6000sw_port_vlan_assign(sc, port, 0, vg->es_untagged_ports); vg->es_vid = port | ETHERSWITCH_VID_VALID; return (0); } static int e6000sw_set_dot1q_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { int i, vlan; vlan = vg->es_vid & ETHERSWITCH_VID_MASK; /* Set VLAN to '0' removes it from table. */ if (vlan == 0) { e6000sw_vtu_update(sc, VTU_PURGE, sc->vlans[vg->es_vlangroup], 0, 0, 0); sc->vlans[vg->es_vlangroup] = 0; return (0); } /* Is this VLAN already in table ? */ for (i = 0; i < etherswitch_info.es_nvlangroups; i++) if (i != vg->es_vlangroup && vlan == sc->vlans[i]) return (EINVAL); sc->vlans[vg->es_vlangroup] = vlan; e6000sw_vtu_update(sc, 0, vlan, vg->es_vlangroup + 1, vg->es_member_ports & sc->ports_mask, vg->es_untagged_ports & sc->ports_mask); return (0); } static int e6000sw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) return (e6000sw_set_port_vlan(sc, vg)); else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) return (e6000sw_set_dot1q_vlan(sc, vg)); return (EINVAL); } static int e6000sw_get_port_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { uint32_t port, reg; port = vg->es_vlangroup; if (port > sc->num_ports) return (EINVAL); if (!e6000sw_is_portenabled(sc, port)) { vg->es_vid = port; return (0); } reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VLAN_MAP); vg->es_untagged_ports = vg->es_member_ports = reg & PORT_MASK(sc); vg->es_vid = port | ETHERSWITCH_VID_VALID; vg->es_fid = (reg & PORT_VLAN_MAP_FID_MASK) >> PORT_VLAN_MAP_FID; reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL1); vg->es_fid |= (reg & PORT_CONTROL1_FID_MASK) << 4; return (0); } static int e6000sw_get_dot1q_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg) { int i, port; uint32_t reg; vg->es_fid = 0; vg->es_vid = sc->vlans[vg->es_vlangroup]; vg->es_untagged_ports = vg->es_member_ports = 0; if (vg->es_vid == 0) return (0); if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "VTU unit is busy, cannot access\n"); return (EBUSY); } e6000sw_writereg(sc, REG_GLOBAL, VTU_VID, vg->es_vid - 1); reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_OPERATION); reg &= ~VTU_OP_MASK; reg |= VTU_GET_NEXT | VTU_BUSY; e6000sw_writereg(sc, REG_GLOBAL, VTU_OPERATION, reg); if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "Timeout while reading\n"); return (EBUSY); } reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_VID); if (reg == VTU_VID_MASK || (reg & VTU_VID_VALID) == 0) return (EINVAL); if ((reg & VTU_VID_MASK) != vg->es_vid) return (EINVAL); vg->es_vid |= ETHERSWITCH_VID_VALID; reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_DATA); for (i = 0; i < sc->num_ports; i++) { if (i == VTU_PPREG(sc)) reg = e6000sw_readreg(sc, REG_GLOBAL, VTU_DATA2); port = (reg >> VTU_PORT(sc, i)) & VTU_PORT_MASK; if (port == VTU_PORT_UNTAGGED) { vg->es_untagged_ports |= (1 << i); vg->es_member_ports |= (1 << i); } else if (port == VTU_PORT_TAGGED) vg->es_member_ports |= (1 << i); } return (0); } static int e6000sw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg) { e6000sw_softc_t *sc; sc = device_get_softc(dev); E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (sc->vlan_mode == ETHERSWITCH_VLAN_PORT) return (e6000sw_get_port_vlan(sc, vg)); else if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) return (e6000sw_get_dot1q_vlan(sc, vg)); return (EINVAL); } static __inline struct mii_data* e6000sw_miiforphy(e6000sw_softc_t *sc, unsigned int phy) { if (!e6000sw_is_phyport(sc, phy)) return (NULL); return (device_get_softc(sc->miibus[phy])); } static int e6000sw_ifmedia_upd(if_t ifp) { e6000sw_softc_t *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = e6000sw_miiforphy(sc, if_getdunit(ifp)); if (mii == NULL) return (ENXIO); mii_mediachg(mii); return (0); } static void e6000sw_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr) { e6000sw_softc_t *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = e6000sw_miiforphy(sc, if_getdunit(ifp)); if (mii == NULL) return; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static int e6000sw_smi_waitready(e6000sw_softc_t *sc, int phy) { int i; for (i = 0; i < E6000SW_SMI_TIMEOUT; i++) { if ((MDIO_READ(sc->dev, phy, SMI_CMD) & SMI_CMD_BUSY) == 0) return (0); DELAY(1); } return (1); } static __inline uint32_t e6000sw_readreg(e6000sw_softc_t *sc, int addr, int reg) { E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!MVSWITCH_MULTICHIP(sc)) return (MDIO_READ(sc->dev, addr, reg) & 0xffff); if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return (0xffff); } MDIO_WRITE(sc->dev, sc->sw_addr, SMI_CMD, SMI_CMD_OP_C22_READ | (reg & SMI_CMD_REG_ADDR_MASK) | ((addr << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK)); if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return (0xffff); } return (MDIO_READ(sc->dev, sc->sw_addr, SMI_DATA) & 0xffff); } static __inline void e6000sw_writereg(e6000sw_softc_t *sc, int addr, int reg, int val) { E6000SW_LOCK_ASSERT(sc, SA_XLOCKED); if (!MVSWITCH_MULTICHIP(sc)) { MDIO_WRITE(sc->dev, addr, reg, val); return; } if (e6000sw_smi_waitready(sc, sc->sw_addr)) { printf("e6000sw: readreg timeout\n"); return; } MDIO_WRITE(sc->dev, sc->sw_addr, SMI_DATA, val); MDIO_WRITE(sc->dev, sc->sw_addr, SMI_CMD, SMI_CMD_OP_C22_WRITE | (reg & SMI_CMD_REG_ADDR_MASK) | ((addr << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK)); } static __inline bool e6000sw_is_cpuport(e6000sw_softc_t *sc, int port) { return ((sc->cpuports_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_fixedport(e6000sw_softc_t *sc, int port) { return ((sc->fixed_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_fixed25port(e6000sw_softc_t *sc, int port) { return ((sc->fixed25_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_phyport(e6000sw_softc_t *sc, int port) { uint32_t phy_mask; phy_mask = ~(sc->fixed_mask | sc->cpuports_mask); return ((phy_mask & (1 << port)) ? true : false); } static __inline bool e6000sw_is_portenabled(e6000sw_softc_t *sc, int port) { return ((sc->ports_mask & (1 << port)) ? true : false); } static __inline void e6000sw_set_pvid(e6000sw_softc_t *sc, int port, int pvid) { uint32_t reg; reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID); reg &= ~PORT_VID_DEF_VID_MASK; reg |= (pvid & PORT_VID_DEF_VID_MASK); e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VID, reg); } static __inline int e6000sw_get_pvid(e6000sw_softc_t *sc, int port, int *pvid) { if (pvid == NULL) return (ENXIO); *pvid = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID) & PORT_VID_DEF_VID_MASK; return (0); } /* * Convert port status to ifmedia. */ static void e6000sw_update_ifmedia(uint16_t portstatus, u_int *media_status, u_int *media_active) { *media_active = IFM_ETHER; *media_status = IFM_AVALID; if ((portstatus & PORT_STATUS_LINK_MASK) != 0) *media_status |= IFM_ACTIVE; else { *media_active |= IFM_NONE; return; } switch (portstatus & PORT_STATUS_SPEED_MASK) { case PORT_STATUS_SPEED_10: *media_active |= IFM_10_T; break; case PORT_STATUS_SPEED_100: *media_active |= IFM_100_TX; break; case PORT_STATUS_SPEED_1000: *media_active |= IFM_1000_T; break; } if ((portstatus & PORT_STATUS_DUPLEX_MASK) == 0) *media_active |= IFM_FDX; else *media_active |= IFM_HDX; } static void e6000sw_tick(void *arg, int p __unused) { e6000sw_softc_t *sc; struct mii_data *mii; struct mii_softc *miisc; uint16_t portstatus; int port; sc = arg; E6000SW_LOCK_ASSERT(sc, SA_UNLOCKED); E6000SW_LOCK(sc); for (port = 0; port < sc->num_ports; port++) { /* Tick only on PHY ports */ if (!e6000sw_is_portenabled(sc, port) || !e6000sw_is_phyport(sc, port)) continue; mii = e6000sw_miiforphy(sc, port); if (mii == NULL) continue; portstatus = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_STATUS); e6000sw_update_ifmedia(portstatus, &mii->mii_media_status, &mii->mii_media_active); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != miisc->mii_inst) continue; mii_phy_update(miisc, MII_POLLSTAT); } } E6000SW_UNLOCK(sc); } static void e6000sw_setup(device_t dev, e6000sw_softc_t *sc) { uint32_t atu_ctrl; /* Set aging time. */ atu_ctrl = e6000sw_readreg(sc, REG_GLOBAL, ATU_CONTROL); atu_ctrl &= ~ATU_CONTROL_AGETIME_MASK; atu_ctrl |= E6000SW_DEFAULT_AGETIME << ATU_CONTROL_AGETIME; e6000sw_writereg(sc, REG_GLOBAL, ATU_CONTROL, atu_ctrl); /* Send all with specific mac address to cpu port */ e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_2x, MGMT_EN_ALL); e6000sw_writereg(sc, REG_GLOBAL2, MGMT_EN_0x, MGMT_EN_ALL); /* Disable Remote Management */ e6000sw_writereg(sc, REG_GLOBAL, SWITCH_GLOBAL_CONTROL2, 0); /* Disable loopback filter and flow control messages */ e6000sw_writereg(sc, REG_GLOBAL2, SWITCH_MGMT, SWITCH_MGMT_PRI_MASK | (1 << SWITCH_MGMT_RSVD2CPU) | SWITCH_MGMT_FC_PRI_MASK | (1 << SWITCH_MGMT_FORCEFLOW)); e6000sw_atu_flush(dev, sc, NO_OPERATION); e6000sw_atu_mac_table(dev, sc, NULL, NO_OPERATION); e6000sw_set_atustat(dev, sc, 0, COUNT_ALL); } static void e6000sw_set_atustat(device_t dev, e6000sw_softc_t *sc, int bin, int flag) { e6000sw_readreg(sc, REG_GLOBAL2, ATU_STATS); e6000sw_writereg(sc, REG_GLOBAL2, ATU_STATS, (bin << ATU_STATS_BIN ) | (flag << ATU_STATS_FLAG)); } static int e6000sw_atu_mac_table(device_t dev, e6000sw_softc_t *sc, struct atu_opt *atu, int flag) { uint16_t ret_opt; uint16_t ret_data; if (flag == NO_OPERATION) return (0); else if ((flag & (LOAD_FROM_FIB | PURGE_FROM_FIB | GET_NEXT_IN_FIB | GET_VIOLATION_DATA | CLEAR_VIOLATION_DATA)) == 0) { device_printf(dev, "Wrong Opcode for ATU operation\n"); return (EINVAL); } if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) { device_printf(dev, "ATU unit is busy, cannot access\n"); return (EBUSY); } ret_opt = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION); if (flag & LOAD_FROM_FIB) { ret_data = e6000sw_readreg(sc, REG_GLOBAL, ATU_DATA); e6000sw_writereg(sc, REG_GLOBAL2, ATU_DATA, (ret_data & ~ENTRY_STATE)); } e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR01, atu->mac_01); e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR23, atu->mac_23); e6000sw_writereg(sc, REG_GLOBAL, ATU_MAC_ADDR45, atu->mac_45); e6000sw_writereg(sc, REG_GLOBAL, ATU_FID, atu->fid); e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION, (ret_opt | ATU_UNIT_BUSY | flag)); if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) device_printf(dev, "Timeout while waiting ATU\n"); else if (flag & GET_NEXT_IN_FIB) { atu->mac_01 = e6000sw_readreg(sc, REG_GLOBAL, ATU_MAC_ADDR01); atu->mac_23 = e6000sw_readreg(sc, REG_GLOBAL, ATU_MAC_ADDR23); atu->mac_45 = e6000sw_readreg(sc, REG_GLOBAL, ATU_MAC_ADDR45); } return (0); } static int e6000sw_atu_flush(device_t dev, e6000sw_softc_t *sc, int flag) { uint32_t reg; if (flag == NO_OPERATION) return (0); if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) { device_printf(dev, "ATU unit is busy, cannot access\n"); return (EBUSY); } reg = e6000sw_readreg(sc, REG_GLOBAL, ATU_OPERATION); e6000sw_writereg(sc, REG_GLOBAL, ATU_OPERATION, (reg | ATU_UNIT_BUSY | flag)); if (E6000SW_WAITREADY(sc, ATU_OPERATION, ATU_UNIT_BUSY)) device_printf(dev, "Timeout while flushing ATU\n"); return (0); } static int e6000sw_vtu_flush(e6000sw_softc_t *sc) { if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "VTU unit is busy, cannot access\n"); return (EBUSY); } e6000sw_writereg(sc, REG_GLOBAL, VTU_OPERATION, VTU_FLUSH | VTU_BUSY); if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "Timeout while flushing VTU\n"); return (ETIMEDOUT); } return (0); } static int e6000sw_vtu_update(e6000sw_softc_t *sc, int purge, int vid, int fid, int members, int untagged) { int i, op; uint32_t data[2]; if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "VTU unit is busy, cannot access\n"); return (EBUSY); } *data = (vid & VTU_VID_MASK); if (purge == 0) *data |= VTU_VID_VALID; e6000sw_writereg(sc, REG_GLOBAL, VTU_VID, *data); if (purge == 0) { data[0] = 0; data[1] = 0; for (i = 0; i < sc->num_ports; i++) { if ((untagged & (1 << i)) != 0) data[i / VTU_PPREG(sc)] |= VTU_PORT_UNTAGGED << VTU_PORT(sc, i); else if ((members & (1 << i)) != 0) data[i / VTU_PPREG(sc)] |= VTU_PORT_TAGGED << VTU_PORT(sc, i); else data[i / VTU_PPREG(sc)] |= VTU_PORT_DISCARD << VTU_PORT(sc, i); } e6000sw_writereg(sc, REG_GLOBAL, VTU_DATA, data[0]); e6000sw_writereg(sc, REG_GLOBAL, VTU_DATA2, data[1]); e6000sw_writereg(sc, REG_GLOBAL, VTU_FID, fid & VTU_FID_MASK(sc)); op = VTU_LOAD; } else op = VTU_PURGE; e6000sw_writereg(sc, REG_GLOBAL, VTU_OPERATION, op | VTU_BUSY); if (E6000SW_WAITREADY(sc, VTU_OPERATION, VTU_BUSY)) { device_printf(sc->dev, "Timeout while flushing VTU\n"); return (ETIMEDOUT); } return (0); } diff --git a/sys/dev/hyperv/input/hv_hid.c b/sys/dev/hyperv/input/hv_hid.c index c54bc43c0f24..a26c46184442 100644 --- a/sys/dev/hyperv/input/hv_hid.c +++ b/sys/dev/hyperv/input/hv_hid.c @@ -1,563 +1,563 @@ /*- * Copyright (c) 2017 Microsoft Corp. * Copyright (c) 2023 Yuri * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hid_if.h" #include "vmbus_if.h" #define HV_HID_VER_MAJOR 2 #define HV_HID_VER_MINOR 0 #define HV_HID_VER (HV_HID_VER_MINOR | (HV_HID_VER_MAJOR) << 16) #define HV_BUFSIZ (4 * PAGE_SIZE) #define HV_HID_RINGBUFF_SEND_SZ (10 * PAGE_SIZE) #define HV_HID_RINGBUFF_RECV_SZ (10 * PAGE_SIZE) typedef struct { device_t dev; struct mtx mtx; /* vmbus */ struct vmbus_channel *hs_chan; struct vmbus_xact_ctx *hs_xact_ctx; uint8_t *buf; int buflen; /* hid */ struct hid_device_info hdi; hid_intr_t *intr; bool intr_on; void *intr_ctx; uint8_t *rdesc; } hv_hid_sc; typedef enum { SH_PROTO_REQ, SH_PROTO_RESP, SH_DEVINFO, SH_DEVINFO_ACK, SH_INPUT_REPORT, } sh_msg_type; typedef struct { sh_msg_type type; uint32_t size; } __packed sh_msg_hdr; typedef struct { sh_msg_hdr hdr; char data[]; } __packed sh_msg; typedef struct { sh_msg_hdr hdr; uint32_t ver; } __packed sh_proto_req; typedef struct { sh_msg_hdr hdr; uint32_t ver; uint32_t app; } __packed sh_proto_resp; typedef struct { u_int size; u_short vendor; u_short product; u_short version; u_short reserved[11]; } __packed sh_devinfo; /* Copied from linux/hid.h */ typedef struct { uint8_t bDescriptorType; uint16_t wDescriptorLength; } __packed sh_hcdesc; typedef struct { uint8_t bLength; uint8_t bDescriptorType; uint16_t bcdHID; uint8_t bCountryCode; uint8_t bNumDescriptors; sh_hcdesc hcdesc[1]; } __packed sh_hdesc; typedef struct { sh_msg_hdr hdr; sh_devinfo devinfo; sh_hdesc hdesc; } __packed sh_devinfo_resp; typedef struct { sh_msg_hdr hdr; uint8_t rsvd; } __packed sh_devinfo_ack; typedef struct { sh_msg_hdr hdr; char buffer[]; } __packed sh_input_report; typedef enum { HV_HID_MSG_INVALID, HV_HID_MSG_DATA, } hv_hid_msg_type; typedef struct { hv_hid_msg_type type; uint32_t size; char data[]; } hv_hid_pmsg; typedef struct { hv_hid_msg_type type; uint32_t size; union { sh_msg msg; sh_proto_req req; sh_proto_resp resp; sh_devinfo_resp dresp; sh_devinfo_ack ack; sh_input_report irep; }; } hv_hid_msg; #define HV_HID_REQ_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_proto_req)) #define HV_HID_RESP_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_proto_resp)) #define HV_HID_ACK_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_devinfo_ack)) /* Somewhat arbitrary, enough to get the devinfo response */ #define HV_HID_REQ_MAX 256 #define HV_HID_RESP_MAX 256 static const struct vmbus_ic_desc vmbus_hid_descs[] = { { .ic_guid = { .hv_guid = { 0x9e, 0xb6, 0xa8, 0xcf, 0x4a, 0x5b, 0xc0, 0x4c, 0xb9, 0x8b, 0x8b, 0xa1, 0xa1, 0xf3, 0xf9, 0x5a} }, .ic_desc = "Hyper-V HID device" }, VMBUS_IC_DESC_END }; /* TODO: add GUID support to devmatch(8) to export vmbus_hid_descs directly */ const struct { char *guid; } vmbus_hid_descs_pnp[] = {{ "cfa8b69e-5b4a-4cc0-b98b-8ba1a1f3f95a" }}; static int hv_hid_attach(device_t dev); static int hv_hid_detach(device_t dev); static int hv_hid_connect_vsp(hv_hid_sc *sc) { struct vmbus_xact *xact; hv_hid_msg *req; const hv_hid_msg *resp; size_t resplen; int ret; xact = vmbus_xact_get(sc->hs_xact_ctx, HV_HID_REQ_SZ); if (xact == NULL) { device_printf(sc->dev, "no xact for init"); return (ENODEV); } req = vmbus_xact_req_data(xact); req->type = HV_HID_MSG_DATA; req->size = sizeof(sh_proto_req); req->req.hdr.type = SH_PROTO_REQ; req->req.hdr.size = sizeof(u_int); req->req.ver = HV_HID_VER; vmbus_xact_activate(xact); ret = vmbus_chan_send(sc->hs_chan, VMBUS_CHANPKT_TYPE_INBAND, VMBUS_CHANPKT_FLAG_RC, req, HV_HID_REQ_SZ, (uint64_t)(uintptr_t)xact); if (ret != 0) { device_printf(sc->dev, "failed to send proto req\n"); vmbus_xact_deactivate(xact); return (ret); } resp = vmbus_chan_xact_wait(sc->hs_chan, xact, &resplen, true); if (resplen != HV_HID_RESP_SZ || !resp->resp.app) { device_printf(sc->dev, "proto req failed\n"); ret = ENODEV; } vmbus_xact_put(xact); return (ret); } static void hv_hid_receive(hv_hid_sc *sc, struct vmbus_chanpkt_hdr *pkt) { const hv_hid_msg *msg; sh_msg_type msg_type; uint32_t msg_len; void *rdesc; msg = VMBUS_CHANPKT_CONST_DATA(pkt); msg_len = VMBUS_CHANPKT_DATALEN(pkt); if (msg->type != HV_HID_MSG_DATA) return; if (msg_len <= sizeof(hv_hid_pmsg)) { device_printf(sc->dev, "invalid packet length\n"); return; } msg_type = msg->msg.hdr.type; switch (msg_type) { case SH_PROTO_RESP: { struct vmbus_xact_ctx *xact_ctx; xact_ctx = sc->hs_xact_ctx; if (xact_ctx != NULL) { vmbus_xact_ctx_wakeup(xact_ctx, VMBUS_CHANPKT_CONST_DATA(pkt), VMBUS_CHANPKT_DATALEN(pkt)); } break; } case SH_DEVINFO: { struct vmbus_xact *xact; struct hid_device_info *hdi; hv_hid_msg ack; const sh_devinfo *devinfo; const sh_hdesc *hdesc; /* Send ack */ ack.type = HV_HID_MSG_DATA; ack.size = sizeof(sh_devinfo_ack); ack.ack.hdr.type = SH_DEVINFO_ACK; ack.ack.hdr.size = 1; ack.ack.rsvd = 0; xact = vmbus_xact_get(sc->hs_xact_ctx, HV_HID_ACK_SZ); if (xact == NULL) break; vmbus_xact_activate(xact); (void) vmbus_chan_send(sc->hs_chan, VMBUS_CHANPKT_TYPE_INBAND, 0, &ack, HV_HID_ACK_SZ, (uint64_t)(uintptr_t)xact); vmbus_xact_deactivate(xact); vmbus_xact_put(xact); /* Check for resume from hibernation */ if (sc->rdesc != NULL) break; /* Parse devinfo response */ devinfo = &msg->dresp.devinfo; hdesc = &msg->dresp.hdesc; if (hdesc->bLength == 0) break; hdi = &sc->hdi; memset(hdi, 0, sizeof(*hdi)); hdi->rdescsize = le16toh(hdesc->hcdesc[0].wDescriptorLength); if (hdi->rdescsize == 0) break; strlcpy(hdi->name, "Hyper-V", sizeof(hdi->name)); hdi->idBus = BUS_VIRTUAL; hdi->idVendor = le16toh(devinfo->vendor); hdi->idProduct = le16toh(devinfo->product); hdi->idVersion = le16toh(devinfo->version); /* Save rdesc copy */ rdesc = malloc(hdi->rdescsize, M_DEVBUF, M_WAITOK | M_ZERO); memcpy(rdesc, (const uint8_t *)hdesc + hdesc->bLength, hdi->rdescsize); mtx_lock(&sc->mtx); sc->rdesc = rdesc; wakeup(sc); mtx_unlock(&sc->mtx); break; } case SH_INPUT_REPORT: { mtx_lock(&sc->mtx); if (sc->intr != NULL && sc->intr_on) sc->intr(sc->intr_ctx, __DECONST(void *, msg->irep.buffer), msg->irep.hdr.size); mtx_unlock(&sc->mtx); break; } default: break; } } static void hv_hid_read_channel(struct vmbus_channel *channel, void *ctx) { hv_hid_sc *sc; uint8_t *buf; int buflen; int ret; sc = ctx; buf = sc->buf; buflen = sc->buflen; for (;;) { struct vmbus_chanpkt_hdr *pkt; int rcvd; pkt = (struct vmbus_chanpkt_hdr *)buf; rcvd = buflen; ret = vmbus_chan_recv_pkt(channel, pkt, &rcvd); if (__predict_false(ret == ENOBUFS)) { buflen = sc->buflen * 2; while (buflen < rcvd) buflen *= 2; buf = malloc(buflen, M_DEVBUF, M_WAITOK | M_ZERO); device_printf(sc->dev, "expand recvbuf %d -> %d\n", sc->buflen, buflen); free(sc->buf, M_DEVBUF); sc->buf = buf; sc->buflen = buflen; continue; } else if (__predict_false(ret == EAGAIN)) { /* No more channel packets; done! */ break; } KASSERT(ret == 0, ("vmbus_chan_recv_pkt failed: %d", ret)); switch (pkt->cph_type) { case VMBUS_CHANPKT_TYPE_COMP: case VMBUS_CHANPKT_TYPE_RXBUF: device_printf(sc->dev, "unhandled event: %d\n", pkt->cph_type); break; case VMBUS_CHANPKT_TYPE_INBAND: hv_hid_receive(sc, pkt); break; default: device_printf(sc->dev, "unknown event: %d\n", pkt->cph_type); break; } } } static int hv_hid_probe(device_t dev) { device_t bus; const struct vmbus_ic_desc *d; if (resource_disabled(device_get_name(dev), 0)) return (ENXIO); bus = device_get_parent(dev); for (d = vmbus_hid_descs; d->ic_desc != NULL; ++d) { if (VMBUS_PROBE_GUID(bus, dev, &d->ic_guid) == 0) { device_set_desc(dev, d->ic_desc); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int hv_hid_attach(device_t dev) { device_t child; hv_hid_sc *sc; int ret; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->mtx, "hvhid lock", NULL, MTX_DEF); sc->hs_chan = vmbus_get_channel(dev); sc->hs_xact_ctx = vmbus_xact_ctx_create(bus_get_dma_tag(dev), HV_HID_REQ_MAX, HV_HID_RESP_MAX, 0); if (sc->hs_xact_ctx == NULL) { ret = ENOMEM; goto out; } sc->buflen = HV_BUFSIZ; sc->buf = malloc(sc->buflen, M_DEVBUF, M_WAITOK | M_ZERO); vmbus_chan_set_readbatch(sc->hs_chan, false); ret = vmbus_chan_open(sc->hs_chan, HV_HID_RINGBUFF_SEND_SZ, HV_HID_RINGBUFF_RECV_SZ, NULL, 0, hv_hid_read_channel, sc); if (ret != 0) goto out; ret = hv_hid_connect_vsp(sc); if (ret != 0) goto out; /* Wait until we have devinfo (or arbitrary timeout of 3s) */ mtx_lock(&sc->mtx); if (sc->rdesc == NULL) ret = mtx_sleep(sc, &sc->mtx, 0, "hvhid", hz * 3); mtx_unlock(&sc->mtx); if (ret != 0) { ret = ENODEV; goto out; } child = device_add_child(sc->dev, "hidbus", -1); if (child == NULL) { device_printf(sc->dev, "failed to add hidbus\n"); ret = ENOMEM; goto out; } device_set_ivars(child, &sc->hdi); bus_attach_children(dev); out: if (ret != 0) hv_hid_detach(dev); return (ret); } static int hv_hid_detach(device_t dev) { hv_hid_sc *sc; int ret; sc = device_get_softc(dev); - ret = device_delete_children(dev); + ret = bus_generic_detach(dev); if (ret != 0) return (ret); if (sc->hs_xact_ctx != NULL) vmbus_xact_ctx_destroy(sc->hs_xact_ctx); vmbus_chan_close(vmbus_get_channel(dev)); free(sc->buf, M_DEVBUF); free(sc->rdesc, M_DEVBUF); mtx_destroy(&sc->mtx); return (0); } static void hv_hid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr, void *ctx, struct hid_rdesc_info *rdesc) { hv_hid_sc *sc; if (intr == NULL) return; sc = device_get_softc(dev); sc->intr = intr; sc->intr_on = false; sc->intr_ctx = ctx; rdesc->rdsize = rdesc->isize; } static void hv_hid_intr_unsetup(device_t dev, device_t child __unused) { hv_hid_sc *sc; sc = device_get_softc(dev); sc->intr = NULL; sc->intr_on = false; sc->intr_ctx = NULL; } static int hv_hid_intr_start(device_t dev, device_t child __unused) { hv_hid_sc *sc; sc = device_get_softc(dev); mtx_lock(&sc->mtx); sc->intr_on = true; mtx_unlock(&sc->mtx); return (0); } static int hv_hid_intr_stop(device_t dev, device_t child __unused) { hv_hid_sc *sc; sc = device_get_softc(dev); mtx_lock(&sc->mtx); sc->intr_on = false; mtx_unlock(&sc->mtx); return (0); } static int hv_hid_get_rdesc(device_t dev, device_t child __unused, void *buf, hid_size_t len) { hv_hid_sc *sc; sc = device_get_softc(dev); if (len < sc->hdi.rdescsize) return (EMSGSIZE); memcpy(buf, sc->rdesc, len); return (0); } static device_method_t hv_hid_methods[] = { DEVMETHOD(device_probe, hv_hid_probe), DEVMETHOD(device_attach, hv_hid_attach), DEVMETHOD(device_detach, hv_hid_detach), DEVMETHOD(hid_intr_setup, hv_hid_intr_setup), DEVMETHOD(hid_intr_unsetup, hv_hid_intr_unsetup), DEVMETHOD(hid_intr_start, hv_hid_intr_start), DEVMETHOD(hid_intr_stop, hv_hid_intr_stop), DEVMETHOD(hid_get_rdesc, hv_hid_get_rdesc), DEVMETHOD_END, }; static driver_t hv_hid_driver = { .name = "hvhid", .methods = hv_hid_methods, .size = sizeof(hv_hid_sc), }; DRIVER_MODULE(hv_hid, vmbus, hv_hid_driver, NULL, NULL); MODULE_VERSION(hv_hid, 1); MODULE_DEPEND(hv_hid, hidbus, 1, 1, 1); MODULE_DEPEND(hv_hid, hms, 1, 1, 1); MODULE_DEPEND(hv_hid, vmbus, 1, 1, 1); MODULE_PNP_INFO("Z:classid", vmbus, hv_hid, vmbus_hid_descs_pnp, nitems(vmbus_hid_descs_pnp)); diff --git a/sys/dev/iicbus/iicbus.c b/sys/dev/iicbus/iicbus.c index 97dc9c437866..0894ddddb8e8 100644 --- a/sys/dev/iicbus/iicbus.c +++ b/sys/dev/iicbus/iicbus.c @@ -1,398 +1,398 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998, 2001 Nicolas Souchu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include /* * Autoconfiguration and support routines for the Philips serial I2C bus */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" /* See comments below for why auto-scanning is a bad idea. */ #define SCAN_IICBUS 0 SYSCTL_NODE(_hw, OID_AUTO, i2c, CTLFLAG_RW, 0, "i2c controls"); static int iicbus_probe(device_t dev) { device_set_desc(dev, "Philips I2C bus"); /* Allow other subclasses to override this driver. */ return (BUS_PROBE_GENERIC); } #if SCAN_IICBUS static int iic_probe_device(device_t dev, u_char addr) { int count; char byte; if ((addr & 1) == 0) { /* is device writable? */ if (!iicbus_start(dev, (u_char)addr, 0)) { iicbus_stop(dev); return (1); } } else { /* is device readable? */ if (!iicbus_block_read(dev, (u_char)addr, &byte, 1, &count)) return (1); } return (0); } #endif /* * We add all the devices which we know about. * The generic attach routine will attach them if they are alive. */ int iicbus_attach_common(device_t dev, u_int bus_freq) { #if SCAN_IICBUS unsigned char addr; #endif struct iicbus_softc *sc = IICBUS_SOFTC(dev); int strict; sc->dev = dev; mtx_init(&sc->lock, "iicbus", NULL, MTX_DEF); iicbus_init_frequency(dev, bus_freq); iicbus_reset(dev, IIC_FASTEST, 0, NULL); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "strict", &strict) == 0) sc->strict = strict; else sc->strict = 1; /* device probing is meaningless since the bus is supposed to be * hot-plug. Moreover, some I2C chips do not appreciate random * accesses like stop after start to fast, reads for less than * x bytes... */ #if SCAN_IICBUS printf("Probing for devices on iicbus%d:", device_get_unit(dev)); /* probe any devices */ for (addr = 16; addr < 240; addr++) { if (iic_probe_device(dev, (u_char)addr)) { printf(" <%x>", addr); } } printf("\n"); #endif bus_identify_children(dev); bus_enumerate_hinted_children(dev); bus_attach_children(dev); return (0); } static int iicbus_attach(device_t dev) { return (iicbus_attach_common(dev, 0)); } int iicbus_detach(device_t dev) { struct iicbus_softc *sc = IICBUS_SOFTC(dev); int err; - if ((err = device_delete_children(dev)) != 0) + if ((err = bus_generic_detach(dev)) != 0) return (err); iicbus_reset(dev, IIC_FASTEST, 0, NULL); mtx_destroy(&sc->lock); return (0); } static int iicbus_print_child(device_t dev, device_t child) { struct iicbus_ivar *devi = IICBUS_IVAR(child); int retval = 0; retval += bus_print_child_header(dev, child); if (devi->addr != 0) retval += printf(" at addr %#x", devi->addr); resource_list_print_type(&devi->rl, "irq", SYS_RES_IRQ, "%jd"); retval += bus_print_child_footer(dev, child); return (retval); } void iicbus_probe_nomatch(device_t bus, device_t child) { struct iicbus_ivar *devi = IICBUS_IVAR(child); device_printf(bus, " at addr %#x\n", devi->addr); } int iicbus_child_location(device_t bus, device_t child, struct sbuf *sb) { struct iicbus_ivar *devi = IICBUS_IVAR(child); sbuf_printf(sb, "addr=%#x", devi->addr); return (0); } int iicbus_child_pnpinfo(device_t bus, device_t child, struct sbuf *sb) { return (0); } int iicbus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct iicbus_ivar *devi = IICBUS_IVAR(child); switch (which) { default: return (EINVAL); case IICBUS_IVAR_ADDR: *result = devi->addr; break; } return (0); } int iicbus_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct iicbus_ivar *devi = IICBUS_IVAR(child); switch (which) { default: return (EINVAL); case IICBUS_IVAR_ADDR: if (devi->addr != 0) return (EINVAL); devi->addr = value; } return (0); } device_t iicbus_add_child_common(device_t dev, u_int order, const char *name, int unit, size_t ivars_size) { device_t child; struct iicbus_ivar *devi; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (child); devi = malloc(ivars_size, M_DEVBUF, M_NOWAIT | M_ZERO); if (devi == NULL) { device_delete_child(dev, child); return (0); } resource_list_init(&devi->rl); device_set_ivars(child, devi); return (child); } static device_t iicbus_add_child(device_t dev, u_int order, const char *name, int unit) { return (iicbus_add_child_common( dev, order, name, unit, sizeof(struct iicbus_ivar))); } static void iicbus_child_deleted(device_t dev, device_t child) { struct iicbus_ivar *devi; devi = device_get_ivars(child); if (devi == NULL) return; resource_list_free(&devi->rl); free(devi, M_DEVBUF); } static void iicbus_hinted_child(device_t bus, const char *dname, int dunit) { device_t child; int irq; struct iicbus_ivar *devi; child = BUS_ADD_CHILD(bus, 0, dname, dunit); devi = IICBUS_IVAR(child); resource_int_value(dname, dunit, "addr", &devi->addr); if (resource_int_value(dname, dunit, "irq", &irq) == 0) { if (bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1) != 0) device_printf(bus, "warning: bus_set_resource() failed\n"); } } static struct resource_list * iicbus_get_resource_list(device_t bus __unused, device_t child) { struct iicbus_ivar *devi; devi = IICBUS_IVAR(child); return (&devi->rl); } int iicbus_generic_intr(device_t dev, int event, char *buf) { return (0); } int iicbus_null_callback(device_t dev, int index, caddr_t data) { return (0); } int iicbus_null_repeated_start(device_t dev, u_char addr) { return (IIC_ENOTSUPP); } void iicbus_init_frequency(device_t dev, u_int bus_freq) { struct iicbus_softc *sc = IICBUS_SOFTC(dev); /* * If a bus frequency value was passed in, use it. Otherwise initialize * it first to the standard i2c 100KHz frequency, then override that * from a hint if one exists. */ if (bus_freq > 0) sc->bus_freq = bus_freq; else { sc->bus_freq = 100000; resource_int_value(device_get_name(dev), device_get_unit(dev), "frequency", (int *)&sc->bus_freq); } /* * Set up the sysctl that allows the bus frequency to be changed. * It is flagged as a tunable so that the user can set the value in * loader(8), and that will override any other setting from any source. * The sysctl tunable/value is the one most directly controlled by the * user and thus the one that always takes precedence. */ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "frequency", CTLFLAG_RWTUN, &sc->bus_freq, sc->bus_freq, "Bus frequency in Hz"); } static u_int iicbus_get_frequency(device_t dev, u_char speed) { struct iicbus_softc *sc = IICBUS_SOFTC(dev); /* * If the frequency has not been configured for the bus, or the request * is specifically for SLOW speed, use the standard 100KHz rate, else * use the configured bus speed. */ if (sc->bus_freq == 0 || speed == IIC_SLOW) return (100000); return (sc->bus_freq); } static device_method_t iicbus_methods[] = { /* device interface */ DEVMETHOD(device_probe, iicbus_probe), DEVMETHOD(device_attach, iicbus_attach), DEVMETHOD(device_detach, iicbus_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* bus interface */ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource_list, iicbus_get_resource_list), DEVMETHOD(bus_add_child, iicbus_add_child), DEVMETHOD(bus_child_deleted, iicbus_child_deleted), DEVMETHOD(bus_print_child, iicbus_print_child), DEVMETHOD(bus_probe_nomatch, iicbus_probe_nomatch), DEVMETHOD(bus_read_ivar, iicbus_read_ivar), DEVMETHOD(bus_write_ivar, iicbus_write_ivar), DEVMETHOD(bus_child_pnpinfo, iicbus_child_pnpinfo), DEVMETHOD(bus_child_location, iicbus_child_location), DEVMETHOD(bus_hinted_child, iicbus_hinted_child), /* iicbus interface */ DEVMETHOD(iicbus_transfer, iicbus_transfer), DEVMETHOD(iicbus_get_frequency, iicbus_get_frequency), DEVMETHOD_END }; driver_t iicbus_driver = { "iicbus", iicbus_methods, sizeof(struct iicbus_softc), }; MODULE_VERSION(iicbus, IICBUS_MODVER); DRIVER_MODULE(iicbus, iichb, iicbus_driver, 0, 0); diff --git a/sys/dev/iicbus/iichid.c b/sys/dev/iicbus/iichid.c index ed016fa0e0cc..346d432a74b5 100644 --- a/sys/dev/iicbus/iichid.c +++ b/sys/dev/iicbus/iichid.c @@ -1,1347 +1,1347 @@ /*- * Copyright (c) 2018-2019 Marc Priggemeyer * Copyright (c) 2019-2020 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * I2C HID transport backend. */ #include #include "opt_hid.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hid_if.h" #ifdef IICHID_DEBUG static int iichid_debug = 0; static SYSCTL_NODE(_hw, OID_AUTO, iichid, CTLFLAG_RW, 0, "I2C HID"); SYSCTL_INT(_hw_iichid, OID_AUTO, debug, CTLFLAG_RWTUN, &iichid_debug, 1, "Debug level"); #define DPRINTFN(sc, n, ...) do { \ if (iichid_debug >= (n)) \ device_printf((sc)->dev, __VA_ARGS__); \ } while (0) #define DPRINTF(sc, ...) DPRINTFN(sc, 1, __VA_ARGS__) #else #define DPRINTFN(...) do {} while (0) #define DPRINTF(...) do {} while (0) #endif typedef hid_size_t iichid_size_t; #define IICHID_SIZE_MAX (UINT16_MAX - 2) /* 7.2 */ enum { I2C_HID_CMD_DESCR = 0x0, I2C_HID_CMD_RESET = 0x1, I2C_HID_CMD_GET_REPORT = 0x2, I2C_HID_CMD_SET_REPORT = 0x3, I2C_HID_CMD_GET_IDLE = 0x4, I2C_HID_CMD_SET_IDLE = 0x5, I2C_HID_CMD_GET_PROTO = 0x6, I2C_HID_CMD_SET_PROTO = 0x7, I2C_HID_CMD_SET_POWER = 0x8, }; #define I2C_HID_POWER_ON 0x0 #define I2C_HID_POWER_OFF 0x1 /* * Since interrupt resource acquisition is not always possible (in case of GPIO * interrupts) iichid now supports a sampling_mode. * Set dev.iichid..sampling_rate_slow to a value greater then 0 * to activate sampling. A value of 0 is possible but will not reset the * callout and, thereby, disable further report requests. Do not set the * sampling_rate_fast value too high as it may result in periodical lags of * cursor motion. */ #define IICHID_SAMPLING_RATE_FAST 80 #define IICHID_SAMPLING_RATE_SLOW 10 #define IICHID_SAMPLING_HYSTERESIS 16 /* ~ 2x fast / slow */ /* 5.1.1 - HID Descriptor Format */ struct i2c_hid_desc { uint16_t wHIDDescLength; uint16_t bcdVersion; uint16_t wReportDescLength; uint16_t wReportDescRegister; uint16_t wInputRegister; uint16_t wMaxInputLength; uint16_t wOutputRegister; uint16_t wMaxOutputLength; uint16_t wCommandRegister; uint16_t wDataRegister; uint16_t wVendorID; uint16_t wProductID; uint16_t wVersionID; uint32_t reserved; } __packed; #define IICHID_REG_NONE -1 #define IICHID_REG_ACPI (UINT16_MAX + 1) #define IICHID_REG_ELAN 0x0001 static const struct iichid_id { char *id; int reg; } iichid_ids[] = { { "ELAN0000", IICHID_REG_ELAN }, { "PNP0C50", IICHID_REG_ACPI }, { "ACPI0C50", IICHID_REG_ACPI }, { NULL, 0 }, }; enum iichid_powerstate_how { IICHID_PS_NULL, IICHID_PS_ON, IICHID_PS_OFF, }; /* * Locking: no internal locks are used. To serialize access to shared members, * external iicbus lock should be taken. That allows to make locking greatly * simple at the cost of running front interrupt handlers with locked bus. */ struct iichid_softc { device_t dev; bool probe_done; int probe_result; struct hid_device_info hw; uint16_t addr; /* Shifted left by 1 */ struct i2c_hid_desc desc; hid_intr_t *intr_handler; void *intr_ctx; uint8_t *intr_buf; iichid_size_t intr_bufsize; int irq_rid; struct resource *irq_res; void *irq_cookie; #ifdef IICHID_SAMPLING int sampling_rate_slow; /* iicbus lock */ int sampling_rate_fast; int sampling_hysteresis; int missing_samples; /* iicbus lock */ int dup_samples; /* iicbus lock */ iichid_size_t dup_size; /* iicbus lock */ bool callout_setup; /* iicbus lock */ uint8_t *dup_buf; struct taskqueue *taskqueue; struct timeout_task sampling_task; /* iicbus lock */ #endif struct task suspend_task; bool open; /* iicbus lock */ bool suspend; /* iicbus lock */ bool power_on; /* iicbus lock */ }; static device_probe_t iichid_probe; static device_attach_t iichid_attach; static device_detach_t iichid_detach; static device_resume_t iichid_resume; static device_suspend_t iichid_suspend; static void iichid_suspend_task(void *, int); #ifdef IICHID_SAMPLING static int iichid_setup_callout(struct iichid_softc *); static int iichid_reset_callout(struct iichid_softc *); static void iichid_teardown_callout(struct iichid_softc *); #endif static inline int acpi_is_iichid(ACPI_HANDLE handle) { const struct iichid_id *ids; UINT32 sta; int reg; for (ids = iichid_ids; ids->id != NULL; ids++) { if (acpi_MatchHid(handle, ids->id)) { reg = ids->reg; break; } } if (ids->id == NULL) return (IICHID_REG_NONE); /* * If no _STA method or if it failed, then assume that * the device is present. */ if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) || ACPI_DEVICE_PRESENT(sta)) return (reg); return (IICHID_REG_NONE); } static ACPI_STATUS iichid_get_config_reg(ACPI_HANDLE handle, uint16_t *config_reg) { ACPI_OBJECT *result; ACPI_BUFFER acpi_buf; ACPI_STATUS status; /* * function (_DSM) to be evaluated to retrieve the address of * the configuration register of the HID device. */ /* 3cdff6f7-4267-4555-ad05-b30a3d8938de */ static uint8_t dsm_guid[ACPI_UUID_LENGTH] = { 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE, }; status = acpi_EvaluateDSMTyped(handle, dsm_guid, 1, 1, NULL, &acpi_buf, ACPI_TYPE_INTEGER); if (ACPI_FAILURE(status)) { printf("%s: error evaluating _DSM\n", __func__); return (status); } result = (ACPI_OBJECT *) acpi_buf.Pointer; *config_reg = result->Integer.Value & 0xFFFF; AcpiOsFree(result); return (status); } static int iichid_cmd_read(struct iichid_softc* sc, void *buf, iichid_size_t maxlen, iichid_size_t *actual_len) { /* * 6.1.3 - Retrieval of Input Reports * DEVICE returns the length (2 Bytes) and the entire Input Report. */ uint8_t actbuf[2] = { 0, 0 }; /* Read actual input report length. */ struct iic_msg msgs[] = { { sc->addr, IIC_M_RD | IIC_M_NOSTOP, sizeof(actbuf), actbuf }, }; uint16_t actlen; int error; error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); if (error != 0) return (error); actlen = actbuf[0] | actbuf[1] << 8; if (actlen <= 2 || actlen == 0xFFFF || maxlen == 0) { /* Read and discard 1 byte to send I2C STOP condition. */ msgs[0] = (struct iic_msg) { sc->addr, IIC_M_RD | IIC_M_NOSTART, 1, actbuf }; actlen = 0; } else { actlen -= 2; if (actlen > maxlen) { DPRINTF(sc, "input report too big. requested=%d " "received=%d\n", maxlen, actlen); actlen = maxlen; } /* Read input report itself. */ msgs[0] = (struct iic_msg) { sc->addr, IIC_M_RD | IIC_M_NOSTART, actlen, buf }; } error = iicbus_transfer(sc->dev, msgs, 1); if (error == 0 && actual_len != NULL) *actual_len = actlen; DPRINTFN(sc, 5, "%*D - %*D\n", 2, actbuf, " ", msgs[0].len, msgs[0].buf, " "); return (error); } static int iichid_cmd_write(struct iichid_softc *sc, const void *buf, iichid_size_t len) { /* 6.2.3 - Sending Output Reports. */ uint8_t *cmdreg = (uint8_t *)&sc->desc.wOutputRegister; uint16_t replen = 2 + len; uint8_t cmd[4] = { cmdreg[0], cmdreg[1], replen & 0xFF, replen >> 8 }; struct iic_msg msgs[] = { {sc->addr, IIC_M_WR | IIC_M_NOSTOP, sizeof(cmd), cmd}, {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)}, }; if (le16toh(sc->desc.wMaxOutputLength) == 0) return (IIC_ENOTSUPP); if (len < 2) return (IIC_ENOTSUPP); DPRINTF(sc, "HID command I2C_HID_CMD_WRITE (len %d): " "%*D\n", len, len, buf, " "); return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); } static int iichid_cmd_get_hid_desc(struct iichid_softc *sc, uint16_t config_reg, struct i2c_hid_desc *hid_desc) { /* * 5.2.2 - HID Descriptor Retrieval * register is passed from the controller. */ uint16_t cmd = htole16(config_reg); struct iic_msg msgs[] = { { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd }, { sc->addr, IIC_M_RD, sizeof(*hid_desc), (uint8_t *)hid_desc }, }; int error; DPRINTF(sc, "HID command I2C_HID_CMD_DESCR at 0x%x\n", config_reg); error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); if (error != 0) return (error); DPRINTF(sc, "HID descriptor: %*D\n", (int)sizeof(struct i2c_hid_desc), hid_desc, " "); return (0); } static int iichid_set_power(struct iichid_softc *sc, uint8_t param) { uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; uint8_t cmd[] = { cmdreg[0], cmdreg[1], param, I2C_HID_CMD_SET_POWER }; struct iic_msg msgs[] = { { sc->addr, IIC_M_WR, sizeof(cmd), cmd }, }; DPRINTF(sc, "HID command I2C_HID_CMD_SET_POWER(%d)\n", param); return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); } static int iichid_reset(struct iichid_softc *sc) { uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; uint8_t cmd[] = { cmdreg[0], cmdreg[1], 0, I2C_HID_CMD_RESET }; struct iic_msg msgs[] = { { sc->addr, IIC_M_WR, sizeof(cmd), cmd }, }; DPRINTF(sc, "HID command I2C_HID_CMD_RESET\n"); return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); } static int iichid_cmd_get_report_desc(struct iichid_softc* sc, void *buf, iichid_size_t len) { uint16_t cmd = sc->desc.wReportDescRegister; struct iic_msg msgs[] = { { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd }, { sc->addr, IIC_M_RD, len, buf }, }; int error; DPRINTF(sc, "HID command I2C_HID_REPORT_DESCR at 0x%x with size %d\n", le16toh(cmd), len); error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); if (error != 0) return (error); DPRINTF(sc, "HID report descriptor: %*D\n", len, buf, " "); return (0); } static int iichid_cmd_get_report(struct iichid_softc* sc, void *buf, iichid_size_t maxlen, iichid_size_t *actual_len, uint8_t type, uint8_t id) { /* * 7.2.2.4 - "The protocol is optimized for Report < 15. If a * report ID >= 15 is necessary, then the Report ID in the Low Byte * must be set to 1111 and a Third Byte is appended to the protocol. * This Third Byte contains the entire/actual report ID." */ uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister; uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; uint8_t cmd[] = { /*________|______id>=15_____|______id<15______*/ cmdreg[0] , cmdreg[1] , (id >= 15 ? 15 | (type << 4): id | (type << 4)), I2C_HID_CMD_GET_REPORT , (id >= 15 ? id : dtareg[0] ), (id >= 15 ? dtareg[0] : dtareg[1] ), (id >= 15 ? dtareg[1] : 0 ), }; int cmdlen = (id >= 15 ? 7 : 6 ); uint8_t actbuf[2] = { 0, 0 }; uint16_t actlen; int d, error; struct iic_msg msgs[] = { { sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd }, { sc->addr, IIC_M_RD | IIC_M_NOSTOP, 2, actbuf }, { sc->addr, IIC_M_RD | IIC_M_NOSTART, maxlen, buf }, }; if (maxlen == 0) return (EINVAL); DPRINTF(sc, "HID command I2C_HID_CMD_GET_REPORT %d " "(type %d, len %d)\n", id, type, maxlen); /* * 7.2.2.2 - Response will be a 2-byte length value, the report * id (1 byte, if defined in Report Descriptor), and then the report. */ error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); if (error != 0) return (error); actlen = actbuf[0] | actbuf[1] << 8; if (actlen != maxlen + 2) DPRINTF(sc, "response size %d != expected length %d\n", actlen, maxlen + 2); if (actlen <= 2 || actlen == 0xFFFF) return (ENOMSG); d = id != 0 ? *(uint8_t *)buf : 0; if (d != id) { DPRINTF(sc, "response report id %d != %d\n", d, id); return (EBADMSG); } actlen -= 2; if (actlen > maxlen) actlen = maxlen; if (actual_len != NULL) *actual_len = actlen; DPRINTF(sc, "response: %*D %*D\n", 2, actbuf, " ", actlen, buf, " "); return (0); } static int iichid_cmd_set_report(struct iichid_softc* sc, const void *buf, iichid_size_t len, uint8_t type, uint8_t id) { /* * 7.2.2.4 - "The protocol is optimized for Report < 15. If a * report ID >= 15 is necessary, then the Report ID in the Low Byte * must be set to 1111 and a Third Byte is appended to the protocol. * This Third Byte contains the entire/actual report ID." */ uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister; uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; uint16_t replen = 2 + len; uint8_t cmd[] = { /*________|______id>=15_____|______id<15______*/ cmdreg[0] , cmdreg[1] , (id >= 15 ? 15 | (type << 4): id | (type << 4)), I2C_HID_CMD_SET_REPORT , (id >= 15 ? id : dtareg[0] ), (id >= 15 ? dtareg[0] : dtareg[1] ), (id >= 15 ? dtareg[1] : replen & 0xff ), (id >= 15 ? replen & 0xff : replen >> 8 ), (id >= 15 ? replen >> 8 : 0 ), }; int cmdlen = (id >= 15 ? 9 : 8 ); struct iic_msg msgs[] = { {sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd}, {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)}, }; DPRINTF(sc, "HID command I2C_HID_CMD_SET_REPORT %d (type %d, len %d): " "%*D\n", id, type, len, len, buf, " "); return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); } #ifdef IICHID_SAMPLING static void iichid_sampling_task(void *context, int pending) { struct iichid_softc *sc; device_t parent; iichid_size_t actual; bool bus_requested; int error, rate; sc = context; parent = device_get_parent(sc->dev); bus_requested = false; if (iicbus_request_bus(parent, sc->dev, IIC_WAIT) != 0) goto rearm; bus_requested = true; if (!sc->power_on) goto out; error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual); if (error == 0) { if (actual > 0) { sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual); sc->missing_samples = 0; if (sc->dup_size != actual || memcmp(sc->dup_buf, sc->intr_buf, actual) != 0) { sc->dup_size = actual; memcpy(sc->dup_buf, sc->intr_buf, actual); sc->dup_samples = 0; } else ++sc->dup_samples; } else { if (++sc->missing_samples == 1) sc->intr_handler(sc->intr_ctx, sc->intr_buf, 0); sc->dup_samples = 0; } } else DPRINTF(sc, "read error occurred: %d\n", error); rearm: if (sc->callout_setup && sc->sampling_rate_slow > 0) { if (sc->missing_samples >= sc->sampling_hysteresis || sc->dup_samples >= sc->sampling_hysteresis) rate = sc->sampling_rate_slow; else rate = sc->sampling_rate_fast; taskqueue_enqueue_timeout_sbt(sc->taskqueue, &sc->sampling_task, SBT_1S / MAX(rate, 1), 0, C_PREL(2)); } out: if (bus_requested) iicbus_release_bus(parent, sc->dev); } #endif /* IICHID_SAMPLING */ static void iichid_intr(void *context) { struct iichid_softc *sc; device_t parent; iichid_size_t maxlen, actual; int error; sc = context; parent = device_get_parent(sc->dev); /* * Designware(IG4) driver-specific hack. * Requesting of an I2C bus with IIC_DONTWAIT parameter enables polled * mode in the driver, making possible iicbus_transfer execution from * interrupt handlers and callouts. */ if (iicbus_request_bus(parent, sc->dev, IIC_DONTWAIT) != 0) return; /* * Reading of input reports of I2C devices residing in SLEEP state is * not allowed and often returns a garbage. If a HOST needs to * communicate with the DEVICE it MUST issue a SET POWER command * (to ON) before any other command. As some hardware requires reads to * acknowledge interrupts we fetch only length header and discard it. */ maxlen = sc->power_on ? sc->intr_bufsize : 0; error = iichid_cmd_read(sc, sc->intr_buf, maxlen, &actual); if (error == 0) { if (sc->power_on) { if (actual != 0) sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual); else DPRINTF(sc, "no data received\n"); } } else DPRINTF(sc, "read error occurred: %d\n", error); iicbus_release_bus(parent, sc->dev); } static int iichid_set_power_state(struct iichid_softc *sc, enum iichid_powerstate_how how_open, enum iichid_powerstate_how how_suspend) { device_t parent; int error; int how_request; bool power_on; /* * Request iicbus early as sc->suspend and sc->power_on * are protected by iicbus internal lock. */ parent = device_get_parent(sc->dev); /* Allow to interrupt open()/close() handlers by SIGINT */ how_request = how_open == IICHID_PS_NULL ? IIC_WAIT : IIC_INTRWAIT; error = iicbus_request_bus(parent, sc->dev, how_request); if (error != 0) return (error); switch (how_open) { case IICHID_PS_ON: sc->open = true; break; case IICHID_PS_OFF: sc->open = false; break; case IICHID_PS_NULL: default: break; } switch (how_suspend) { case IICHID_PS_ON: sc->suspend = false; break; case IICHID_PS_OFF: sc->suspend = true; break; case IICHID_PS_NULL: default: break; } power_on = sc->open & !sc->suspend; if (power_on != sc->power_on) { error = iichid_set_power(sc, power_on ? I2C_HID_POWER_ON : I2C_HID_POWER_OFF); sc->power_on = power_on; #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow >= 0 && sc->intr_handler != NULL) { if (power_on) { iichid_setup_callout(sc); iichid_reset_callout(sc); } else iichid_teardown_callout(sc); } #endif } iicbus_release_bus(parent, sc->dev); return (error); } static int iichid_setup_interrupt(struct iichid_softc *sc) { sc->irq_cookie = 0; int error = bus_setup_intr(sc->dev, sc->irq_res, INTR_TYPE_TTY|INTR_MPSAFE, NULL, iichid_intr, sc, &sc->irq_cookie); if (error != 0) DPRINTF(sc, "Could not setup interrupt handler\n"); else DPRINTF(sc, "successfully setup interrupt\n"); return (error); } static void iichid_teardown_interrupt(struct iichid_softc *sc) { if (sc->irq_cookie) bus_teardown_intr(sc->dev, sc->irq_res, sc->irq_cookie); sc->irq_cookie = 0; } #ifdef IICHID_SAMPLING static int iichid_setup_callout(struct iichid_softc *sc) { if (sc->sampling_rate_slow < 0) { DPRINTF(sc, "sampling_rate is below 0, can't setup callout\n"); return (EINVAL); } sc->callout_setup = true; DPRINTF(sc, "successfully setup callout\n"); return (0); } static int iichid_reset_callout(struct iichid_softc *sc) { if (sc->sampling_rate_slow <= 0) { DPRINTF(sc, "sampling_rate is below or equal to 0, " "can't reset callout\n"); return (EINVAL); } if (!sc->callout_setup) return (EINVAL); /* Start with slow sampling. */ sc->missing_samples = sc->sampling_hysteresis; sc->dup_samples = 0; sc->dup_size = 0; taskqueue_enqueue_timeout(sc->taskqueue, &sc->sampling_task, 0); return (0); } static void iichid_teardown_callout(struct iichid_softc *sc) { sc->callout_setup = false; taskqueue_cancel_timeout(sc->taskqueue, &sc->sampling_task, NULL); DPRINTF(sc, "tore callout down\n"); } static int iichid_sysctl_sampling_rate_handler(SYSCTL_HANDLER_ARGS) { struct iichid_softc *sc; device_t parent; int error, oldval, value; sc = arg1; value = sc->sampling_rate_slow; error = sysctl_handle_int(oidp, &value, 0, req); if (error != 0 || req->newptr == NULL || value == sc->sampling_rate_slow) return (error); /* Can't switch to interrupt mode if it is not supported. */ if (sc->irq_res == NULL && value < 0) return (EINVAL); parent = device_get_parent(sc->dev); error = iicbus_request_bus(parent, sc->dev, IIC_WAIT); if (error != 0) return (iic2errno(error)); oldval = sc->sampling_rate_slow; sc->sampling_rate_slow = value; if (oldval < 0 && value >= 0) { iichid_teardown_interrupt(sc); if (sc->power_on) iichid_setup_callout(sc); } else if (oldval >= 0 && value < 0) { if (sc->power_on) iichid_teardown_callout(sc); iichid_setup_interrupt(sc); } if (sc->power_on && value > 0) iichid_reset_callout(sc); iicbus_release_bus(parent, sc->dev); DPRINTF(sc, "new sampling_rate value: %d\n", value); return (0); } #endif /* IICHID_SAMPLING */ static void iichid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr, void *context, struct hid_rdesc_info *rdesc) { struct iichid_softc *sc; if (intr == NULL) return; sc = device_get_softc(dev); /* * Do not rely on wMaxInputLength, as some devices may set it to * a wrong length. Find the longest input report in report descriptor. */ rdesc->rdsize = rdesc->isize; /* Write and get/set_report sizes are limited by I2C-HID protocol. */ rdesc->grsize = rdesc->srsize = IICHID_SIZE_MAX; rdesc->wrsize = IICHID_SIZE_MAX; sc->intr_handler = intr; sc->intr_ctx = context; sc->intr_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO); sc->intr_bufsize = rdesc->rdsize; #ifdef IICHID_SAMPLING sc->dup_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO); taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY, "%s taskq", device_get_nameunit(sc->dev)); #endif } static void iichid_intr_unsetup(device_t dev, device_t child __unused) { struct iichid_softc *sc; sc = device_get_softc(dev); #ifdef IICHID_SAMPLING taskqueue_drain_all(sc->taskqueue); free(sc->dup_buf, M_DEVBUF); #endif free(sc->intr_buf, M_DEVBUF); } static int iichid_intr_start(device_t dev, device_t child __unused) { struct iichid_softc *sc; sc = device_get_softc(dev); DPRINTF(sc, "iichid device open\n"); iichid_set_power_state(sc, IICHID_PS_ON, IICHID_PS_NULL); return (0); } static int iichid_intr_stop(device_t dev, device_t child __unused) { struct iichid_softc *sc; sc = device_get_softc(dev); DPRINTF(sc, "iichid device close\n"); /* * 8.2 - The HOST determines that there are no active applications * that are currently using the specific HID DEVICE. The HOST * is recommended to issue a HIPO command to the DEVICE to force * the DEVICE in to a lower power state. */ iichid_set_power_state(sc, IICHID_PS_OFF, IICHID_PS_NULL); return (0); } static void iichid_intr_poll(device_t dev, device_t child __unused) { struct iichid_softc *sc; iichid_size_t actual; int error; sc = device_get_softc(dev); error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual); if (error == 0 && actual != 0) sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual); } /* * HID interface */ static int iichid_get_rdesc(device_t dev, device_t child __unused, void *buf, hid_size_t len) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); error = iichid_cmd_get_report_desc(sc, buf, len); if (error) DPRINTF(sc, "failed to fetch report descriptor: %d\n", error); return (iic2errno(error)); } static int iichid_read(device_t dev, device_t child __unused, void *buf, hid_size_t maxlen, hid_size_t *actlen) { struct iichid_softc *sc; device_t parent; int error; if (maxlen > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); parent = device_get_parent(sc->dev); error = iicbus_request_bus(parent, sc->dev, IIC_WAIT); if (error == 0) { error = iichid_cmd_read(sc, buf, maxlen, actlen); iicbus_release_bus(parent, sc->dev); } return (iic2errno(error)); } static int iichid_write(device_t dev, device_t child __unused, const void *buf, hid_size_t len) { struct iichid_softc *sc; if (len > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); return (iic2errno(iichid_cmd_write(sc, buf, len))); } static int iichid_get_report(device_t dev, device_t child __unused, void *buf, hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id) { struct iichid_softc *sc; if (maxlen > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); return (iic2errno( iichid_cmd_get_report(sc, buf, maxlen, actlen, type, id))); } static int iichid_set_report(device_t dev, device_t child __unused, const void *buf, hid_size_t len, uint8_t type, uint8_t id) { struct iichid_softc *sc; if (len > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); return (iic2errno(iichid_cmd_set_report(sc, buf, len, type, id))); } static int iichid_set_idle(device_t dev, device_t child __unused, uint16_t duration, uint8_t id) { return (ENOTSUP); } static int iichid_set_protocol(device_t dev, device_t child __unused, uint16_t protocol) { return (ENOTSUP); } static int iichid_ioctl(device_t dev, device_t child __unused, unsigned long cmd, uintptr_t data) { int error; switch (cmd) { case I2CRDWR: error = iic2errno(iicbus_transfer(dev, ((struct iic_rdwr_data *)data)->msgs, ((struct iic_rdwr_data *)data)->nmsgs)); break; default: error = EINVAL; } return (error); } static int iichid_fill_device_info(struct i2c_hid_desc *desc, ACPI_HANDLE handle, struct hid_device_info *hw) { ACPI_DEVICE_INFO *device_info; hw->idBus = BUS_I2C; hw->idVendor = le16toh(desc->wVendorID); hw->idProduct = le16toh(desc->wProductID); hw->idVersion = le16toh(desc->wVersionID); /* get ACPI HID. It is a base part of the device name. */ if (ACPI_FAILURE(AcpiGetObjectInfo(handle, &device_info))) return (ENXIO); if (device_info->Valid & ACPI_VALID_HID) strlcpy(hw->idPnP, device_info->HardwareId.String, HID_PNP_ID_SIZE); snprintf(hw->name, sizeof(hw->name), "%s:%02lX %04X:%04X", (device_info->Valid & ACPI_VALID_HID) ? device_info->HardwareId.String : "Unknown", (device_info->Valid & ACPI_VALID_UID) ? strtoul(device_info->UniqueId.String, NULL, 10) : 0UL, le16toh(desc->wVendorID), le16toh(desc->wProductID)); AcpiOsFree(device_info); strlcpy(hw->serial, "", sizeof(hw->serial)); hw->rdescsize = le16toh(desc->wReportDescLength); if (desc->wOutputRegister == 0 || desc->wMaxOutputLength == 0) hid_add_dynamic_quirk(hw, HQ_NOWRITE); return (0); } static int iichid_probe(device_t dev) { struct iichid_softc *sc; ACPI_HANDLE handle; uint16_t config_reg; int error, reg; sc = device_get_softc(dev); sc->dev = dev; if (sc->probe_done) goto done; sc->probe_done = true; sc->probe_result = ENXIO; if (acpi_disabled("iichid")) return (ENXIO); sc->addr = iicbus_get_addr(dev) << 1; if (sc->addr == 0) return (ENXIO); handle = acpi_get_handle(dev); if (handle == NULL) return (ENXIO); reg = acpi_is_iichid(handle); if (reg == IICHID_REG_NONE) return (ENXIO); if (reg == IICHID_REG_ACPI) { if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg))) return (ENXIO); } else config_reg = (uint16_t)reg; DPRINTF(sc, " IICbus addr : 0x%02X\n", sc->addr >> 1); DPRINTF(sc, " HID descriptor reg: 0x%02X\n", config_reg); error = iichid_cmd_get_hid_desc(sc, config_reg, &sc->desc); if (error) { DPRINTF(sc, "could not retrieve HID descriptor from the " "device: %d\n", error); return (ENXIO); } if (le16toh(sc->desc.wHIDDescLength) != 30 || le16toh(sc->desc.bcdVersion) != 0x100) { DPRINTF(sc, "HID descriptor is broken\n"); return (ENXIO); } /* Setup hid_device_info so we can figure out quirks for the device. */ if (iichid_fill_device_info(&sc->desc, handle, &sc->hw) != 0) { DPRINTF(sc, "error evaluating AcpiGetObjectInfo\n"); return (ENXIO); } if (hid_test_quirk(&sc->hw, HQ_HID_IGNORE)) return (ENXIO); sc->probe_result = BUS_PROBE_DEFAULT; done: if (sc->probe_result <= BUS_PROBE_SPECIFIC) device_set_descf(dev, "%s I2C HID device", sc->hw.name); return (sc->probe_result); } static int iichid_attach(device_t dev) { struct iichid_softc *sc; device_t child; int error; sc = device_get_softc(dev); error = iichid_set_power(sc, I2C_HID_POWER_ON); if (error) { device_printf(dev, "failed to power on: %d\n", error); return (ENXIO); } /* * Windows driver sleeps for 1ms between the SET_POWER and RESET * commands. So we too as some devices may depend on this. */ pause("iichid", (hz + 999) / 1000); error = iichid_reset(sc); if (error) { device_printf(dev, "failed to reset hardware: %d\n", error); error = ENXIO; goto done; } sc->power_on = true; TASK_INIT(&sc->suspend_task, 0, iichid_suspend_task, sc); #ifdef IICHID_SAMPLING sc->taskqueue = taskqueue_create_fast("iichid_tq", M_WAITOK | M_ZERO, taskqueue_thread_enqueue, &sc->taskqueue); TIMEOUT_TASK_INIT(sc->taskqueue, &sc->sampling_task, 0, iichid_sampling_task, sc); sc->sampling_rate_slow = -1; sc->sampling_rate_fast = IICHID_SAMPLING_RATE_FAST; sc->sampling_hysteresis = IICHID_SAMPLING_HYSTERESIS; #endif sc->irq_rid = 0; sc->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irq_rid, RF_ACTIVE); if (sc->irq_res != NULL) { DPRINTF(sc, "allocated irq at %p and rid %d\n", sc->irq_res, sc->irq_rid); error = iichid_setup_interrupt(sc); } if (sc->irq_res == NULL || error != 0) { #ifdef IICHID_SAMPLING device_printf(sc->dev, "Using sampling mode\n"); sc->sampling_rate_slow = IICHID_SAMPLING_RATE_SLOW; #else device_printf(sc->dev, "Interrupt setup failed\n"); if (sc->irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); error = ENXIO; goto done; #endif } #ifdef IICHID_SAMPLING SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "sampling_rate_slow", CTLTYPE_INT | CTLFLAG_RWTUN, sc, 0, iichid_sysctl_sampling_rate_handler, "I", "idle sampling rate in num/second"); SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "sampling_rate_fast", CTLFLAG_RWTUN, &sc->sampling_rate_fast, 0, "active sampling rate in num/second"); SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "sampling_hysteresis", CTLFLAG_RWTUN, &sc->sampling_hysteresis, 0, "number of missing samples before enabling of slow mode"); hid_add_dynamic_quirk(&sc->hw, HQ_IICHID_SAMPLING); if (sc->sampling_rate_slow >= 0) { pause("iichid", (hz + 999) / 1000); (void)iichid_cmd_read(sc, NULL, 0, NULL); } #endif /* IICHID_SAMPLING */ child = device_add_child(dev, "hidbus", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(sc->dev, "Could not add I2C device\n"); iichid_detach(dev); error = ENOMEM; goto done; } device_set_ivars(child, &sc->hw); bus_attach_children(dev); error = 0; done: iicbus_request_bus(device_get_parent(dev), dev, IIC_WAIT); if (!sc->open) { (void)iichid_set_power(sc, I2C_HID_POWER_OFF); sc->power_on = false; } iicbus_release_bus(device_get_parent(dev), dev); return (error); } static int iichid_detach(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); - error = device_delete_children(dev); + error = bus_generic_detach(dev); if (error) return (error); iichid_teardown_interrupt(sc); if (sc->irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); #ifdef IICHID_SAMPLING if (sc->taskqueue != NULL) taskqueue_free(sc->taskqueue); sc->taskqueue = NULL; #endif return (0); } static void iichid_suspend_task(void *context, int pending) { struct iichid_softc *sc = context; iichid_teardown_interrupt(sc); } static int iichid_suspend(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); (void)bus_generic_suspend(dev); /* * 8.2 - The HOST is going into a deep power optimized state and wishes * to put all the devices into a low power state also. The HOST * is recommended to issue a HIPO command to the DEVICE to force * the DEVICE in to a lower power state. */ DPRINTF(sc, "Suspend called, setting device to power_state 1\n"); error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_OFF); if (error != 0) DPRINTF(sc, "Could not set power_state, error: %d\n", error); else DPRINTF(sc, "Successfully set power_state\n"); #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow < 0) #endif { /* * bus_teardown_intr can not be executed right here as it wants * to run on certain CPU to interacts with LAPIC while suspend * thread is bound to CPU0. So run it from taskqueue context. */ #ifdef IICHID_SAMPLING #define suspend_thread sc->taskqueue #else #define suspend_thread taskqueue_thread #endif taskqueue_enqueue(suspend_thread, &sc->suspend_task); taskqueue_drain(suspend_thread, &sc->suspend_task); } return (0); } static int iichid_resume(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow < 0) #endif iichid_setup_interrupt(sc); DPRINTF(sc, "Resume called, setting device to power_state 0\n"); error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_ON); if (error != 0) DPRINTF(sc, "Could not set power_state, error: %d\n", error); else DPRINTF(sc, "Successfully set power_state\n"); (void)bus_generic_resume(dev); return (0); } static device_method_t iichid_methods[] = { DEVMETHOD(device_probe, iichid_probe), DEVMETHOD(device_attach, iichid_attach), DEVMETHOD(device_detach, iichid_detach), DEVMETHOD(device_suspend, iichid_suspend), DEVMETHOD(device_resume, iichid_resume), DEVMETHOD(hid_intr_setup, iichid_intr_setup), DEVMETHOD(hid_intr_unsetup, iichid_intr_unsetup), DEVMETHOD(hid_intr_start, iichid_intr_start), DEVMETHOD(hid_intr_stop, iichid_intr_stop), DEVMETHOD(hid_intr_poll, iichid_intr_poll), /* HID interface */ DEVMETHOD(hid_get_rdesc, iichid_get_rdesc), DEVMETHOD(hid_read, iichid_read), DEVMETHOD(hid_write, iichid_write), DEVMETHOD(hid_get_report, iichid_get_report), DEVMETHOD(hid_set_report, iichid_set_report), DEVMETHOD(hid_set_idle, iichid_set_idle), DEVMETHOD(hid_set_protocol, iichid_set_protocol), DEVMETHOD(hid_ioctl, iichid_ioctl), DEVMETHOD_END }; static driver_t iichid_driver = { .name = "iichid", .methods = iichid_methods, .size = sizeof(struct iichid_softc), }; DRIVER_MODULE(iichid, iicbus, iichid_driver, NULL, NULL); MODULE_DEPEND(iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_DEPEND(iichid, acpi, 1, 1, 1); MODULE_DEPEND(iichid, hid, 1, 1, 1); MODULE_DEPEND(iichid, hidbus, 1, 1, 1); MODULE_VERSION(iichid, 1); IICBUS_ACPI_PNP_INFO(iichid_ids); diff --git a/sys/dev/intel/spi.c b/sys/dev/intel/spi.c index 4c45ef7863a7..9a0d4305227d 100644 --- a/sys/dev/intel/spi.c +++ b/sys/dev/intel/spi.c @@ -1,618 +1,623 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2016 Oleksandr Tymoshenko * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include /** * Macros for driver mutex locking */ #define INTELSPI_IN_POLLING_MODE() (SCHEDULER_STOPPED() || kdb_active) #define INTELSPI_LOCK(_sc) do { \ if(!INTELSPI_IN_POLLING_MODE()) \ mtx_lock(&(_sc)->sc_mtx); \ } while (0) #define INTELSPI_UNLOCK(_sc) do { \ if(!INTELSPI_IN_POLLING_MODE()) \ mtx_unlock(&(_sc)->sc_mtx); \ } while (0) #define INTELSPI_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ "intelspi", MTX_DEF) #define INTELSPI_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) #define INTELSPI_ASSERT_LOCKED(_sc) do { \ if(!INTELSPI_IN_POLLING_MODE()) \ mtx_assert(&(_sc)->sc_mtx, MA_OWNED); \ } while (0) #define INTELSPI_ASSERT_UNLOCKED(_sc) do { \ if(!INTELSPI_IN_POLLING_MODE()) \ mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED);\ } while (0) #define INTELSPI_WRITE(_sc, _off, _val) \ bus_write_4((_sc)->sc_mem_res, (_off), (_val)) #define INTELSPI_READ(_sc, _off) \ bus_read_4((_sc)->sc_mem_res, (_off)) #define INTELSPI_BUSY 0x1 #define TX_FIFO_THRESHOLD 2 #define RX_FIFO_THRESHOLD 2 #define CLOCK_DIV_10MHZ 5 #define DATA_SIZE_8BITS 8 #define MAX_CLOCK_RATE 50000000 #define CS_LOW 0 #define CS_HIGH 1 #define INTELSPI_SSPREG_SSCR0 0x0 #define SSCR0_SCR(n) ((((n) - 1) & 0xfff) << 8) #define SSCR0_SSE (1 << 7) #define SSCR0_FRF_SPI (0 << 4) #define SSCR0_DSS(n) (((n) - 1) << 0) #define INTELSPI_SSPREG_SSCR1 0x4 #define SSCR1_TINTE (1 << 19) #define SSCR1_RFT(n) (((n) - 1) << 10) #define SSCR1_RFT_MASK (0xf << 10) #define SSCR1_TFT(n) (((n) - 1) << 6) #define SSCR1_SPI_SPH (1 << 4) #define SSCR1_SPI_SPO (1 << 3) #define SSCR1_MODE_MASK (SSCR1_SPI_SPO | SSCR1_SPI_SPH) #define SSCR1_TIE (1 << 1) #define SSCR1_RIE (1 << 0) #define INTELSPI_SSPREG_SSSR 0x8 #define SSSR_RFL_MASK (0xf << 12) #define SSSR_TFL_MASK (0xf << 8) #define SSSR_RNE (1 << 3) #define SSSR_TNF (1 << 2) #define INTELSPI_SSPREG_SSITR 0xC #define INTELSPI_SSPREG_SSDR 0x10 #define INTELSPI_SSPREG_SSTO 0x28 #define INTELSPI_SSPREG_SSPSP 0x2C #define INTELSPI_SSPREG_SSTSA 0x30 #define INTELSPI_SSPREG_SSRSA 0x34 #define INTELSPI_SSPREG_SSTSS 0x38 #define INTELSPI_SSPREG_SSACD 0x3C #define INTELSPI_SSPREG_ITF 0x40 #define INTELSPI_SSPREG_SITF 0x44 #define INTELSPI_SSPREG_SIRF 0x48 #define SPI_CS_CTRL(sc) \ (intelspi_infos[sc->sc_vers].reg_lpss_base + \ intelspi_infos[sc->sc_vers].reg_cs_ctrl) #define SPI_CS_CTRL_CS_MASK (3) #define SPI_CS_CTRL_SW_MODE (1 << 0) #define SPI_CS_CTRL_HW_MODE (1 << 0) #define SPI_CS_CTRL_CS_HIGH (1 << 1) #define INTELSPI_RESETS 0x204 #define INTELSPI_RESET_HOST (1 << 0) | (1 << 1) #define INTELSPI_RESET_DMA (1 << 2) /* Same order as intelspi_vers */ static const struct intelspi_info { uint32_t reg_lpss_base; uint32_t reg_cs_ctrl; } intelspi_infos[] = { [SPI_BAYTRAIL] = { .reg_lpss_base = 0x400, .reg_cs_ctrl = 0x18, }, [SPI_BRASWELL] = { .reg_lpss_base = 0x400, .reg_cs_ctrl = 0x18, }, [SPI_LYNXPOINT] = { .reg_lpss_base = 0x800, .reg_cs_ctrl = 0x18, }, [SPI_SUNRISEPOINT] = { .reg_lpss_base = 0x200, .reg_cs_ctrl = 0x24, }, }; static void intelspi_intr(void *); static int intelspi_txfifo_full(struct intelspi_softc *sc) { uint32_t sssr; INTELSPI_ASSERT_LOCKED(sc); sssr = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); if (sssr & SSSR_TNF) return (0); return (1); } static int intelspi_rxfifo_empty(struct intelspi_softc *sc) { uint32_t sssr; INTELSPI_ASSERT_LOCKED(sc); sssr = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); if (sssr & SSSR_RNE) return (0); else return (1); } static void intelspi_fill_tx_fifo(struct intelspi_softc *sc) { struct spi_command *cmd; uint32_t written; uint8_t *data; INTELSPI_ASSERT_LOCKED(sc); cmd = sc->sc_cmd; while (sc->sc_written < sc->sc_len && !intelspi_txfifo_full(sc)) { data = (uint8_t *)cmd->tx_cmd; written = sc->sc_written++; if (written >= cmd->tx_cmd_sz) { data = (uint8_t *)cmd->tx_data; written -= cmd->tx_cmd_sz; } INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSDR, data[written]); } } static void intelspi_drain_rx_fifo(struct intelspi_softc *sc) { struct spi_command *cmd; uint32_t read; uint8_t *data; INTELSPI_ASSERT_LOCKED(sc); cmd = sc->sc_cmd; while (sc->sc_read < sc->sc_len && !intelspi_rxfifo_empty(sc)) { data = (uint8_t *)cmd->rx_cmd; read = sc->sc_read++; if (read >= cmd->rx_cmd_sz) { data = (uint8_t *)cmd->rx_data; read -= cmd->rx_cmd_sz; } data[read] = INTELSPI_READ(sc, INTELSPI_SSPREG_SSDR) & 0xff; } } static int intelspi_transaction_done(struct intelspi_softc *sc) { int txfifo_empty; uint32_t sssr; INTELSPI_ASSERT_LOCKED(sc); if (sc->sc_written != sc->sc_len || sc->sc_read != sc->sc_len) return (0); sssr = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); txfifo_empty = ((sssr & SSSR_TFL_MASK) == 0) && (sssr & SSSR_TNF); if (txfifo_empty && !(sssr & SSSR_RNE)) return (1); return (0); } static int intelspi_transact(struct intelspi_softc *sc) { INTELSPI_ASSERT_LOCKED(sc); /* TX - Fill up the FIFO. */ intelspi_fill_tx_fifo(sc); /* RX - Drain the FIFO. */ intelspi_drain_rx_fifo(sc); /* Check for end of transfer. */ return intelspi_transaction_done(sc); } static void intelspi_intr(void *arg) { struct intelspi_softc *sc; uint32_t reg; sc = (struct intelspi_softc *)arg; INTELSPI_LOCK(sc); if ((sc->sc_flags & INTELSPI_BUSY) == 0) { INTELSPI_UNLOCK(sc); return; } /* Check if SSP if off */ reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); if (reg == 0xffffffffU) { INTELSPI_UNLOCK(sc); return; } /* Check for end of transfer. */ if (intelspi_transact(sc)) { /* Disable interrupts */ reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); reg &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, reg); wakeup(sc->sc_dev); } INTELSPI_UNLOCK(sc); } static void intelspi_init(struct intelspi_softc *sc) { uint32_t reg; INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, 0); /* Manual CS control */ reg = INTELSPI_READ(sc, SPI_CS_CTRL(sc)); reg &= ~(SPI_CS_CTRL_CS_MASK); reg |= (SPI_CS_CTRL_SW_MODE | SPI_CS_CTRL_CS_HIGH); INTELSPI_WRITE(sc, SPI_CS_CTRL(sc), reg); /* Set TX/RX FIFO IRQ threshold levels */ reg = SSCR1_TFT(TX_FIFO_THRESHOLD) | SSCR1_RFT(RX_FIFO_THRESHOLD); INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, reg); reg = SSCR0_SCR(CLOCK_DIV_10MHZ); /* Put SSP in SPI mode */ reg |= SSCR0_FRF_SPI; /* Data size */ reg |= SSCR0_DSS(DATA_SIZE_8BITS); /* Enable SSP */ reg |= SSCR0_SSE; INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, reg); } static void intelspi_set_cs(struct intelspi_softc *sc, int level) { uint32_t reg; reg = INTELSPI_READ(sc, SPI_CS_CTRL(sc)); reg &= ~(SPI_CS_CTRL_CS_MASK); reg |= SPI_CS_CTRL_SW_MODE; if (level == CS_HIGH) reg |= SPI_CS_CTRL_CS_HIGH; INTELSPI_WRITE(sc, SPI_CS_CTRL(sc), reg); } int intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd) { struct intelspi_softc *sc; int err, poll_limit; uint32_t sscr0, sscr1, mode, clock, cs_delay; bool restart = false; sc = device_get_softc(dev); err = 0; KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz, ("TX/RX command sizes should be equal")); KASSERT(cmd->tx_data_sz == cmd->rx_data_sz, ("TX/RX data sizes should be equal")); INTELSPI_LOCK(sc); if (!INTELSPI_IN_POLLING_MODE()) { /* If the controller is in use wait until it is available. */ while (sc->sc_flags & INTELSPI_BUSY) { if ((cmd->flags & SPI_FLAG_NO_SLEEP) != 0) { INTELSPI_UNLOCK(sc); return (EBUSY); } err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", 0); if (err == EINTR) { INTELSPI_UNLOCK(sc); return (err); } } } else { /* * Now we are in the middle of other transfer. Try to reset * controller state to get predictable context. */ if ((sc->sc_flags & INTELSPI_BUSY) != 0) intelspi_init(sc); } /* Now we have control over SPI controller. */ sc->sc_flags = INTELSPI_BUSY; /* Configure the clock rate and SPI mode. */ spibus_get_clock(child, &clock); spibus_get_mode(child, &mode); if (clock != sc->sc_clock || mode != sc->sc_mode) { sscr0 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR0); sscr0 &= ~SSCR0_SSE; INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, sscr0); restart = true; } if (clock != sc->sc_clock) { sscr0 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR0); sscr0 &= ~SSCR0_SCR(0xfff); if (clock == 0) sscr0 |= SSCR0_SCR(CLOCK_DIV_10MHZ); else sscr0 |= SSCR0_SCR(howmany(MAX_CLOCK_RATE, min(MAX_CLOCK_RATE, clock))); INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, sscr0); sc->sc_clock = clock; } if (mode != sc->sc_mode) { sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); sscr1 &= ~SSCR1_MODE_MASK; if (mode & SPIBUS_MODE_CPHA) sscr1 |= SSCR1_SPI_SPH; if (mode & SPIBUS_MODE_CPOL) sscr1 |= SSCR1_SPI_SPO; INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1); sc->sc_mode = mode; } if (restart) { sscr0 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR0); sscr0 |= SSCR0_SSE; INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, sscr0); } /* Save a pointer to the SPI command. */ sc->sc_cmd = cmd; sc->sc_read = 0; sc->sc_written = 0; sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz; /* Enable CS */ intelspi_set_cs(sc, CS_LOW); /* Wait the CS delay */ spibus_get_cs_delay(child, &cs_delay); DELAY(cs_delay); /* Transfer as much as possible to FIFOs */ if ((cmd->flags & SPI_FLAG_NO_SLEEP) != 0 || INTELSPI_IN_POLLING_MODE() || cold) { /* We cannot wait with mtx_sleep if we're called from e.g. an ithread */ poll_limit = 2000; while (!intelspi_transact(sc) && poll_limit-- > 0) DELAY(1000); if (poll_limit == 0) { device_printf(dev, "polling was stuck, transaction not finished\n"); err = EIO; } } else { if (!intelspi_transact(sc)) { /* If FIFO is not large enough - enable interrupts */ sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); sscr1 |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1); /* and wait for transaction to complete */ err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", hz * 2); } } /* De-assert CS */ if ((cmd->flags & SPI_FLAG_KEEP_CS) == 0) intelspi_set_cs(sc, CS_HIGH); /* Clear transaction details */ sc->sc_cmd = NULL; sc->sc_read = 0; sc->sc_written = 0; sc->sc_len = 0; /* Make sure the SPI engine and interrupts are disabled. */ sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); sscr1 &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1); /* Release the controller and wakeup the next thread waiting for it. */ sc->sc_flags = 0; if (!INTELSPI_IN_POLLING_MODE()) wakeup_one(dev); INTELSPI_UNLOCK(sc); /* * Check for transfer timeout. The SPI controller doesn't * return errors. */ if (err == EWOULDBLOCK) { device_printf(sc->sc_dev, "transfer timeout\n"); err = EIO; } return (err); } int intelspi_attach(device_t dev) { struct intelspi_softc *sc; sc = device_get_softc(dev); sc->sc_dev = dev; INTELSPI_LOCK_INIT(sc); sc->sc_mem_res = bus_alloc_resource_any(sc->sc_dev, SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "can't allocate memory resource\n"); goto error; } /* Release LPSS reset */ if (sc->sc_vers == SPI_SUNRISEPOINT) INTELSPI_WRITE(sc, INTELSPI_RESETS, (INTELSPI_RESET_HOST | INTELSPI_RESET_DMA)); sc->sc_irq_res = bus_alloc_resource_any(sc->sc_dev, SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE | RF_SHAREABLE); if (sc->sc_irq_res == NULL) { device_printf(dev, "can't allocate IRQ resource\n"); goto error; } /* Hook up our interrupt handler. */ if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, intelspi_intr, sc, &sc->sc_irq_ih)) { device_printf(dev, "cannot setup the interrupt handler\n"); goto error; } intelspi_init(sc); device_add_child(dev, "spibus", DEVICE_UNIT_ANY); bus_delayed_attach_children(dev); return (0); error: INTELSPI_LOCK_DESTROY(sc); if (sc->sc_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); if (sc->sc_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); return (ENXIO); } int intelspi_detach(device_t dev) { struct intelspi_softc *sc; + int error; sc = device_get_softc(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); + INTELSPI_LOCK_DESTROY(sc); if (sc->sc_irq_ih) bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_ih); if (sc->sc_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); if (sc->sc_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); - return (device_delete_children(dev)); + return (0); } int intelspi_suspend(device_t dev) { struct intelspi_softc *sc; int err, i; sc = device_get_softc(dev); err = bus_generic_suspend(dev); if (err) return (err); for (i = 0; i < 9; i++) { unsigned long offset = i * sizeof(uint32_t); sc->sc_regs[i] = INTELSPI_READ(sc, intelspi_infos[sc->sc_vers].reg_lpss_base + offset); } /* Shutdown just in case */ INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, 0); return (0); } int intelspi_resume(device_t dev) { struct intelspi_softc *sc; int i; sc = device_get_softc(dev); for (i = 0; i < 9; i++) { unsigned long offset = i * sizeof(uint32_t); INTELSPI_WRITE(sc, intelspi_infos[sc->sc_vers].reg_lpss_base + offset, sc->sc_regs[i]); } intelspi_init(sc); /* Ensure the next transfer would reconfigure these */ sc->sc_clock = 0; sc->sc_mode = 0; return (bus_generic_resume(dev)); } diff --git a/sys/dev/mmc/host/dwmmc.c b/sys/dev/mmc/host/dwmmc.c index f39a2033f64c..51709bcbb7e9 100644 --- a/sys/dev/mmc/host/dwmmc.c +++ b/sys/dev/mmc/host/dwmmc.c @@ -1,1578 +1,1578 @@ /*- * Copyright (c) 2014-2019 Ruslan Bukin * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Synopsys DesignWare Mobile Storage Host Controller * Chapter 14, Altera Cyclone V Device Handbook (CV-5V2 2014.07.22) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_mmccam.h" #ifdef MMCCAM #include #include #include #include #include #include "mmc_sim_if.h" #endif #include "mmcbr_if.h" #ifdef DEBUG #define dprintf(fmt, args...) printf(fmt, ##args) #else #define dprintf(x, arg...) #endif #define READ4(_sc, _reg) \ bus_read_4((_sc)->res[0], _reg) #define WRITE4(_sc, _reg, _val) \ bus_write_4((_sc)->res[0], _reg, _val) #define DIV_ROUND_UP(n, d) howmany(n, d) #define DWMMC_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define DWMMC_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define DWMMC_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ "dwmmc", MTX_DEF) #define DWMMC_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); #define DWMMC_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); #define DWMMC_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); #define PENDING_CMD 0x01 #define PENDING_STOP 0x02 #define CARD_INIT_DONE 0x04 #define DWMMC_DATA_ERR_FLAGS (SDMMC_INTMASK_DRT | SDMMC_INTMASK_DCRC \ |SDMMC_INTMASK_SBE | SDMMC_INTMASK_EBE) #define DWMMC_CMD_ERR_FLAGS (SDMMC_INTMASK_RTO | SDMMC_INTMASK_RCRC \ |SDMMC_INTMASK_RE) #define DWMMC_ERR_FLAGS (DWMMC_DATA_ERR_FLAGS | DWMMC_CMD_ERR_FLAGS \ |SDMMC_INTMASK_HLE) #define DES0_DIC (1 << 1) /* Disable Interrupt on Completion */ #define DES0_LD (1 << 2) /* Last Descriptor */ #define DES0_FS (1 << 3) /* First Descriptor */ #define DES0_CH (1 << 4) /* second address CHained */ #define DES0_ER (1 << 5) /* End of Ring */ #define DES0_CES (1 << 30) /* Card Error Summary */ #define DES0_OWN (1 << 31) /* OWN */ #define DES1_BS1_MASK 0x1fff struct idmac_desc { uint32_t des0; /* control */ uint32_t des1; /* bufsize */ uint32_t des2; /* buf1 phys addr */ uint32_t des3; /* buf2 phys addr or next descr */ }; #define IDMAC_DESC_SEGS (PAGE_SIZE / (sizeof(struct idmac_desc))) #define IDMAC_DESC_SIZE (sizeof(struct idmac_desc) * IDMAC_DESC_SEGS) #define DEF_MSIZE 0x2 /* Burst size of multiple transaction */ /* * Size field in DMA descriptor is 13 bits long (up to 4095 bytes), * but must be a multiple of the data bus size.Additionally, we must ensure * that bus_dmamap_load() doesn't additionally fragments buffer (because it * is processed with page size granularity). Thus limit fragment size to half * of page. * XXX switch descriptor format to array and use second buffer pointer for * second half of page */ #define IDMAC_MAX_SIZE 2048 /* * Busdma may bounce buffers, so we must reserve 2 descriptors * (on start and on end) for bounced fragments. */ #define DWMMC_MAX_DATA (IDMAC_MAX_SIZE * (IDMAC_DESC_SEGS - 2)) / MMC_SECTOR_SIZE static void dwmmc_next_operation(struct dwmmc_softc *); static int dwmmc_setup_bus(struct dwmmc_softc *, int); static int dma_done(struct dwmmc_softc *, struct mmc_command *); static int dma_stop(struct dwmmc_softc *); static void pio_read(struct dwmmc_softc *, struct mmc_command *); static void pio_write(struct dwmmc_softc *, struct mmc_command *); static void dwmmc_handle_card_present(struct dwmmc_softc *sc, bool is_present); static struct resource_spec dwmmc_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; #define HWTYPE_MASK (0x0000ffff) #define HWFLAG_MASK (0xffff << 16) static void dwmmc_get1paddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { if (nsegs != 1) panic("%s: nsegs != 1 (%d)\n", __func__, nsegs); if (error != 0) panic("%s: error != 0 (%d)\n", __func__, error); *(bus_addr_t *)arg = segs[0].ds_addr; } static void dwmmc_ring_setup(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct dwmmc_softc *sc; int idx; sc = arg; dprintf("nsegs %d seg0len %lu\n", nsegs, segs[0].ds_len); if (error != 0) panic("%s: error != 0 (%d)\n", __func__, error); for (idx = 0; idx < nsegs; idx++) { sc->desc_ring[idx].des0 = DES0_DIC | DES0_CH; sc->desc_ring[idx].des1 = segs[idx].ds_len & DES1_BS1_MASK; sc->desc_ring[idx].des2 = segs[idx].ds_addr; if (idx == 0) sc->desc_ring[idx].des0 |= DES0_FS; if (idx == (nsegs - 1)) { sc->desc_ring[idx].des0 &= ~(DES0_DIC | DES0_CH); sc->desc_ring[idx].des0 |= DES0_LD; } wmb(); sc->desc_ring[idx].des0 |= DES0_OWN; } } static int dwmmc_ctrl_reset(struct dwmmc_softc *sc, int reset_bits) { int reg; int i; reg = READ4(sc, SDMMC_CTRL); reg |= (reset_bits); WRITE4(sc, SDMMC_CTRL, reg); /* Wait reset done */ for (i = 0; i < 100; i++) { if (!(READ4(sc, SDMMC_CTRL) & reset_bits)) return (0); DELAY(10); } device_printf(sc->dev, "Reset failed\n"); return (1); } static int dma_setup(struct dwmmc_softc *sc) { int error; int nidx; int idx; /* * Set up TX descriptor ring, descriptors, and dma maps. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->dev), /* Parent tag. */ 4096, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ IDMAC_DESC_SIZE, 1, /* maxsize, nsegments */ IDMAC_DESC_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->desc_tag); if (error != 0) { device_printf(sc->dev, "could not create ring DMA tag.\n"); return (1); } error = bus_dmamem_alloc(sc->desc_tag, (void**)&sc->desc_ring, BUS_DMA_COHERENT | BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->desc_map); if (error != 0) { device_printf(sc->dev, "could not allocate descriptor ring.\n"); return (1); } error = bus_dmamap_load(sc->desc_tag, sc->desc_map, sc->desc_ring, IDMAC_DESC_SIZE, dwmmc_get1paddr, &sc->desc_ring_paddr, 0); if (error != 0) { device_printf(sc->dev, "could not load descriptor ring map.\n"); return (1); } for (idx = 0; idx < IDMAC_DESC_SEGS; idx++) { sc->desc_ring[idx].des0 = DES0_CH; sc->desc_ring[idx].des1 = 0; nidx = (idx + 1) % IDMAC_DESC_SEGS; sc->desc_ring[idx].des3 = sc->desc_ring_paddr + \ (nidx * sizeof(struct idmac_desc)); } sc->desc_ring[idx - 1].des3 = sc->desc_ring_paddr; sc->desc_ring[idx - 1].des0 |= DES0_ER; error = bus_dma_tag_create( bus_get_dma_tag(sc->dev), /* Parent tag. */ 8, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ IDMAC_MAX_SIZE * IDMAC_DESC_SEGS, /* maxsize */ IDMAC_DESC_SEGS, /* nsegments */ IDMAC_MAX_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->buf_tag); if (error != 0) { device_printf(sc->dev, "could not create ring DMA tag.\n"); return (1); } error = bus_dmamap_create(sc->buf_tag, 0, &sc->buf_map); if (error != 0) { device_printf(sc->dev, "could not create TX buffer DMA map.\n"); return (1); } return (0); } static void dwmmc_cmd_done(struct dwmmc_softc *sc) { struct mmc_command *cmd; #ifdef MMCCAM union ccb *ccb; #endif #ifdef MMCCAM ccb = sc->ccb; if (ccb == NULL) return; cmd = &ccb->mmcio.cmd; #else cmd = sc->curcmd; #endif if (cmd == NULL) return; if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { cmd->resp[3] = READ4(sc, SDMMC_RESP0); cmd->resp[2] = READ4(sc, SDMMC_RESP1); cmd->resp[1] = READ4(sc, SDMMC_RESP2); cmd->resp[0] = READ4(sc, SDMMC_RESP3); } else { cmd->resp[3] = 0; cmd->resp[2] = 0; cmd->resp[1] = 0; cmd->resp[0] = READ4(sc, SDMMC_RESP0); } } } static void dwmmc_tasklet(struct dwmmc_softc *sc) { struct mmc_command *cmd; cmd = sc->curcmd; if (cmd == NULL) return; if (!sc->cmd_done) return; if (cmd->error != MMC_ERR_NONE || !cmd->data) { dwmmc_next_operation(sc); } else if (cmd->data && sc->dto_rcvd) { if ((cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK || cmd->opcode == MMC_READ_MULTIPLE_BLOCK) && sc->use_auto_stop) { if (sc->acd_rcvd) dwmmc_next_operation(sc); } else { dwmmc_next_operation(sc); } } } static void dwmmc_intr(void *arg) { struct mmc_command *cmd; struct dwmmc_softc *sc; uint32_t reg; sc = arg; DWMMC_LOCK(sc); cmd = sc->curcmd; /* First handle SDMMC controller interrupts */ reg = READ4(sc, SDMMC_MINTSTS); if (reg) { dprintf("%s 0x%08x\n", __func__, reg); if (reg & DWMMC_CMD_ERR_FLAGS) { dprintf("cmd err 0x%08x cmd 0x%08x\n", reg, cmd->opcode); cmd->error = MMC_ERR_TIMEOUT; } if (reg & DWMMC_DATA_ERR_FLAGS) { dprintf("data err 0x%08x cmd 0x%08x\n", reg, cmd->opcode); cmd->error = MMC_ERR_FAILED; if (!sc->use_pio) { dma_done(sc, cmd); dma_stop(sc); } } if (reg & SDMMC_INTMASK_CMD_DONE) { dwmmc_cmd_done(sc); sc->cmd_done = 1; } if (reg & SDMMC_INTMASK_ACD) sc->acd_rcvd = 1; if (reg & SDMMC_INTMASK_DTO) sc->dto_rcvd = 1; if (reg & SDMMC_INTMASK_CD) { dwmmc_handle_card_present(sc, READ4(sc, SDMMC_CDETECT) == 0 ? true : false); } } /* Ack interrupts */ WRITE4(sc, SDMMC_RINTSTS, reg); if (sc->use_pio) { if (reg & (SDMMC_INTMASK_RXDR|SDMMC_INTMASK_DTO)) { pio_read(sc, cmd); } if (reg & (SDMMC_INTMASK_TXDR|SDMMC_INTMASK_DTO)) { pio_write(sc, cmd); } } else { /* Now handle DMA interrupts */ reg = READ4(sc, SDMMC_IDSTS); if (reg) { dprintf("dma intr 0x%08x\n", reg); if (reg & (SDMMC_IDINTEN_TI | SDMMC_IDINTEN_RI)) { WRITE4(sc, SDMMC_IDSTS, (SDMMC_IDINTEN_TI | SDMMC_IDINTEN_RI)); WRITE4(sc, SDMMC_IDSTS, SDMMC_IDINTEN_NI); dma_done(sc, cmd); } } } dwmmc_tasklet(sc); DWMMC_UNLOCK(sc); } static void dwmmc_handle_card_present(struct dwmmc_softc *sc, bool is_present) { bool was_present; if (dumping || SCHEDULER_STOPPED()) return; was_present = sc->child != NULL; if (!was_present && is_present) { taskqueue_enqueue_timeout(taskqueue_swi_giant, &sc->card_delayed_task, -(hz / 2)); } else if (was_present && !is_present) { taskqueue_enqueue(taskqueue_swi_giant, &sc->card_task); } } static void dwmmc_card_task(void *arg, int pending __unused) { struct dwmmc_softc *sc = arg; #ifdef MMCCAM mmc_cam_sim_discover(&sc->mmc_sim); #else DWMMC_LOCK(sc); if (READ4(sc, SDMMC_CDETECT) == 0 || (sc->mmc_helper.props & MMC_PROP_BROKEN_CD)) { if (sc->child == NULL) { if (bootverbose) device_printf(sc->dev, "Card inserted\n"); sc->child = device_add_child(sc->dev, "mmc", DEVICE_UNIT_ANY); DWMMC_UNLOCK(sc); if (sc->child) { device_set_ivars(sc->child, sc); (void)device_probe_and_attach(sc->child); } } else DWMMC_UNLOCK(sc); } else { /* Card isn't present, detach if necessary */ if (sc->child != NULL) { if (bootverbose) device_printf(sc->dev, "Card removed\n"); DWMMC_UNLOCK(sc); device_delete_child(sc->dev, sc->child); sc->child = NULL; } else DWMMC_UNLOCK(sc); } #endif /* MMCCAM */ } static int parse_fdt(struct dwmmc_softc *sc) { pcell_t dts_value[3]; phandle_t node; uint32_t bus_hz = 0; int len; int error; if ((node = ofw_bus_get_node(sc->dev)) == -1) return (ENXIO); /* Set some defaults for freq and supported mode */ sc->host.f_min = 400000; sc->host.f_max = 200000000; sc->host.host_ocr = MMC_OCR_320_330 | MMC_OCR_330_340; sc->host.caps = MMC_CAP_HSPEED | MMC_CAP_SIGNALING_330; mmc_fdt_parse(sc->dev, node, &sc->mmc_helper, &sc->host); /* fifo-depth */ if ((len = OF_getproplen(node, "fifo-depth")) > 0) { OF_getencprop(node, "fifo-depth", dts_value, len); sc->fifo_depth = dts_value[0]; } /* num-slots (Deprecated) */ sc->num_slots = 1; if ((len = OF_getproplen(node, "num-slots")) > 0) { device_printf(sc->dev, "num-slots property is deprecated\n"); OF_getencprop(node, "num-slots", dts_value, len); sc->num_slots = dts_value[0]; } /* clock-frequency */ if ((len = OF_getproplen(node, "clock-frequency")) > 0) { OF_getencprop(node, "clock-frequency", dts_value, len); bus_hz = dts_value[0]; } /* IP block reset is optional */ error = hwreset_get_by_ofw_name(sc->dev, 0, "reset", &sc->hwreset); if (error != 0 && error != ENOENT && error != ENODEV) { device_printf(sc->dev, "Cannot get reset\n"); goto fail; } /* vmmc regulator is optional */ error = regulator_get_by_ofw_property(sc->dev, 0, "vmmc-supply", &sc->vmmc); if (error != 0 && error != ENOENT && error != ENODEV) { device_printf(sc->dev, "Cannot get regulator 'vmmc-supply'\n"); goto fail; } /* vqmmc regulator is optional */ error = regulator_get_by_ofw_property(sc->dev, 0, "vqmmc-supply", &sc->vqmmc); if (error != 0 && error != ENOENT && error != ENODEV) { device_printf(sc->dev, "Cannot get regulator 'vqmmc-supply'\n"); goto fail; } /* Assert reset first */ if (sc->hwreset != NULL) { error = hwreset_assert(sc->hwreset); if (error != 0) { device_printf(sc->dev, "Cannot assert reset\n"); goto fail; } } /* BIU (Bus Interface Unit clock) is optional */ error = clk_get_by_ofw_name(sc->dev, 0, "biu", &sc->biu); if (error != 0 && error != ENOENT && error != ENODEV) { device_printf(sc->dev, "Cannot get 'biu' clock\n"); goto fail; } if (sc->biu) { error = clk_enable(sc->biu); if (error != 0) { device_printf(sc->dev, "cannot enable biu clock\n"); goto fail; } } /* * CIU (Controller Interface Unit clock) is mandatory * if no clock-frequency property is given */ error = clk_get_by_ofw_name(sc->dev, 0, "ciu", &sc->ciu); if (error != 0 && error != ENOENT && error != ENODEV) { device_printf(sc->dev, "Cannot get 'ciu' clock\n"); goto fail; } if (sc->ciu) { if (bus_hz != 0) { error = clk_set_freq(sc->ciu, bus_hz, 0); if (error != 0) device_printf(sc->dev, "cannot set ciu clock to %u\n", bus_hz); } error = clk_enable(sc->ciu); if (error != 0) { device_printf(sc->dev, "cannot enable ciu clock\n"); goto fail; } clk_get_freq(sc->ciu, &sc->bus_hz); } /* Enable regulators */ if (sc->vmmc != NULL) { error = regulator_enable(sc->vmmc); if (error != 0) { device_printf(sc->dev, "Cannot enable vmmc regulator\n"); goto fail; } } if (sc->vqmmc != NULL) { error = regulator_enable(sc->vqmmc); if (error != 0) { device_printf(sc->dev, "Cannot enable vqmmc regulator\n"); goto fail; } } /* Take dwmmc out of reset */ if (sc->hwreset != NULL) { error = hwreset_deassert(sc->hwreset); if (error != 0) { device_printf(sc->dev, "Cannot deassert reset\n"); goto fail; } } if (sc->bus_hz == 0) { device_printf(sc->dev, "No bus speed provided\n"); goto fail; } return (0); fail: return (ENXIO); } int dwmmc_attach(device_t dev) { struct dwmmc_softc *sc; int error; sc = device_get_softc(dev); sc->dev = dev; /* Why not to use Auto Stop? It save a hundred of irq per second */ sc->use_auto_stop = 1; error = parse_fdt(sc); if (error != 0) { device_printf(dev, "Can't get FDT property.\n"); return (ENXIO); } DWMMC_LOCK_INIT(sc); if (bus_alloc_resources(dev, dwmmc_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } /* Setup interrupt handler. */ error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_NET | INTR_MPSAFE, NULL, dwmmc_intr, sc, &sc->intr_cookie); if (error != 0) { device_printf(dev, "could not setup interrupt handler.\n"); return (ENXIO); } device_printf(dev, "Hardware version ID is %04x\n", READ4(sc, SDMMC_VERID) & 0xffff); /* Reset all */ if (dwmmc_ctrl_reset(sc, (SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET | SDMMC_CTRL_DMA_RESET))) return (ENXIO); dwmmc_setup_bus(sc, sc->host.f_min); if (sc->fifo_depth == 0) { sc->fifo_depth = 1 + ((READ4(sc, SDMMC_FIFOTH) >> SDMMC_FIFOTH_RXWMARK_S) & 0xfff); device_printf(dev, "No fifo-depth, using FIFOTH %x\n", sc->fifo_depth); } if (!sc->use_pio) { dma_stop(sc); if (dma_setup(sc)) return (ENXIO); /* Install desc base */ WRITE4(sc, SDMMC_DBADDR, sc->desc_ring_paddr); /* Enable DMA interrupts */ WRITE4(sc, SDMMC_IDSTS, SDMMC_IDINTEN_MASK); WRITE4(sc, SDMMC_IDINTEN, (SDMMC_IDINTEN_NI | SDMMC_IDINTEN_RI | SDMMC_IDINTEN_TI)); } /* Clear and disable interrups for a while */ WRITE4(sc, SDMMC_RINTSTS, 0xffffffff); WRITE4(sc, SDMMC_INTMASK, 0); /* Maximum timeout */ WRITE4(sc, SDMMC_TMOUT, 0xffffffff); /* Enable interrupts */ WRITE4(sc, SDMMC_RINTSTS, 0xffffffff); WRITE4(sc, SDMMC_INTMASK, (SDMMC_INTMASK_CMD_DONE | SDMMC_INTMASK_DTO | SDMMC_INTMASK_ACD | SDMMC_INTMASK_TXDR | SDMMC_INTMASK_RXDR | DWMMC_ERR_FLAGS | SDMMC_INTMASK_CD)); WRITE4(sc, SDMMC_CTRL, SDMMC_CTRL_INT_ENABLE); TASK_INIT(&sc->card_task, 0, dwmmc_card_task, sc); TIMEOUT_TASK_INIT(taskqueue_swi_giant, &sc->card_delayed_task, 0, dwmmc_card_task, sc); #ifdef MMCCAM sc->ccb = NULL; if (mmc_cam_sim_alloc(dev, "dw_mmc", &sc->mmc_sim) != 0) { device_printf(dev, "cannot alloc cam sim\n"); dwmmc_detach(dev); return (ENXIO); } #endif /* * Schedule a card detection as we won't get an interrupt * if the card is inserted when we attach */ dwmmc_card_task(sc, 0); return (0); } int dwmmc_detach(device_t dev) { struct dwmmc_softc *sc; int ret; sc = device_get_softc(dev); - ret = device_delete_children(dev); + ret = bus_generic_detach(dev); if (ret != 0) return (ret); taskqueue_drain(taskqueue_swi_giant, &sc->card_task); taskqueue_drain_timeout(taskqueue_swi_giant, &sc->card_delayed_task); if (sc->intr_cookie != NULL) { ret = bus_teardown_intr(dev, sc->res[1], sc->intr_cookie); if (ret != 0) return (ret); } bus_release_resources(dev, dwmmc_spec, sc->res); DWMMC_LOCK_DESTROY(sc); if (sc->hwreset != NULL && hwreset_deassert(sc->hwreset) != 0) device_printf(sc->dev, "cannot deassert reset\n"); if (sc->biu != NULL && clk_disable(sc->biu) != 0) device_printf(sc->dev, "cannot disable biu clock\n"); if (sc->ciu != NULL && clk_disable(sc->ciu) != 0) device_printf(sc->dev, "cannot disable ciu clock\n"); if (sc->vmmc && regulator_disable(sc->vmmc) != 0) device_printf(sc->dev, "Cannot disable vmmc regulator\n"); if (sc->vqmmc && regulator_disable(sc->vqmmc) != 0) device_printf(sc->dev, "Cannot disable vqmmc regulator\n"); #ifdef MMCCAM mmc_cam_sim_free(&sc->mmc_sim); #endif return (0); } static int dwmmc_setup_bus(struct dwmmc_softc *sc, int freq) { int tout; int div; if (freq == 0) { WRITE4(sc, SDMMC_CLKENA, 0); WRITE4(sc, SDMMC_CMD, (SDMMC_CMD_WAIT_PRVDATA | SDMMC_CMD_UPD_CLK_ONLY | SDMMC_CMD_START)); tout = 1000; do { if (tout-- < 0) { device_printf(sc->dev, "Failed update clk\n"); return (1); } } while (READ4(sc, SDMMC_CMD) & SDMMC_CMD_START); return (0); } WRITE4(sc, SDMMC_CLKENA, 0); WRITE4(sc, SDMMC_CLKSRC, 0); div = (sc->bus_hz != freq) ? DIV_ROUND_UP(sc->bus_hz, 2 * freq) : 0; WRITE4(sc, SDMMC_CLKDIV, div); WRITE4(sc, SDMMC_CMD, (SDMMC_CMD_WAIT_PRVDATA | SDMMC_CMD_UPD_CLK_ONLY | SDMMC_CMD_START)); tout = 1000; do { if (tout-- < 0) { device_printf(sc->dev, "Failed to update clk\n"); return (1); } } while (READ4(sc, SDMMC_CMD) & SDMMC_CMD_START); WRITE4(sc, SDMMC_CLKENA, (SDMMC_CLKENA_CCLK_EN | SDMMC_CLKENA_LP)); WRITE4(sc, SDMMC_CMD, SDMMC_CMD_WAIT_PRVDATA | SDMMC_CMD_UPD_CLK_ONLY | SDMMC_CMD_START); tout = 1000; do { if (tout-- < 0) { device_printf(sc->dev, "Failed to enable clk\n"); return (1); } } while (READ4(sc, SDMMC_CMD) & SDMMC_CMD_START); return (0); } static int dwmmc_update_ios(device_t brdev, device_t reqdev) { struct dwmmc_softc *sc; struct mmc_ios *ios; uint32_t reg; int ret = 0; sc = device_get_softc(brdev); ios = &sc->host.ios; dprintf("Setting up clk %u bus_width %d, timing: %d\n", ios->clock, ios->bus_width, ios->timing); switch (ios->power_mode) { case power_on: break; case power_off: WRITE4(sc, SDMMC_PWREN, 0); break; case power_up: WRITE4(sc, SDMMC_PWREN, 1); break; } mmc_fdt_set_power(&sc->mmc_helper, ios->power_mode); if (ios->bus_width == bus_width_8) WRITE4(sc, SDMMC_CTYPE, SDMMC_CTYPE_8BIT); else if (ios->bus_width == bus_width_4) WRITE4(sc, SDMMC_CTYPE, SDMMC_CTYPE_4BIT); else WRITE4(sc, SDMMC_CTYPE, 0); if ((sc->hwtype & HWTYPE_MASK) == HWTYPE_EXYNOS) { /* XXX: take care about DDR or SDR use here */ WRITE4(sc, SDMMC_CLKSEL, sc->sdr_timing); } /* Set DDR mode */ reg = READ4(sc, SDMMC_UHS_REG); if (ios->timing == bus_timing_uhs_ddr50 || ios->timing == bus_timing_mmc_ddr52 || ios->timing == bus_timing_mmc_hs400) reg |= (SDMMC_UHS_REG_DDR); else reg &= ~(SDMMC_UHS_REG_DDR); WRITE4(sc, SDMMC_UHS_REG, reg); if (sc->update_ios) ret = sc->update_ios(sc, ios); dwmmc_setup_bus(sc, ios->clock); return (ret); } static int dma_done(struct dwmmc_softc *sc, struct mmc_command *cmd) { struct mmc_data *data; data = cmd->data; if (data->flags & MMC_DATA_WRITE) bus_dmamap_sync(sc->buf_tag, sc->buf_map, BUS_DMASYNC_POSTWRITE); else bus_dmamap_sync(sc->buf_tag, sc->buf_map, BUS_DMASYNC_POSTREAD); bus_dmamap_sync(sc->desc_tag, sc->desc_map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->buf_tag, sc->buf_map); return (0); } static int dma_stop(struct dwmmc_softc *sc) { int reg; reg = READ4(sc, SDMMC_CTRL); reg &= ~(SDMMC_CTRL_USE_IDMAC); reg |= (SDMMC_CTRL_DMA_RESET); WRITE4(sc, SDMMC_CTRL, reg); reg = READ4(sc, SDMMC_BMOD); reg &= ~(SDMMC_BMOD_DE | SDMMC_BMOD_FB); reg |= (SDMMC_BMOD_SWR); WRITE4(sc, SDMMC_BMOD, reg); return (0); } static int dma_prepare(struct dwmmc_softc *sc, struct mmc_command *cmd) { struct mmc_data *data; int err; int reg; data = cmd->data; reg = READ4(sc, SDMMC_INTMASK); reg &= ~(SDMMC_INTMASK_TXDR | SDMMC_INTMASK_RXDR); WRITE4(sc, SDMMC_INTMASK, reg); dprintf("%s: bus_dmamap_load size: %zu\n", __func__, data->len); err = bus_dmamap_load(sc->buf_tag, sc->buf_map, data->data, data->len, dwmmc_ring_setup, sc, BUS_DMA_NOWAIT); if (err != 0) panic("dmamap_load failed\n"); /* Ensure the device can see the desc */ bus_dmamap_sync(sc->desc_tag, sc->desc_map, BUS_DMASYNC_PREWRITE); if (data->flags & MMC_DATA_WRITE) bus_dmamap_sync(sc->buf_tag, sc->buf_map, BUS_DMASYNC_PREWRITE); else bus_dmamap_sync(sc->buf_tag, sc->buf_map, BUS_DMASYNC_PREREAD); reg = (DEF_MSIZE << SDMMC_FIFOTH_MSIZE_S); reg |= ((sc->fifo_depth / 2) - 1) << SDMMC_FIFOTH_RXWMARK_S; reg |= (sc->fifo_depth / 2) << SDMMC_FIFOTH_TXWMARK_S; WRITE4(sc, SDMMC_FIFOTH, reg); wmb(); reg = READ4(sc, SDMMC_CTRL); reg |= (SDMMC_CTRL_USE_IDMAC | SDMMC_CTRL_DMA_ENABLE); WRITE4(sc, SDMMC_CTRL, reg); wmb(); reg = READ4(sc, SDMMC_BMOD); reg |= (SDMMC_BMOD_DE | SDMMC_BMOD_FB); WRITE4(sc, SDMMC_BMOD, reg); /* Start */ WRITE4(sc, SDMMC_PLDMND, 1); return (0); } static int pio_prepare(struct dwmmc_softc *sc, struct mmc_command *cmd) { struct mmc_data *data; int reg; data = cmd->data; data->xfer_len = 0; reg = (DEF_MSIZE << SDMMC_FIFOTH_MSIZE_S); reg |= ((sc->fifo_depth / 2) - 1) << SDMMC_FIFOTH_RXWMARK_S; reg |= (sc->fifo_depth / 2) << SDMMC_FIFOTH_TXWMARK_S; WRITE4(sc, SDMMC_FIFOTH, reg); wmb(); return (0); } static void pio_read(struct dwmmc_softc *sc, struct mmc_command *cmd) { struct mmc_data *data; uint32_t *p, status; if (cmd == NULL || cmd->data == NULL) return; data = cmd->data; if ((data->flags & MMC_DATA_READ) == 0) return; KASSERT((data->xfer_len & 3) == 0, ("xfer_len not aligned")); p = (uint32_t *)data->data + (data->xfer_len >> 2); while (data->xfer_len < data->len) { status = READ4(sc, SDMMC_STATUS); if (status & SDMMC_STATUS_FIFO_EMPTY) break; *p++ = READ4(sc, SDMMC_DATA); data->xfer_len += 4; } WRITE4(sc, SDMMC_RINTSTS, SDMMC_INTMASK_RXDR); } static void pio_write(struct dwmmc_softc *sc, struct mmc_command *cmd) { struct mmc_data *data; uint32_t *p, status; if (cmd == NULL || cmd->data == NULL) return; data = cmd->data; if ((data->flags & MMC_DATA_WRITE) == 0) return; KASSERT((data->xfer_len & 3) == 0, ("xfer_len not aligned")); p = (uint32_t *)data->data + (data->xfer_len >> 2); while (data->xfer_len < data->len) { status = READ4(sc, SDMMC_STATUS); if (status & SDMMC_STATUS_FIFO_FULL) break; WRITE4(sc, SDMMC_DATA, *p++); data->xfer_len += 4; } WRITE4(sc, SDMMC_RINTSTS, SDMMC_INTMASK_TXDR); } static void dwmmc_start_cmd(struct dwmmc_softc *sc, struct mmc_command *cmd) { struct mmc_data *data; uint32_t blksz; uint32_t cmdr; dprintf("%s\n", __func__); sc->curcmd = cmd; data = cmd->data; #ifndef MMCCAM /* XXX Upper layers don't always set this */ cmd->mrq = sc->req; #endif /* Begin setting up command register. */ cmdr = cmd->opcode; dprintf("cmd->opcode 0x%08x\n", cmd->opcode); if (cmd->opcode == MMC_STOP_TRANSMISSION || cmd->opcode == MMC_GO_IDLE_STATE || cmd->opcode == MMC_GO_INACTIVE_STATE) cmdr |= SDMMC_CMD_STOP_ABORT; else if (cmd->opcode != MMC_SEND_STATUS && data) cmdr |= SDMMC_CMD_WAIT_PRVDATA; /* Set up response handling. */ if (MMC_RSP(cmd->flags) != MMC_RSP_NONE) { cmdr |= SDMMC_CMD_RESP_EXP; if (cmd->flags & MMC_RSP_136) cmdr |= SDMMC_CMD_RESP_LONG; } if (cmd->flags & MMC_RSP_CRC) cmdr |= SDMMC_CMD_RESP_CRC; /* * XXX: Not all platforms want this. */ cmdr |= SDMMC_CMD_USE_HOLD_REG; if ((sc->flags & CARD_INIT_DONE) == 0) { sc->flags |= (CARD_INIT_DONE); cmdr |= SDMMC_CMD_SEND_INIT; } if (data) { if ((cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK || cmd->opcode == MMC_READ_MULTIPLE_BLOCK) && sc->use_auto_stop) cmdr |= SDMMC_CMD_SEND_ASTOP; cmdr |= SDMMC_CMD_DATA_EXP; if (data->flags & MMC_DATA_STREAM) cmdr |= SDMMC_CMD_MODE_STREAM; if (data->flags & MMC_DATA_WRITE) cmdr |= SDMMC_CMD_DATA_WRITE; WRITE4(sc, SDMMC_TMOUT, 0xffffffff); #ifdef MMCCAM if (cmd->data->flags & MMC_DATA_BLOCK_SIZE) { WRITE4(sc, SDMMC_BLKSIZ, cmd->data->block_size); WRITE4(sc, SDMMC_BYTCNT, cmd->data->len); } else #endif { WRITE4(sc, SDMMC_BYTCNT, data->len); blksz = (data->len < MMC_SECTOR_SIZE) ? \ data->len : MMC_SECTOR_SIZE; WRITE4(sc, SDMMC_BLKSIZ, blksz); } if (sc->use_pio) { pio_prepare(sc, cmd); } else { dma_prepare(sc, cmd); } wmb(); } dprintf("cmdr 0x%08x\n", cmdr); WRITE4(sc, SDMMC_CMDARG, cmd->arg); wmb(); WRITE4(sc, SDMMC_CMD, cmdr | SDMMC_CMD_START); }; static void dwmmc_next_operation(struct dwmmc_softc *sc) { struct mmc_command *cmd; dprintf("%s\n", __func__); #ifdef MMCCAM union ccb *ccb; ccb = sc->ccb; if (ccb == NULL) return; cmd = &ccb->mmcio.cmd; #else struct mmc_request *req; req = sc->req; if (req == NULL) return; cmd = req->cmd; #endif sc->acd_rcvd = 0; sc->dto_rcvd = 0; sc->cmd_done = 0; /* * XXX: Wait until card is still busy. * We do need this to prevent data timeouts, * mostly caused by multi-block write command * followed by single-read. */ while(READ4(sc, SDMMC_STATUS) & (SDMMC_STATUS_DATA_BUSY)) continue; if (sc->flags & PENDING_CMD) { sc->flags &= ~PENDING_CMD; dwmmc_start_cmd(sc, cmd); return; } else if (sc->flags & PENDING_STOP && !sc->use_auto_stop) { sc->flags &= ~PENDING_STOP; /// XXX: What to do with this? //dwmmc_start_cmd(sc, req->stop); return; } #ifdef MMCCAM sc->ccb = NULL; sc->curcmd = NULL; ccb->ccb_h.status = (ccb->mmcio.cmd.error == 0 ? CAM_REQ_CMP : CAM_REQ_CMP_ERR); xpt_done(ccb); #else sc->req = NULL; sc->curcmd = NULL; req->done(req); #endif } static int dwmmc_request(device_t brdev, device_t reqdev, struct mmc_request *req) { struct dwmmc_softc *sc; sc = device_get_softc(brdev); dprintf("%s\n", __func__); DWMMC_LOCK(sc); #ifdef MMCCAM sc->flags |= PENDING_CMD; #else if (sc->req != NULL) { DWMMC_UNLOCK(sc); return (EBUSY); } sc->req = req; sc->flags |= PENDING_CMD; if (sc->req->stop) sc->flags |= PENDING_STOP; #endif dwmmc_next_operation(sc); DWMMC_UNLOCK(sc); return (0); } #ifndef MMCCAM static int dwmmc_get_ro(device_t brdev, device_t reqdev) { dprintf("%s\n", __func__); return (0); } static int dwmmc_acquire_host(device_t brdev, device_t reqdev) { struct dwmmc_softc *sc; sc = device_get_softc(brdev); DWMMC_LOCK(sc); while (sc->bus_busy) msleep(sc, &sc->sc_mtx, PZERO, "dwmmcah", hz / 5); sc->bus_busy++; DWMMC_UNLOCK(sc); return (0); } static int dwmmc_release_host(device_t brdev, device_t reqdev) { struct dwmmc_softc *sc; sc = device_get_softc(brdev); DWMMC_LOCK(sc); sc->bus_busy--; wakeup(sc); DWMMC_UNLOCK(sc); return (0); } #endif /* !MMCCAM */ static int dwmmc_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct dwmmc_softc *sc; sc = device_get_softc(bus); switch (which) { default: return (EINVAL); case MMCBR_IVAR_BUS_MODE: *(int *)result = sc->host.ios.bus_mode; break; case MMCBR_IVAR_BUS_WIDTH: *(int *)result = sc->host.ios.bus_width; break; case MMCBR_IVAR_CHIP_SELECT: *(int *)result = sc->host.ios.chip_select; break; case MMCBR_IVAR_CLOCK: *(int *)result = sc->host.ios.clock; break; case MMCBR_IVAR_F_MIN: *(int *)result = sc->host.f_min; break; case MMCBR_IVAR_F_MAX: *(int *)result = sc->host.f_max; break; case MMCBR_IVAR_HOST_OCR: *(int *)result = sc->host.host_ocr; break; case MMCBR_IVAR_MODE: *(int *)result = sc->host.mode; break; case MMCBR_IVAR_OCR: *(int *)result = sc->host.ocr; break; case MMCBR_IVAR_POWER_MODE: *(int *)result = sc->host.ios.power_mode; break; case MMCBR_IVAR_VDD: *(int *)result = sc->host.ios.vdd; break; case MMCBR_IVAR_VCCQ: *(int *)result = sc->host.ios.vccq; break; case MMCBR_IVAR_CAPS: *(int *)result = sc->host.caps; break; case MMCBR_IVAR_MAX_DATA: *(int *)result = DWMMC_MAX_DATA; break; case MMCBR_IVAR_TIMING: *(int *)result = sc->host.ios.timing; break; } return (0); } static int dwmmc_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct dwmmc_softc *sc; sc = device_get_softc(bus); switch (which) { default: return (EINVAL); case MMCBR_IVAR_BUS_MODE: sc->host.ios.bus_mode = value; break; case MMCBR_IVAR_BUS_WIDTH: sc->host.ios.bus_width = value; break; case MMCBR_IVAR_CHIP_SELECT: sc->host.ios.chip_select = value; break; case MMCBR_IVAR_CLOCK: sc->host.ios.clock = value; break; case MMCBR_IVAR_MODE: sc->host.mode = value; break; case MMCBR_IVAR_OCR: sc->host.ocr = value; break; case MMCBR_IVAR_POWER_MODE: sc->host.ios.power_mode = value; break; case MMCBR_IVAR_VDD: sc->host.ios.vdd = value; break; case MMCBR_IVAR_TIMING: sc->host.ios.timing = value; break; case MMCBR_IVAR_VCCQ: sc->host.ios.vccq = value; break; /* These are read-only */ case MMCBR_IVAR_CAPS: case MMCBR_IVAR_HOST_OCR: case MMCBR_IVAR_F_MIN: case MMCBR_IVAR_F_MAX: case MMCBR_IVAR_MAX_DATA: return (EINVAL); } return (0); } #ifdef MMCCAM /* Note: this function likely belongs to the specific driver impl */ static int dwmmc_switch_vccq(device_t dev, device_t child) { device_printf(dev, "This is a default impl of switch_vccq() that always fails\n"); return EINVAL; } static int dwmmc_get_tran_settings(device_t dev, struct ccb_trans_settings_mmc *cts) { struct dwmmc_softc *sc; sc = device_get_softc(dev); cts->host_ocr = sc->host.host_ocr; cts->host_f_min = sc->host.f_min; cts->host_f_max = sc->host.f_max; cts->host_caps = sc->host.caps; cts->host_max_data = DWMMC_MAX_DATA; memcpy(&cts->ios, &sc->host.ios, sizeof(struct mmc_ios)); return (0); } static int dwmmc_set_tran_settings(device_t dev, struct ccb_trans_settings_mmc *cts) { struct dwmmc_softc *sc; struct mmc_ios *ios; struct mmc_ios *new_ios; int res; sc = device_get_softc(dev); ios = &sc->host.ios; new_ios = &cts->ios; /* Update only requested fields */ if (cts->ios_valid & MMC_CLK) { ios->clock = new_ios->clock; if (bootverbose) device_printf(sc->dev, "Clock => %d\n", ios->clock); } if (cts->ios_valid & MMC_VDD) { ios->vdd = new_ios->vdd; if (bootverbose) device_printf(sc->dev, "VDD => %d\n", ios->vdd); } if (cts->ios_valid & MMC_CS) { ios->chip_select = new_ios->chip_select; if (bootverbose) device_printf(sc->dev, "CS => %d\n", ios->chip_select); } if (cts->ios_valid & MMC_BW) { ios->bus_width = new_ios->bus_width; if (bootverbose) device_printf(sc->dev, "Bus width => %d\n", ios->bus_width); } if (cts->ios_valid & MMC_PM) { ios->power_mode = new_ios->power_mode; if (bootverbose) device_printf(sc->dev, "Power mode => %d\n", ios->power_mode); } if (cts->ios_valid & MMC_BT) { ios->timing = new_ios->timing; if (bootverbose) device_printf(sc->dev, "Timing => %d\n", ios->timing); } if (cts->ios_valid & MMC_BM) { ios->bus_mode = new_ios->bus_mode; if (bootverbose) device_printf(sc->dev, "Bus mode => %d\n", ios->bus_mode); } if (cts->ios_valid & MMC_VCCQ) { ios->vccq = new_ios->vccq; if (bootverbose) device_printf(sc->dev, "VCCQ => %d\n", ios->vccq); res = dwmmc_switch_vccq(sc->dev, NULL); device_printf(sc->dev, "VCCQ switch result: %d\n", res); } return (dwmmc_update_ios(sc->dev, NULL)); } static int dwmmc_cam_request(device_t dev, union ccb *ccb) { struct dwmmc_softc *sc; struct ccb_mmcio *mmcio; sc = device_get_softc(dev); mmcio = &ccb->mmcio; DWMMC_LOCK(sc); #ifdef DEBUG if (__predict_false(bootverbose)) { device_printf(sc->dev, "CMD%u arg %#x flags %#x dlen %u dflags %#x\n", mmcio->cmd.opcode, mmcio->cmd.arg, mmcio->cmd.flags, mmcio->cmd.data != NULL ? (unsigned int) mmcio->cmd.data->len : 0, mmcio->cmd.data != NULL ? mmcio->cmd.data->flags: 0); } #endif if (mmcio->cmd.data != NULL) { if (mmcio->cmd.data->len == 0 || mmcio->cmd.data->flags == 0) panic("data->len = %d, data->flags = %d -- something is b0rked", (int)mmcio->cmd.data->len, mmcio->cmd.data->flags); } if (sc->ccb != NULL) { device_printf(sc->dev, "Controller still has an active command\n"); return (EBUSY); } sc->ccb = ccb; DWMMC_UNLOCK(sc); dwmmc_request(sc->dev, NULL, NULL); return (0); } static void dwmmc_cam_poll(device_t dev) { struct dwmmc_softc *sc; sc = device_get_softc(dev); dwmmc_intr(sc); } #endif /* MMCCAM */ static device_method_t dwmmc_methods[] = { /* Bus interface */ DEVMETHOD(bus_read_ivar, dwmmc_read_ivar), DEVMETHOD(bus_write_ivar, dwmmc_write_ivar), #ifndef MMCCAM /* mmcbr_if */ DEVMETHOD(mmcbr_update_ios, dwmmc_update_ios), DEVMETHOD(mmcbr_request, dwmmc_request), DEVMETHOD(mmcbr_get_ro, dwmmc_get_ro), DEVMETHOD(mmcbr_acquire_host, dwmmc_acquire_host), DEVMETHOD(mmcbr_release_host, dwmmc_release_host), #endif #ifdef MMCCAM /* MMCCAM interface */ DEVMETHOD(mmc_sim_get_tran_settings, dwmmc_get_tran_settings), DEVMETHOD(mmc_sim_set_tran_settings, dwmmc_set_tran_settings), DEVMETHOD(mmc_sim_cam_request, dwmmc_cam_request), DEVMETHOD(mmc_sim_cam_poll, dwmmc_cam_poll), DEVMETHOD(bus_add_child, bus_generic_add_child), #endif DEVMETHOD_END }; DEFINE_CLASS_0(dwmmc, dwmmc_driver, dwmmc_methods, sizeof(struct dwmmc_softc)); diff --git a/sys/dev/mvs/mvs_pci.c b/sys/dev/mvs/mvs_pci.c index 9743328d9593..9dfb5e3bbedd 100644 --- a/sys/dev/mvs/mvs_pci.c +++ b/sys/dev/mvs/mvs_pci.c @@ -1,521 +1,524 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2010 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mvs.h" /* local prototypes */ static int mvs_setup_interrupt(device_t dev); static void mvs_intr(void *data); static int mvs_suspend(device_t dev); static int mvs_resume(device_t dev); static int mvs_ctlr_setup(device_t dev); static struct { uint32_t id; uint8_t rev; const char *name; int ports; int quirks; } mvs_ids[] = { {0x504011ab, 0x00, "Marvell 88SX5040", 4, MVS_Q_GENI}, {0x504111ab, 0x00, "Marvell 88SX5041", 4, MVS_Q_GENI}, {0x508011ab, 0x00, "Marvell 88SX5080", 8, MVS_Q_GENI}, {0x508111ab, 0x00, "Marvell 88SX5081", 8, MVS_Q_GENI}, {0x604011ab, 0x00, "Marvell 88SX6040", 4, MVS_Q_GENII}, {0x604111ab, 0x00, "Marvell 88SX6041", 4, MVS_Q_GENII}, {0x604211ab, 0x00, "Marvell 88SX6042", 4, MVS_Q_GENIIE}, {0x608011ab, 0x00, "Marvell 88SX6080", 8, MVS_Q_GENII}, {0x608111ab, 0x00, "Marvell 88SX6081", 8, MVS_Q_GENII}, {0x704211ab, 0x00, "Marvell 88SX7042", 4, MVS_Q_GENIIE|MVS_Q_CT}, {0x02419005, 0x00, "Adaptec 1420SA", 4, MVS_Q_GENII}, {0x02439005, 0x00, "Adaptec 1430SA", 4, MVS_Q_GENIIE|MVS_Q_CT}, {0x00000000, 0x00, NULL, 0, 0} }; static int mvs_probe(device_t dev) { int i; uint32_t devid = pci_get_devid(dev); uint8_t revid = pci_get_revid(dev); for (i = 0; mvs_ids[i].id != 0; i++) { if (mvs_ids[i].id == devid && mvs_ids[i].rev <= revid) { device_set_descf(dev, "%s SATA controller", mvs_ids[i].name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int mvs_attach(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); device_t child; int error, unit, i; uint32_t devid = pci_get_devid(dev); uint8_t revid = pci_get_revid(dev); ctlr->dev = dev; i = 0; while (mvs_ids[i].id != 0 && (mvs_ids[i].id != devid || mvs_ids[i].rev > revid)) i++; ctlr->channels = mvs_ids[i].ports; ctlr->quirks = mvs_ids[i].quirks; ctlr->ccc = 0; resource_int_value(device_get_name(dev), device_get_unit(dev), "ccc", &ctlr->ccc); ctlr->cccc = 8; resource_int_value(device_get_name(dev), device_get_unit(dev), "cccc", &ctlr->cccc); if (ctlr->ccc == 0 || ctlr->cccc == 0) { ctlr->ccc = 0; ctlr->cccc = 0; } if (ctlr->ccc > 100000) ctlr->ccc = 100000; device_printf(dev, "Gen-%s, %d %sGbps ports, Port Multiplier %s%s\n", ((ctlr->quirks & MVS_Q_GENI) ? "I" : ((ctlr->quirks & MVS_Q_GENII) ? "II" : "IIe")), ctlr->channels, ((ctlr->quirks & MVS_Q_GENI) ? "1.5" : "3"), ((ctlr->quirks & MVS_Q_GENI) ? "not supported" : "supported"), ((ctlr->quirks & MVS_Q_GENIIE) ? " with FBS" : "")); mtx_init(&ctlr->mtx, "MVS controller lock", NULL, MTX_DEF); /* We should have a memory BAR(0). */ ctlr->r_rid = PCIR_BAR(0); if (!(ctlr->r_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &ctlr->r_rid, RF_ACTIVE))) return ENXIO; /* Setup our own memory management for channels. */ ctlr->sc_iomem.rm_start = rman_get_start(ctlr->r_mem); ctlr->sc_iomem.rm_end = rman_get_end(ctlr->r_mem); ctlr->sc_iomem.rm_type = RMAN_ARRAY; ctlr->sc_iomem.rm_descr = "I/O memory addresses"; if ((error = rman_init(&ctlr->sc_iomem)) != 0) { bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); return (error); } if ((error = rman_manage_region(&ctlr->sc_iomem, rman_get_start(ctlr->r_mem), rman_get_end(ctlr->r_mem))) != 0) { bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); rman_fini(&ctlr->sc_iomem); return (error); } pci_enable_busmaster(dev); mvs_ctlr_setup(dev); /* Setup interrupts. */ if (mvs_setup_interrupt(dev)) { bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); rman_fini(&ctlr->sc_iomem); return ENXIO; } /* Attach all channels on this controller */ for (unit = 0; unit < ctlr->channels; unit++) { child = device_add_child(dev, "mvsch", DEVICE_UNIT_ANY); if (child == NULL) device_printf(dev, "failed to add channel device\n"); else device_set_ivars(child, (void *)(intptr_t)unit); } bus_attach_children(dev); return 0; } static int mvs_detach(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); + int error; /* Detach & delete all children */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); /* Free interrupt. */ if (ctlr->irq.r_irq) { bus_teardown_intr(dev, ctlr->irq.r_irq, ctlr->irq.handle); bus_release_resource(dev, SYS_RES_IRQ, ctlr->irq.r_irq_rid, ctlr->irq.r_irq); } pci_release_msi(dev); /* Free memory. */ rman_fini(&ctlr->sc_iomem); if (ctlr->r_mem) bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); mtx_destroy(&ctlr->mtx); return (0); } static int mvs_ctlr_setup(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); int i, ccc = ctlr->ccc, cccc = ctlr->cccc, ccim = 0; /* Mask chip interrupts */ ATA_OUTL(ctlr->r_mem, CHIP_MIM, 0x00000000); /* Mask PCI interrupts */ ATA_OUTL(ctlr->r_mem, CHIP_PCIIM, 0x00000000); /* Clear PCI interrupts */ ATA_OUTL(ctlr->r_mem, CHIP_PCIIC, 0x00000000); if (ccc && bootverbose) { device_printf(dev, "CCC with %dus/%dcmd enabled\n", ctlr->ccc, ctlr->cccc); } ccc *= 150; /* Configure chip-global CCC */ if (ctlr->channels > 4 && (ctlr->quirks & MVS_Q_GENI) == 0) { ATA_OUTL(ctlr->r_mem, CHIP_ICT, cccc); ATA_OUTL(ctlr->r_mem, CHIP_ITT, ccc); ATA_OUTL(ctlr->r_mem, CHIP_ICC, ~CHIP_ICC_ALL_PORTS); if (ccc) ccim |= IC_ALL_PORTS_COAL_DONE; ccc = 0; cccc = 0; } for (i = 0; i < ctlr->channels / 4; i++) { /* Configure per-HC CCC */ ATA_OUTL(ctlr->r_mem, HC_BASE(i) + HC_ICT, cccc); ATA_OUTL(ctlr->r_mem, HC_BASE(i) + HC_ITT, ccc); if (ccc) ccim |= (IC_HC0_COAL_DONE << (i * IC_HC_SHIFT)); /* Clear HC interrupts */ ATA_OUTL(ctlr->r_mem, HC_BASE(i) + HC_IC, 0x00000000); } /* Enable chip interrupts */ ctlr->gmim = (ccim ? ccim : (IC_DONE_HC0 | IC_DONE_HC1)) | IC_ERR_HC0 | IC_ERR_HC1; ctlr->mim = ctlr->gmim | ctlr->pmim; ATA_OUTL(ctlr->r_mem, CHIP_MIM, ctlr->mim); /* Enable PCI interrupts */ ATA_OUTL(ctlr->r_mem, CHIP_PCIIM, 0x007fffff); return (0); } static void mvs_edma(device_t dev, device_t child, int mode) { struct mvs_controller *ctlr = device_get_softc(dev); int unit = ((struct mvs_channel *)device_get_softc(child))->unit; int bit = IC_DONE_IRQ << (unit * 2 + unit / 4) ; if (ctlr->ccc == 0) return; /* CCC is not working for non-EDMA mode. Unmask device interrupts. */ mtx_lock(&ctlr->mtx); if (mode == MVS_EDMA_OFF) ctlr->pmim |= bit; else ctlr->pmim &= ~bit; ctlr->mim = ctlr->gmim | ctlr->pmim; if (!ctlr->msia) ATA_OUTL(ctlr->r_mem, CHIP_MIM, ctlr->mim); mtx_unlock(&ctlr->mtx); } static int mvs_suspend(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); bus_generic_suspend(dev); /* Mask chip interrupts */ ATA_OUTL(ctlr->r_mem, CHIP_MIM, 0x00000000); /* Mask PCI interrupts */ ATA_OUTL(ctlr->r_mem, CHIP_PCIIM, 0x00000000); return 0; } static int mvs_resume(device_t dev) { mvs_ctlr_setup(dev); return (bus_generic_resume(dev)); } static int mvs_setup_interrupt(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); int msi = 0; /* Process hints. */ resource_int_value(device_get_name(dev), device_get_unit(dev), "msi", &msi); if (msi < 0) msi = 0; else if (msi > 0) msi = min(1, pci_msi_count(dev)); /* Allocate MSI if needed/present. */ if (msi && pci_alloc_msi(dev, &msi) != 0) msi = 0; ctlr->msi = msi; /* Allocate all IRQs. */ ctlr->irq.r_irq_rid = msi ? 1 : 0; if (!(ctlr->irq.r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &ctlr->irq.r_irq_rid, RF_SHAREABLE | RF_ACTIVE))) { device_printf(dev, "unable to map interrupt\n"); return (ENXIO); } if ((bus_setup_intr(dev, ctlr->irq.r_irq, ATA_INTR_FLAGS, NULL, mvs_intr, ctlr, &ctlr->irq.handle))) { device_printf(dev, "unable to setup interrupt\n"); bus_release_resource(dev, SYS_RES_IRQ, ctlr->irq.r_irq_rid, ctlr->irq.r_irq); ctlr->irq.r_irq = NULL; return (ENXIO); } return (0); } /* * Common case interrupt handler. */ static void mvs_intr(void *data) { struct mvs_controller *ctlr = data; struct mvs_intr_arg arg; void (*function)(void *); int p; u_int32_t ic, aic; ic = ATA_INL(ctlr->r_mem, CHIP_MIC); if (ctlr->msi) { /* We have to mask MSI during processing. */ mtx_lock(&ctlr->mtx); ATA_OUTL(ctlr->r_mem, CHIP_MIM, 0); ctlr->msia = 1; /* Deny MIM update during processing. */ mtx_unlock(&ctlr->mtx); } else if (ic == 0) return; /* Acknowledge all-ports CCC interrupt. */ if (ic & IC_ALL_PORTS_COAL_DONE) ATA_OUTL(ctlr->r_mem, CHIP_ICC, ~CHIP_ICC_ALL_PORTS); for (p = 0; p < ctlr->channels; p++) { if ((p & 3) == 0) { if (p != 0) ic >>= 1; if ((ic & IC_HC0) == 0) { p += 3; ic >>= 8; continue; } /* Acknowledge interrupts of this HC. */ aic = 0; if (ic & (IC_DONE_IRQ << 0)) aic |= HC_IC_DONE(0) | HC_IC_DEV(0); if (ic & (IC_DONE_IRQ << 2)) aic |= HC_IC_DONE(1) | HC_IC_DEV(1); if (ic & (IC_DONE_IRQ << 4)) aic |= HC_IC_DONE(2) | HC_IC_DEV(2); if (ic & (IC_DONE_IRQ << 6)) aic |= HC_IC_DONE(3) | HC_IC_DEV(3); if (ic & IC_HC0_COAL_DONE) aic |= HC_IC_COAL; ATA_OUTL(ctlr->r_mem, HC_BASE(p == 4) + HC_IC, ~aic); } /* Call per-port interrupt handler. */ arg.cause = ic & (IC_ERR_IRQ|IC_DONE_IRQ); if ((arg.cause != 0) && (function = ctlr->interrupt[p].function)) { arg.arg = ctlr->interrupt[p].argument; function(&arg); } ic >>= 2; } if (ctlr->msi) { /* Unmasking MSI triggers next interrupt, if needed. */ mtx_lock(&ctlr->mtx); ctlr->msia = 0; /* Allow MIM update. */ ATA_OUTL(ctlr->r_mem, CHIP_MIM, ctlr->mim); mtx_unlock(&ctlr->mtx); } } static struct resource * mvs_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct mvs_controller *ctlr = device_get_softc(dev); int unit = ((struct mvs_channel *)device_get_softc(child))->unit; struct resource *res = NULL; int offset = HC_BASE(unit >> 2) + PORT_BASE(unit & 0x03); rman_res_t st; switch (type) { case SYS_RES_MEMORY: st = rman_get_start(ctlr->r_mem); res = rman_reserve_resource(&ctlr->sc_iomem, st + offset, st + offset + PORT_SIZE - 1, PORT_SIZE, RF_ACTIVE, child); if (res) { bus_space_handle_t bsh; bus_space_tag_t bst; bsh = rman_get_bushandle(ctlr->r_mem); bst = rman_get_bustag(ctlr->r_mem); bus_space_subregion(bst, bsh, offset, PORT_SIZE, &bsh); rman_set_bushandle(res, bsh); rman_set_bustag(res, bst); } break; case SYS_RES_IRQ: if (*rid == ATA_IRQ_RID) res = ctlr->irq.r_irq; break; } return (res); } static int mvs_release_resource(device_t dev, device_t child, struct resource *r) { switch (rman_get_type(r)) { case SYS_RES_MEMORY: rman_release_resource(r); return (0); case SYS_RES_IRQ: if (rman_get_rid(r) != ATA_IRQ_RID) return ENOENT; return (0); } return (EINVAL); } static int mvs_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *function, void *argument, void **cookiep) { struct mvs_controller *ctlr = device_get_softc(dev); int unit = (intptr_t)device_get_ivars(child); if (filter != NULL) { printf("mvs.c: we cannot use a filter here\n"); return (EINVAL); } ctlr->interrupt[unit].function = function; ctlr->interrupt[unit].argument = argument; return (0); } static int mvs_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { struct mvs_controller *ctlr = device_get_softc(dev); int unit = (intptr_t)device_get_ivars(child); ctlr->interrupt[unit].function = NULL; ctlr->interrupt[unit].argument = NULL; return (0); } static int mvs_print_child(device_t dev, device_t child) { int retval; retval = bus_print_child_header(dev, child); retval += printf(" at channel %d", (int)(intptr_t)device_get_ivars(child)); retval += bus_print_child_footer(dev, child); return (retval); } static int mvs_child_location(device_t dev, device_t child, struct sbuf *sb) { sbuf_printf(sb, "channel=%d", (int)(intptr_t)device_get_ivars(child)); return (0); } static bus_dma_tag_t mvs_get_dma_tag(device_t bus, device_t child) { return (bus_get_dma_tag(bus)); } static device_method_t mvs_methods[] = { DEVMETHOD(device_probe, mvs_probe), DEVMETHOD(device_attach, mvs_attach), DEVMETHOD(device_detach, mvs_detach), DEVMETHOD(device_suspend, mvs_suspend), DEVMETHOD(device_resume, mvs_resume), DEVMETHOD(bus_print_child, mvs_print_child), DEVMETHOD(bus_alloc_resource, mvs_alloc_resource), DEVMETHOD(bus_release_resource, mvs_release_resource), DEVMETHOD(bus_setup_intr, mvs_setup_intr), DEVMETHOD(bus_teardown_intr,mvs_teardown_intr), DEVMETHOD(bus_child_location, mvs_child_location), DEVMETHOD(bus_get_dma_tag, mvs_get_dma_tag), DEVMETHOD(mvs_edma, mvs_edma), { 0, 0 } }; static driver_t mvs_driver = { "mvs", mvs_methods, sizeof(struct mvs_controller) }; DRIVER_MODULE(mvs, pci, mvs_driver, 0, 0); MODULE_PNP_INFO("W32:vendor/device", pci, mvs, mvs_ids, nitems(mvs_ids) - 1); MODULE_VERSION(mvs, 1); MODULE_DEPEND(mvs, cam, 1, 1, 1); diff --git a/sys/dev/mvs/mvs_soc.c b/sys/dev/mvs/mvs_soc.c index 41f2bf648c9d..8712dc0123e8 100644 --- a/sys/dev/mvs/mvs_soc.c +++ b/sys/dev/mvs/mvs_soc.c @@ -1,462 +1,465 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2010 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mvs.h" /* local prototypes */ static int mvs_setup_interrupt(device_t dev); static void mvs_intr(void *data); static int mvs_suspend(device_t dev); static int mvs_resume(device_t dev); static int mvs_ctlr_setup(device_t dev); static struct { uint32_t id; uint8_t rev; const char *name; int ports; int quirks; } mvs_ids[] = { {MV_DEV_88F5182, 0x00, "Marvell 88F5182", 2, MVS_Q_GENIIE|MVS_Q_SOC}, {MV_DEV_88F6281, 0x00, "Marvell 88F6281", 2, MVS_Q_GENIIE|MVS_Q_SOC}, {MV_DEV_88F6282, 0x00, "Marvell 88F6282", 2, MVS_Q_GENIIE|MVS_Q_SOC}, {MV_DEV_MV78100, 0x00, "Marvell MV78100", 2, MVS_Q_GENIIE|MVS_Q_SOC}, {MV_DEV_MV78100_Z0, 0x00,"Marvell MV78100", 2, MVS_Q_GENIIE|MVS_Q_SOC}, {MV_DEV_MV78260, 0x00, "Marvell MV78260", 2, MVS_Q_GENIIE|MVS_Q_SOC}, {MV_DEV_MV78460, 0x00, "Marvell MV78460", 2, MVS_Q_GENIIE|MVS_Q_SOC}, {0, 0x00, NULL, 0, 0} }; static int mvs_probe(device_t dev) { int i; uint32_t devid, revid; if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "mrvl,sata")) return (ENXIO); soc_id(&devid, &revid); for (i = 0; mvs_ids[i].id != 0; i++) { if (mvs_ids[i].id == devid && mvs_ids[i].rev <= revid) { device_set_descf(dev, "%s SATA controller", mvs_ids[i].name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int mvs_attach(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); device_t child; int error, unit, i; uint32_t devid, revid; soc_id(&devid, &revid); ctlr->dev = dev; i = 0; while (mvs_ids[i].id != 0 && (mvs_ids[i].id != devid || mvs_ids[i].rev > revid)) i++; ctlr->channels = mvs_ids[i].ports; ctlr->quirks = mvs_ids[i].quirks; ctlr->ccc = 0; resource_int_value(device_get_name(dev), device_get_unit(dev), "ccc", &ctlr->ccc); ctlr->cccc = 8; resource_int_value(device_get_name(dev), device_get_unit(dev), "cccc", &ctlr->cccc); if (ctlr->ccc == 0 || ctlr->cccc == 0) { ctlr->ccc = 0; ctlr->cccc = 0; } if (ctlr->ccc > 100000) ctlr->ccc = 100000; device_printf(dev, "Gen-%s, %d %sGbps ports, Port Multiplier %s%s\n", ((ctlr->quirks & MVS_Q_GENI) ? "I" : ((ctlr->quirks & MVS_Q_GENII) ? "II" : "IIe")), ctlr->channels, ((ctlr->quirks & MVS_Q_GENI) ? "1.5" : "3"), ((ctlr->quirks & MVS_Q_GENI) ? "not supported" : "supported"), ((ctlr->quirks & MVS_Q_GENIIE) ? " with FBS" : "")); mtx_init(&ctlr->mtx, "MVS controller lock", NULL, MTX_DEF); /* We should have a memory BAR(0). */ ctlr->r_rid = 0; if (!(ctlr->r_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &ctlr->r_rid, RF_ACTIVE))) return ENXIO; if (ATA_INL(ctlr->r_mem, PORT_BASE(0) + SATA_PHYCFG_OFS) != 0) ctlr->quirks |= MVS_Q_SOC65; /* Setup our own memory management for channels. */ ctlr->sc_iomem.rm_start = rman_get_start(ctlr->r_mem); ctlr->sc_iomem.rm_end = rman_get_end(ctlr->r_mem); ctlr->sc_iomem.rm_type = RMAN_ARRAY; ctlr->sc_iomem.rm_descr = "I/O memory addresses"; if ((error = rman_init(&ctlr->sc_iomem)) != 0) { bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); return (error); } if ((error = rman_manage_region(&ctlr->sc_iomem, rman_get_start(ctlr->r_mem), rman_get_end(ctlr->r_mem))) != 0) { bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); rman_fini(&ctlr->sc_iomem); return (error); } mvs_ctlr_setup(dev); /* Setup interrupts. */ if (mvs_setup_interrupt(dev)) { bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); rman_fini(&ctlr->sc_iomem); return ENXIO; } /* Attach all channels on this controller */ for (unit = 0; unit < ctlr->channels; unit++) { child = device_add_child(dev, "mvsch", DEVICE_UNIT_ANY); if (child == NULL) device_printf(dev, "failed to add channel device\n"); else device_set_ivars(child, (void *)(intptr_t)unit); } bus_attach_children(dev); return 0; } static int mvs_detach(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); + int error; /* Detach & delete all children */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); /* Free interrupt. */ if (ctlr->irq.r_irq) { bus_teardown_intr(dev, ctlr->irq.r_irq, ctlr->irq.handle); bus_release_resource(dev, SYS_RES_IRQ, ctlr->irq.r_irq_rid, ctlr->irq.r_irq); } /* Free memory. */ rman_fini(&ctlr->sc_iomem); if (ctlr->r_mem) bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); mtx_destroy(&ctlr->mtx); return (0); } static int mvs_ctlr_setup(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); int ccc = ctlr->ccc, cccc = ctlr->cccc; /* Mask chip interrupts */ ATA_OUTL(ctlr->r_mem, CHIP_SOC_MIM, 0x00000000); /* Clear HC interrupts */ ATA_OUTL(ctlr->r_mem, HC_IC, 0x00000000); /* Clear chip interrupts */ ATA_OUTL(ctlr->r_mem, CHIP_SOC_MIC, 0); /* Configure per-HC CCC */ if (ccc && bootverbose) { device_printf(dev, "CCC with %dus/%dcmd enabled\n", ctlr->ccc, ctlr->cccc); } ccc *= 150; ATA_OUTL(ctlr->r_mem, HC_ICT, cccc); ATA_OUTL(ctlr->r_mem, HC_ITT, ccc); /* Enable chip interrupts */ ctlr->gmim = ((ccc ? IC_HC0_COAL_DONE : (IC_DONE_HC0 & CHIP_SOC_HC0_MASK(ctlr->channels))) | (IC_ERR_HC0 & CHIP_SOC_HC0_MASK(ctlr->channels))); ATA_OUTL(ctlr->r_mem, CHIP_SOC_MIM, ctlr->gmim | ctlr->pmim); return (0); } static void mvs_edma(device_t dev, device_t child, int mode) { struct mvs_controller *ctlr = device_get_softc(dev); int unit = ((struct mvs_channel *)device_get_softc(child))->unit; int bit = IC_DONE_IRQ << (unit * 2); if (ctlr->ccc == 0) return; /* CCC is not working for non-EDMA mode. Unmask device interrupts. */ mtx_lock(&ctlr->mtx); if (mode == MVS_EDMA_OFF) ctlr->pmim |= bit; else ctlr->pmim &= ~bit; ATA_OUTL(ctlr->r_mem, CHIP_SOC_MIM, ctlr->gmim | ctlr->pmim); mtx_unlock(&ctlr->mtx); } static int mvs_suspend(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); bus_generic_suspend(dev); /* Mask chip interrupts */ ATA_OUTL(ctlr->r_mem, CHIP_SOC_MIM, 0x00000000); return 0; } static int mvs_resume(device_t dev) { mvs_ctlr_setup(dev); return (bus_generic_resume(dev)); } static int mvs_setup_interrupt(device_t dev) { struct mvs_controller *ctlr = device_get_softc(dev); /* Allocate all IRQs. */ ctlr->irq.r_irq_rid = 0; if (!(ctlr->irq.r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &ctlr->irq.r_irq_rid, RF_SHAREABLE | RF_ACTIVE))) { device_printf(dev, "unable to map interrupt\n"); return (ENXIO); } if ((bus_setup_intr(dev, ctlr->irq.r_irq, ATA_INTR_FLAGS, NULL, mvs_intr, ctlr, &ctlr->irq.handle))) { device_printf(dev, "unable to setup interrupt\n"); bus_release_resource(dev, SYS_RES_IRQ, ctlr->irq.r_irq_rid, ctlr->irq.r_irq); ctlr->irq.r_irq = NULL; return (ENXIO); } return (0); } /* * Common case interrupt handler. */ static void mvs_intr(void *data) { struct mvs_controller *ctlr = data; struct mvs_intr_arg arg; void (*function)(void *); int p, chan_num; u_int32_t ic, aic; ic = ATA_INL(ctlr->r_mem, CHIP_SOC_MIC); if ((ic & IC_HC0) == 0) return; /* Acknowledge interrupts of this HC. */ aic = 0; /* Processing interrupts from each initialized channel */ for (chan_num = 0; chan_num < ctlr->channels; chan_num++) { if (ic & (IC_DONE_IRQ << (chan_num * 2))) aic |= HC_IC_DONE(chan_num) | HC_IC_DEV(chan_num); } if (ic & IC_HC0_COAL_DONE) aic |= HC_IC_COAL; ATA_OUTL(ctlr->r_mem, HC_IC, ~aic); /* Call per-port interrupt handler. */ for (p = 0; p < ctlr->channels; p++) { arg.cause = ic & (IC_ERR_IRQ|IC_DONE_IRQ); if ((arg.cause != 0) && (function = ctlr->interrupt[p].function)) { arg.arg = ctlr->interrupt[p].argument; function(&arg); } ic >>= 2; } } static struct resource * mvs_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct mvs_controller *ctlr = device_get_softc(dev); int unit = ((struct mvs_channel *)device_get_softc(child))->unit; struct resource *res = NULL; int offset = PORT_BASE(unit & 0x03); rman_res_t st; switch (type) { case SYS_RES_MEMORY: st = rman_get_start(ctlr->r_mem); res = rman_reserve_resource(&ctlr->sc_iomem, st + offset, st + offset + PORT_SIZE - 1, PORT_SIZE, RF_ACTIVE, child); if (res) { bus_space_handle_t bsh; bus_space_tag_t bst; bsh = rman_get_bushandle(ctlr->r_mem); bst = rman_get_bustag(ctlr->r_mem); bus_space_subregion(bst, bsh, offset, PORT_SIZE, &bsh); rman_set_bushandle(res, bsh); rman_set_bustag(res, bst); } break; case SYS_RES_IRQ: if (*rid == ATA_IRQ_RID) res = ctlr->irq.r_irq; break; } return (res); } static int mvs_release_resource(device_t dev, device_t child, struct resource *r) { switch (rman_get_type(r)) { case SYS_RES_MEMORY: rman_release_resource(r); return (0); case SYS_RES_IRQ: if (rman_get_rid(r) != ATA_IRQ_RID) return ENOENT; return (0); } return (EINVAL); } static int mvs_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *function, void *argument, void **cookiep) { struct mvs_controller *ctlr = device_get_softc(dev); int unit = (intptr_t)device_get_ivars(child); if (filter != NULL) { printf("mvs.c: we cannot use a filter here\n"); return (EINVAL); } ctlr->interrupt[unit].function = function; ctlr->interrupt[unit].argument = argument; return (0); } static int mvs_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { struct mvs_controller *ctlr = device_get_softc(dev); int unit = (intptr_t)device_get_ivars(child); ctlr->interrupt[unit].function = NULL; ctlr->interrupt[unit].argument = NULL; return (0); } static int mvs_print_child(device_t dev, device_t child) { int retval; retval = bus_print_child_header(dev, child); retval += printf(" at channel %d", (int)(intptr_t)device_get_ivars(child)); retval += bus_print_child_footer(dev, child); return (retval); } static int mvs_child_location(device_t dev, device_t child, struct sbuf *sb) { sbuf_printf(sb, "channel=%d", (int)(intptr_t)device_get_ivars(child)); return (0); } static bus_dma_tag_t mvs_get_dma_tag(device_t bus, device_t child) { return (bus_get_dma_tag(bus)); } static device_method_t mvs_methods[] = { DEVMETHOD(device_probe, mvs_probe), DEVMETHOD(device_attach, mvs_attach), DEVMETHOD(device_detach, mvs_detach), DEVMETHOD(device_suspend, mvs_suspend), DEVMETHOD(device_resume, mvs_resume), DEVMETHOD(bus_print_child, mvs_print_child), DEVMETHOD(bus_alloc_resource, mvs_alloc_resource), DEVMETHOD(bus_release_resource, mvs_release_resource), DEVMETHOD(bus_setup_intr, mvs_setup_intr), DEVMETHOD(bus_teardown_intr,mvs_teardown_intr), DEVMETHOD(bus_child_location, mvs_child_location), DEVMETHOD(bus_get_dma_tag, mvs_get_dma_tag), DEVMETHOD(mvs_edma, mvs_edma), { 0, 0 } }; static driver_t mvs_driver = { "mvs", mvs_methods, sizeof(struct mvs_controller) }; DRIVER_MODULE(mvs, simplebus, mvs_driver, 0, 0); MODULE_VERSION(mvs, 1); MODULE_DEPEND(mvs, cam, 1, 1, 1); diff --git a/sys/dev/neta/if_mvneta.c b/sys/dev/neta/if_mvneta.c index 84cbc1e43462..1c6247adb56b 100644 --- a/sys/dev/neta/if_mvneta.c +++ b/sys/dev/neta/if_mvneta.c @@ -1,3613 +1,3613 @@ /* * Copyright (c) 2017 Stormshield. * Copyright (c) 2017 Semihalf. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef MVNETA_KTR #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(__aarch64__) #include #include #endif #include "if_mvnetareg.h" #include "if_mvnetavar.h" #include "miibus_if.h" #include "mdio_if.h" #ifdef MVNETA_DEBUG #define STATIC /* nothing */ #else #define STATIC static #endif #define DASSERT(x) KASSERT((x), (#x)) #define A3700_TCLK_250MHZ 250000000 /* Device Register Initialization */ STATIC int mvneta_initreg(if_t); /* Descriptor Ring Control for each of queues */ STATIC int mvneta_ring_alloc_rx_queue(struct mvneta_softc *, int); STATIC int mvneta_ring_alloc_tx_queue(struct mvneta_softc *, int); STATIC void mvneta_ring_dealloc_rx_queue(struct mvneta_softc *, int); STATIC void mvneta_ring_dealloc_tx_queue(struct mvneta_softc *, int); STATIC int mvneta_ring_init_rx_queue(struct mvneta_softc *, int); STATIC int mvneta_ring_init_tx_queue(struct mvneta_softc *, int); STATIC void mvneta_ring_flush_rx_queue(struct mvneta_softc *, int); STATIC void mvneta_ring_flush_tx_queue(struct mvneta_softc *, int); STATIC void mvneta_dmamap_cb(void *, bus_dma_segment_t *, int, int); STATIC int mvneta_dma_create(struct mvneta_softc *); /* Rx/Tx Queue Control */ STATIC int mvneta_rx_queue_init(if_t, int); STATIC int mvneta_tx_queue_init(if_t, int); STATIC int mvneta_rx_queue_enable(if_t, int); STATIC int mvneta_tx_queue_enable(if_t, int); STATIC void mvneta_rx_lockq(struct mvneta_softc *, int); STATIC void mvneta_rx_unlockq(struct mvneta_softc *, int); STATIC void mvneta_tx_lockq(struct mvneta_softc *, int); STATIC void mvneta_tx_unlockq(struct mvneta_softc *, int); /* Interrupt Handlers */ STATIC void mvneta_disable_intr(struct mvneta_softc *); STATIC void mvneta_enable_intr(struct mvneta_softc *); STATIC void mvneta_rxtxth_intr(void *); STATIC int mvneta_misc_intr(struct mvneta_softc *); STATIC void mvneta_tick(void *); /* struct ifnet and mii callbacks*/ STATIC int mvneta_xmitfast_locked(struct mvneta_softc *, int, struct mbuf **); STATIC int mvneta_xmit_locked(struct mvneta_softc *, int); #ifdef MVNETA_MULTIQUEUE STATIC int mvneta_transmit(if_t, struct mbuf *); #else /* !MVNETA_MULTIQUEUE */ STATIC void mvneta_start(if_t); #endif STATIC void mvneta_qflush(if_t); STATIC void mvneta_tx_task(void *, int); STATIC int mvneta_ioctl(if_t, u_long, caddr_t); STATIC void mvneta_init(void *); STATIC void mvneta_init_locked(void *); STATIC void mvneta_stop(struct mvneta_softc *); STATIC void mvneta_stop_locked(struct mvneta_softc *); STATIC int mvneta_mediachange(if_t); STATIC void mvneta_mediastatus(if_t, struct ifmediareq *); STATIC void mvneta_portup(struct mvneta_softc *); STATIC void mvneta_portdown(struct mvneta_softc *); /* Link State Notify */ STATIC void mvneta_update_autoneg(struct mvneta_softc *, int); STATIC int mvneta_update_media(struct mvneta_softc *, int); STATIC void mvneta_adjust_link(struct mvneta_softc *); STATIC void mvneta_update_eee(struct mvneta_softc *); STATIC void mvneta_update_fc(struct mvneta_softc *); STATIC void mvneta_link_isr(struct mvneta_softc *); STATIC void mvneta_linkupdate(struct mvneta_softc *, boolean_t); STATIC void mvneta_linkup(struct mvneta_softc *); STATIC void mvneta_linkdown(struct mvneta_softc *); STATIC void mvneta_linkreset(struct mvneta_softc *); /* Tx Subroutines */ STATIC int mvneta_tx_queue(struct mvneta_softc *, struct mbuf **, int); STATIC void mvneta_tx_set_csumflag(if_t, struct mvneta_tx_desc *, struct mbuf *); STATIC void mvneta_tx_queue_complete(struct mvneta_softc *, int); STATIC void mvneta_tx_drain(struct mvneta_softc *); /* Rx Subroutines */ STATIC int mvneta_rx(struct mvneta_softc *, int, int); STATIC void mvneta_rx_queue(struct mvneta_softc *, int, int); STATIC void mvneta_rx_queue_refill(struct mvneta_softc *, int); STATIC void mvneta_rx_set_csumflag(if_t, struct mvneta_rx_desc *, struct mbuf *); STATIC void mvneta_rx_buf_free(struct mvneta_softc *, struct mvneta_buf *); /* MAC address filter */ STATIC void mvneta_filter_setup(struct mvneta_softc *); /* sysctl(9) */ STATIC int sysctl_read_mib(SYSCTL_HANDLER_ARGS); STATIC int sysctl_clear_mib(SYSCTL_HANDLER_ARGS); STATIC int sysctl_set_queue_rxthtime(SYSCTL_HANDLER_ARGS); STATIC void sysctl_mvneta_init(struct mvneta_softc *); /* MIB */ STATIC void mvneta_clear_mib(struct mvneta_softc *); STATIC uint64_t mvneta_read_mib(struct mvneta_softc *, int); STATIC void mvneta_update_mib(struct mvneta_softc *); /* Switch */ STATIC boolean_t mvneta_has_switch(device_t); #define mvneta_sc_lock(sc) mtx_lock(&sc->mtx) #define mvneta_sc_unlock(sc) mtx_unlock(&sc->mtx) STATIC struct mtx mii_mutex; STATIC int mii_init = 0; /* Device */ STATIC int mvneta_detach(device_t); /* MII */ STATIC int mvneta_miibus_readreg(device_t, int, int); STATIC int mvneta_miibus_writereg(device_t, int, int, int); static device_method_t mvneta_methods[] = { /* Device interface */ DEVMETHOD(device_detach, mvneta_detach), /* MII interface */ DEVMETHOD(miibus_readreg, mvneta_miibus_readreg), DEVMETHOD(miibus_writereg, mvneta_miibus_writereg), /* MDIO interface */ DEVMETHOD(mdio_readreg, mvneta_miibus_readreg), DEVMETHOD(mdio_writereg, mvneta_miibus_writereg), /* End */ DEVMETHOD_END }; DEFINE_CLASS_0(mvneta, mvneta_driver, mvneta_methods, sizeof(struct mvneta_softc)); DRIVER_MODULE(miibus, mvneta, miibus_driver, 0, 0); DRIVER_MODULE(mdio, mvneta, mdio_driver, 0, 0); MODULE_DEPEND(mvneta, mdio, 1, 1, 1); MODULE_DEPEND(mvneta, ether, 1, 1, 1); MODULE_DEPEND(mvneta, miibus, 1, 1, 1); MODULE_DEPEND(mvneta, mvxpbm, 1, 1, 1); /* * List of MIB register and names */ enum mvneta_mib_idx { MVNETA_MIB_RX_GOOD_OCT_IDX, MVNETA_MIB_RX_BAD_OCT_IDX, MVNETA_MIB_TX_MAC_TRNS_ERR_IDX, MVNETA_MIB_RX_GOOD_FRAME_IDX, MVNETA_MIB_RX_BAD_FRAME_IDX, MVNETA_MIB_RX_BCAST_FRAME_IDX, MVNETA_MIB_RX_MCAST_FRAME_IDX, MVNETA_MIB_RX_FRAME64_OCT_IDX, MVNETA_MIB_RX_FRAME127_OCT_IDX, MVNETA_MIB_RX_FRAME255_OCT_IDX, MVNETA_MIB_RX_FRAME511_OCT_IDX, MVNETA_MIB_RX_FRAME1023_OCT_IDX, MVNETA_MIB_RX_FRAMEMAX_OCT_IDX, MVNETA_MIB_TX_GOOD_OCT_IDX, MVNETA_MIB_TX_GOOD_FRAME_IDX, MVNETA_MIB_TX_EXCES_COL_IDX, MVNETA_MIB_TX_MCAST_FRAME_IDX, MVNETA_MIB_TX_BCAST_FRAME_IDX, MVNETA_MIB_TX_MAC_CTL_ERR_IDX, MVNETA_MIB_FC_SENT_IDX, MVNETA_MIB_FC_GOOD_IDX, MVNETA_MIB_FC_BAD_IDX, MVNETA_MIB_PKT_UNDERSIZE_IDX, MVNETA_MIB_PKT_FRAGMENT_IDX, MVNETA_MIB_PKT_OVERSIZE_IDX, MVNETA_MIB_PKT_JABBER_IDX, MVNETA_MIB_MAC_RX_ERR_IDX, MVNETA_MIB_MAC_CRC_ERR_IDX, MVNETA_MIB_MAC_COL_IDX, MVNETA_MIB_MAC_LATE_COL_IDX, }; STATIC struct mvneta_mib_def { uint32_t regnum; int reg64; const char *sysctl_name; const char *desc; } mvneta_mib_list[] = { [MVNETA_MIB_RX_GOOD_OCT_IDX] = {MVNETA_MIB_RX_GOOD_OCT, 1, "rx_good_oct", "Good Octets Rx"}, [MVNETA_MIB_RX_BAD_OCT_IDX] = {MVNETA_MIB_RX_BAD_OCT, 0, "rx_bad_oct", "Bad Octets Rx"}, [MVNETA_MIB_TX_MAC_TRNS_ERR_IDX] = {MVNETA_MIB_TX_MAC_TRNS_ERR, 0, "tx_mac_err", "MAC Transmit Error"}, [MVNETA_MIB_RX_GOOD_FRAME_IDX] = {MVNETA_MIB_RX_GOOD_FRAME, 0, "rx_good_frame", "Good Frames Rx"}, [MVNETA_MIB_RX_BAD_FRAME_IDX] = {MVNETA_MIB_RX_BAD_FRAME, 0, "rx_bad_frame", "Bad Frames Rx"}, [MVNETA_MIB_RX_BCAST_FRAME_IDX] = {MVNETA_MIB_RX_BCAST_FRAME, 0, "rx_bcast_frame", "Broadcast Frames Rx"}, [MVNETA_MIB_RX_MCAST_FRAME_IDX] = {MVNETA_MIB_RX_MCAST_FRAME, 0, "rx_mcast_frame", "Multicast Frames Rx"}, [MVNETA_MIB_RX_FRAME64_OCT_IDX] = {MVNETA_MIB_RX_FRAME64_OCT, 0, "rx_frame_1_64", "Frame Size 1 - 64"}, [MVNETA_MIB_RX_FRAME127_OCT_IDX] = {MVNETA_MIB_RX_FRAME127_OCT, 0, "rx_frame_65_127", "Frame Size 65 - 127"}, [MVNETA_MIB_RX_FRAME255_OCT_IDX] = {MVNETA_MIB_RX_FRAME255_OCT, 0, "rx_frame_128_255", "Frame Size 128 - 255"}, [MVNETA_MIB_RX_FRAME511_OCT_IDX] = {MVNETA_MIB_RX_FRAME511_OCT, 0, "rx_frame_256_511", "Frame Size 256 - 511"}, [MVNETA_MIB_RX_FRAME1023_OCT_IDX] = {MVNETA_MIB_RX_FRAME1023_OCT, 0, "rx_frame_512_1023", "Frame Size 512 - 1023"}, [MVNETA_MIB_RX_FRAMEMAX_OCT_IDX] = {MVNETA_MIB_RX_FRAMEMAX_OCT, 0, "rx_fame_1024_max", "Frame Size 1024 - Max"}, [MVNETA_MIB_TX_GOOD_OCT_IDX] = {MVNETA_MIB_TX_GOOD_OCT, 1, "tx_good_oct", "Good Octets Tx"}, [MVNETA_MIB_TX_GOOD_FRAME_IDX] = {MVNETA_MIB_TX_GOOD_FRAME, 0, "tx_good_frame", "Good Frames Tx"}, [MVNETA_MIB_TX_EXCES_COL_IDX] = {MVNETA_MIB_TX_EXCES_COL, 0, "tx_exces_collision", "Excessive Collision"}, [MVNETA_MIB_TX_MCAST_FRAME_IDX] = {MVNETA_MIB_TX_MCAST_FRAME, 0, "tx_mcast_frame", "Multicast Frames Tx"}, [MVNETA_MIB_TX_BCAST_FRAME_IDX] = {MVNETA_MIB_TX_BCAST_FRAME, 0, "tx_bcast_frame", "Broadcast Frames Tx"}, [MVNETA_MIB_TX_MAC_CTL_ERR_IDX] = {MVNETA_MIB_TX_MAC_CTL_ERR, 0, "tx_mac_ctl_err", "Unknown MAC Control"}, [MVNETA_MIB_FC_SENT_IDX] = {MVNETA_MIB_FC_SENT, 0, "fc_tx", "Flow Control Tx"}, [MVNETA_MIB_FC_GOOD_IDX] = {MVNETA_MIB_FC_GOOD, 0, "fc_rx_good", "Good Flow Control Rx"}, [MVNETA_MIB_FC_BAD_IDX] = {MVNETA_MIB_FC_BAD, 0, "fc_rx_bad", "Bad Flow Control Rx"}, [MVNETA_MIB_PKT_UNDERSIZE_IDX] = {MVNETA_MIB_PKT_UNDERSIZE, 0, "pkt_undersize", "Undersized Packets Rx"}, [MVNETA_MIB_PKT_FRAGMENT_IDX] = {MVNETA_MIB_PKT_FRAGMENT, 0, "pkt_fragment", "Fragmented Packets Rx"}, [MVNETA_MIB_PKT_OVERSIZE_IDX] = {MVNETA_MIB_PKT_OVERSIZE, 0, "pkt_oversize", "Oversized Packets Rx"}, [MVNETA_MIB_PKT_JABBER_IDX] = {MVNETA_MIB_PKT_JABBER, 0, "pkt_jabber", "Jabber Packets Rx"}, [MVNETA_MIB_MAC_RX_ERR_IDX] = {MVNETA_MIB_MAC_RX_ERR, 0, "mac_rx_err", "MAC Rx Errors"}, [MVNETA_MIB_MAC_CRC_ERR_IDX] = {MVNETA_MIB_MAC_CRC_ERR, 0, "mac_crc_err", "MAC CRC Errors"}, [MVNETA_MIB_MAC_COL_IDX] = {MVNETA_MIB_MAC_COL, 0, "mac_collision", "MAC Collision"}, [MVNETA_MIB_MAC_LATE_COL_IDX] = {MVNETA_MIB_MAC_LATE_COL, 0, "mac_late_collision", "MAC Late Collision"}, }; static struct resource_spec res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0} }; static struct { driver_intr_t *handler; char * description; } mvneta_intrs[] = { { mvneta_rxtxth_intr, "MVNETA aggregated interrupt" }, }; static int mvneta_set_mac_address(struct mvneta_softc *sc, uint8_t *addr) { unsigned int mac_h; unsigned int mac_l; mac_l = (addr[4] << 8) | (addr[5]); mac_h = (addr[0] << 24) | (addr[1] << 16) | (addr[2] << 8) | (addr[3] << 0); MVNETA_WRITE(sc, MVNETA_MACAL, mac_l); MVNETA_WRITE(sc, MVNETA_MACAH, mac_h); return (0); } static int mvneta_get_mac_address(struct mvneta_softc *sc, uint8_t *addr) { uint32_t mac_l, mac_h; #ifdef FDT if (mvneta_fdt_mac_address(sc, addr) == 0) return (0); #endif /* * Fall back -- use the currently programmed address. */ mac_l = MVNETA_READ(sc, MVNETA_MACAL); mac_h = MVNETA_READ(sc, MVNETA_MACAH); if (mac_l == 0 && mac_h == 0) { /* * Generate pseudo-random MAC. * Set lower part to random number | unit number. */ mac_l = arc4random() & ~0xff; mac_l |= device_get_unit(sc->dev) & 0xff; mac_h = arc4random(); mac_h &= ~(3 << 24); /* Clear multicast and LAA bits */ if (bootverbose) { device_printf(sc->dev, "Could not acquire MAC address. " "Using randomized one.\n"); } } addr[0] = (mac_h & 0xff000000) >> 24; addr[1] = (mac_h & 0x00ff0000) >> 16; addr[2] = (mac_h & 0x0000ff00) >> 8; addr[3] = (mac_h & 0x000000ff); addr[4] = (mac_l & 0x0000ff00) >> 8; addr[5] = (mac_l & 0x000000ff); return (0); } STATIC boolean_t mvneta_has_switch(device_t self) { #ifdef FDT return (mvneta_has_switch_fdt(self)); #endif return (false); } STATIC int mvneta_dma_create(struct mvneta_softc *sc) { size_t maxsize, maxsegsz; size_t q; int error; /* * Create Tx DMA */ maxsize = maxsegsz = sizeof(struct mvneta_tx_desc) * MVNETA_TX_RING_CNT; error = bus_dma_tag_create( bus_get_dma_tag(sc->dev), /* parent */ 16, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ maxsize, /* maxsize */ 1, /* nsegments */ maxsegsz, /* maxsegsz */ 0, /* flags */ NULL, NULL, /* lockfunc, lockfuncarg */ &sc->tx_dtag); /* dmat */ if (error != 0) { device_printf(sc->dev, "Failed to create DMA tag for Tx descriptors.\n"); goto fail; } error = bus_dma_tag_create( bus_get_dma_tag(sc->dev), /* parent */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ MVNETA_MAX_FRAME, /* maxsize */ MVNETA_TX_SEGLIMIT, /* nsegments */ MVNETA_MAX_FRAME, /* maxsegsz */ BUS_DMA_ALLOCNOW, /* flags */ NULL, NULL, /* lockfunc, lockfuncarg */ &sc->txmbuf_dtag); if (error != 0) { device_printf(sc->dev, "Failed to create DMA tag for Tx mbufs.\n"); goto fail; } for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) { error = mvneta_ring_alloc_tx_queue(sc, q); if (error != 0) { device_printf(sc->dev, "Failed to allocate DMA safe memory for TxQ: %zu\n", q); goto fail; } } /* * Create Rx DMA. */ /* Create tag for Rx descripors */ error = bus_dma_tag_create( bus_get_dma_tag(sc->dev), /* parent */ 32, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ sizeof(struct mvneta_rx_desc) * MVNETA_RX_RING_CNT, /* maxsize */ 1, /* nsegments */ sizeof(struct mvneta_rx_desc) * MVNETA_RX_RING_CNT, /* maxsegsz */ 0, /* flags */ NULL, NULL, /* lockfunc, lockfuncarg */ &sc->rx_dtag); /* dmat */ if (error != 0) { device_printf(sc->dev, "Failed to create DMA tag for Rx descriptors.\n"); goto fail; } /* Create tag for Rx buffers */ error = bus_dma_tag_create( bus_get_dma_tag(sc->dev), /* parent */ 32, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filtfunc, filtfuncarg */ MVNETA_MAX_FRAME, 1, /* maxsize, nsegments */ MVNETA_MAX_FRAME, /* maxsegsz */ 0, /* flags */ NULL, NULL, /* lockfunc, lockfuncarg */ &sc->rxbuf_dtag); /* dmat */ if (error != 0) { device_printf(sc->dev, "Failed to create DMA tag for Rx buffers.\n"); goto fail; } for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { if (mvneta_ring_alloc_rx_queue(sc, q) != 0) { device_printf(sc->dev, "Failed to allocate DMA safe memory for RxQ: %zu\n", q); goto fail; } } return (0); fail: mvneta_detach(sc->dev); return (error); } /* ARGSUSED */ int mvneta_attach(device_t self) { struct mvneta_softc *sc; if_t ifp; device_t child; int ifm_target; int q, error; #if !defined(__aarch64__) uint32_t reg; #endif clk_t clk; sc = device_get_softc(self); sc->dev = self; mtx_init(&sc->mtx, "mvneta_sc", NULL, MTX_DEF); error = bus_alloc_resources(self, res_spec, sc->res); if (error) { device_printf(self, "could not allocate resources\n"); return (ENXIO); } sc->version = MVNETA_READ(sc, MVNETA_PV); device_printf(self, "version is %x\n", sc->version); callout_init(&sc->tick_ch, 0); /* * make sure DMA engines are in reset state */ MVNETA_WRITE(sc, MVNETA_PRXINIT, 0x00000001); MVNETA_WRITE(sc, MVNETA_PTXINIT, 0x00000001); error = clk_get_by_ofw_index(sc->dev, ofw_bus_get_node(sc->dev), 0, &clk); if (error != 0) { #if defined(__aarch64__) device_printf(sc->dev, "Cannot get clock, using default frequency: %d\n", A3700_TCLK_250MHZ); sc->clk_freq = A3700_TCLK_250MHZ; #else device_printf(sc->dev, "Cannot get clock, using get_tclk()\n"); sc->clk_freq = get_tclk(); #endif } else { error = clk_get_freq(clk, &sc->clk_freq); if (error != 0) { device_printf(sc->dev, "Cannot obtain frequency from parent clock\n"); bus_release_resources(sc->dev, res_spec, sc->res); return (error); } } #if !defined(__aarch64__) /* * Disable port snoop for buffers and descriptors * to avoid L2 caching of both without DRAM copy. * Obtain coherency settings from the first MBUS * window attribute. */ if ((MVNETA_READ(sc, MV_WIN_NETA_BASE(0)) & IO_WIN_COH_ATTR_MASK) == 0) { reg = MVNETA_READ(sc, MVNETA_PSNPCFG); reg &= ~MVNETA_PSNPCFG_DESCSNP_MASK; reg &= ~MVNETA_PSNPCFG_BUFSNP_MASK; MVNETA_WRITE(sc, MVNETA_PSNPCFG, reg); } #endif error = bus_setup_intr(self, sc->res[1], INTR_TYPE_NET | INTR_MPSAFE, NULL, mvneta_intrs[0].handler, sc, &sc->ih_cookie[0]); if (error) { device_printf(self, "could not setup %s\n", mvneta_intrs[0].description); mvneta_detach(self); return (error); } /* * MAC address */ if (mvneta_get_mac_address(sc, sc->enaddr)) { device_printf(self, "no mac address.\n"); return (ENXIO); } mvneta_set_mac_address(sc, sc->enaddr); mvneta_disable_intr(sc); /* Allocate network interface */ ifp = sc->ifp = if_alloc(IFT_ETHER); if_initname(ifp, device_get_name(self), device_get_unit(self)); /* * We can support 802.1Q VLAN-sized frames and jumbo * Ethernet frames. */ if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU | IFCAP_JUMBO_MTU, 0); if_setsoftc(ifp, sc); if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); #ifdef MVNETA_MULTIQUEUE if_settransmitfn(ifp, mvneta_transmit); if_setqflushfn(ifp, mvneta_qflush); #else /* !MVNETA_MULTIQUEUE */ if_setstartfn(ifp, mvneta_start); if_setsendqlen(ifp, MVNETA_TX_RING_CNT - 1); if_setsendqready(ifp); #endif if_setinitfn(ifp, mvneta_init); if_setioctlfn(ifp, mvneta_ioctl); /* * We can do IPv4/TCPv4/UDPv4/TCPv6/UDPv6 checksums in hardware. */ if_setcapabilitiesbit(ifp, IFCAP_HWCSUM, 0); /* * As VLAN hardware tagging is not supported * but is necessary to perform VLAN hardware checksums, * it is done in the driver */ if_setcapabilitiesbit(ifp, IFCAP_VLAN_HWTAGGING | IFCAP_VLAN_HWCSUM, 0); /* * Currently IPv6 HW checksum is broken, so make sure it is disabled. */ if_setcapabilitiesbit(ifp, 0, IFCAP_HWCSUM_IPV6); if_setcapenable(ifp, if_getcapabilities(ifp)); /* * Disabled option(s): * - Support for Large Receive Offload */ if_setcapabilitiesbit(ifp, IFCAP_LRO, 0); if_sethwassist(ifp, CSUM_IP | CSUM_TCP | CSUM_UDP); sc->rx_frame_size = MCLBYTES; /* ether_ifattach() always sets normal mtu */ /* * Device DMA Buffer allocation. * Handles resource deallocation in case of failure. */ error = mvneta_dma_create(sc); if (error != 0) { mvneta_detach(self); return (error); } /* Initialize queues */ for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) { error = mvneta_ring_init_tx_queue(sc, q); if (error != 0) { mvneta_detach(self); return (error); } } for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { error = mvneta_ring_init_rx_queue(sc, q); if (error != 0) { mvneta_detach(self); return (error); } } /* * Enable DMA engines and Initialize Device Registers. */ MVNETA_WRITE(sc, MVNETA_PRXINIT, 0x00000000); MVNETA_WRITE(sc, MVNETA_PTXINIT, 0x00000000); MVNETA_WRITE(sc, MVNETA_PACC, MVNETA_PACC_ACCELERATIONMODE_EDM); mvneta_sc_lock(sc); mvneta_filter_setup(sc); mvneta_sc_unlock(sc); mvneta_initreg(ifp); /* * Now MAC is working, setup MII. */ if (mii_init == 0) { /* * MII bus is shared by all MACs and all PHYs in SoC. * serializing the bus access should be safe. */ mtx_init(&mii_mutex, "mvneta_mii", NULL, MTX_DEF); mii_init = 1; } /* Attach PHY(s) */ if ((sc->phy_addr != MII_PHY_ANY) && (!sc->use_inband_status)) { error = mii_attach(self, &sc->miibus, ifp, mvneta_mediachange, mvneta_mediastatus, BMSR_DEFCAPMASK, sc->phy_addr, MII_OFFSET_ANY, 0); if (error != 0) { device_printf(self, "MII attach failed, error: %d\n", error); ether_ifdetach(sc->ifp); mvneta_detach(self); return (error); } sc->mii = device_get_softc(sc->miibus); sc->phy_attached = 1; /* Disable auto-negotiation in MAC - rely on PHY layer */ mvneta_update_autoneg(sc, FALSE); } else if (sc->use_inband_status == TRUE) { /* In-band link status */ ifmedia_init(&sc->mvneta_ifmedia, 0, mvneta_mediachange, mvneta_mediastatus); /* Configure media */ ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_1000_T | IFM_FDX, 0, NULL); ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_100_TX, 0, NULL); ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_100_TX | IFM_FDX, 0, NULL); ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_10_T, 0, NULL); ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_10_T | IFM_FDX, 0, NULL); ifmedia_add(&sc->mvneta_ifmedia, IFM_ETHER | IFM_AUTO, 0, NULL); ifmedia_set(&sc->mvneta_ifmedia, IFM_ETHER | IFM_AUTO); /* Enable auto-negotiation */ mvneta_update_autoneg(sc, TRUE); mvneta_sc_lock(sc); if (MVNETA_IS_LINKUP(sc)) mvneta_linkup(sc); else mvneta_linkdown(sc); mvneta_sc_unlock(sc); } else { /* Fixed-link, use predefined values */ mvneta_update_autoneg(sc, FALSE); ifmedia_init(&sc->mvneta_ifmedia, 0, mvneta_mediachange, mvneta_mediastatus); ifm_target = IFM_ETHER; switch (sc->phy_speed) { case 2500: if (sc->phy_mode != MVNETA_PHY_SGMII && sc->phy_mode != MVNETA_PHY_QSGMII) { device_printf(self, "2.5G speed can work only in (Q)SGMII mode\n"); ether_ifdetach(sc->ifp); mvneta_detach(self); return (ENXIO); } ifm_target |= IFM_2500_T; break; case 1000: ifm_target |= IFM_1000_T; break; case 100: ifm_target |= IFM_100_TX; break; case 10: ifm_target |= IFM_10_T; break; default: ether_ifdetach(sc->ifp); mvneta_detach(self); return (ENXIO); } if (sc->phy_fdx) ifm_target |= IFM_FDX; else ifm_target |= IFM_HDX; ifmedia_add(&sc->mvneta_ifmedia, ifm_target, 0, NULL); ifmedia_set(&sc->mvneta_ifmedia, ifm_target); if_link_state_change(sc->ifp, LINK_STATE_UP); if (mvneta_has_switch(self)) { if (bootverbose) device_printf(self, "This device is attached to a switch\n"); child = device_add_child(sc->dev, "mdio", DEVICE_UNIT_ANY); if (child == NULL) { ether_ifdetach(sc->ifp); mvneta_detach(self); return (ENXIO); } bus_attach_children(sc->dev); bus_attach_children(child); } /* Configure MAC media */ mvneta_update_media(sc, ifm_target); } ether_ifattach(ifp, sc->enaddr); callout_reset(&sc->tick_ch, 0, mvneta_tick, sc); sysctl_mvneta_init(sc); return (0); } STATIC int mvneta_detach(device_t dev) { struct mvneta_softc *sc; int q; sc = device_get_softc(dev); if (device_is_attached(dev)) { mvneta_stop(sc); callout_drain(&sc->tick_ch); ether_ifdetach(sc->ifp); } for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) mvneta_ring_dealloc_rx_queue(sc, q); for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) mvneta_ring_dealloc_tx_queue(sc, q); - device_delete_children(dev); + bus_generic_detach(dev); if (sc->ih_cookie[0] != NULL) bus_teardown_intr(dev, sc->res[1], sc->ih_cookie[0]); if (sc->tx_dtag != NULL) bus_dma_tag_destroy(sc->tx_dtag); if (sc->rx_dtag != NULL) bus_dma_tag_destroy(sc->rx_dtag); if (sc->txmbuf_dtag != NULL) bus_dma_tag_destroy(sc->txmbuf_dtag); if (sc->rxbuf_dtag != NULL) bus_dma_tag_destroy(sc->rxbuf_dtag); bus_release_resources(dev, res_spec, sc->res); if (sc->ifp) if_free(sc->ifp); if (mtx_initialized(&sc->mtx)) mtx_destroy(&sc->mtx); return (0); } /* * MII */ STATIC int mvneta_miibus_readreg(device_t dev, int phy, int reg) { struct mvneta_softc *sc; if_t ifp; uint32_t smi, val; int i; sc = device_get_softc(dev); ifp = sc->ifp; mtx_lock(&mii_mutex); for (i = 0; i < MVNETA_PHY_TIMEOUT; i++) { if ((MVNETA_READ(sc, MVNETA_SMI) & MVNETA_SMI_BUSY) == 0) break; DELAY(1); } if (i == MVNETA_PHY_TIMEOUT) { if_printf(ifp, "SMI busy timeout\n"); mtx_unlock(&mii_mutex); return (-1); } smi = MVNETA_SMI_PHYAD(phy) | MVNETA_SMI_REGAD(reg) | MVNETA_SMI_OPCODE_READ; MVNETA_WRITE(sc, MVNETA_SMI, smi); for (i = 0; i < MVNETA_PHY_TIMEOUT; i++) { if ((MVNETA_READ(sc, MVNETA_SMI) & MVNETA_SMI_BUSY) == 0) break; DELAY(1); } if (i == MVNETA_PHY_TIMEOUT) { if_printf(ifp, "SMI busy timeout\n"); mtx_unlock(&mii_mutex); return (-1); } for (i = 0; i < MVNETA_PHY_TIMEOUT; i++) { smi = MVNETA_READ(sc, MVNETA_SMI); if (smi & MVNETA_SMI_READVALID) break; DELAY(1); } if (i == MVNETA_PHY_TIMEOUT) { if_printf(ifp, "SMI busy timeout\n"); mtx_unlock(&mii_mutex); return (-1); } mtx_unlock(&mii_mutex); #ifdef MVNETA_KTR CTR3(KTR_SPARE2, "%s i=%d, timeout=%d\n", if_getname(ifp), i, MVNETA_PHY_TIMEOUT); #endif val = smi & MVNETA_SMI_DATA_MASK; #ifdef MVNETA_KTR CTR4(KTR_SPARE2, "%s phy=%d, reg=%#x, val=%#x\n", if_getname(ifp), phy, reg, val); #endif return (val); } STATIC int mvneta_miibus_writereg(device_t dev, int phy, int reg, int val) { struct mvneta_softc *sc; if_t ifp; uint32_t smi; int i; sc = device_get_softc(dev); ifp = sc->ifp; #ifdef MVNETA_KTR CTR4(KTR_SPARE2, "%s phy=%d, reg=%#x, val=%#x\n", if_name(ifp), phy, reg, val); #endif mtx_lock(&mii_mutex); for (i = 0; i < MVNETA_PHY_TIMEOUT; i++) { if ((MVNETA_READ(sc, MVNETA_SMI) & MVNETA_SMI_BUSY) == 0) break; DELAY(1); } if (i == MVNETA_PHY_TIMEOUT) { if_printf(ifp, "SMI busy timeout\n"); mtx_unlock(&mii_mutex); return (0); } smi = MVNETA_SMI_PHYAD(phy) | MVNETA_SMI_REGAD(reg) | MVNETA_SMI_OPCODE_WRITE | (val & MVNETA_SMI_DATA_MASK); MVNETA_WRITE(sc, MVNETA_SMI, smi); for (i = 0; i < MVNETA_PHY_TIMEOUT; i++) { if ((MVNETA_READ(sc, MVNETA_SMI) & MVNETA_SMI_BUSY) == 0) break; DELAY(1); } mtx_unlock(&mii_mutex); if (i == MVNETA_PHY_TIMEOUT) if_printf(ifp, "phy write timed out\n"); return (0); } STATIC void mvneta_portup(struct mvneta_softc *sc) { int q; for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { mvneta_rx_lockq(sc, q); mvneta_rx_queue_enable(sc->ifp, q); mvneta_rx_unlockq(sc, q); } for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) { mvneta_tx_lockq(sc, q); mvneta_tx_queue_enable(sc->ifp, q); mvneta_tx_unlockq(sc, q); } } STATIC void mvneta_portdown(struct mvneta_softc *sc) { struct mvneta_rx_ring *rx; struct mvneta_tx_ring *tx; int q, cnt; uint32_t reg; for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { rx = MVNETA_RX_RING(sc, q); mvneta_rx_lockq(sc, q); rx->queue_status = MVNETA_QUEUE_DISABLED; mvneta_rx_unlockq(sc, q); } for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) { tx = MVNETA_TX_RING(sc, q); mvneta_tx_lockq(sc, q); tx->queue_status = MVNETA_QUEUE_DISABLED; mvneta_tx_unlockq(sc, q); } /* Wait for all Rx activity to terminate. */ reg = MVNETA_READ(sc, MVNETA_RQC) & MVNETA_RQC_EN_MASK; reg = MVNETA_RQC_DIS(reg); MVNETA_WRITE(sc, MVNETA_RQC, reg); cnt = 0; do { if (cnt >= RX_DISABLE_TIMEOUT) { if_printf(sc->ifp, "timeout for RX stopped. rqc 0x%x\n", reg); break; } cnt++; reg = MVNETA_READ(sc, MVNETA_RQC); } while ((reg & MVNETA_RQC_EN_MASK) != 0); /* Wait for all Tx activity to terminate. */ reg = MVNETA_READ(sc, MVNETA_PIE); reg &= ~MVNETA_PIE_TXPKTINTRPTENB_MASK; MVNETA_WRITE(sc, MVNETA_PIE, reg); reg = MVNETA_READ(sc, MVNETA_PRXTXTIM); reg &= ~MVNETA_PRXTXTI_TBTCQ_MASK; MVNETA_WRITE(sc, MVNETA_PRXTXTIM, reg); reg = MVNETA_READ(sc, MVNETA_TQC) & MVNETA_TQC_EN_MASK; reg = MVNETA_TQC_DIS(reg); MVNETA_WRITE(sc, MVNETA_TQC, reg); cnt = 0; do { if (cnt >= TX_DISABLE_TIMEOUT) { if_printf(sc->ifp, "timeout for TX stopped. tqc 0x%x\n", reg); break; } cnt++; reg = MVNETA_READ(sc, MVNETA_TQC); } while ((reg & MVNETA_TQC_EN_MASK) != 0); /* Wait for all Tx FIFO is empty */ cnt = 0; do { if (cnt >= TX_FIFO_EMPTY_TIMEOUT) { if_printf(sc->ifp, "timeout for TX FIFO drained. ps0 0x%x\n", reg); break; } cnt++; reg = MVNETA_READ(sc, MVNETA_PS0); } while (((reg & MVNETA_PS0_TXFIFOEMP) == 0) && ((reg & MVNETA_PS0_TXINPROG) != 0)); } /* * Device Register Initialization * reset device registers to device driver default value. * the device is not enabled here. */ STATIC int mvneta_initreg(if_t ifp) { struct mvneta_softc *sc; int q; uint32_t reg; sc = if_getsoftc(ifp); #ifdef MVNETA_KTR CTR1(KTR_SPARE2, "%s initializing device register", if_name(ifp)); #endif /* Disable Legacy WRR, Disable EJP, Release from reset. */ MVNETA_WRITE(sc, MVNETA_TQC_1, 0); /* Enable mbus retry. */ MVNETA_WRITE(sc, MVNETA_MBUS_CONF, MVNETA_MBUS_RETRY_EN); /* Init TX/RX Queue Registers */ for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { mvneta_rx_lockq(sc, q); if (mvneta_rx_queue_init(ifp, q) != 0) { device_printf(sc->dev, "initialization failed: cannot initialize queue\n"); mvneta_rx_unlockq(sc, q); return (ENOBUFS); } mvneta_rx_unlockq(sc, q); } for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) { mvneta_tx_lockq(sc, q); if (mvneta_tx_queue_init(ifp, q) != 0) { device_printf(sc->dev, "initialization failed: cannot initialize queue\n"); mvneta_tx_unlockq(sc, q); return (ENOBUFS); } mvneta_tx_unlockq(sc, q); } /* * Ethernet Unit Control - disable automatic PHY management by HW. * In case the port uses SMI-controlled PHY, poll its status with * mii_tick() and update MAC settings accordingly. */ reg = MVNETA_READ(sc, MVNETA_EUC); reg &= ~MVNETA_EUC_POLLING; MVNETA_WRITE(sc, MVNETA_EUC, reg); /* EEE: Low Power Idle */ reg = MVNETA_LPIC0_LILIMIT(MVNETA_LPI_LI); reg |= MVNETA_LPIC0_TSLIMIT(MVNETA_LPI_TS); MVNETA_WRITE(sc, MVNETA_LPIC0, reg); reg = MVNETA_LPIC1_TWLIMIT(MVNETA_LPI_TW); MVNETA_WRITE(sc, MVNETA_LPIC1, reg); reg = MVNETA_LPIC2_MUSTSET; MVNETA_WRITE(sc, MVNETA_LPIC2, reg); /* Port MAC Control set 0 */ reg = MVNETA_PMACC0_MUSTSET; /* must write 0x1 */ reg &= ~MVNETA_PMACC0_PORTEN; /* port is still disabled */ reg |= MVNETA_PMACC0_FRAMESIZELIMIT(if_getmtu(ifp) + MVNETA_ETHER_SIZE); MVNETA_WRITE(sc, MVNETA_PMACC0, reg); /* Port MAC Control set 2 */ reg = MVNETA_READ(sc, MVNETA_PMACC2); switch (sc->phy_mode) { case MVNETA_PHY_QSGMII: reg |= (MVNETA_PMACC2_PCSEN | MVNETA_PMACC2_RGMIIEN); MVNETA_WRITE(sc, MVNETA_PSERDESCFG, MVNETA_PSERDESCFG_QSGMII); break; case MVNETA_PHY_SGMII: reg |= (MVNETA_PMACC2_PCSEN | MVNETA_PMACC2_RGMIIEN); MVNETA_WRITE(sc, MVNETA_PSERDESCFG, MVNETA_PSERDESCFG_SGMII); break; case MVNETA_PHY_RGMII: case MVNETA_PHY_RGMII_ID: reg |= MVNETA_PMACC2_RGMIIEN; break; } reg |= MVNETA_PMACC2_MUSTSET; reg &= ~MVNETA_PMACC2_PORTMACRESET; MVNETA_WRITE(sc, MVNETA_PMACC2, reg); /* Port Configuration Extended: enable Tx CRC generation */ reg = MVNETA_READ(sc, MVNETA_PXCX); reg &= ~MVNETA_PXCX_TXCRCDIS; MVNETA_WRITE(sc, MVNETA_PXCX, reg); /* clear MIB counter registers(clear by read) */ mvneta_sc_lock(sc); mvneta_clear_mib(sc); mvneta_sc_unlock(sc); /* Set SDC register except IPGINT bits */ reg = MVNETA_SDC_RXBSZ_16_64BITWORDS; reg |= MVNETA_SDC_TXBSZ_16_64BITWORDS; reg |= MVNETA_SDC_BLMR; reg |= MVNETA_SDC_BLMT; MVNETA_WRITE(sc, MVNETA_SDC, reg); return (0); } STATIC void mvneta_dmamap_cb(void *arg, bus_dma_segment_t * segs, int nseg, int error) { if (error != 0) return; *(bus_addr_t *)arg = segs->ds_addr; } STATIC int mvneta_ring_alloc_rx_queue(struct mvneta_softc *sc, int q) { struct mvneta_rx_ring *rx; struct mvneta_buf *rxbuf; bus_dmamap_t dmap; int i, error; if (q >= MVNETA_RX_QNUM_MAX) return (EINVAL); rx = MVNETA_RX_RING(sc, q); mtx_init(&rx->ring_mtx, "mvneta_rx", NULL, MTX_DEF); /* Allocate DMA memory for Rx descriptors */ error = bus_dmamem_alloc(sc->rx_dtag, (void**)&(rx->desc), BUS_DMA_NOWAIT | BUS_DMA_ZERO, &rx->desc_map); if (error != 0 || rx->desc == NULL) goto fail; error = bus_dmamap_load(sc->rx_dtag, rx->desc_map, rx->desc, sizeof(struct mvneta_rx_desc) * MVNETA_RX_RING_CNT, mvneta_dmamap_cb, &rx->desc_pa, BUS_DMA_NOWAIT); if (error != 0) goto fail; for (i = 0; i < MVNETA_RX_RING_CNT; i++) { error = bus_dmamap_create(sc->rxbuf_dtag, 0, &dmap); if (error != 0) { device_printf(sc->dev, "Failed to create DMA map for Rx buffer num: %d\n", i); goto fail; } rxbuf = &rx->rxbuf[i]; rxbuf->dmap = dmap; rxbuf->m = NULL; } return (0); fail: mvneta_rx_lockq(sc, q); mvneta_ring_flush_rx_queue(sc, q); mvneta_rx_unlockq(sc, q); mvneta_ring_dealloc_rx_queue(sc, q); device_printf(sc->dev, "DMA Ring buffer allocation failure.\n"); return (error); } STATIC int mvneta_ring_alloc_tx_queue(struct mvneta_softc *sc, int q) { struct mvneta_tx_ring *tx; int error; if (q >= MVNETA_TX_QNUM_MAX) return (EINVAL); tx = MVNETA_TX_RING(sc, q); mtx_init(&tx->ring_mtx, "mvneta_tx", NULL, MTX_DEF); error = bus_dmamem_alloc(sc->tx_dtag, (void**)&(tx->desc), BUS_DMA_NOWAIT | BUS_DMA_ZERO, &tx->desc_map); if (error != 0 || tx->desc == NULL) goto fail; error = bus_dmamap_load(sc->tx_dtag, tx->desc_map, tx->desc, sizeof(struct mvneta_tx_desc) * MVNETA_TX_RING_CNT, mvneta_dmamap_cb, &tx->desc_pa, BUS_DMA_NOWAIT); if (error != 0) goto fail; #ifdef MVNETA_MULTIQUEUE tx->br = buf_ring_alloc(MVNETA_BUFRING_SIZE, M_DEVBUF, M_NOWAIT, &tx->ring_mtx); if (tx->br == NULL) { device_printf(sc->dev, "Could not setup buffer ring for TxQ(%d)\n", q); error = ENOMEM; goto fail; } #endif return (0); fail: mvneta_tx_lockq(sc, q); mvneta_ring_flush_tx_queue(sc, q); mvneta_tx_unlockq(sc, q); mvneta_ring_dealloc_tx_queue(sc, q); device_printf(sc->dev, "DMA Ring buffer allocation failure.\n"); return (error); } STATIC void mvneta_ring_dealloc_tx_queue(struct mvneta_softc *sc, int q) { struct mvneta_tx_ring *tx; struct mvneta_buf *txbuf; void *kva; int error; int i; if (q >= MVNETA_TX_QNUM_MAX) return; tx = MVNETA_TX_RING(sc, q); if (tx->taskq != NULL) { /* Remove task */ while (taskqueue_cancel(tx->taskq, &tx->task, NULL) != 0) taskqueue_drain(tx->taskq, &tx->task); } #ifdef MVNETA_MULTIQUEUE if (tx->br != NULL) drbr_free(tx->br, M_DEVBUF); #endif if (sc->txmbuf_dtag != NULL) { for (i = 0; i < MVNETA_TX_RING_CNT; i++) { txbuf = &tx->txbuf[i]; if (txbuf->dmap != NULL) { error = bus_dmamap_destroy(sc->txmbuf_dtag, txbuf->dmap); if (error != 0) { panic("%s: map busy for Tx descriptor (Q%d, %d)", __func__, q, i); } } } } if (tx->desc_pa != 0) bus_dmamap_unload(sc->tx_dtag, tx->desc_map); kva = (void *)tx->desc; if (kva != NULL) bus_dmamem_free(sc->tx_dtag, tx->desc, tx->desc_map); if (mtx_name(&tx->ring_mtx) != NULL) mtx_destroy(&tx->ring_mtx); memset(tx, 0, sizeof(*tx)); } STATIC void mvneta_ring_dealloc_rx_queue(struct mvneta_softc *sc, int q) { struct mvneta_rx_ring *rx; struct lro_ctrl *lro; void *kva; if (q >= MVNETA_RX_QNUM_MAX) return; rx = MVNETA_RX_RING(sc, q); if (rx->desc_pa != 0) bus_dmamap_unload(sc->rx_dtag, rx->desc_map); kva = (void *)rx->desc; if (kva != NULL) bus_dmamem_free(sc->rx_dtag, rx->desc, rx->desc_map); lro = &rx->lro; tcp_lro_free(lro); if (mtx_name(&rx->ring_mtx) != NULL) mtx_destroy(&rx->ring_mtx); memset(rx, 0, sizeof(*rx)); } STATIC int mvneta_ring_init_rx_queue(struct mvneta_softc *sc, int q) { struct mvneta_rx_ring *rx; struct lro_ctrl *lro; int error; if (q >= MVNETA_RX_QNUM_MAX) return (0); rx = MVNETA_RX_RING(sc, q); rx->dma = rx->cpu = 0; rx->queue_th_received = MVNETA_RXTH_COUNT; rx->queue_th_time = (sc->clk_freq / 1000) / 10; /* 0.1 [ms] */ /* Initialize LRO */ rx->lro_enabled = FALSE; if ((if_getcapenable(sc->ifp) & IFCAP_LRO) != 0) { lro = &rx->lro; error = tcp_lro_init(lro); if (error != 0) device_printf(sc->dev, "LRO Initialization failed!\n"); else { rx->lro_enabled = TRUE; lro->ifp = sc->ifp; } } return (0); } STATIC int mvneta_ring_init_tx_queue(struct mvneta_softc *sc, int q) { struct mvneta_tx_ring *tx; struct mvneta_buf *txbuf; int i, error; if (q >= MVNETA_TX_QNUM_MAX) return (0); tx = MVNETA_TX_RING(sc, q); /* Tx handle */ for (i = 0; i < MVNETA_TX_RING_CNT; i++) { txbuf = &tx->txbuf[i]; txbuf->m = NULL; /* Tx handle needs DMA map for busdma_load_mbuf() */ error = bus_dmamap_create(sc->txmbuf_dtag, 0, &txbuf->dmap); if (error != 0) { device_printf(sc->dev, "can't create dma map (tx ring %d)\n", i); return (error); } } tx->dma = tx->cpu = 0; tx->used = 0; tx->drv_error = 0; tx->queue_status = MVNETA_QUEUE_DISABLED; tx->queue_hung = FALSE; tx->ifp = sc->ifp; tx->qidx = q; TASK_INIT(&tx->task, 0, mvneta_tx_task, tx); tx->taskq = taskqueue_create_fast("mvneta_tx_taskq", M_WAITOK, taskqueue_thread_enqueue, &tx->taskq); taskqueue_start_threads(&tx->taskq, 1, PI_NET, "%s: tx_taskq(%d)", device_get_nameunit(sc->dev), q); return (0); } STATIC void mvneta_ring_flush_tx_queue(struct mvneta_softc *sc, int q) { struct mvneta_tx_ring *tx; struct mvneta_buf *txbuf; int i; tx = MVNETA_TX_RING(sc, q); KASSERT_TX_MTX(sc, q); /* Tx handle */ for (i = 0; i < MVNETA_TX_RING_CNT; i++) { txbuf = &tx->txbuf[i]; bus_dmamap_unload(sc->txmbuf_dtag, txbuf->dmap); if (txbuf->m != NULL) { m_freem(txbuf->m); txbuf->m = NULL; } } tx->dma = tx->cpu = 0; tx->used = 0; } STATIC void mvneta_ring_flush_rx_queue(struct mvneta_softc *sc, int q) { struct mvneta_rx_ring *rx; struct mvneta_buf *rxbuf; int i; rx = MVNETA_RX_RING(sc, q); KASSERT_RX_MTX(sc, q); /* Rx handle */ for (i = 0; i < MVNETA_RX_RING_CNT; i++) { rxbuf = &rx->rxbuf[i]; mvneta_rx_buf_free(sc, rxbuf); } rx->dma = rx->cpu = 0; } /* * Rx/Tx Queue Control */ STATIC int mvneta_rx_queue_init(if_t ifp, int q) { struct mvneta_softc *sc; struct mvneta_rx_ring *rx; uint32_t reg; sc = if_getsoftc(ifp); KASSERT_RX_MTX(sc, q); rx = MVNETA_RX_RING(sc, q); DASSERT(rx->desc_pa != 0); /* descriptor address */ MVNETA_WRITE(sc, MVNETA_PRXDQA(q), rx->desc_pa); /* Rx buffer size and descriptor ring size */ reg = MVNETA_PRXDQS_BUFFERSIZE(sc->rx_frame_size >> 3); reg |= MVNETA_PRXDQS_DESCRIPTORSQUEUESIZE(MVNETA_RX_RING_CNT); MVNETA_WRITE(sc, MVNETA_PRXDQS(q), reg); #ifdef MVNETA_KTR CTR3(KTR_SPARE2, "%s PRXDQS(%d): %#x", if_name(ifp), q, MVNETA_READ(sc, MVNETA_PRXDQS(q))); #endif /* Rx packet offset address */ reg = MVNETA_PRXC_PACKETOFFSET(MVNETA_PACKET_OFFSET >> 3); MVNETA_WRITE(sc, MVNETA_PRXC(q), reg); #ifdef MVNETA_KTR CTR3(KTR_SPARE2, "%s PRXC(%d): %#x", if_name(ifp), q, MVNETA_READ(sc, MVNETA_PRXC(q))); #endif /* if DMA is not working, register is not updated */ DASSERT(MVNETA_READ(sc, MVNETA_PRXDQA(q)) == rx->desc_pa); return (0); } STATIC int mvneta_tx_queue_init(if_t ifp, int q) { struct mvneta_softc *sc; struct mvneta_tx_ring *tx; uint32_t reg; sc = if_getsoftc(ifp); KASSERT_TX_MTX(sc, q); tx = MVNETA_TX_RING(sc, q); DASSERT(tx->desc_pa != 0); /* descriptor address */ MVNETA_WRITE(sc, MVNETA_PTXDQA(q), tx->desc_pa); /* descriptor ring size */ reg = MVNETA_PTXDQS_DQS(MVNETA_TX_RING_CNT); MVNETA_WRITE(sc, MVNETA_PTXDQS(q), reg); /* if DMA is not working, register is not updated */ DASSERT(MVNETA_READ(sc, MVNETA_PTXDQA(q)) == tx->desc_pa); return (0); } STATIC int mvneta_rx_queue_enable(if_t ifp, int q) { struct mvneta_softc *sc; struct mvneta_rx_ring *rx; uint32_t reg; sc = if_getsoftc(ifp); rx = MVNETA_RX_RING(sc, q); KASSERT_RX_MTX(sc, q); /* Set Rx interrupt threshold */ reg = MVNETA_PRXDQTH_ODT(rx->queue_th_received); MVNETA_WRITE(sc, MVNETA_PRXDQTH(q), reg); reg = MVNETA_PRXITTH_RITT(rx->queue_th_time); MVNETA_WRITE(sc, MVNETA_PRXITTH(q), reg); /* Unmask RXTX_TH Intr. */ reg = MVNETA_READ(sc, MVNETA_PRXTXTIM); reg |= MVNETA_PRXTXTI_RBICTAPQ(q); /* Rx Buffer Interrupt Coalese */ MVNETA_WRITE(sc, MVNETA_PRXTXTIM, reg); /* Enable Rx queue */ reg = MVNETA_READ(sc, MVNETA_RQC) & MVNETA_RQC_EN_MASK; reg |= MVNETA_RQC_ENQ(q); MVNETA_WRITE(sc, MVNETA_RQC, reg); rx->queue_status = MVNETA_QUEUE_WORKING; return (0); } STATIC int mvneta_tx_queue_enable(if_t ifp, int q) { struct mvneta_softc *sc; struct mvneta_tx_ring *tx; sc = if_getsoftc(ifp); tx = MVNETA_TX_RING(sc, q); KASSERT_TX_MTX(sc, q); /* Enable Tx queue */ MVNETA_WRITE(sc, MVNETA_TQC, MVNETA_TQC_ENQ(q)); tx->queue_status = MVNETA_QUEUE_IDLE; tx->queue_hung = FALSE; return (0); } STATIC __inline void mvneta_rx_lockq(struct mvneta_softc *sc, int q) { DASSERT(q >= 0); DASSERT(q < MVNETA_RX_QNUM_MAX); mtx_lock(&sc->rx_ring[q].ring_mtx); } STATIC __inline void mvneta_rx_unlockq(struct mvneta_softc *sc, int q) { DASSERT(q >= 0); DASSERT(q < MVNETA_RX_QNUM_MAX); mtx_unlock(&sc->rx_ring[q].ring_mtx); } STATIC __inline int __unused mvneta_tx_trylockq(struct mvneta_softc *sc, int q) { DASSERT(q >= 0); DASSERT(q < MVNETA_TX_QNUM_MAX); return (mtx_trylock(&sc->tx_ring[q].ring_mtx)); } STATIC __inline void mvneta_tx_lockq(struct mvneta_softc *sc, int q) { DASSERT(q >= 0); DASSERT(q < MVNETA_TX_QNUM_MAX); mtx_lock(&sc->tx_ring[q].ring_mtx); } STATIC __inline void mvneta_tx_unlockq(struct mvneta_softc *sc, int q) { DASSERT(q >= 0); DASSERT(q < MVNETA_TX_QNUM_MAX); mtx_unlock(&sc->tx_ring[q].ring_mtx); } /* * Interrupt Handlers */ STATIC void mvneta_disable_intr(struct mvneta_softc *sc) { MVNETA_WRITE(sc, MVNETA_EUIM, 0); MVNETA_WRITE(sc, MVNETA_EUIC, 0); MVNETA_WRITE(sc, MVNETA_PRXTXTIM, 0); MVNETA_WRITE(sc, MVNETA_PRXTXTIC, 0); MVNETA_WRITE(sc, MVNETA_PRXTXIM, 0); MVNETA_WRITE(sc, MVNETA_PRXTXIC, 0); MVNETA_WRITE(sc, MVNETA_PMIM, 0); MVNETA_WRITE(sc, MVNETA_PMIC, 0); MVNETA_WRITE(sc, MVNETA_PIE, 0); } STATIC void mvneta_enable_intr(struct mvneta_softc *sc) { uint32_t reg; /* Enable Summary Bit to check all interrupt cause. */ reg = MVNETA_READ(sc, MVNETA_PRXTXTIM); reg |= MVNETA_PRXTXTI_PMISCICSUMMARY; MVNETA_WRITE(sc, MVNETA_PRXTXTIM, reg); if (!sc->phy_attached || sc->use_inband_status) { /* Enable Port MISC Intr. (via RXTX_TH_Summary bit) */ MVNETA_WRITE(sc, MVNETA_PMIM, MVNETA_PMI_PHYSTATUSCHNG | MVNETA_PMI_LINKCHANGE | MVNETA_PMI_PSCSYNCCHANGE); } /* Enable All Queue Interrupt */ reg = MVNETA_READ(sc, MVNETA_PIE); reg |= MVNETA_PIE_RXPKTINTRPTENB_MASK; reg |= MVNETA_PIE_TXPKTINTRPTENB_MASK; MVNETA_WRITE(sc, MVNETA_PIE, reg); } STATIC void mvneta_rxtxth_intr(void *arg) { struct mvneta_softc *sc; if_t ifp; uint32_t ic, queues; sc = arg; ifp = sc->ifp; #ifdef MVNETA_KTR CTR1(KTR_SPARE2, "%s got RXTX_TH_Intr", if_name(ifp)); #endif ic = MVNETA_READ(sc, MVNETA_PRXTXTIC); if (ic == 0) return; MVNETA_WRITE(sc, MVNETA_PRXTXTIC, ~ic); /* Ack maintenance interrupt first */ if (__predict_false((ic & MVNETA_PRXTXTI_PMISCICSUMMARY) && (!sc->phy_attached || sc->use_inband_status))) { mvneta_sc_lock(sc); mvneta_misc_intr(sc); mvneta_sc_unlock(sc); } if (__predict_false(!(if_getdrvflags(ifp) & IFF_DRV_RUNNING))) return; /* RxTxTH interrupt */ queues = MVNETA_PRXTXTI_GET_RBICTAPQ(ic); if (__predict_true(queues)) { #ifdef MVNETA_KTR CTR1(KTR_SPARE2, "%s got PRXTXTIC: +RXEOF", if_name(ifp)); #endif /* At the moment the driver support only one RX queue. */ DASSERT(MVNETA_IS_QUEUE_SET(queues, 0)); mvneta_rx(sc, 0, 0); } } STATIC int mvneta_misc_intr(struct mvneta_softc *sc) { uint32_t ic; int claimed = 0; #ifdef MVNETA_KTR CTR1(KTR_SPARE2, "%s got MISC_INTR", if_name(sc->ifp)); #endif KASSERT_SC_MTX(sc); for (;;) { ic = MVNETA_READ(sc, MVNETA_PMIC); ic &= MVNETA_READ(sc, MVNETA_PMIM); if (ic == 0) break; MVNETA_WRITE(sc, MVNETA_PMIC, ~ic); claimed = 1; if (ic & (MVNETA_PMI_PHYSTATUSCHNG | MVNETA_PMI_LINKCHANGE | MVNETA_PMI_PSCSYNCCHANGE)) mvneta_link_isr(sc); } return (claimed); } STATIC void mvneta_tick(void *arg) { struct mvneta_softc *sc; struct mvneta_tx_ring *tx; struct mvneta_rx_ring *rx; int q; uint32_t fc_prev, fc_curr; sc = arg; /* * This is done before mib update to get the right stats * for this tick. */ mvneta_tx_drain(sc); /* Extract previous flow-control frame received counter. */ fc_prev = sc->sysctl_mib[MVNETA_MIB_FC_GOOD_IDX].counter; /* Read mib registers (clear by read). */ mvneta_update_mib(sc); /* Extract current flow-control frame received counter. */ fc_curr = sc->sysctl_mib[MVNETA_MIB_FC_GOOD_IDX].counter; if (sc->phy_attached && if_getflags(sc->ifp) & IFF_UP) { mvneta_sc_lock(sc); mii_tick(sc->mii); /* Adjust MAC settings */ mvneta_adjust_link(sc); mvneta_sc_unlock(sc); } /* * We were unable to refill the rx queue and left the rx func, leaving * the ring without mbuf and no way to call the refill func. */ for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { rx = MVNETA_RX_RING(sc, q); if (rx->needs_refill == TRUE) { mvneta_rx_lockq(sc, q); mvneta_rx_queue_refill(sc, q); mvneta_rx_unlockq(sc, q); } } /* * Watchdog: * - check if queue is mark as hung. * - ignore hung status if we received some pause frame * as hardware may have paused packet transmit. */ for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) { /* * We should take queue lock, but as we only read * queue status we can do it without lock, we may * only missdetect queue status for one tick. */ tx = MVNETA_TX_RING(sc, q); if (tx->queue_hung && (fc_curr - fc_prev) == 0) goto timeout; } callout_schedule(&sc->tick_ch, hz); return; timeout: if_printf(sc->ifp, "watchdog timeout\n"); mvneta_sc_lock(sc); sc->counter_watchdog++; sc->counter_watchdog_mib++; /* Trigger reinitialize sequence. */ mvneta_stop_locked(sc); mvneta_init_locked(sc); mvneta_sc_unlock(sc); } STATIC void mvneta_qflush(if_t ifp) { #ifdef MVNETA_MULTIQUEUE struct mvneta_softc *sc; struct mvneta_tx_ring *tx; struct mbuf *m; size_t q; sc = if_getsoftc(ifp); for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) { tx = MVNETA_TX_RING(sc, q); mvneta_tx_lockq(sc, q); while ((m = buf_ring_dequeue_sc(tx->br)) != NULL) m_freem(m); mvneta_tx_unlockq(sc, q); } #endif if_qflush(ifp); } STATIC void mvneta_tx_task(void *arg, int pending) { struct mvneta_softc *sc; struct mvneta_tx_ring *tx; if_t ifp; int error; tx = arg; ifp = tx->ifp; sc = if_getsoftc(ifp); mvneta_tx_lockq(sc, tx->qidx); error = mvneta_xmit_locked(sc, tx->qidx); mvneta_tx_unlockq(sc, tx->qidx); /* Try again */ if (__predict_false(error != 0 && error != ENETDOWN)) { pause("mvneta_tx_task_sleep", 1); taskqueue_enqueue(tx->taskq, &tx->task); } } STATIC int mvneta_xmitfast_locked(struct mvneta_softc *sc, int q, struct mbuf **m) { struct mvneta_tx_ring *tx; if_t ifp; int error; KASSERT_TX_MTX(sc, q); tx = MVNETA_TX_RING(sc, q); error = 0; ifp = sc->ifp; /* Dont enqueue packet if the queue is disabled. */ if (__predict_false(tx->queue_status == MVNETA_QUEUE_DISABLED)) { m_freem(*m); *m = NULL; return (ENETDOWN); } /* Reclaim mbuf if above threshold. */ if (__predict_true(tx->used > MVNETA_TX_RECLAIM_COUNT)) mvneta_tx_queue_complete(sc, q); /* Do not call transmit path if queue is already too full. */ if (__predict_false(tx->used > MVNETA_TX_RING_CNT - MVNETA_TX_SEGLIMIT)) return (ENOBUFS); error = mvneta_tx_queue(sc, m, q); if (__predict_false(error != 0)) return (error); /* Send a copy of the frame to the BPF listener */ ETHER_BPF_MTAP(ifp, *m); /* Set watchdog on */ tx->watchdog_time = ticks; tx->queue_status = MVNETA_QUEUE_WORKING; return (error); } #ifdef MVNETA_MULTIQUEUE STATIC int mvneta_transmit(if_t ifp, struct mbuf *m) { struct mvneta_softc *sc; struct mvneta_tx_ring *tx; int error; int q; sc = if_getsoftc(ifp); /* Use default queue if there is no flow id as thread can migrate. */ if (__predict_true(M_HASHTYPE_GET(m) != M_HASHTYPE_NONE)) q = m->m_pkthdr.flowid % MVNETA_TX_QNUM_MAX; else q = 0; tx = MVNETA_TX_RING(sc, q); /* If buf_ring is full start transmit immediately. */ if (buf_ring_full(tx->br)) { mvneta_tx_lockq(sc, q); mvneta_xmit_locked(sc, q); mvneta_tx_unlockq(sc, q); } /* * If the buf_ring is empty we will not reorder packets. * If the lock is available transmit without using buf_ring. */ if (buf_ring_empty(tx->br) && mvneta_tx_trylockq(sc, q) != 0) { error = mvneta_xmitfast_locked(sc, q, &m); mvneta_tx_unlockq(sc, q); if (__predict_true(error == 0)) return (0); /* Transmit can fail in fastpath. */ if (__predict_false(m == NULL)) return (error); } /* Enqueue then schedule taskqueue. */ error = drbr_enqueue(ifp, tx->br, m); if (__predict_false(error != 0)) return (error); taskqueue_enqueue(tx->taskq, &tx->task); return (0); } STATIC int mvneta_xmit_locked(struct mvneta_softc *sc, int q) { if_t ifp; struct mvneta_tx_ring *tx; struct mbuf *m; int error; KASSERT_TX_MTX(sc, q); ifp = sc->ifp; tx = MVNETA_TX_RING(sc, q); error = 0; while ((m = drbr_peek(ifp, tx->br)) != NULL) { error = mvneta_xmitfast_locked(sc, q, &m); if (__predict_false(error != 0)) { if (m != NULL) drbr_putback(ifp, tx->br, m); else drbr_advance(ifp, tx->br); break; } drbr_advance(ifp, tx->br); } return (error); } #else /* !MVNETA_MULTIQUEUE */ STATIC void mvneta_start(if_t ifp) { struct mvneta_softc *sc; struct mvneta_tx_ring *tx; int error; sc = if_getsoftc(ifp); tx = MVNETA_TX_RING(sc, 0); mvneta_tx_lockq(sc, 0); error = mvneta_xmit_locked(sc, 0); mvneta_tx_unlockq(sc, 0); /* Handle retransmit in the background taskq. */ if (__predict_false(error != 0 && error != ENETDOWN)) taskqueue_enqueue(tx->taskq, &tx->task); } STATIC int mvneta_xmit_locked(struct mvneta_softc *sc, int q) { if_t ifp; struct mbuf *m; int error; KASSERT_TX_MTX(sc, q); ifp = sc->ifp; error = 0; while (!if_sendq_empty(ifp)) { m = if_dequeue(ifp); if (m == NULL) break; error = mvneta_xmitfast_locked(sc, q, &m); if (__predict_false(error != 0)) { if (m != NULL) if_sendq_prepend(ifp, m); break; } } return (error); } #endif STATIC int mvneta_ioctl(if_t ifp, u_long cmd, caddr_t data) { struct mvneta_softc *sc; struct mvneta_rx_ring *rx; struct ifreq *ifr; int error, mask; uint32_t flags; bool reinit; int q; error = 0; reinit = false; sc = if_getsoftc(ifp); ifr = (struct ifreq *)data; switch (cmd) { case SIOCSIFFLAGS: mvneta_sc_lock(sc); if (if_getflags(ifp) & IFF_UP) { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { flags = if_getflags(ifp) ^ sc->mvneta_if_flags; if (flags != 0) sc->mvneta_if_flags = if_getflags(ifp); if ((flags & IFF_PROMISC) != 0) mvneta_filter_setup(sc); } else { mvneta_init_locked(sc); sc->mvneta_if_flags = if_getflags(ifp); if (sc->phy_attached) mii_mediachg(sc->mii); mvneta_sc_unlock(sc); break; } } else if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) mvneta_stop_locked(sc); sc->mvneta_if_flags = if_getflags(ifp); mvneta_sc_unlock(sc); break; case SIOCSIFCAP: if (if_getmtu(ifp) > sc->tx_csum_limit && ifr->ifr_reqcap & IFCAP_TXCSUM) ifr->ifr_reqcap &= ~IFCAP_TXCSUM; mask = if_getcapenable(ifp) ^ ifr->ifr_reqcap; if (mask & IFCAP_HWCSUM) { if_setcapenablebit(ifp, IFCAP_HWCSUM & ifr->ifr_reqcap, IFCAP_HWCSUM); if (if_getcapenable(ifp) & IFCAP_TXCSUM) if_sethwassist(ifp, CSUM_IP | CSUM_TCP | CSUM_UDP); else if_sethwassist(ifp, 0); } if (mask & IFCAP_LRO) { mvneta_sc_lock(sc); if_togglecapenable(ifp, IFCAP_LRO); if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0) { for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { rx = MVNETA_RX_RING(sc, q); rx->lro_enabled = !rx->lro_enabled; } } mvneta_sc_unlock(sc); } VLAN_CAPABILITIES(ifp); break; case SIOCSIFMEDIA: if ((IFM_SUBTYPE(ifr->ifr_media) == IFM_1000_T || IFM_SUBTYPE(ifr->ifr_media) == IFM_2500_T) && (ifr->ifr_media & IFM_FDX) == 0) { device_printf(sc->dev, "%s half-duplex unsupported\n", IFM_SUBTYPE(ifr->ifr_media) == IFM_1000_T ? "1000Base-T" : "2500Base-T"); error = EINVAL; break; } case SIOCGIFMEDIA: /* FALLTHROUGH */ case SIOCGIFXMEDIA: if (!sc->phy_attached) error = ifmedia_ioctl(ifp, ifr, &sc->mvneta_ifmedia, cmd); else error = ifmedia_ioctl(ifp, ifr, &sc->mii->mii_media, cmd); break; case SIOCSIFMTU: if (ifr->ifr_mtu < 68 || ifr->ifr_mtu > MVNETA_MAX_FRAME - MVNETA_ETHER_SIZE) { error = EINVAL; } else { if_setmtu(ifp, ifr->ifr_mtu); mvneta_sc_lock(sc); if (if_getmtu(ifp) + MVNETA_ETHER_SIZE <= MCLBYTES) { sc->rx_frame_size = MCLBYTES; } else { sc->rx_frame_size = MJUM9BYTES; } if (if_getmtu(ifp) > sc->tx_csum_limit) { if_setcapenablebit(ifp, 0, IFCAP_TXCSUM); if_sethwassist(ifp, 0); } else { if_setcapenablebit(ifp, IFCAP_TXCSUM, 0); if_sethwassist(ifp, CSUM_IP | CSUM_TCP | CSUM_UDP); } /* * Reinitialize RX queues. * We need to update RX descriptor size. */ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { reinit = true; mvneta_stop_locked(sc); } for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { mvneta_rx_lockq(sc, q); if (mvneta_rx_queue_init(ifp, q) != 0) { device_printf(sc->dev, "initialization failed:" " cannot initialize queue\n"); mvneta_rx_unlockq(sc, q); error = ENOBUFS; break; } mvneta_rx_unlockq(sc, q); } if (reinit) mvneta_init_locked(sc); mvneta_sc_unlock(sc); } break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } STATIC void mvneta_init_locked(void *arg) { struct mvneta_softc *sc; if_t ifp; uint32_t reg; int q, cpu; sc = arg; ifp = sc->ifp; if (!device_is_attached(sc->dev) || (if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0) return; mvneta_disable_intr(sc); callout_stop(&sc->tick_ch); /* Get the latest mac address */ bcopy(if_getlladdr(ifp), sc->enaddr, ETHER_ADDR_LEN); mvneta_set_mac_address(sc, sc->enaddr); mvneta_filter_setup(sc); /* Start DMA Engine */ MVNETA_WRITE(sc, MVNETA_PRXINIT, 0x00000000); MVNETA_WRITE(sc, MVNETA_PTXINIT, 0x00000000); MVNETA_WRITE(sc, MVNETA_PACC, MVNETA_PACC_ACCELERATIONMODE_EDM); /* Enable port */ reg = MVNETA_READ(sc, MVNETA_PMACC0); reg |= MVNETA_PMACC0_PORTEN; reg &= ~MVNETA_PMACC0_FRAMESIZELIMIT_MASK; reg |= MVNETA_PMACC0_FRAMESIZELIMIT(if_getmtu(ifp) + MVNETA_ETHER_SIZE); MVNETA_WRITE(sc, MVNETA_PMACC0, reg); /* Allow access to each TXQ/RXQ from both CPU's */ for (cpu = 0; cpu < mp_ncpus; ++cpu) MVNETA_WRITE(sc, MVNETA_PCP2Q(cpu), MVNETA_PCP2Q_TXQEN_MASK | MVNETA_PCP2Q_RXQEN_MASK); for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { mvneta_rx_lockq(sc, q); mvneta_rx_queue_refill(sc, q); mvneta_rx_unlockq(sc, q); } if (!sc->phy_attached) mvneta_linkup(sc); /* Enable interrupt */ mvneta_enable_intr(sc); /* Set Counter */ callout_schedule(&sc->tick_ch, hz); if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0); } STATIC void mvneta_init(void *arg) { struct mvneta_softc *sc; sc = arg; mvneta_sc_lock(sc); mvneta_init_locked(sc); if (sc->phy_attached) mii_mediachg(sc->mii); mvneta_sc_unlock(sc); } /* ARGSUSED */ STATIC void mvneta_stop_locked(struct mvneta_softc *sc) { if_t ifp; uint32_t reg; int q; ifp = sc->ifp; if (ifp == NULL || (if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) return; mvneta_disable_intr(sc); callout_stop(&sc->tick_ch); if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING); /* Link down */ if (sc->linkup == TRUE) mvneta_linkdown(sc); /* Reset the MAC Port Enable bit */ reg = MVNETA_READ(sc, MVNETA_PMACC0); reg &= ~MVNETA_PMACC0_PORTEN; MVNETA_WRITE(sc, MVNETA_PMACC0, reg); /* Disable each of queue */ for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { mvneta_rx_lockq(sc, q); mvneta_ring_flush_rx_queue(sc, q); mvneta_rx_unlockq(sc, q); } /* * Hold Reset state of DMA Engine * (must write 0x0 to restart it) */ MVNETA_WRITE(sc, MVNETA_PRXINIT, 0x00000001); MVNETA_WRITE(sc, MVNETA_PTXINIT, 0x00000001); for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) { mvneta_tx_lockq(sc, q); mvneta_ring_flush_tx_queue(sc, q); mvneta_tx_unlockq(sc, q); } } STATIC void mvneta_stop(struct mvneta_softc *sc) { mvneta_sc_lock(sc); mvneta_stop_locked(sc); mvneta_sc_unlock(sc); } STATIC int mvneta_mediachange(if_t ifp) { struct mvneta_softc *sc; sc = if_getsoftc(ifp); if (!sc->phy_attached && !sc->use_inband_status) { /* We shouldn't be here */ if_printf(ifp, "Cannot change media in fixed-link mode!\n"); return (0); } if (sc->use_inband_status) { mvneta_update_media(sc, sc->mvneta_ifmedia.ifm_media); return (0); } mvneta_sc_lock(sc); /* Update PHY */ mii_mediachg(sc->mii); mvneta_sc_unlock(sc); return (0); } STATIC void mvneta_get_media(struct mvneta_softc *sc, struct ifmediareq *ifmr) { uint32_t psr; psr = MVNETA_READ(sc, MVNETA_PSR); /* Speed */ if (psr & MVNETA_PSR_GMIISPEED) ifmr->ifm_active = IFM_ETHER_SUBTYPE_SET(IFM_1000_T); else if (psr & MVNETA_PSR_MIISPEED) ifmr->ifm_active = IFM_ETHER_SUBTYPE_SET(IFM_100_TX); else if (psr & MVNETA_PSR_LINKUP) ifmr->ifm_active = IFM_ETHER_SUBTYPE_SET(IFM_10_T); /* Duplex */ if (psr & MVNETA_PSR_FULLDX) ifmr->ifm_active |= IFM_FDX; /* Link */ ifmr->ifm_status = IFM_AVALID; if (psr & MVNETA_PSR_LINKUP) ifmr->ifm_status |= IFM_ACTIVE; } STATIC void mvneta_mediastatus(if_t ifp, struct ifmediareq *ifmr) { struct mvneta_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); if (!sc->phy_attached && !sc->use_inband_status) { ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE; return; } mvneta_sc_lock(sc); if (sc->use_inband_status) { mvneta_get_media(sc, ifmr); mvneta_sc_unlock(sc); return; } mii = sc->mii; mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; mvneta_sc_unlock(sc); } /* * Link State Notify */ STATIC void mvneta_update_autoneg(struct mvneta_softc *sc, int enable) { int reg; if (enable) { reg = MVNETA_READ(sc, MVNETA_PANC); reg &= ~(MVNETA_PANC_FORCELINKFAIL | MVNETA_PANC_FORCELINKPASS | MVNETA_PANC_ANFCEN); reg |= MVNETA_PANC_ANDUPLEXEN | MVNETA_PANC_ANSPEEDEN | MVNETA_PANC_INBANDANEN; MVNETA_WRITE(sc, MVNETA_PANC, reg); reg = MVNETA_READ(sc, MVNETA_PMACC2); reg |= MVNETA_PMACC2_INBANDANMODE; MVNETA_WRITE(sc, MVNETA_PMACC2, reg); reg = MVNETA_READ(sc, MVNETA_PSOMSCD); reg |= MVNETA_PSOMSCD_ENABLE; MVNETA_WRITE(sc, MVNETA_PSOMSCD, reg); } else { reg = MVNETA_READ(sc, MVNETA_PANC); reg &= ~(MVNETA_PANC_FORCELINKFAIL | MVNETA_PANC_FORCELINKPASS | MVNETA_PANC_ANDUPLEXEN | MVNETA_PANC_ANSPEEDEN | MVNETA_PANC_INBANDANEN); MVNETA_WRITE(sc, MVNETA_PANC, reg); reg = MVNETA_READ(sc, MVNETA_PMACC2); reg &= ~MVNETA_PMACC2_INBANDANMODE; MVNETA_WRITE(sc, MVNETA_PMACC2, reg); reg = MVNETA_READ(sc, MVNETA_PSOMSCD); reg &= ~MVNETA_PSOMSCD_ENABLE; MVNETA_WRITE(sc, MVNETA_PSOMSCD, reg); } } STATIC int mvneta_update_media(struct mvneta_softc *sc, int media) { int reg, err; boolean_t running; err = 0; mvneta_sc_lock(sc); mvneta_linkreset(sc); running = (if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING) != 0; if (running) mvneta_stop_locked(sc); sc->autoneg = (IFM_SUBTYPE(media) == IFM_AUTO); if (!sc->phy_attached || sc->use_inband_status) mvneta_update_autoneg(sc, IFM_SUBTYPE(media) == IFM_AUTO); mvneta_update_eee(sc); mvneta_update_fc(sc); if (IFM_SUBTYPE(media) != IFM_AUTO) { reg = MVNETA_READ(sc, MVNETA_PANC); reg &= ~(MVNETA_PANC_SETGMIISPEED | MVNETA_PANC_SETMIISPEED | MVNETA_PANC_SETFULLDX); if (IFM_SUBTYPE(media) == IFM_1000_T || IFM_SUBTYPE(media) == IFM_2500_T) { if ((media & IFM_FDX) == 0) { device_printf(sc->dev, "%s half-duplex unsupported\n", IFM_SUBTYPE(media) == IFM_1000_T ? "1000Base-T" : "2500Base-T"); err = EINVAL; goto out; } reg |= MVNETA_PANC_SETGMIISPEED; } else if (IFM_SUBTYPE(media) == IFM_100_TX) reg |= MVNETA_PANC_SETMIISPEED; if (media & IFM_FDX) reg |= MVNETA_PANC_SETFULLDX; MVNETA_WRITE(sc, MVNETA_PANC, reg); } out: if (running) mvneta_init_locked(sc); mvneta_sc_unlock(sc); return (err); } STATIC void mvneta_adjust_link(struct mvneta_softc *sc) { boolean_t phy_linkup; int reg; /* Update eee/fc */ mvneta_update_eee(sc); mvneta_update_fc(sc); /* Check for link change */ phy_linkup = (sc->mii->mii_media_status & (IFM_AVALID | IFM_ACTIVE)) == (IFM_AVALID | IFM_ACTIVE); if (sc->linkup != phy_linkup) mvneta_linkupdate(sc, phy_linkup); /* Don't update media on disabled link */ if (!phy_linkup) return; /* Check for media type change */ if (sc->mvneta_media != sc->mii->mii_media_active) { sc->mvneta_media = sc->mii->mii_media_active; reg = MVNETA_READ(sc, MVNETA_PANC); reg &= ~(MVNETA_PANC_SETGMIISPEED | MVNETA_PANC_SETMIISPEED | MVNETA_PANC_SETFULLDX); if (IFM_SUBTYPE(sc->mvneta_media) == IFM_1000_T || IFM_SUBTYPE(sc->mvneta_media) == IFM_2500_T) { reg |= MVNETA_PANC_SETGMIISPEED; } else if (IFM_SUBTYPE(sc->mvneta_media) == IFM_100_TX) reg |= MVNETA_PANC_SETMIISPEED; if (sc->mvneta_media & IFM_FDX) reg |= MVNETA_PANC_SETFULLDX; MVNETA_WRITE(sc, MVNETA_PANC, reg); } } STATIC void mvneta_link_isr(struct mvneta_softc *sc) { int linkup; KASSERT_SC_MTX(sc); linkup = MVNETA_IS_LINKUP(sc) ? TRUE : FALSE; if (sc->linkup == linkup) return; if (linkup == TRUE) mvneta_linkup(sc); else mvneta_linkdown(sc); #ifdef DEBUG device_printf(sc->dev, "%s: link %s\n", if_name(sc->ifp), linkup ? "up" : "down"); #endif } STATIC void mvneta_linkupdate(struct mvneta_softc *sc, boolean_t linkup) { KASSERT_SC_MTX(sc); if (linkup == TRUE) mvneta_linkup(sc); else mvneta_linkdown(sc); #ifdef DEBUG device_printf(sc->dev, "%s: link %s\n", if_name(sc->ifp), linkup ? "up" : "down"); #endif } STATIC void mvneta_update_eee(struct mvneta_softc *sc) { uint32_t reg; KASSERT_SC_MTX(sc); /* set EEE parameters */ reg = MVNETA_READ(sc, MVNETA_LPIC1); if (sc->cf_lpi) reg |= MVNETA_LPIC1_LPIRE; else reg &= ~MVNETA_LPIC1_LPIRE; MVNETA_WRITE(sc, MVNETA_LPIC1, reg); } STATIC void mvneta_update_fc(struct mvneta_softc *sc) { uint32_t reg; KASSERT_SC_MTX(sc); reg = MVNETA_READ(sc, MVNETA_PANC); if (sc->cf_fc) { /* Flow control negotiation */ reg |= MVNETA_PANC_PAUSEADV; reg |= MVNETA_PANC_ANFCEN; } else { /* Disable flow control negotiation */ reg &= ~MVNETA_PANC_PAUSEADV; reg &= ~MVNETA_PANC_ANFCEN; } MVNETA_WRITE(sc, MVNETA_PANC, reg); } STATIC void mvneta_linkup(struct mvneta_softc *sc) { uint32_t reg; KASSERT_SC_MTX(sc); if (!sc->phy_attached || !sc->use_inband_status) { reg = MVNETA_READ(sc, MVNETA_PANC); reg |= MVNETA_PANC_FORCELINKPASS; reg &= ~MVNETA_PANC_FORCELINKFAIL; MVNETA_WRITE(sc, MVNETA_PANC, reg); } mvneta_qflush(sc->ifp); mvneta_portup(sc); sc->linkup = TRUE; if_link_state_change(sc->ifp, LINK_STATE_UP); } STATIC void mvneta_linkdown(struct mvneta_softc *sc) { uint32_t reg; KASSERT_SC_MTX(sc); if (!sc->phy_attached || !sc->use_inband_status) { reg = MVNETA_READ(sc, MVNETA_PANC); reg &= ~MVNETA_PANC_FORCELINKPASS; reg |= MVNETA_PANC_FORCELINKFAIL; MVNETA_WRITE(sc, MVNETA_PANC, reg); } mvneta_portdown(sc); mvneta_qflush(sc->ifp); sc->linkup = FALSE; if_link_state_change(sc->ifp, LINK_STATE_DOWN); } STATIC void mvneta_linkreset(struct mvneta_softc *sc) { struct mii_softc *mii; if (sc->phy_attached) { /* Force reset PHY */ mii = LIST_FIRST(&sc->mii->mii_phys); if (mii) mii_phy_reset(mii); } } /* * Tx Subroutines */ STATIC int mvneta_tx_queue(struct mvneta_softc *sc, struct mbuf **mbufp, int q) { if_t ifp; bus_dma_segment_t txsegs[MVNETA_TX_SEGLIMIT]; struct mbuf *mtmp, *mbuf; struct mvneta_tx_ring *tx; struct mvneta_buf *txbuf; struct mvneta_tx_desc *t; uint32_t ptxsu; int used, error, i, txnsegs; mbuf = *mbufp; tx = MVNETA_TX_RING(sc, q); DASSERT(tx->used >= 0); DASSERT(tx->used <= MVNETA_TX_RING_CNT); t = NULL; ifp = sc->ifp; if (__predict_false(mbuf->m_flags & M_VLANTAG)) { mbuf = ether_vlanencap(mbuf, mbuf->m_pkthdr.ether_vtag); if (mbuf == NULL) { tx->drv_error++; *mbufp = NULL; return (ENOBUFS); } mbuf->m_flags &= ~M_VLANTAG; *mbufp = mbuf; } if (__predict_false(mbuf->m_next != NULL && (mbuf->m_pkthdr.csum_flags & (CSUM_IP | CSUM_TCP | CSUM_UDP)) != 0)) { if (M_WRITABLE(mbuf) == 0) { mtmp = m_dup(mbuf, M_NOWAIT); m_freem(mbuf); if (mtmp == NULL) { tx->drv_error++; *mbufp = NULL; return (ENOBUFS); } *mbufp = mbuf = mtmp; } } /* load mbuf using dmamap of 1st descriptor */ txbuf = &tx->txbuf[tx->cpu]; error = bus_dmamap_load_mbuf_sg(sc->txmbuf_dtag, txbuf->dmap, mbuf, txsegs, &txnsegs, BUS_DMA_NOWAIT); if (__predict_false(error != 0)) { #ifdef MVNETA_KTR CTR3(KTR_SPARE2, "%s:%u bus_dmamap_load_mbuf_sg error=%d", if_name(ifp), q, error); #endif /* This is the only recoverable error (except EFBIG). */ if (error != ENOMEM) { tx->drv_error++; m_freem(mbuf); *mbufp = NULL; return (ENOBUFS); } return (error); } if (__predict_false(txnsegs <= 0 || (txnsegs + tx->used) > MVNETA_TX_RING_CNT)) { /* we have no enough descriptors or mbuf is broken */ #ifdef MVNETA_KTR CTR3(KTR_SPARE2, "%s:%u not enough descriptors txnsegs=%d", if_name(ifp), q, txnsegs); #endif bus_dmamap_unload(sc->txmbuf_dtag, txbuf->dmap); return (ENOBUFS); } DASSERT(txbuf->m == NULL); /* remember mbuf using 1st descriptor */ txbuf->m = mbuf; bus_dmamap_sync(sc->txmbuf_dtag, txbuf->dmap, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); /* load to tx descriptors */ used = 0; for (i = 0; i < txnsegs; i++) { t = &tx->desc[tx->cpu]; t->command = 0; t->l4ichk = 0; t->flags = 0; if (__predict_true(i == 0)) { /* 1st descriptor */ t->command |= MVNETA_TX_CMD_W_PACKET_OFFSET(0); t->command |= MVNETA_TX_CMD_F; mvneta_tx_set_csumflag(ifp, t, mbuf); } t->bufptr_pa = txsegs[i].ds_addr; t->bytecnt = txsegs[i].ds_len; tx->cpu = tx_counter_adv(tx->cpu, 1); tx->used++; used++; } /* t is last descriptor here */ DASSERT(t != NULL); t->command |= MVNETA_TX_CMD_L|MVNETA_TX_CMD_PADDING; bus_dmamap_sync(sc->tx_dtag, tx->desc_map, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); while (__predict_false(used > 255)) { ptxsu = MVNETA_PTXSU_NOWD(255); MVNETA_WRITE(sc, MVNETA_PTXSU(q), ptxsu); used -= 255; } if (__predict_true(used > 0)) { ptxsu = MVNETA_PTXSU_NOWD(used); MVNETA_WRITE(sc, MVNETA_PTXSU(q), ptxsu); } return (0); } STATIC void mvneta_tx_set_csumflag(if_t ifp, struct mvneta_tx_desc *t, struct mbuf *m) { struct ether_header *eh; struct ether_vlan_header *evh; int csum_flags; uint32_t iphl, ipoff; struct ip *ip; iphl = ipoff = 0; csum_flags = if_gethwassist(ifp) & m->m_pkthdr.csum_flags; eh = mtod(m, struct ether_header *); switch (ntohs(eh->ether_type)) { case ETHERTYPE_IP: ipoff = ETHER_HDR_LEN; break; case ETHERTYPE_VLAN: ipoff = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN; evh = mtod(m, struct ether_vlan_header *); if (ntohs(evh->evl_proto) == ETHERTYPE_VLAN) ipoff += ETHER_VLAN_ENCAP_LEN; break; default: csum_flags = 0; } if (__predict_true(csum_flags & (CSUM_IP|CSUM_IP_TCP|CSUM_IP_UDP))) { ip = (struct ip *)(m->m_data + ipoff); iphl = ip->ip_hl<<2; t->command |= MVNETA_TX_CMD_L3_IP4; } else { t->command |= MVNETA_TX_CMD_L4_CHECKSUM_NONE; return; } /* L3 */ if (csum_flags & CSUM_IP) { t->command |= MVNETA_TX_CMD_IP4_CHECKSUM; } /* L4 */ if (csum_flags & CSUM_IP_TCP) { t->command |= MVNETA_TX_CMD_L4_CHECKSUM_NOFRAG; t->command |= MVNETA_TX_CMD_L4_TCP; } else if (csum_flags & CSUM_IP_UDP) { t->command |= MVNETA_TX_CMD_L4_CHECKSUM_NOFRAG; t->command |= MVNETA_TX_CMD_L4_UDP; } else t->command |= MVNETA_TX_CMD_L4_CHECKSUM_NONE; t->l4ichk = 0; t->command |= MVNETA_TX_CMD_IP_HEADER_LEN(iphl >> 2); t->command |= MVNETA_TX_CMD_L3_OFFSET(ipoff); } STATIC void mvneta_tx_queue_complete(struct mvneta_softc *sc, int q) { struct mvneta_tx_ring *tx; struct mvneta_buf *txbuf; struct mvneta_tx_desc *t __diagused; uint32_t ptxs, ptxsu, ndesc; int i; KASSERT_TX_MTX(sc, q); tx = MVNETA_TX_RING(sc, q); if (__predict_false(tx->queue_status == MVNETA_QUEUE_DISABLED)) return; ptxs = MVNETA_READ(sc, MVNETA_PTXS(q)); ndesc = MVNETA_PTXS_GET_TBC(ptxs); if (__predict_false(ndesc == 0)) { if (tx->used == 0) tx->queue_status = MVNETA_QUEUE_IDLE; else if (tx->queue_status == MVNETA_QUEUE_WORKING && ((ticks - tx->watchdog_time) > MVNETA_WATCHDOG)) tx->queue_hung = TRUE; return; } #ifdef MVNETA_KTR CTR3(KTR_SPARE2, "%s:%u tx_complete begin ndesc=%u", if_name(sc->ifp), q, ndesc); #endif bus_dmamap_sync(sc->tx_dtag, tx->desc_map, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE); for (i = 0; i < ndesc; i++) { t = &tx->desc[tx->dma]; #ifdef MVNETA_KTR if (t->flags & MVNETA_TX_F_ES) CTR3(KTR_SPARE2, "%s tx error queue %d desc %d", if_name(sc->ifp), q, tx->dma); #endif txbuf = &tx->txbuf[tx->dma]; if (__predict_true(txbuf->m != NULL)) { DASSERT((t->command & MVNETA_TX_CMD_F) != 0); bus_dmamap_unload(sc->txmbuf_dtag, txbuf->dmap); m_freem(txbuf->m); txbuf->m = NULL; } else DASSERT((t->flags & MVNETA_TX_CMD_F) == 0); tx->dma = tx_counter_adv(tx->dma, 1); tx->used--; } DASSERT(tx->used >= 0); DASSERT(tx->used <= MVNETA_TX_RING_CNT); while (__predict_false(ndesc > 255)) { ptxsu = MVNETA_PTXSU_NORB(255); MVNETA_WRITE(sc, MVNETA_PTXSU(q), ptxsu); ndesc -= 255; } if (__predict_true(ndesc > 0)) { ptxsu = MVNETA_PTXSU_NORB(ndesc); MVNETA_WRITE(sc, MVNETA_PTXSU(q), ptxsu); } #ifdef MVNETA_KTR CTR5(KTR_SPARE2, "%s:%u tx_complete tx_cpu=%d tx_dma=%d tx_used=%d", if_name(sc->ifp), q, tx->cpu, tx->dma, tx->used); #endif tx->watchdog_time = ticks; if (tx->used == 0) tx->queue_status = MVNETA_QUEUE_IDLE; } /* * Do a final TX complete when TX is idle. */ STATIC void mvneta_tx_drain(struct mvneta_softc *sc) { struct mvneta_tx_ring *tx; int q; /* * Handle trailing mbuf on TX queue. * Check is done lockess to avoid TX path contention. */ for (q = 0; q < MVNETA_TX_QNUM_MAX; q++) { tx = MVNETA_TX_RING(sc, q); if ((ticks - tx->watchdog_time) > MVNETA_WATCHDOG_TXCOMP && tx->used > 0) { mvneta_tx_lockq(sc, q); mvneta_tx_queue_complete(sc, q); mvneta_tx_unlockq(sc, q); } } } /* * Rx Subroutines */ STATIC int mvneta_rx(struct mvneta_softc *sc, int q, int count) { uint32_t prxs, npkt; int more; more = 0; mvneta_rx_lockq(sc, q); prxs = MVNETA_READ(sc, MVNETA_PRXS(q)); npkt = MVNETA_PRXS_GET_ODC(prxs); if (__predict_false(npkt == 0)) goto out; if (count > 0 && npkt > count) { more = 1; npkt = count; } mvneta_rx_queue(sc, q, npkt); out: mvneta_rx_unlockq(sc, q); return more; } /* * Helper routine for updating PRXSU register of a given queue. * Handles number of processed descriptors bigger than maximum acceptable value. */ STATIC __inline void mvneta_prxsu_update(struct mvneta_softc *sc, int q, int processed) { uint32_t prxsu; while (__predict_false(processed > 255)) { prxsu = MVNETA_PRXSU_NOOFPROCESSEDDESCRIPTORS(255); MVNETA_WRITE(sc, MVNETA_PRXSU(q), prxsu); processed -= 255; } prxsu = MVNETA_PRXSU_NOOFPROCESSEDDESCRIPTORS(processed); MVNETA_WRITE(sc, MVNETA_PRXSU(q), prxsu); } static __inline void mvneta_prefetch(void *p) { __builtin_prefetch(p); } STATIC void mvneta_rx_queue(struct mvneta_softc *sc, int q, int npkt) { if_t ifp; struct mvneta_rx_ring *rx; struct mvneta_rx_desc *r; struct mvneta_buf *rxbuf; struct mbuf *m; void *pktbuf; int i, pktlen, processed, ndma; KASSERT_RX_MTX(sc, q); ifp = sc->ifp; rx = MVNETA_RX_RING(sc, q); processed = 0; if (__predict_false(rx->queue_status == MVNETA_QUEUE_DISABLED)) return; bus_dmamap_sync(sc->rx_dtag, rx->desc_map, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE); for (i = 0; i < npkt; i++) { /* Prefetch next desc, rxbuf. */ ndma = rx_counter_adv(rx->dma, 1); mvneta_prefetch(&rx->desc[ndma]); mvneta_prefetch(&rx->rxbuf[ndma]); /* get descriptor and packet */ r = &rx->desc[rx->dma]; rxbuf = &rx->rxbuf[rx->dma]; m = rxbuf->m; rxbuf->m = NULL; DASSERT(m != NULL); bus_dmamap_sync(sc->rxbuf_dtag, rxbuf->dmap, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->rxbuf_dtag, rxbuf->dmap); /* Prefetch mbuf header. */ mvneta_prefetch(m); processed++; /* Drop desc with error status or not in a single buffer. */ DASSERT((r->status & (MVNETA_RX_F|MVNETA_RX_L)) == (MVNETA_RX_F|MVNETA_RX_L)); if (__predict_false((r->status & MVNETA_RX_ES) || (r->status & (MVNETA_RX_F|MVNETA_RX_L)) != (MVNETA_RX_F|MVNETA_RX_L))) goto rx_error; /* * [ OFF | MH | PKT | CRC ] * bytecnt cover MH, PKT, CRC */ pktlen = r->bytecnt - ETHER_CRC_LEN - MVNETA_HWHEADER_SIZE; pktbuf = (uint8_t *)rx->rxbuf_virt_addr[rx->dma] + MVNETA_PACKET_OFFSET + MVNETA_HWHEADER_SIZE; /* Prefetch mbuf data. */ mvneta_prefetch(pktbuf); /* Write value to mbuf (avoid read). */ m->m_data = pktbuf; m->m_len = m->m_pkthdr.len = pktlen; m->m_pkthdr.rcvif = ifp; mvneta_rx_set_csumflag(ifp, r, m); /* Increase rx_dma before releasing the lock. */ rx->dma = ndma; if (__predict_false(rx->lro_enabled && ((r->status & MVNETA_RX_L3_IP) != 0) && ((r->status & MVNETA_RX_L4_MASK) == MVNETA_RX_L4_TCP) && (m->m_pkthdr.csum_flags & (CSUM_DATA_VALID | CSUM_PSEUDO_HDR)) == (CSUM_DATA_VALID | CSUM_PSEUDO_HDR))) { if (rx->lro.lro_cnt != 0) { if (tcp_lro_rx(&rx->lro, m, 0) == 0) goto rx_done; } } mvneta_rx_unlockq(sc, q); if_input(ifp, m); mvneta_rx_lockq(sc, q); /* * Check whether this queue has been disabled in the * meantime. If yes, then clear LRO and exit. */ if(__predict_false(rx->queue_status == MVNETA_QUEUE_DISABLED)) goto rx_lro; rx_done: /* Refresh receive ring to avoid stall and minimize jitter. */ if (processed >= MVNETA_RX_REFILL_COUNT) { mvneta_prxsu_update(sc, q, processed); mvneta_rx_queue_refill(sc, q); processed = 0; } continue; rx_error: m_freem(m); rx->dma = ndma; /* Refresh receive ring to avoid stall and minimize jitter. */ if (processed >= MVNETA_RX_REFILL_COUNT) { mvneta_prxsu_update(sc, q, processed); mvneta_rx_queue_refill(sc, q); processed = 0; } } #ifdef MVNETA_KTR CTR3(KTR_SPARE2, "%s:%u %u packets received", if_name(ifp), q, npkt); #endif /* DMA status update */ mvneta_prxsu_update(sc, q, processed); /* Refill the rest of buffers if there are any to refill */ mvneta_rx_queue_refill(sc, q); rx_lro: /* * Flush any outstanding LRO work */ tcp_lro_flush_all(&rx->lro); } STATIC void mvneta_rx_buf_free(struct mvneta_softc *sc, struct mvneta_buf *rxbuf) { bus_dmamap_unload(sc->rxbuf_dtag, rxbuf->dmap); /* This will remove all data at once */ m_freem(rxbuf->m); } STATIC void mvneta_rx_queue_refill(struct mvneta_softc *sc, int q) { struct mvneta_rx_ring *rx; struct mvneta_rx_desc *r; struct mvneta_buf *rxbuf; bus_dma_segment_t segs; struct mbuf *m; uint32_t prxs, prxsu, ndesc; int npkt, refill, nsegs, error; KASSERT_RX_MTX(sc, q); rx = MVNETA_RX_RING(sc, q); prxs = MVNETA_READ(sc, MVNETA_PRXS(q)); ndesc = MVNETA_PRXS_GET_NODC(prxs) + MVNETA_PRXS_GET_ODC(prxs); refill = MVNETA_RX_RING_CNT - ndesc; #ifdef MVNETA_KTR CTR3(KTR_SPARE2, "%s:%u refill %u packets", if_name(sc->ifp), q, refill); #endif if (__predict_false(refill <= 0)) return; for (npkt = 0; npkt < refill; npkt++) { rxbuf = &rx->rxbuf[rx->cpu]; m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR, sc->rx_frame_size); if (__predict_false(m == NULL)) { error = ENOBUFS; break; } m->m_len = m->m_pkthdr.len = m->m_ext.ext_size; error = bus_dmamap_load_mbuf_sg(sc->rxbuf_dtag, rxbuf->dmap, m, &segs, &nsegs, BUS_DMA_NOWAIT); if (__predict_false(error != 0 || nsegs != 1)) { KASSERT(1, ("Failed to load Rx mbuf DMA map")); m_freem(m); break; } /* Add the packet to the ring */ rxbuf->m = m; r = &rx->desc[rx->cpu]; r->bufptr_pa = segs.ds_addr; rx->rxbuf_virt_addr[rx->cpu] = m->m_data; rx->cpu = rx_counter_adv(rx->cpu, 1); } if (npkt == 0) { if (refill == MVNETA_RX_RING_CNT) rx->needs_refill = TRUE; return; } rx->needs_refill = FALSE; bus_dmamap_sync(sc->rx_dtag, rx->desc_map, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); while (__predict_false(npkt > 255)) { prxsu = MVNETA_PRXSU_NOOFNEWDESCRIPTORS(255); MVNETA_WRITE(sc, MVNETA_PRXSU(q), prxsu); npkt -= 255; } if (__predict_true(npkt > 0)) { prxsu = MVNETA_PRXSU_NOOFNEWDESCRIPTORS(npkt); MVNETA_WRITE(sc, MVNETA_PRXSU(q), prxsu); } } STATIC __inline void mvneta_rx_set_csumflag(if_t ifp, struct mvneta_rx_desc *r, struct mbuf *m) { uint32_t csum_flags; csum_flags = 0; if (__predict_false((r->status & (MVNETA_RX_IP_HEADER_OK|MVNETA_RX_L3_IP)) == 0)) return; /* not a IP packet */ /* L3 */ if (__predict_true((r->status & MVNETA_RX_IP_HEADER_OK) == MVNETA_RX_IP_HEADER_OK)) csum_flags |= CSUM_L3_CALC|CSUM_L3_VALID; if (__predict_true((r->status & (MVNETA_RX_IP_HEADER_OK|MVNETA_RX_L3_IP)) == (MVNETA_RX_IP_HEADER_OK|MVNETA_RX_L3_IP))) { /* L4 */ switch (r->status & MVNETA_RX_L4_MASK) { case MVNETA_RX_L4_TCP: case MVNETA_RX_L4_UDP: csum_flags |= CSUM_L4_CALC; if (__predict_true((r->status & MVNETA_RX_L4_CHECKSUM_OK) == MVNETA_RX_L4_CHECKSUM_OK)) { csum_flags |= CSUM_L4_VALID; m->m_pkthdr.csum_data = htons(0xffff); } break; case MVNETA_RX_L4_OTH: default: break; } } m->m_pkthdr.csum_flags = csum_flags; } /* * MAC address filter */ STATIC void mvneta_filter_setup(struct mvneta_softc *sc) { if_t ifp; uint32_t dfut[MVNETA_NDFUT], dfsmt[MVNETA_NDFSMT], dfomt[MVNETA_NDFOMT]; uint32_t pxc; int i; KASSERT_SC_MTX(sc); memset(dfut, 0, sizeof(dfut)); memset(dfsmt, 0, sizeof(dfsmt)); memset(dfomt, 0, sizeof(dfomt)); ifp = sc->ifp; if_setflagbits(ifp, IFF_ALLMULTI, 0); if (if_getflags(ifp) & (IFF_ALLMULTI | IFF_PROMISC)) { for (i = 0; i < MVNETA_NDFSMT; i++) { dfsmt[i] = dfomt[i] = MVNETA_DF(0, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS) | MVNETA_DF(1, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS) | MVNETA_DF(2, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS) | MVNETA_DF(3, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS); } } pxc = MVNETA_READ(sc, MVNETA_PXC); pxc &= ~(MVNETA_PXC_UPM | MVNETA_PXC_RXQ_MASK | MVNETA_PXC_RXQARP_MASK | MVNETA_PXC_TCPQ_MASK | MVNETA_PXC_UDPQ_MASK | MVNETA_PXC_BPDUQ_MASK); pxc |= MVNETA_PXC_RXQ(MVNETA_RX_QNUM_MAX-1); pxc |= MVNETA_PXC_RXQARP(MVNETA_RX_QNUM_MAX-1); pxc |= MVNETA_PXC_TCPQ(MVNETA_RX_QNUM_MAX-1); pxc |= MVNETA_PXC_UDPQ(MVNETA_RX_QNUM_MAX-1); pxc |= MVNETA_PXC_BPDUQ(MVNETA_RX_QNUM_MAX-1); pxc |= MVNETA_PXC_RB | MVNETA_PXC_RBIP | MVNETA_PXC_RBARP; if (if_getflags(ifp) & IFF_BROADCAST) { pxc &= ~(MVNETA_PXC_RB | MVNETA_PXC_RBIP | MVNETA_PXC_RBARP); } if (if_getflags(ifp) & IFF_PROMISC) { pxc |= MVNETA_PXC_UPM; } MVNETA_WRITE(sc, MVNETA_PXC, pxc); /* Set Destination Address Filter Unicast Table */ if (if_getflags(ifp) & IFF_PROMISC) { /* pass all unicast addresses */ for (i = 0; i < MVNETA_NDFUT; i++) { dfut[i] = MVNETA_DF(0, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS) | MVNETA_DF(1, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS) | MVNETA_DF(2, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS) | MVNETA_DF(3, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS); } } else { i = sc->enaddr[5] & 0xf; /* last nibble */ dfut[i>>2] = MVNETA_DF(i&3, MVNETA_DF_QUEUE(0) | MVNETA_DF_PASS); } MVNETA_WRITE_REGION(sc, MVNETA_DFUT(0), dfut, MVNETA_NDFUT); /* Set Destination Address Filter Multicast Tables */ MVNETA_WRITE_REGION(sc, MVNETA_DFSMT(0), dfsmt, MVNETA_NDFSMT); MVNETA_WRITE_REGION(sc, MVNETA_DFOMT(0), dfomt, MVNETA_NDFOMT); } /* * sysctl(9) */ STATIC int sysctl_read_mib(SYSCTL_HANDLER_ARGS) { struct mvneta_sysctl_mib *arg; struct mvneta_softc *sc; uint64_t val; arg = (struct mvneta_sysctl_mib *)arg1; if (arg == NULL) return (EINVAL); sc = arg->sc; if (sc == NULL) return (EINVAL); if (arg->index < 0 || arg->index > MVNETA_PORTMIB_NOCOUNTER) return (EINVAL); mvneta_sc_lock(sc); val = arg->counter; mvneta_sc_unlock(sc); return sysctl_handle_64(oidp, &val, 0, req); } STATIC int sysctl_clear_mib(SYSCTL_HANDLER_ARGS) { struct mvneta_softc *sc; int err, val; val = 0; sc = (struct mvneta_softc *)arg1; if (sc == NULL) return (EINVAL); err = sysctl_handle_int(oidp, &val, 0, req); if (err != 0) return (err); if (val < 0 || val > 1) return (EINVAL); if (val == 1) { mvneta_sc_lock(sc); mvneta_clear_mib(sc); mvneta_sc_unlock(sc); } return (0); } STATIC int sysctl_set_queue_rxthtime(SYSCTL_HANDLER_ARGS) { struct mvneta_sysctl_queue *arg; struct mvneta_rx_ring *rx; struct mvneta_softc *sc; uint32_t reg, time_mvtclk; int err, time_us; rx = NULL; arg = (struct mvneta_sysctl_queue *)arg1; if (arg == NULL) return (EINVAL); if (arg->queue < 0 || arg->queue > MVNETA_RX_RING_CNT) return (EINVAL); if (arg->rxtx != MVNETA_SYSCTL_RX) return (EINVAL); sc = arg->sc; if (sc == NULL) return (EINVAL); /* read queue length */ mvneta_sc_lock(sc); mvneta_rx_lockq(sc, arg->queue); rx = MVNETA_RX_RING(sc, arg->queue); time_mvtclk = rx->queue_th_time; time_us = ((uint64_t)time_mvtclk * 1000ULL * 1000ULL) / sc->clk_freq; mvneta_rx_unlockq(sc, arg->queue); mvneta_sc_unlock(sc); err = sysctl_handle_int(oidp, &time_us, 0, req); if (err != 0) return (err); mvneta_sc_lock(sc); mvneta_rx_lockq(sc, arg->queue); /* update queue length (0[sec] - 1[sec]) */ if (time_us < 0 || time_us > (1000 * 1000)) { mvneta_rx_unlockq(sc, arg->queue); mvneta_sc_unlock(sc); return (EINVAL); } time_mvtclk = sc->clk_freq * (uint64_t)time_us / (1000ULL * 1000ULL); rx->queue_th_time = time_mvtclk; reg = MVNETA_PRXITTH_RITT(rx->queue_th_time); MVNETA_WRITE(sc, MVNETA_PRXITTH(arg->queue), reg); mvneta_rx_unlockq(sc, arg->queue); mvneta_sc_unlock(sc); return (0); } STATIC void sysctl_mvneta_init(struct mvneta_softc *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid_list *children; struct sysctl_oid_list *rxchildren; struct sysctl_oid_list *qchildren, *mchildren; struct sysctl_oid *tree; int i, q; struct mvneta_sysctl_queue *rxarg; #define MVNETA_SYSCTL_NAME(num) "queue" # num static const char *sysctl_queue_names[] = { MVNETA_SYSCTL_NAME(0), MVNETA_SYSCTL_NAME(1), MVNETA_SYSCTL_NAME(2), MVNETA_SYSCTL_NAME(3), MVNETA_SYSCTL_NAME(4), MVNETA_SYSCTL_NAME(5), MVNETA_SYSCTL_NAME(6), MVNETA_SYSCTL_NAME(7), }; #undef MVNETA_SYSCTL_NAME #ifndef NO_SYSCTL_DESCR #define MVNETA_SYSCTL_DESCR(num) "configuration parameters for queue " # num static const char *sysctl_queue_descrs[] = { MVNETA_SYSCTL_DESCR(0), MVNETA_SYSCTL_DESCR(1), MVNETA_SYSCTL_DESCR(2), MVNETA_SYSCTL_DESCR(3), MVNETA_SYSCTL_DESCR(4), MVNETA_SYSCTL_DESCR(5), MVNETA_SYSCTL_DESCR(6), MVNETA_SYSCTL_DESCR(7), }; #undef MVNETA_SYSCTL_DESCR #endif ctx = device_get_sysctl_ctx(sc->dev); children = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)); tree = SYSCTL_ADD_NODE(ctx, children, OID_AUTO, "rx", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "NETA RX"); rxchildren = SYSCTL_CHILDREN(tree); tree = SYSCTL_ADD_NODE(ctx, children, OID_AUTO, "mib", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "NETA MIB"); mchildren = SYSCTL_CHILDREN(tree); SYSCTL_ADD_INT(ctx, children, OID_AUTO, "flow_control", CTLFLAG_RW, &sc->cf_fc, 0, "flow control"); SYSCTL_ADD_INT(ctx, children, OID_AUTO, "lpi", CTLFLAG_RW, &sc->cf_lpi, 0, "Low Power Idle"); /* * MIB access */ /* dev.mvneta.[unit].mib. */ for (i = 0; i < MVNETA_PORTMIB_NOCOUNTER; i++) { struct mvneta_sysctl_mib *mib_arg = &sc->sysctl_mib[i]; mib_arg->sc = sc; mib_arg->index = i; SYSCTL_ADD_PROC(ctx, mchildren, OID_AUTO, mvneta_mib_list[i].sysctl_name, CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_NEEDGIANT, (void *)mib_arg, 0, sysctl_read_mib, "I", mvneta_mib_list[i].desc); } SYSCTL_ADD_UQUAD(ctx, mchildren, OID_AUTO, "rx_discard", CTLFLAG_RD, &sc->counter_pdfc, "Port Rx Discard Frame Counter"); SYSCTL_ADD_UQUAD(ctx, mchildren, OID_AUTO, "overrun", CTLFLAG_RD, &sc->counter_pofc, "Port Overrun Frame Counter"); SYSCTL_ADD_UINT(ctx, mchildren, OID_AUTO, "watchdog", CTLFLAG_RD, &sc->counter_watchdog, 0, "TX Watchdog Counter"); SYSCTL_ADD_PROC(ctx, mchildren, OID_AUTO, "reset", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, (void *)sc, 0, sysctl_clear_mib, "I", "Reset MIB counters"); for (q = 0; q < MVNETA_RX_QNUM_MAX; q++) { rxarg = &sc->sysctl_rx_queue[q]; rxarg->sc = sc; rxarg->queue = q; rxarg->rxtx = MVNETA_SYSCTL_RX; /* hw.mvneta.mvneta[unit].rx.[queue] */ tree = SYSCTL_ADD_NODE(ctx, rxchildren, OID_AUTO, sysctl_queue_names[q], CTLFLAG_RD | CTLFLAG_MPSAFE, 0, sysctl_queue_descrs[q]); qchildren = SYSCTL_CHILDREN(tree); /* hw.mvneta.mvneta[unit].rx.[queue].threshold_timer_us */ SYSCTL_ADD_PROC(ctx, qchildren, OID_AUTO, "threshold_timer_us", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, rxarg, 0, sysctl_set_queue_rxthtime, "I", "interrupt coalescing threshold timer [us]"); } } /* * MIB */ STATIC uint64_t mvneta_read_mib(struct mvneta_softc *sc, int index) { struct mvneta_mib_def *mib; uint64_t val; mib = &mvneta_mib_list[index]; val = MVNETA_READ_MIB(sc, mib->regnum); if (mib->reg64) val |= (uint64_t)MVNETA_READ_MIB(sc, mib->regnum + 4) << 32; return (val); } STATIC void mvneta_clear_mib(struct mvneta_softc *sc) { int i; KASSERT_SC_MTX(sc); for (i = 0; i < nitems(mvneta_mib_list); i++) { (void)mvneta_read_mib(sc, i); sc->sysctl_mib[i].counter = 0; } MVNETA_READ(sc, MVNETA_PDFC); sc->counter_pdfc = 0; MVNETA_READ(sc, MVNETA_POFC); sc->counter_pofc = 0; sc->counter_watchdog = 0; } STATIC void mvneta_update_mib(struct mvneta_softc *sc) { struct mvneta_tx_ring *tx; int i; uint64_t val; uint32_t reg; for (i = 0; i < nitems(mvneta_mib_list); i++) { val = mvneta_read_mib(sc, i); if (val == 0) continue; sc->sysctl_mib[i].counter += val; switch (mvneta_mib_list[i].regnum) { case MVNETA_MIB_RX_GOOD_OCT: if_inc_counter(sc->ifp, IFCOUNTER_IBYTES, val); break; case MVNETA_MIB_RX_BAD_FRAME: if_inc_counter(sc->ifp, IFCOUNTER_IERRORS, val); break; case MVNETA_MIB_RX_GOOD_FRAME: if_inc_counter(sc->ifp, IFCOUNTER_IPACKETS, val); break; case MVNETA_MIB_RX_MCAST_FRAME: if_inc_counter(sc->ifp, IFCOUNTER_IMCASTS, val); break; case MVNETA_MIB_TX_GOOD_OCT: if_inc_counter(sc->ifp, IFCOUNTER_OBYTES, val); break; case MVNETA_MIB_TX_GOOD_FRAME: if_inc_counter(sc->ifp, IFCOUNTER_OPACKETS, val); break; case MVNETA_MIB_TX_MCAST_FRAME: if_inc_counter(sc->ifp, IFCOUNTER_OMCASTS, val); break; case MVNETA_MIB_MAC_COL: if_inc_counter(sc->ifp, IFCOUNTER_COLLISIONS, val); break; case MVNETA_MIB_TX_MAC_TRNS_ERR: case MVNETA_MIB_TX_EXCES_COL: case MVNETA_MIB_MAC_LATE_COL: if_inc_counter(sc->ifp, IFCOUNTER_OERRORS, val); break; } } reg = MVNETA_READ(sc, MVNETA_PDFC); sc->counter_pdfc += reg; if_inc_counter(sc->ifp, IFCOUNTER_IQDROPS, reg); reg = MVNETA_READ(sc, MVNETA_POFC); sc->counter_pofc += reg; if_inc_counter(sc->ifp, IFCOUNTER_IQDROPS, reg); /* TX watchdog. */ if (sc->counter_watchdog_mib > 0) { if_inc_counter(sc->ifp, IFCOUNTER_OERRORS, sc->counter_watchdog_mib); sc->counter_watchdog_mib = 0; } /* * TX driver errors: * We do not take queue locks to not disrupt TX path. * We may only miss one drv error which will be fixed at * next mib update. We may also clear counter when TX path * is incrementing it but we only do it if counter was not zero * thus we may only loose one error. */ for (i = 0; i < MVNETA_TX_QNUM_MAX; i++) { tx = MVNETA_TX_RING(sc, i); if (tx->drv_error > 0) { if_inc_counter(sc->ifp, IFCOUNTER_OERRORS, tx->drv_error); tx->drv_error = 0; } } } diff --git a/sys/dev/ow/owc_gpiobus.c b/sys/dev/ow/owc_gpiobus.c index 231902e02536..f010a4dc75f1 100644 --- a/sys/dev/ow/owc_gpiobus.c +++ b/sys/dev/ow/owc_gpiobus.c @@ -1,399 +1,399 @@ /*- * Copyright (c) 2015 M. Warner Losh * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include static struct ofw_compat_data compat_data[] = { {"w1-gpio", true}, {NULL, false} }; OFWBUS_PNP_INFO(compat_data); SIMPLEBUS_PNP_INFO(compat_data); #endif /* FDT */ #define OW_PIN 0 #define OWC_GPIOBUS_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define OWC_GPIOBUS_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define OWC_GPIOBUS_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \ "owc_gpiobus", MTX_DEF) #define OWC_GPIOBUS_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); struct owc_gpiobus_softc { device_t sc_dev; gpio_pin_t sc_pin; struct mtx sc_mtx; }; static int owc_gpiobus_probe(device_t); static int owc_gpiobus_attach(device_t); static int owc_gpiobus_detach(device_t); static int owc_gpiobus_probe(device_t dev) { int rv; /* * By default we only bid to attach if specifically added by our parent * (usually via hint.owc_gpiobus.#.at=busname). On FDT systems we bid * as the default driver based on being configured in the FDT data. */ rv = BUS_PROBE_NOWILDCARD; #ifdef FDT if (ofw_bus_status_okay(dev) && ofw_bus_search_compatible(dev, compat_data)->ocd_data) rv = BUS_PROBE_DEFAULT; #endif device_set_desc(dev, "GPIO one-wire bus"); return (rv); } static int owc_gpiobus_attach(device_t dev) { struct owc_gpiobus_softc *sc; int err; sc = device_get_softc(dev); sc->sc_dev = dev; #ifdef FDT /* Try to configure our pin from fdt data on fdt-based systems. */ err = gpio_pin_get_by_ofw_idx(dev, ofw_bus_get_node(dev), OW_PIN, &sc->sc_pin); #else err = ENOENT; #endif /* * If we didn't get configured by fdt data and our parent is gpiobus, * see if we can be configured by the bus (allows hinted attachment even * on fdt-based systems). */ if (err != 0 && strcmp("gpiobus", device_get_name(device_get_parent(dev))) == 0) err = gpio_pin_get_by_child_index(dev, OW_PIN, &sc->sc_pin); /* If we didn't get configured by either method, whine and punt. */ if (err != 0) { device_printf(sc->sc_dev, "cannot acquire gpio pin (config error)\n"); return (err); } OWC_GPIOBUS_LOCK_INIT(sc); /* * Add the ow bus as a child, but defer probing and attaching it until * interrupts work, because we can't do IO for them until we can read * the system timecounter (which initializes after device attachments). */ device_add_child(sc->sc_dev, "ow", DEVICE_UNIT_ANY); bus_delayed_attach_children(dev); return (0); } static int owc_gpiobus_detach(device_t dev) { struct owc_gpiobus_softc *sc; int err; sc = device_get_softc(dev); - if ((err = device_delete_children(dev)) != 0) + if ((err = bus_generic_detach(dev)) != 0) return (err); gpio_pin_release(sc->sc_pin); OWC_GPIOBUS_LOCK_DESTROY(sc); return (0); } /* * In the diagrams below, R is driven by the resistor pullup, M is driven by the * master, and S is driven by the slave / target. */ /* * These macros let what why we're doing stuff shine in the code * below, and let the how be confined to here. */ #define OUTPIN(sc) gpio_pin_setflags((sc)->sc_pin, GPIO_PIN_OUTPUT) #define INPIN(sc) gpio_pin_setflags((sc)->sc_pin, GPIO_PIN_INPUT) #define GETPIN(sc, bp) gpio_pin_is_active((sc)->sc_pin, (bp)) #define LOW(sc) gpio_pin_set_active((sc)->sc_pin, false) /* * WRITE-ONE (see owll_if.m for timings) From Figure 4-1 AN-937 * * |<---------tSLOT---->|<-tREC->| * High RRRRM | RRRRRRRRRRRR|RRRRRRRRM * M | R | | | M * M| R | | | M * Low MMMMMMM | | | MMMMMM... * |<-tLOW1->| | | * |<------15us--->| | * |<--------60us---->| */ static int owc_gpiobus_write_one(device_t dev, struct ow_timing *t) { struct owc_gpiobus_softc *sc; sc = device_get_softc(dev); critical_enter(); /* Force low */ OUTPIN(sc); LOW(sc); DELAY(t->t_low1); /* Allow resistor to float line high */ INPIN(sc); DELAY(t->t_slot - t->t_low1 + t->t_rec); critical_exit(); return (0); } /* * WRITE-ZERO (see owll_if.m for timings) From Figure 4-2 AN-937 * * |<---------tSLOT------>|<-tREC->| * High RRRRM | | |RRRRRRRM * M | | R M * M| | | |R M * Low MMMMMMMMMMMMMMMMMMMMMR MMMMMM... * |<--15us->| | | * |<------60us--->| | * |<-------tLOW0------>| */ static int owc_gpiobus_write_zero(device_t dev, struct ow_timing *t) { struct owc_gpiobus_softc *sc; sc = device_get_softc(dev); critical_enter(); /* Force low */ OUTPIN(sc); LOW(sc); DELAY(t->t_low0); /* Allow resistor to float line high */ INPIN(sc); DELAY(t->t_slot - t->t_low0 + t->t_rec); critical_exit(); return (0); } /* * READ-DATA (see owll_if.m for timings) From Figure 4-3 AN-937 * * |<---------tSLOT------>|<-tREC->| * High RRRRM | rrrrrrrrrrrrrrrRRRRRRRM * M | r | R M * M| r | |R M * Low MMMMMMMSSSSSSSSSSSSSSR MMMMMM... * |< sample > | * |<------tRDV---->| | * ->| |<-tRELEASE * * r -- allowed to pull high via the resitor when slave writes a 1-bit * */ static int owc_gpiobus_read_data(device_t dev, struct ow_timing *t, int *bit) { struct owc_gpiobus_softc *sc; bool sample; sbintime_t then, now; sc = device_get_softc(dev); critical_enter(); /* Force low for t_lowr microseconds */ then = sbinuptime(); OUTPIN(sc); LOW(sc); DELAY(t->t_lowr); /* * Slave is supposed to hold the line low for t_rdv microseconds for 0 * and immediately float it high for a 1. This is measured from the * master's pushing the line low. */ INPIN(sc); do { now = sbinuptime(); GETPIN(sc, &sample); } while (now - then < (t->t_rdv + 2) * SBT_1US && sample == false); critical_exit(); if (now - then < t->t_rdv * SBT_1US) *bit = 1; else *bit = 0; /* Wait out the rest of t_slot */ do { now = sbinuptime(); } while (now - then < (t->t_slot + t->t_rec) * SBT_1US); return (0); } /* * RESET AND PRESENCE PULSE (see owll_if.m for timings) From Figure 4-4 AN-937 * * |<---------tRSTH------------>| * High RRRM | | RRRRRRRS | RRRR RRM * M | |R| |S | R M * M| R | | S |R M * Low MMMMMMMM MMMMMM| | | SSSSSSSSSS MMMMMM * |<----tRSTL--->| | |<-tPDL---->| * | ->| |<-tR | | * || * * Note: for Regular Speed operations, tRSTL + tR should be less than 960us to * avoid interfering with other devices on the bus. * * Return values in *bit: * -1 = Bus wiring error (stuck low). * 0 = no presence pulse * 1 = presence pulse detected */ static int owc_gpiobus_reset_and_presence(device_t dev, struct ow_timing *t, int *bit) { struct owc_gpiobus_softc *sc; bool sample; sc = device_get_softc(dev); /* * Read the current state of the bus. The steady state of an idle bus is * high. Badly wired buses that are missing the required pull up, or * that have a short circuit to ground cause all kinds of mischief when * we try to read them later. Return EIO if the bus is currently low. */ INPIN(sc); GETPIN(sc, &sample); if (sample == false) { *bit = -1; return (EIO); } critical_enter(); /* Force low */ OUTPIN(sc); LOW(sc); DELAY(t->t_rstl); /* Allow resistor to float line high and then wait for reset pulse */ INPIN(sc); DELAY(t->t_pdh + t->t_pdl / 2); /* Read presence pulse */ GETPIN(sc, &sample); *bit = sample; critical_exit(); DELAY(t->t_rsth - (t->t_pdh + t->t_pdl / 2)); /* Timing not critical for this one */ /* * Read the state of the bus after we've waited past the end of the rest * window. It should return to high. If it is low, then we have some * problem and should abort the reset. */ GETPIN(sc, &sample); if (sample == false) { *bit = -1; return (EIO); } return (0); } static device_method_t owc_gpiobus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, owc_gpiobus_probe), DEVMETHOD(device_attach, owc_gpiobus_attach), DEVMETHOD(device_detach, owc_gpiobus_detach), DEVMETHOD(owll_write_one, owc_gpiobus_write_one), DEVMETHOD(owll_write_zero, owc_gpiobus_write_zero), DEVMETHOD(owll_read_data, owc_gpiobus_read_data), DEVMETHOD(owll_reset_and_presence, owc_gpiobus_reset_and_presence), { 0, 0 } }; static driver_t owc_gpiobus_driver = { "owc", owc_gpiobus_methods, sizeof(struct owc_gpiobus_softc), }; #ifdef FDT DRIVER_MODULE(owc_gpiobus, simplebus, owc_gpiobus_driver, 0, 0); #endif DRIVER_MODULE(owc_gpiobus, gpiobus, owc_gpiobus_driver, 0, 0); MODULE_DEPEND(owc_gpiobus, ow, 1, 1, 1); MODULE_DEPEND(owc_gpiobus, gpiobus, 1, 1, 1); MODULE_VERSION(owc_gpiobus, 1); diff --git a/sys/dev/p2sb/p2sb.c b/sys/dev/p2sb/p2sb.c index 941e35469c69..950ee4e86866 100644 --- a/sys/dev/p2sb/p2sb.c +++ b/sys/dev/p2sb/p2sb.c @@ -1,214 +1,218 @@ /*- * Copyright (c) 2018 Stormshield * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Implementation of Primary to Sideband bridge (P2SB), the documentation is available here : * https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/c620-series-chipset-datasheet.pdf * section 36.9 P2SB Bridge. * This device exposes a 16MB memory block, this block is composed of 256 64KB blocks called ports. * The indexes of this array (target port ID) can be found on the Table 36-10 of the documentation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "p2sb.h" #define PCI_PRODUCT_LEWISBURG_P2SB 0xa1a08086 #define P2SB_PORT2ADDRESS_SHIFT 16 #define P2SB_PORT_ADDRESS(port) ((uint32_t)port << P2SB_PORT2ADDRESS_SHIFT) static const uint8_t lbg_communities[] = { 0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0x11 }; /* The softc holds our per-instance data. */ struct p2sb_softc { device_t dev; int rid; struct resource *res; struct intel_community *communities; int ncommunities; struct mtx mutex; }; int p2sb_get_port(device_t dev, int unit) { if (unit >= nitems(lbg_communities)) return (EINVAL); return (lbg_communities[unit]); } uint32_t p2sb_port_read_4(device_t dev, uint8_t port, uint32_t reg) { struct p2sb_softc *sc; KASSERT(reg < (1<res, P2SB_PORT_ADDRESS(port) + reg)); } void p2sb_port_write_4(device_t dev, uint8_t port, uint32_t reg, uint32_t val) { struct p2sb_softc *sc; KASSERT(reg < (1<res, P2SB_PORT_ADDRESS(port) + reg, val); } void p2sb_lock(device_t dev) { struct p2sb_softc *sc; sc = device_get_softc(dev); mtx_lock_spin(&sc->mutex); } void p2sb_unlock(device_t dev) { struct p2sb_softc *sc; sc = device_get_softc(dev); mtx_unlock_spin(&sc->mutex); } static int p2sb_probe(device_t dev) { if (pci_get_devid(dev) == PCI_PRODUCT_LEWISBURG_P2SB) { device_set_desc(dev, "Lewisburg P2SB"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } /* Attach function is only called if the probe is successful. */ static int p2sb_attach(device_t dev) { struct p2sb_softc *sc; int i; sc = device_get_softc(dev); sc->dev = dev; sc->rid = PCIR_BAR(0); sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid, RF_ACTIVE); if (sc->res == NULL) { device_printf(dev, "Could not allocate memory.\n"); return (ENXIO); } mtx_init(&sc->mutex, device_get_nameunit(dev), NULL, MTX_SPIN); for (i = 0; i < nitems(lbg_communities); ++i) device_add_child(dev, "lbggpiocm", i); bus_attach_children(dev); return (0); } /* Detach device. */ static int p2sb_detach(device_t dev) { struct p2sb_softc *sc; + int error; /* Teardown the state in our softc created in our attach routine. */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); + sc = device_get_softc(dev); mtx_destroy(&sc->mutex); if (sc->res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->rid, sc->res); return (0); } /* Called during system shutdown after sync. */ static int p2sb_shutdown(device_t dev) { return (0); } /* * Device suspend routine. */ static int p2sb_suspend(device_t dev) { return (0); } /* * Device resume routine. */ static int p2sb_resume(device_t dev) { return (0); } static device_method_t p2sb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, p2sb_probe), DEVMETHOD(device_attach, p2sb_attach), DEVMETHOD(device_detach, p2sb_detach), DEVMETHOD(device_shutdown, p2sb_shutdown), DEVMETHOD(device_suspend, p2sb_suspend), DEVMETHOD(device_resume, p2sb_resume), DEVMETHOD_END }; DEFINE_CLASS_0(p2sb, p2sb_driver, p2sb_methods, sizeof(struct p2sb_softc)); DRIVER_MODULE(p2sb, pci, p2sb_driver, 0, 0); diff --git a/sys/dev/ppc/ppc.c b/sys/dev/ppc/ppc.c index 020043732222..9870379e2eba 100644 --- a/sys/dev/ppc/ppc.c +++ b/sys/dev/ppc/ppc.c @@ -1,1988 +1,1991 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997-2000 Nicolas Souchu * Copyright (c) 2001 Alcove - Nicolas Souchu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_ppc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __i386__ #include #include #include #include #endif #include #include #include #include #include "ppbus_if.h" static void ppcintr(void *arg); #define IO_LPTSIZE_EXTENDED 8 /* "Extended" LPT controllers */ #define IO_LPTSIZE_NORMAL 4 /* "Normal" LPT controllers */ #define LOG_PPC(function, ppc, string) \ if (bootverbose) printf("%s: %s\n", function, string) #define DEVTOSOFTC(dev) ((struct ppc_data *)device_get_softc(dev)) /* * We use critical enter/exit for the simple config locking needed to * detect the devices. We just want to make sure that both of our writes * happen without someone else also writing to those config registers. Since * we just do this at startup, Giant keeps multiple threads from executing, * and critical_enter() then is all that's needed to keep us from being preempted * during the critical sequences with the hardware. * * Note: this doesn't prevent multiple threads from putting the chips into * config mode, but since we only do that to detect the type at startup the * extra overhead isn't needed since Giant protects us from multiple entry * and no other code changes these registers. */ #define PPC_CONFIG_LOCK(ppc) critical_enter() #define PPC_CONFIG_UNLOCK(ppc) critical_exit() const char ppc_driver_name[] = "ppc"; static char *ppc_models[] = { "SMC-like", "SMC FDC37C665GT", "SMC FDC37C666GT", "PC87332", "PC87306", "82091AA", "Generic", "W83877F", "W83877AF", "Winbond", "PC87334", "SMC FDC37C935", "PC87303", 0 }; /* list of available modes */ static char *ppc_avms[] = { "COMPATIBLE", "NIBBLE-only", "PS2-only", "PS2/NIBBLE", "EPP-only", "EPP/NIBBLE", "EPP/PS2", "EPP/PS2/NIBBLE", "ECP-only", "ECP/NIBBLE", "ECP/PS2", "ECP/PS2/NIBBLE", "ECP/EPP", "ECP/EPP/NIBBLE", "ECP/EPP/PS2", "ECP/EPP/PS2/NIBBLE", 0 }; /* list of current executing modes * Note that few modes do not actually exist. */ static char *ppc_modes[] = { "COMPATIBLE", "NIBBLE", "PS/2", "PS/2", "EPP", "EPP", "EPP", "EPP", "ECP", "ECP", "ECP+PS2", "ECP+PS2", "ECP+EPP", "ECP+EPP", "ECP+EPP", "ECP+EPP", 0 }; static char *ppc_epp_protocol[] = { " (EPP 1.9)", " (EPP 1.7)", 0 }; #ifdef __i386__ /* * BIOS printer list - used by BIOS probe. */ #define BIOS_PPC_PORTS 0x408 #define BIOS_PORTS ((short *)BIOS_PADDRTOVADDR(BIOS_PPC_PORTS)) #define BIOS_MAX_PPC 4 #endif /* * ppc_ecp_sync() XXX */ int ppc_ecp_sync(device_t dev) { int i, r; struct ppc_data *ppc = DEVTOSOFTC(dev); PPC_ASSERT_LOCKED(ppc); if (!(ppc->ppc_avm & PPB_ECP) && !(ppc->ppc_dtm & PPB_ECP)) return 0; r = r_ecr(ppc); if ((r & 0xe0) != PPC_ECR_EPP) return 0; for (i = 0; i < 100; i++) { r = r_ecr(ppc); if (r & 0x1) return 0; DELAY(100); } device_printf(dev, "ECP sync failed as data still present in FIFO.\n"); return 0; } /* * ppc_detect_fifo() * * Detect parallel port FIFO */ static int ppc_detect_fifo(struct ppc_data *ppc) { char ecr_sav; char ctr_sav, ctr; short i; /* save registers */ ecr_sav = r_ecr(ppc); ctr_sav = r_ctr(ppc); /* enter ECP configuration mode, no interrupt, no DMA */ w_ecr(ppc, 0xf4); /* read PWord size - transfers in FIFO mode must be PWord aligned */ ppc->ppc_pword = (r_cnfgA(ppc) & PPC_PWORD_MASK); /* XXX 16 and 32 bits implementations not supported */ if (ppc->ppc_pword != PPC_PWORD_8) { LOG_PPC(__func__, ppc, "PWord not supported"); goto error; } w_ecr(ppc, 0x34); /* byte mode, no interrupt, no DMA */ ctr = r_ctr(ppc); w_ctr(ppc, ctr | PCD); /* set direction to 1 */ /* enter ECP test mode, no interrupt, no DMA */ w_ecr(ppc, 0xd4); /* flush the FIFO */ for (i=0; i<1024; i++) { if (r_ecr(ppc) & PPC_FIFO_EMPTY) break; r_fifo(ppc); } if (i >= 1024) { LOG_PPC(__func__, ppc, "can't flush FIFO"); goto error; } /* enable interrupts, no DMA */ w_ecr(ppc, 0xd0); /* determine readIntrThreshold * fill the FIFO until serviceIntr is set */ for (i=0; i<1024; i++) { w_fifo(ppc, (char)i); if (!ppc->ppc_rthr && (r_ecr(ppc) & PPC_SERVICE_INTR)) { /* readThreshold reached */ ppc->ppc_rthr = i+1; } if (r_ecr(ppc) & PPC_FIFO_FULL) { ppc->ppc_fifo = i+1; break; } } if (i >= 1024) { LOG_PPC(__func__, ppc, "can't fill FIFO"); goto error; } w_ecr(ppc, 0xd4); /* test mode, no interrupt, no DMA */ w_ctr(ppc, ctr & ~PCD); /* set direction to 0 */ w_ecr(ppc, 0xd0); /* enable interrupts */ /* determine writeIntrThreshold * empty the FIFO until serviceIntr is set */ for (i=ppc->ppc_fifo; i>0; i--) { if (r_fifo(ppc) != (char)(ppc->ppc_fifo-i)) { LOG_PPC(__func__, ppc, "invalid data in FIFO"); goto error; } if (r_ecr(ppc) & PPC_SERVICE_INTR) { /* writeIntrThreshold reached */ ppc->ppc_wthr = ppc->ppc_fifo - i+1; } /* if FIFO empty before the last byte, error */ if (i>1 && (r_ecr(ppc) & PPC_FIFO_EMPTY)) { LOG_PPC(__func__, ppc, "data lost in FIFO"); goto error; } } /* FIFO must be empty after the last byte */ if (!(r_ecr(ppc) & PPC_FIFO_EMPTY)) { LOG_PPC(__func__, ppc, "can't empty the FIFO"); goto error; } w_ctr(ppc, ctr_sav); w_ecr(ppc, ecr_sav); return (0); error: w_ctr(ppc, ctr_sav); w_ecr(ppc, ecr_sav); return (EINVAL); } static int ppc_detect_port(struct ppc_data *ppc) { w_ctr(ppc, 0x0c); /* To avoid missing PS2 ports */ w_dtr(ppc, 0xaa); if (r_dtr(ppc) != 0xaa) return (0); return (1); } /* * EPP timeout, according to the PC87332 manual * Semantics of clearing EPP timeout bit. * PC87332 - reading SPP_STR does it... * SMC - write 1 to EPP timeout bit XXX * Others - (?) write 0 to EPP timeout bit */ static void ppc_reset_epp_timeout(struct ppc_data *ppc) { char r; r = r_str(ppc); w_str(ppc, r | 0x1); w_str(ppc, r & 0xfe); return; } static int ppc_check_epp_timeout(struct ppc_data *ppc) { ppc_reset_epp_timeout(ppc); return (!(r_str(ppc) & TIMEOUT)); } /* * Configure current operating mode */ static int ppc_generic_setmode(struct ppc_data *ppc, int mode) { u_char ecr = 0; /* check if mode is available */ if (mode && !(ppc->ppc_avm & mode)) return (EINVAL); /* if ECP mode, configure ecr register */ if ((ppc->ppc_avm & PPB_ECP) || (ppc->ppc_dtm & PPB_ECP)) { /* return to byte mode (keeping direction bit), * no interrupt, no DMA to be able to change to * ECP */ w_ecr(ppc, PPC_ECR_RESET); ecr = PPC_DISABLE_INTR; if (mode & PPB_EPP) return (EINVAL); else if (mode & PPB_ECP) /* select ECP mode */ ecr |= PPC_ECR_ECP; else if (mode & PPB_PS2) /* select PS2 mode with ECP */ ecr |= PPC_ECR_PS2; else /* select COMPATIBLE/NIBBLE mode */ ecr |= PPC_ECR_STD; w_ecr(ppc, ecr); } ppc->ppc_mode = mode; return (0); } /* * The ppc driver is free to choose options like FIFO or DMA * if ECP mode is available. * * The 'RAW' option allows the upper drivers to force the ppc mode * even with FIFO, DMA available. */ static int ppc_smclike_setmode(struct ppc_data *ppc, int mode) { u_char ecr = 0; /* check if mode is available */ if (mode && !(ppc->ppc_avm & mode)) return (EINVAL); /* if ECP mode, configure ecr register */ if ((ppc->ppc_avm & PPB_ECP) || (ppc->ppc_dtm & PPB_ECP)) { /* return to byte mode (keeping direction bit), * no interrupt, no DMA to be able to change to * ECP or EPP mode */ w_ecr(ppc, PPC_ECR_RESET); ecr = PPC_DISABLE_INTR; if (mode & PPB_EPP) /* select EPP mode */ ecr |= PPC_ECR_EPP; else if (mode & PPB_ECP) /* select ECP mode */ ecr |= PPC_ECR_ECP; else if (mode & PPB_PS2) /* select PS2 mode with ECP */ ecr |= PPC_ECR_PS2; else /* select COMPATIBLE/NIBBLE mode */ ecr |= PPC_ECR_STD; w_ecr(ppc, ecr); } ppc->ppc_mode = mode; return (0); } #ifdef PPC_PROBE_CHIPSET /* * ppc_pc873xx_detect * * Probe for a Natsemi PC873xx-family part. * * References in this function are to the National Semiconductor * PC87332 datasheet TL/C/11930, May 1995 revision. */ static int pc873xx_basetab[] = {0x0398, 0x026e, 0x015c, 0x002e, 0}; static int pc873xx_porttab[] = {0x0378, 0x03bc, 0x0278, 0}; static int pc873xx_irqtab[] = {5, 7, 5, 0}; static int pc873xx_regstab[] = { PC873_FER, PC873_FAR, PC873_PTR, PC873_FCR, PC873_PCR, PC873_PMC, PC873_TUP, PC873_SID, PC873_PNP0, PC873_PNP1, PC873_LPTBA, -1 }; static char *pc873xx_rnametab[] = { "FER", "FAR", "PTR", "FCR", "PCR", "PMC", "TUP", "SID", "PNP0", "PNP1", "LPTBA", NULL }; static int ppc_pc873xx_detect(struct ppc_data *ppc, int chipset_mode) /* XXX mode never forced */ { static int index = 0; int idport, irq; int ptr, pcr, val, i; while ((idport = pc873xx_basetab[index++])) { /* XXX should check first to see if this location is already claimed */ /* * Pull the 873xx through the power-on ID cycle (2.2,1.). * We can't use this to locate the chip as it may already have * been used by the BIOS. */ (void)inb(idport); (void)inb(idport); (void)inb(idport); (void)inb(idport); /* * Read the SID byte. Possible values are : * * 01010xxx PC87334 * 0001xxxx PC87332 * 01110xxx PC87306 * 00110xxx PC87303 */ outb(idport, PC873_SID); val = inb(idport + 1); if ((val & 0xf0) == 0x10) { ppc->ppc_model = NS_PC87332; } else if ((val & 0xf8) == 0x70) { ppc->ppc_model = NS_PC87306; } else if ((val & 0xf8) == 0x50) { ppc->ppc_model = NS_PC87334; } else if ((val & 0xf8) == 0x40) { /* Should be 0x30 by the documentation, but probing yielded 0x40... */ ppc->ppc_model = NS_PC87303; } else { if (bootverbose && (val != 0xff)) printf("PC873xx probe at 0x%x got unknown ID 0x%x\n", idport, val); continue ; /* not recognised */ } /* print registers */ if (bootverbose) { printf("PC873xx"); for (i=0; pc873xx_regstab[i] != -1; i++) { outb(idport, pc873xx_regstab[i]); printf(" %s=0x%x", pc873xx_rnametab[i], inb(idport + 1) & 0xff); } printf("\n"); } /* * We think we have one. Is it enabled and where we want it to be? */ outb(idport, PC873_FER); val = inb(idport + 1); if (!(val & PC873_PPENABLE)) { if (bootverbose) printf("PC873xx parallel port disabled\n"); continue; } outb(idport, PC873_FAR); val = inb(idport + 1); /* XXX we should create a driver instance for every port found */ if (pc873xx_porttab[val & 0x3] != ppc->ppc_base) { /* First try to change the port address to that requested... */ switch (ppc->ppc_base) { case 0x378: val &= 0xfc; break; case 0x3bc: val &= 0xfd; break; case 0x278: val &= 0xfe; break; default: val &= 0xfd; break; } outb(idport, PC873_FAR); outb(idport + 1, val); outb(idport + 1, val); /* Check for success by reading back the value we supposedly wrote and comparing...*/ outb(idport, PC873_FAR); val = inb(idport + 1) & 0x3; /* If we fail, report the failure... */ if (pc873xx_porttab[val] != ppc->ppc_base) { if (bootverbose) printf("PC873xx at 0x%x not for driver at port 0x%x\n", pc873xx_porttab[val], ppc->ppc_base); } continue; } outb(idport, PC873_PTR); ptr = inb(idport + 1); /* get irq settings */ if (ppc->ppc_base == 0x378) irq = (ptr & PC873_LPTBIRQ7) ? 7 : 5; else irq = pc873xx_irqtab[val]; if (bootverbose) printf("PC873xx irq %d at 0x%x\n", irq, ppc->ppc_base); /* * Check if irq settings are correct */ if (irq != ppc->ppc_irq) { /* * If the chipset is not locked and base address is 0x378, * we have another chance */ if (ppc->ppc_base == 0x378 && !(ptr & PC873_CFGLOCK)) { if (ppc->ppc_irq == 7) { outb(idport + 1, (ptr | PC873_LPTBIRQ7)); outb(idport + 1, (ptr | PC873_LPTBIRQ7)); } else { outb(idport + 1, (ptr & ~PC873_LPTBIRQ7)); outb(idport + 1, (ptr & ~PC873_LPTBIRQ7)); } if (bootverbose) printf("PC873xx irq set to %d\n", ppc->ppc_irq); } else { if (bootverbose) printf("PC873xx sorry, can't change irq setting\n"); } } else { if (bootverbose) printf("PC873xx irq settings are correct\n"); } outb(idport, PC873_PCR); pcr = inb(idport + 1); if ((ptr & PC873_CFGLOCK) || !chipset_mode) { if (bootverbose) printf("PC873xx %s", (ptr & PC873_CFGLOCK)?"locked":"unlocked"); ppc->ppc_avm |= PPB_NIBBLE; if (bootverbose) printf(", NIBBLE"); if (pcr & PC873_EPPEN) { ppc->ppc_avm |= PPB_EPP; if (bootverbose) printf(", EPP"); if (pcr & PC873_EPP19) ppc->ppc_epp = EPP_1_9; else ppc->ppc_epp = EPP_1_7; if ((ppc->ppc_model == NS_PC87332) && bootverbose) { outb(idport, PC873_PTR); ptr = inb(idport + 1); if (ptr & PC873_EPPRDIR) printf(", Regular mode"); else printf(", Automatic mode"); } } else if (pcr & PC873_ECPEN) { ppc->ppc_avm |= PPB_ECP; if (bootverbose) printf(", ECP"); if (pcr & PC873_ECPCLK) { /* XXX */ ppc->ppc_avm |= PPB_PS2; if (bootverbose) printf(", PS/2"); } } else { outb(idport, PC873_PTR); ptr = inb(idport + 1); if (ptr & PC873_EXTENDED) { ppc->ppc_avm |= PPB_SPP; if (bootverbose) printf(", SPP"); } } } else { if (bootverbose) printf("PC873xx unlocked"); if (chipset_mode & PPB_ECP) { if ((chipset_mode & PPB_EPP) && bootverbose) printf(", ECP+EPP not supported"); pcr &= ~PC873_EPPEN; pcr |= (PC873_ECPEN | PC873_ECPCLK); /* XXX */ outb(idport + 1, pcr); outb(idport + 1, pcr); if (bootverbose) printf(", ECP"); } else if (chipset_mode & PPB_EPP) { pcr &= ~(PC873_ECPEN | PC873_ECPCLK); pcr |= (PC873_EPPEN | PC873_EPP19); outb(idport + 1, pcr); outb(idport + 1, pcr); ppc->ppc_epp = EPP_1_9; /* XXX */ if (bootverbose) printf(", EPP1.9"); /* enable automatic direction turnover */ if (ppc->ppc_model == NS_PC87332) { outb(idport, PC873_PTR); ptr = inb(idport + 1); ptr &= ~PC873_EPPRDIR; outb(idport + 1, ptr); outb(idport + 1, ptr); if (bootverbose) printf(", Automatic mode"); } } else { pcr &= ~(PC873_ECPEN | PC873_ECPCLK | PC873_EPPEN); outb(idport + 1, pcr); outb(idport + 1, pcr); /* configure extended bit in PTR */ outb(idport, PC873_PTR); ptr = inb(idport + 1); if (chipset_mode & PPB_PS2) { ptr |= PC873_EXTENDED; if (bootverbose) printf(", PS/2"); } else { /* default to NIBBLE mode */ ptr &= ~PC873_EXTENDED; if (bootverbose) printf(", NIBBLE"); } outb(idport + 1, ptr); outb(idport + 1, ptr); } ppc->ppc_avm = chipset_mode; } if (bootverbose) printf("\n"); ppc->ppc_type = PPC_TYPE_GENERIC; ppc_generic_setmode(ppc, chipset_mode); return(chipset_mode); } return(-1); } /* * ppc_smc37c66xgt_detect * * SMC FDC37C66xGT configuration. */ static int ppc_smc37c66xgt_detect(struct ppc_data *ppc, int chipset_mode) { int i; u_char r; int type = -1; int csr = SMC66x_CSR; /* initial value is 0x3F0 */ int port_address[] = { -1 /* disabled */ , 0x3bc, 0x378, 0x278 }; #define cio csr+1 /* config IO port is either 0x3F1 or 0x371 */ /* * Detection: enter configuration mode and read CRD register. */ PPC_CONFIG_LOCK(ppc); outb(csr, SMC665_iCODE); outb(csr, SMC665_iCODE); PPC_CONFIG_UNLOCK(ppc); outb(csr, 0xd); if (inb(cio) == 0x65) { type = SMC_37C665GT; goto config; } for (i = 0; i < 2; i++) { PPC_CONFIG_LOCK(ppc); outb(csr, SMC666_iCODE); outb(csr, SMC666_iCODE); PPC_CONFIG_UNLOCK(ppc); outb(csr, 0xd); if (inb(cio) == 0x66) { type = SMC_37C666GT; break; } /* Another chance, CSR may be hard-configured to be at 0x370 */ csr = SMC666_CSR; } config: /* * If chipset not found, do not continue. */ if (type == -1) { outb(csr, 0xaa); /* end config mode */ return (-1); } /* select CR1 */ outb(csr, 0x1); /* read the port's address: bits 0 and 1 of CR1 */ r = inb(cio) & SMC_CR1_ADDR; if (port_address[(int)r] != ppc->ppc_base) { outb(csr, 0xaa); /* end config mode */ return (-1); } ppc->ppc_model = type; /* * CR1 and CR4 registers bits 3 and 0/1 for mode configuration * If SPP mode is detected, try to set ECP+EPP mode */ if (bootverbose) { outb(csr, 0x1); device_printf(ppc->ppc_dev, "SMC registers CR1=0x%x", inb(cio) & 0xff); outb(csr, 0x4); printf(" CR4=0x%x", inb(cio) & 0xff); } /* select CR1 */ outb(csr, 0x1); if (!chipset_mode) { /* autodetect mode */ /* 666GT is ~certainly~ hardwired to an extended ECP+EPP mode */ if (type == SMC_37C666GT) { ppc->ppc_avm |= PPB_ECP | PPB_EPP | PPB_SPP; if (bootverbose) printf(" configuration hardwired, supposing " \ "ECP+EPP SPP"); } else if ((inb(cio) & SMC_CR1_MODE) == 0) { /* already in extended parallel port mode, read CR4 */ outb(csr, 0x4); r = (inb(cio) & SMC_CR4_EMODE); switch (r) { case SMC_SPP: ppc->ppc_avm |= PPB_SPP; if (bootverbose) printf(" SPP"); break; case SMC_EPPSPP: ppc->ppc_avm |= PPB_EPP | PPB_SPP; if (bootverbose) printf(" EPP SPP"); break; case SMC_ECP: ppc->ppc_avm |= PPB_ECP | PPB_SPP; if (bootverbose) printf(" ECP SPP"); break; case SMC_ECPEPP: ppc->ppc_avm |= PPB_ECP | PPB_EPP | PPB_SPP; if (bootverbose) printf(" ECP+EPP SPP"); break; } } else { /* not an extended port mode */ ppc->ppc_avm |= PPB_SPP; if (bootverbose) printf(" SPP"); } } else { /* mode forced */ ppc->ppc_avm = chipset_mode; /* 666GT is ~certainly~ hardwired to an extended ECP+EPP mode */ if (type == SMC_37C666GT) goto end_detect; r = inb(cio); if ((chipset_mode & (PPB_ECP | PPB_EPP)) == 0) { /* do not use ECP when the mode is not forced to */ outb(cio, r | SMC_CR1_MODE); if (bootverbose) printf(" SPP"); } else { /* an extended mode is selected */ outb(cio, r & ~SMC_CR1_MODE); /* read CR4 register and reset mode field */ outb(csr, 0x4); r = inb(cio) & ~SMC_CR4_EMODE; if (chipset_mode & PPB_ECP) { if (chipset_mode & PPB_EPP) { outb(cio, r | SMC_ECPEPP); if (bootverbose) printf(" ECP+EPP"); } else { outb(cio, r | SMC_ECP); if (bootverbose) printf(" ECP"); } } else { /* PPB_EPP is set */ outb(cio, r | SMC_EPPSPP); if (bootverbose) printf(" EPP SPP"); } } ppc->ppc_avm = chipset_mode; } /* set FIFO threshold to 16 */ if (ppc->ppc_avm & PPB_ECP) { /* select CRA */ outb(csr, 0xa); outb(cio, 16); } end_detect: if (bootverbose) printf ("\n"); if (ppc->ppc_avm & PPB_EPP) { /* select CR4 */ outb(csr, 0x4); r = inb(cio); /* * Set the EPP protocol... * Low=EPP 1.9 (1284 standard) and High=EPP 1.7 */ if (ppc->ppc_epp == EPP_1_9) outb(cio, (r & ~SMC_CR4_EPPTYPE)); else outb(cio, (r | SMC_CR4_EPPTYPE)); } outb(csr, 0xaa); /* end config mode */ ppc->ppc_type = PPC_TYPE_SMCLIKE; ppc_smclike_setmode(ppc, chipset_mode); return (chipset_mode); } /* * SMC FDC37C935 configuration * Found on many Alpha machines */ static int ppc_smc37c935_detect(struct ppc_data *ppc, int chipset_mode) { int type = -1; PPC_CONFIG_LOCK(ppc); outb(SMC935_CFG, 0x55); /* enter config mode */ outb(SMC935_CFG, 0x55); PPC_CONFIG_UNLOCK(ppc); outb(SMC935_IND, SMC935_ID); /* check device id */ if (inb(SMC935_DAT) == 0x2) type = SMC_37C935; if (type == -1) { outb(SMC935_CFG, 0xaa); /* exit config mode */ return (-1); } ppc->ppc_model = type; outb(SMC935_IND, SMC935_LOGDEV); /* select parallel port, */ outb(SMC935_DAT, 3); /* which is logical device 3 */ /* set io port base */ outb(SMC935_IND, SMC935_PORTHI); outb(SMC935_DAT, (u_char)((ppc->ppc_base & 0xff00) >> 8)); outb(SMC935_IND, SMC935_PORTLO); outb(SMC935_DAT, (u_char)(ppc->ppc_base & 0xff)); if (!chipset_mode) ppc->ppc_avm = PPB_COMPATIBLE; /* default mode */ else { ppc->ppc_avm = chipset_mode; outb(SMC935_IND, SMC935_PPMODE); outb(SMC935_DAT, SMC935_CENT); /* start in compatible mode */ /* SPP + EPP or just plain SPP */ if (chipset_mode & (PPB_SPP)) { if (chipset_mode & PPB_EPP) { if (ppc->ppc_epp == EPP_1_9) { outb(SMC935_IND, SMC935_PPMODE); outb(SMC935_DAT, SMC935_EPP19SPP); } if (ppc->ppc_epp == EPP_1_7) { outb(SMC935_IND, SMC935_PPMODE); outb(SMC935_DAT, SMC935_EPP17SPP); } } else { outb(SMC935_IND, SMC935_PPMODE); outb(SMC935_DAT, SMC935_SPP); } } /* ECP + EPP or just plain ECP */ if (chipset_mode & PPB_ECP) { if (chipset_mode & PPB_EPP) { if (ppc->ppc_epp == EPP_1_9) { outb(SMC935_IND, SMC935_PPMODE); outb(SMC935_DAT, SMC935_ECPEPP19); } if (ppc->ppc_epp == EPP_1_7) { outb(SMC935_IND, SMC935_PPMODE); outb(SMC935_DAT, SMC935_ECPEPP17); } } else { outb(SMC935_IND, SMC935_PPMODE); outb(SMC935_DAT, SMC935_ECP); } } } outb(SMC935_CFG, 0xaa); /* exit config mode */ ppc->ppc_type = PPC_TYPE_SMCLIKE; ppc_smclike_setmode(ppc, chipset_mode); return (chipset_mode); } /* * Winbond W83877F stuff * * EFER: extended function enable register * EFIR: extended function index register * EFDR: extended function data register */ #define efir ((efer == 0x250) ? 0x251 : 0x3f0) #define efdr ((efer == 0x250) ? 0x252 : 0x3f1) static int w83877f_efers[] = { 0x250, 0x3f0, 0x3f0, 0x250 }; static int w83877f_keys[] = { 0x89, 0x86, 0x87, 0x88 }; static int w83877f_keyiter[] = { 1, 2, 2, 1 }; static int w83877f_hefs[] = { WINB_HEFERE, WINB_HEFRAS, WINB_HEFERE | WINB_HEFRAS, 0 }; static int ppc_w83877f_detect(struct ppc_data *ppc, int chipset_mode) { int i, j, efer; unsigned char r, hefere, hefras; for (i = 0; i < 4; i ++) { /* first try to enable configuration registers */ efer = w83877f_efers[i]; /* write the key to the EFER */ for (j = 0; j < w83877f_keyiter[i]; j ++) outb (efer, w83877f_keys[i]); /* then check HEFERE and HEFRAS bits */ outb (efir, 0x0c); hefere = inb(efdr) & WINB_HEFERE; outb (efir, 0x16); hefras = inb(efdr) & WINB_HEFRAS; /* * HEFRAS HEFERE * 0 1 write 89h to 250h (power-on default) * 1 0 write 86h twice to 3f0h * 1 1 write 87h twice to 3f0h * 0 0 write 88h to 250h */ if ((hefere | hefras) == w83877f_hefs[i]) goto found; } return (-1); /* failed */ found: /* check base port address - read from CR23 */ outb(efir, 0x23); if (ppc->ppc_base != inb(efdr) * 4) /* 4 bytes boundaries */ return (-1); /* read CHIP ID from CR9/bits0-3 */ outb(efir, 0x9); switch (inb(efdr) & WINB_CHIPID) { case WINB_W83877F_ID: ppc->ppc_model = WINB_W83877F; break; case WINB_W83877AF_ID: ppc->ppc_model = WINB_W83877AF; break; default: ppc->ppc_model = WINB_UNKNOWN; } if (bootverbose) { /* dump of registers */ device_printf(ppc->ppc_dev, "0x%x - ", w83877f_keys[i]); for (i = 0; i <= 0xd; i ++) { outb(efir, i); printf("0x%x ", inb(efdr)); } for (i = 0x10; i <= 0x17; i ++) { outb(efir, i); printf("0x%x ", inb(efdr)); } outb(efir, 0x1e); printf("0x%x ", inb(efdr)); for (i = 0x20; i <= 0x29; i ++) { outb(efir, i); printf("0x%x ", inb(efdr)); } printf("\n"); } ppc->ppc_type = PPC_TYPE_GENERIC; if (!chipset_mode) { /* autodetect mode */ /* select CR0 */ outb(efir, 0x0); r = inb(efdr) & (WINB_PRTMODS0 | WINB_PRTMODS1); /* select CR9 */ outb(efir, 0x9); r |= (inb(efdr) & WINB_PRTMODS2); switch (r) { case WINB_W83757: if (bootverbose) device_printf(ppc->ppc_dev, "W83757 compatible mode\n"); return (-1); /* generic or SMC-like */ case WINB_EXTFDC: case WINB_EXTADP: case WINB_EXT2FDD: case WINB_JOYSTICK: if (bootverbose) device_printf(ppc->ppc_dev, "not in parallel port mode\n"); return (-1); case (WINB_PARALLEL | WINB_EPP_SPP): ppc->ppc_avm |= PPB_EPP | PPB_SPP; if (bootverbose) device_printf(ppc->ppc_dev, "EPP SPP\n"); break; case (WINB_PARALLEL | WINB_ECP): ppc->ppc_avm |= PPB_ECP | PPB_SPP; if (bootverbose) device_printf(ppc->ppc_dev, "ECP SPP\n"); break; case (WINB_PARALLEL | WINB_ECP_EPP): ppc->ppc_avm |= PPB_ECP | PPB_EPP | PPB_SPP; ppc->ppc_type = PPC_TYPE_SMCLIKE; if (bootverbose) device_printf(ppc->ppc_dev, "ECP+EPP SPP\n"); break; default: printf("%s: unknown case (0x%x)!\n", __func__, r); } } else { /* mode forced */ /* select CR9 and set PRTMODS2 bit */ outb(efir, 0x9); outb(efdr, inb(efdr) & ~WINB_PRTMODS2); /* select CR0 and reset PRTMODSx bits */ outb(efir, 0x0); outb(efdr, inb(efdr) & ~(WINB_PRTMODS0 | WINB_PRTMODS1)); if (chipset_mode & PPB_ECP) { if (chipset_mode & PPB_EPP) { outb(efdr, inb(efdr) | WINB_ECP_EPP); if (bootverbose) device_printf(ppc->ppc_dev, "ECP+EPP\n"); ppc->ppc_type = PPC_TYPE_SMCLIKE; } else { outb(efdr, inb(efdr) | WINB_ECP); if (bootverbose) device_printf(ppc->ppc_dev, "ECP\n"); } } else { /* select EPP_SPP otherwise */ outb(efdr, inb(efdr) | WINB_EPP_SPP); if (bootverbose) device_printf(ppc->ppc_dev, "EPP SPP\n"); } ppc->ppc_avm = chipset_mode; } /* exit configuration mode */ outb(efer, 0xaa); switch (ppc->ppc_type) { case PPC_TYPE_SMCLIKE: ppc_smclike_setmode(ppc, chipset_mode); break; default: ppc_generic_setmode(ppc, chipset_mode); break; } return (chipset_mode); } #endif /* * ppc_generic_detect */ static int ppc_generic_detect(struct ppc_data *ppc, int chipset_mode) { /* default to generic */ ppc->ppc_type = PPC_TYPE_GENERIC; if (bootverbose) device_printf(ppc->ppc_dev, "SPP"); /* first, check for ECP */ w_ecr(ppc, PPC_ECR_PS2); if ((r_ecr(ppc) & 0xe0) == PPC_ECR_PS2) { ppc->ppc_dtm |= PPB_ECP | PPB_SPP; if (bootverbose) printf(" ECP "); /* search for SMC style ECP+EPP mode */ w_ecr(ppc, PPC_ECR_EPP); } /* try to reset EPP timeout bit */ if (ppc_check_epp_timeout(ppc)) { ppc->ppc_dtm |= PPB_EPP; if (ppc->ppc_dtm & PPB_ECP) { /* SMC like chipset found */ ppc->ppc_model = SMC_LIKE; ppc->ppc_type = PPC_TYPE_SMCLIKE; if (bootverbose) printf(" ECP+EPP"); } else { if (bootverbose) printf(" EPP"); } } else { /* restore to standard mode */ w_ecr(ppc, PPC_ECR_STD); } /* XXX try to detect NIBBLE and PS2 modes */ ppc->ppc_dtm |= PPB_NIBBLE; if (chipset_mode) ppc->ppc_avm = chipset_mode; else ppc->ppc_avm = ppc->ppc_dtm; if (bootverbose) printf("\n"); switch (ppc->ppc_type) { case PPC_TYPE_SMCLIKE: ppc_smclike_setmode(ppc, chipset_mode); break; default: ppc_generic_setmode(ppc, chipset_mode); break; } return (chipset_mode); } /* * ppc_detect() * * mode is the mode suggested at boot */ static int ppc_detect(struct ppc_data *ppc, int chipset_mode) { #ifdef PPC_PROBE_CHIPSET int i, mode; /* list of supported chipsets */ int (*chipset_detect[])(struct ppc_data *, int) = { ppc_pc873xx_detect, ppc_smc37c66xgt_detect, ppc_w83877f_detect, ppc_smc37c935_detect, ppc_generic_detect, NULL }; #endif /* if can't find the port and mode not forced return error */ if (!ppc_detect_port(ppc) && chipset_mode == 0) return (EIO); /* failed, port not present */ /* assume centronics compatible mode is supported */ ppc->ppc_avm = PPB_COMPATIBLE; #ifdef PPC_PROBE_CHIPSET /* we have to differenciate available chipset modes, * chipset running modes and IEEE-1284 operating modes * * after detection, the port must support running in compatible mode */ if (ppc->ppc_flags & 0x40) { if (bootverbose) printf("ppc: chipset forced to generic\n"); #endif ppc->ppc_mode = ppc_generic_detect(ppc, chipset_mode); #ifdef PPC_PROBE_CHIPSET } else { for (i=0; chipset_detect[i] != NULL; i++) { if ((mode = chipset_detect[i](ppc, chipset_mode)) != -1) { ppc->ppc_mode = mode; break; } } } #endif /* configure/detect ECP FIFO */ if ((ppc->ppc_avm & PPB_ECP) && !(ppc->ppc_flags & 0x80)) ppc_detect_fifo(ppc); return (0); } /* * ppc_exec_microseq() * * Execute a microsequence. * Microsequence mechanism is supposed to handle fast I/O operations. */ int ppc_exec_microseq(device_t dev, struct ppb_microseq **p_msq) { struct ppc_data *ppc = DEVTOSOFTC(dev); struct ppb_microseq *mi; char cc, *p; int i, iter, len; int error; int reg; char mask; int accum = 0; char *ptr = NULL; struct ppb_microseq *stack = NULL; /* microsequence registers are equivalent to PC-like port registers */ #define r_reg(reg,ppc) (bus_read_1((ppc)->res_ioport, reg)) #define w_reg(reg, ppc, byte) (bus_write_1((ppc)->res_ioport, reg, byte)) #define INCR_PC (mi ++) /* increment program counter */ PPC_ASSERT_LOCKED(ppc); mi = *p_msq; for (;;) { switch (mi->opcode) { case MS_OP_RSET: cc = r_reg(mi->arg[0].i, ppc); cc &= (char)mi->arg[2].i; /* clear mask */ cc |= (char)mi->arg[1].i; /* assert mask */ w_reg(mi->arg[0].i, ppc, cc); INCR_PC; break; case MS_OP_RASSERT_P: reg = mi->arg[1].i; ptr = ppc->ppc_ptr; if ((len = mi->arg[0].i) == MS_ACCUM) { accum = ppc->ppc_accum; for (; accum; accum--) w_reg(reg, ppc, *ptr++); ppc->ppc_accum = accum; } else for (i=0; ippc_ptr = ptr; INCR_PC; break; case MS_OP_RFETCH_P: reg = mi->arg[1].i; mask = (char)mi->arg[2].i; ptr = ppc->ppc_ptr; if ((len = mi->arg[0].i) == MS_ACCUM) { accum = ppc->ppc_accum; for (; accum; accum--) *ptr++ = r_reg(reg, ppc) & mask; ppc->ppc_accum = accum; } else for (i=0; ippc_ptr = ptr; INCR_PC; break; case MS_OP_RFETCH: *((char *) mi->arg[2].p) = r_reg(mi->arg[0].i, ppc) & (char)mi->arg[1].i; INCR_PC; break; case MS_OP_RASSERT: case MS_OP_DELAY: /* let's suppose the next instr. is the same */ prefetch: for (;mi->opcode == MS_OP_RASSERT; INCR_PC) w_reg(mi->arg[0].i, ppc, (char)mi->arg[1].i); if (mi->opcode == MS_OP_DELAY) { DELAY(mi->arg[0].i); INCR_PC; goto prefetch; } break; case MS_OP_ADELAY: if (mi->arg[0].i) { PPC_UNLOCK(ppc); pause("ppbdelay", mi->arg[0].i * (hz/1000)); PPC_LOCK(ppc); } INCR_PC; break; case MS_OP_TRIG: reg = mi->arg[0].i; iter = mi->arg[1].i; p = (char *)mi->arg[2].p; /* XXX delay limited to 255 us */ for (i=0; ippc_accum = mi->arg[0].i; INCR_PC; break; case MS_OP_DBRA: if (--ppc->ppc_accum > 0) mi += mi->arg[0].i; INCR_PC; break; case MS_OP_BRSET: cc = r_str(ppc); if ((cc & (char)mi->arg[0].i) == (char)mi->arg[0].i) mi += mi->arg[1].i; INCR_PC; break; case MS_OP_BRCLEAR: cc = r_str(ppc); if ((cc & (char)mi->arg[0].i) == 0) mi += mi->arg[1].i; INCR_PC; break; case MS_OP_BRSTAT: cc = r_str(ppc); if ((cc & ((char)mi->arg[0].i | (char)mi->arg[1].i)) == (char)mi->arg[0].i) mi += mi->arg[2].i; INCR_PC; break; case MS_OP_C_CALL: /* * If the C call returns !0 then end the microseq. * The current state of ptr is passed to the C function */ if ((error = mi->arg[0].f(mi->arg[1].p, ppc->ppc_ptr))) return (error); INCR_PC; break; case MS_OP_PTR: ppc->ppc_ptr = (char *)mi->arg[0].p; INCR_PC; break; case MS_OP_CALL: if (stack) panic("%s: too much calls", __func__); if (mi->arg[0].p) { /* store the state of the actual * microsequence */ stack = mi; /* jump to the new microsequence */ mi = (struct ppb_microseq *)mi->arg[0].p; } else INCR_PC; break; case MS_OP_SUBRET: /* retrieve microseq and pc state before the call */ mi = stack; /* reset the stack */ stack = NULL; /* XXX return code */ INCR_PC; break; case MS_OP_PUT: case MS_OP_GET: case MS_OP_RET: /* can't return to ppb level during the execution * of a submicrosequence */ if (stack) panic("%s: can't return to ppb level", __func__); /* update pc for ppb level of execution */ *p_msq = mi; /* return to ppb level of execution */ return (0); default: panic("%s: unknown microsequence opcode 0x%x", __func__, mi->opcode); } } /* unreached */ } static void ppcintr(void *arg) { struct ppc_data *ppc = arg; u_char ctr, ecr, str; /* * If we have any child interrupt handlers registered, let * them handle this interrupt. * * XXX: If DMA is in progress should we just complete that w/o * doing this? */ PPC_LOCK(ppc); if (ppc->ppc_intr_hook != NULL && ppc->ppc_intr_hook(ppc->ppc_intr_arg) == 0) { PPC_UNLOCK(ppc); return; } str = r_str(ppc); ctr = r_ctr(ppc); ecr = r_ecr(ppc); #if defined(PPC_DEBUG) && PPC_DEBUG > 1 printf("![%x/%x/%x]", ctr, ecr, str); #endif /* don't use ecp mode with IRQENABLE set */ if (ctr & IRQENABLE) { PPC_UNLOCK(ppc); return; } /* interrupts are generated by nFault signal * only in ECP mode */ if ((str & nFAULT) && (ppc->ppc_mode & PPB_ECP)) { /* check if ppc driver has programmed the * nFault interrupt */ if (ppc->ppc_irqstat & PPC_IRQ_nFAULT) { w_ecr(ppc, ecr | PPC_nFAULT_INTR); ppc->ppc_irqstat &= ~PPC_IRQ_nFAULT; } else { /* shall be handled by underlying layers XXX */ PPC_UNLOCK(ppc); return; } } if (ppc->ppc_irqstat & PPC_IRQ_DMA) { /* disable interrupts (should be done by hardware though) */ w_ecr(ppc, ecr | PPC_SERVICE_INTR); ppc->ppc_irqstat &= ~PPC_IRQ_DMA; ecr = r_ecr(ppc); /* check if DMA completed */ if ((ppc->ppc_avm & PPB_ECP) && (ecr & PPC_ENABLE_DMA)) { #ifdef PPC_DEBUG printf("a"); #endif /* stop DMA */ w_ecr(ppc, ecr & ~PPC_ENABLE_DMA); ecr = r_ecr(ppc); if (ppc->ppc_dmastat == PPC_DMA_STARTED) { #ifdef PPC_DEBUG printf("d"); #endif ppc->ppc_dmadone(ppc); ppc->ppc_dmastat = PPC_DMA_COMPLETE; /* wakeup the waiting process */ wakeup(ppc); } } } else if (ppc->ppc_irqstat & PPC_IRQ_FIFO) { /* classic interrupt I/O */ ppc->ppc_irqstat &= ~PPC_IRQ_FIFO; } PPC_UNLOCK(ppc); return; } int ppc_read(device_t dev, char *buf, int len, int mode) { return (EINVAL); } int ppc_write(device_t dev, char *buf, int len, int how) { return (EINVAL); } int ppc_reset_epp(device_t dev) { struct ppc_data *ppc = DEVTOSOFTC(dev); PPC_ASSERT_LOCKED(ppc); ppc_reset_epp_timeout(ppc); return 0; } int ppc_setmode(device_t dev, int mode) { struct ppc_data *ppc = DEVTOSOFTC(dev); PPC_ASSERT_LOCKED(ppc); switch (ppc->ppc_type) { case PPC_TYPE_SMCLIKE: return (ppc_smclike_setmode(ppc, mode)); break; case PPC_TYPE_GENERIC: default: return (ppc_generic_setmode(ppc, mode)); break; } /* not reached */ return (ENXIO); } int ppc_probe(device_t dev, int rid) { struct ppc_data *ppc; #ifdef __i386__ static short next_bios_ppc = 0; int error; rman_res_t port; #endif /* * Allocate the ppc_data structure. */ ppc = DEVTOSOFTC(dev); bzero(ppc, sizeof(struct ppc_data)); ppc->rid_ioport = rid; #ifdef __i386__ /* retrieve ISA parameters */ error = bus_get_resource(dev, SYS_RES_IOPORT, rid, &port, NULL); /* * If port not specified, use bios list. */ if (error) { if ((next_bios_ppc < BIOS_MAX_PPC) && (*(BIOS_PORTS + next_bios_ppc) != 0)) { port = *(BIOS_PORTS + next_bios_ppc++); if (bootverbose) device_printf(dev, "parallel port found at 0x%jx\n", port); } else { device_printf(dev, "parallel port not found.\n"); return (ENXIO); } bus_set_resource(dev, SYS_RES_IOPORT, rid, port, IO_LPTSIZE_EXTENDED); } #endif /* IO port is mandatory */ /* Try "extended" IO port range...*/ ppc->res_ioport = bus_alloc_resource_anywhere(dev, SYS_RES_IOPORT, &ppc->rid_ioport, IO_LPTSIZE_EXTENDED, RF_ACTIVE); if (ppc->res_ioport != 0) { if (bootverbose) device_printf(dev, "using extended I/O port range\n"); } else { /* Failed? If so, then try the "normal" IO port range... */ ppc->res_ioport = bus_alloc_resource_anywhere(dev, SYS_RES_IOPORT, &ppc->rid_ioport, IO_LPTSIZE_NORMAL, RF_ACTIVE); if (ppc->res_ioport != 0) { if (bootverbose) device_printf(dev, "using normal I/O port range\n"); } else { if (bootverbose) device_printf(dev, "cannot reserve I/O port range\n"); goto error; } } ppc->ppc_base = rman_get_start(ppc->res_ioport); ppc->ppc_flags = device_get_flags(dev); if (!(ppc->ppc_flags & 0x20)) { ppc->res_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &ppc->rid_irq, RF_SHAREABLE); ppc->res_drq = bus_alloc_resource_any(dev, SYS_RES_DRQ, &ppc->rid_drq, RF_ACTIVE); } if (ppc->res_irq) ppc->ppc_irq = rman_get_start(ppc->res_irq); if (ppc->res_drq) ppc->ppc_dmachan = rman_get_start(ppc->res_drq); ppc->ppc_dev = dev; ppc->ppc_model = GENERIC; ppc->ppc_mode = PPB_COMPATIBLE; ppc->ppc_epp = (ppc->ppc_flags & 0x10) >> 4; ppc->ppc_type = PPC_TYPE_GENERIC; /* * Try to detect the chipset and its mode. */ if (ppc_detect(ppc, ppc->ppc_flags & 0xf)) goto error; return (0); error: if (ppc->res_irq != 0) { bus_release_resource(dev, SYS_RES_IRQ, ppc->rid_irq, ppc->res_irq); } if (ppc->res_ioport != 0) { bus_release_resource(dev, SYS_RES_IOPORT, ppc->rid_ioport, ppc->res_ioport); } if (ppc->res_drq != 0) { bus_release_resource(dev, SYS_RES_DRQ, ppc->rid_drq, ppc->res_drq); } return (ENXIO); } int ppc_attach(device_t dev) { struct ppc_data *ppc = DEVTOSOFTC(dev); int error; mtx_init(&ppc->ppc_lock, device_get_nameunit(dev), "ppc", MTX_DEF); device_printf(dev, "%s chipset (%s) in %s mode%s\n", ppc_models[ppc->ppc_model], ppc_avms[ppc->ppc_avm], ppc_modes[ppc->ppc_mode], (PPB_IS_EPP(ppc->ppc_mode)) ? ppc_epp_protocol[ppc->ppc_epp] : ""); if (ppc->ppc_fifo) device_printf(dev, "FIFO with %d/%d/%d bytes threshold\n", ppc->ppc_fifo, ppc->ppc_wthr, ppc->ppc_rthr); if (ppc->res_irq) { /* default to the tty mask for registration */ /* XXX */ error = bus_setup_intr(dev, ppc->res_irq, INTR_TYPE_TTY | INTR_MPSAFE, NULL, ppcintr, ppc, &ppc->intr_cookie); if (error) { device_printf(dev, "failed to register interrupt handler: %d\n", error); mtx_destroy(&ppc->ppc_lock); return (error); } } /* add ppbus as a child of this isa to parallel bridge */ ppc->ppbus = device_add_child(dev, "ppbus", DEVICE_UNIT_ANY); /* * Probe the ppbus and attach devices found. */ device_probe_and_attach(ppc->ppbus); return (0); } int ppc_detach(device_t dev) { struct ppc_data *ppc = DEVTOSOFTC(dev); + int error; if (ppc->res_irq == 0) { return (ENXIO); } /* detach & delete all children */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); if (ppc->res_irq != 0) { bus_teardown_intr(dev, ppc->res_irq, ppc->intr_cookie); bus_release_resource(dev, SYS_RES_IRQ, ppc->rid_irq, ppc->res_irq); } if (ppc->res_ioport != 0) { bus_release_resource(dev, SYS_RES_IOPORT, ppc->rid_ioport, ppc->res_ioport); } if (ppc->res_drq != 0) { bus_release_resource(dev, SYS_RES_DRQ, ppc->rid_drq, ppc->res_drq); } mtx_destroy(&ppc->ppc_lock); return (0); } u_char ppc_io(device_t ppcdev, int iop, u_char *addr, int cnt, u_char byte) { struct ppc_data *ppc = DEVTOSOFTC(ppcdev); PPC_ASSERT_LOCKED(ppc); switch (iop) { case PPB_OUTSB_EPP: bus_write_multi_1(ppc->res_ioport, PPC_EPP_DATA, addr, cnt); break; case PPB_OUTSW_EPP: bus_write_multi_2(ppc->res_ioport, PPC_EPP_DATA, (u_int16_t *)addr, cnt); break; case PPB_OUTSL_EPP: bus_write_multi_4(ppc->res_ioport, PPC_EPP_DATA, (u_int32_t *)addr, cnt); break; case PPB_INSB_EPP: bus_read_multi_1(ppc->res_ioport, PPC_EPP_DATA, addr, cnt); break; case PPB_INSW_EPP: bus_read_multi_2(ppc->res_ioport, PPC_EPP_DATA, (u_int16_t *)addr, cnt); break; case PPB_INSL_EPP: bus_read_multi_4(ppc->res_ioport, PPC_EPP_DATA, (u_int32_t *)addr, cnt); break; case PPB_RDTR: return (r_dtr(ppc)); case PPB_RSTR: return (r_str(ppc)); case PPB_RCTR: return (r_ctr(ppc)); case PPB_REPP_A: return (r_epp_A(ppc)); case PPB_REPP_D: return (r_epp_D(ppc)); case PPB_RECR: return (r_ecr(ppc)); case PPB_RFIFO: return (r_fifo(ppc)); case PPB_WDTR: w_dtr(ppc, byte); break; case PPB_WSTR: w_str(ppc, byte); break; case PPB_WCTR: w_ctr(ppc, byte); break; case PPB_WEPP_A: w_epp_A(ppc, byte); break; case PPB_WEPP_D: w_epp_D(ppc, byte); break; case PPB_WECR: w_ecr(ppc, byte); break; case PPB_WFIFO: w_fifo(ppc, byte); break; default: panic("%s: unknown I/O operation", __func__); break; } return (0); /* not significative */ } int ppc_read_ivar(device_t bus, device_t dev, int index, uintptr_t *val) { struct ppc_data *ppc = (struct ppc_data *)device_get_softc(bus); switch (index) { case PPC_IVAR_EPP_PROTO: PPC_ASSERT_LOCKED(ppc); *val = (u_long)ppc->ppc_epp; break; case PPC_IVAR_LOCK: *val = (uintptr_t)&ppc->ppc_lock; break; default: return (ENOENT); } return (0); } int ppc_write_ivar(device_t bus, device_t dev, int index, uintptr_t val) { struct ppc_data *ppc = (struct ppc_data *)device_get_softc(bus); switch (index) { case PPC_IVAR_INTR_HANDLER: PPC_ASSERT_LOCKED(ppc); if (dev != ppc->ppbus) return (EINVAL); if (val == 0) { ppc->ppc_intr_hook = NULL; break; } if (ppc->ppc_intr_hook != NULL) return (EBUSY); ppc->ppc_intr_hook = (void *)val; ppc->ppc_intr_arg = device_get_softc(dev); break; default: return (ENOENT); } return (0); } /* * We allow child devices to allocate an IRQ resource at rid 0 for their * interrupt handlers. */ struct resource * ppc_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct ppc_data *ppc = DEVTOSOFTC(bus); switch (type) { case SYS_RES_IRQ: if (*rid == 0) return (ppc->res_irq); break; } return (NULL); } int ppc_release_resource(device_t bus, device_t child, struct resource *r) { struct ppc_data *ppc = DEVTOSOFTC(bus); if (r == ppc->res_irq) return (0); return (EINVAL); } MODULE_DEPEND(ppc, ppbus, 1, 1, 1); diff --git a/sys/dev/rtsx/rtsx.c b/sys/dev/rtsx/rtsx.c index a293d5e12e5e..f06b493e0c15 100644 --- a/sys/dev/rtsx/rtsx.c +++ b/sys/dev/rtsx/rtsx.c @@ -1,3911 +1,3911 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 Uwe Stuehler * Copyright (c) 2012 Stefan Sperling * Copyright (c) 2020 Henri Hennebert * Copyright (c) 2020 Gary Jennejohn * Copyright (c) 2020 Jesper Schmitz Mouridsen * All rights reserved. * * Patch from: * - Lutz Bichler * * Base on OpenBSD /sys/dev/pci/rtsx_pci.c & /dev/ic/rtsx.c * on Linux /drivers/mmc/host/rtsx_pci_sdmmc.c, * /include/linux/rtsx_pci.h & * /drivers/misc/cardreader/rtsx_pcr.c * on NetBSD /sys/dev/ic/rtsx.c * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include /* For FreeBSD 11 */ #include /* For FreeBSD 11 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_mmccam.h" #ifdef MMCCAM #include #include #include #include #include #include #include "mmc_sim_if.h" #endif /* MMCCAM */ #include "rtsxreg.h" /* The softc holds our per-instance data. */ struct rtsx_softc { struct mtx rtsx_mtx; /* device mutex */ device_t rtsx_dev; /* device */ uint16_t rtsx_flags; /* device flags */ uint16_t rtsx_device_id; /* device ID */ device_t rtsx_mmc_dev; /* device of mmc bus */ uint32_t rtsx_intr_enabled; /* enabled interrupts */ uint32_t rtsx_intr_status; /* soft interrupt status */ int rtsx_irq_res_id; /* bus IRQ resource id */ struct resource *rtsx_irq_res; /* bus IRQ resource */ void *rtsx_irq_cookie; /* bus IRQ resource cookie */ struct callout rtsx_timeout_callout; /* callout for timeout */ int rtsx_timeout_cmd; /* interrupt timeout for setup commands */ int rtsx_timeout_io; /* interrupt timeout for I/O commands */ void (*rtsx_intr_trans_ok)(struct rtsx_softc *sc); /* function to call if transfer succeed */ void (*rtsx_intr_trans_ko)(struct rtsx_softc *sc); /* function to call if transfer fail */ struct timeout_task rtsx_card_insert_task; /* card insert delayed task */ struct task rtsx_card_remove_task; /* card remove task */ int rtsx_mem_res_id; /* bus memory resource id */ struct resource *rtsx_mem_res; /* bus memory resource */ bus_space_tag_t rtsx_mem_btag; /* host register set tag */ bus_space_handle_t rtsx_mem_bhandle; /* host register set handle */ bus_dma_tag_t rtsx_cmd_dma_tag; /* DMA tag for command transfer */ bus_dmamap_t rtsx_cmd_dmamap; /* DMA map for command transfer */ void *rtsx_cmd_dmamem; /* DMA mem for command transfer */ bus_addr_t rtsx_cmd_buffer; /* device visible address of the DMA segment */ int rtsx_cmd_index; /* index in rtsx_cmd_buffer */ bus_dma_tag_t rtsx_data_dma_tag; /* DMA tag for data transfer */ bus_dmamap_t rtsx_data_dmamap; /* DMA map for data transfer */ void *rtsx_data_dmamem; /* DMA mem for data transfer */ bus_addr_t rtsx_data_buffer; /* device visible address of the DMA segment */ #ifdef MMCCAM union ccb *rtsx_ccb; /* CAM control block */ struct mmc_sim rtsx_mmc_sim; /* CAM generic sim */ struct mmc_request rtsx_cam_req; /* CAM MMC request */ #endif /* MMCCAM */ struct mmc_request *rtsx_req; /* MMC request */ struct mmc_host rtsx_host; /* host parameters */ int rtsx_pcie_cap; /* PCIe capability offset */ int8_t rtsx_bus_busy; /* bus busy status */ int8_t rtsx_ios_bus_width; /* current host.ios.bus_width */ int32_t rtsx_ios_clock; /* current host.ios.clock */ int8_t rtsx_ios_power_mode; /* current host.ios.power mode */ int8_t rtsx_ios_timing; /* current host.ios.timing */ int8_t rtsx_ios_vccq; /* current host.ios.vccq */ uint8_t rtsx_read_only; /* card read only status */ uint8_t rtsx_inversion; /* inversion of card detection and read only status */ uint8_t rtsx_force_timing; /* force bus_timing_uhs_sdr50 */ uint8_t rtsx_debug_mask; /* debugging mask */ #define RTSX_DEBUG_BASIC 0x01 /* debug basic flow */ #define RTSX_TRACE_SD_CMD 0x02 /* trace SD commands */ #define RTSX_DEBUG_TUNING 0x04 /* debug tuning */ #ifdef MMCCAM uint8_t rtsx_cam_status; /* CAM status - 1 if card in use */ #endif /* MMCCAM */ uint64_t rtsx_read_count; /* count of read operations */ uint64_t rtsx_write_count; /* count of write operations */ bool rtsx_discovery_mode; /* are we in discovery mode? */ bool rtsx_tuning_mode; /* are we tuning */ bool rtsx_double_clk; /* double clock freqency */ bool rtsx_vpclk; /* voltage at Pulse-width Modulation(PWM) clock? */ uint8_t rtsx_ssc_depth; /* Spread spectrum clocking depth */ uint8_t rtsx_card_drive_sel; /* value for RTSX_CARD_DRIVE_SEL */ uint8_t rtsx_sd30_drive_sel_3v3;/* value for RTSX_SD30_DRIVE_SEL */ }; /* rtsx_flags values */ #define RTSX_F_DEFAULT 0x0000 #define RTSX_F_CARD_PRESENT 0x0001 #define RTSX_F_SDIO_SUPPORT 0x0002 #define RTSX_F_VERSION_A 0x0004 #define RTSX_F_VERSION_B 0x0008 #define RTSX_F_VERSION_C 0x0010 #define RTSX_F_VERSION_D 0x0020 #define RTSX_F_8411B_QFN48 0x0040 #define RTSX_F_REVERSE_SOCKET 0x0080 #define RTSX_REALTEK 0x10ec #define RTSX_RTS5209 0x5209 #define RTSX_RTS5227 0x5227 #define RTSX_RTS5229 0x5229 #define RTSX_RTS522A 0x522a #define RTSX_RTS525A 0x525a #define RTSX_RTS5249 0x5249 #define RTSX_RTS5260 0x5260 #define RTSX_RTL8402 0x5286 #define RTSX_RTL8411 0x5289 #define RTSX_RTL8411B 0x5287 #define RTSX_VERSION "2.1g" static const struct rtsx_pciids { uint16_t device_id; const char *desc; } rtsx_ids[] = { { RTSX_RTS5209, RTSX_VERSION " Realtek RTS5209 PCIe SD Card Reader" }, { RTSX_RTS5227, RTSX_VERSION " Realtek RTS5227 PCIe SD Card Reader" }, { RTSX_RTS5229, RTSX_VERSION " Realtek RTS5229 PCIe SD Card Reader" }, { RTSX_RTS522A, RTSX_VERSION " Realtek RTS522A PCIe SD Card Reader" }, { RTSX_RTS525A, RTSX_VERSION " Realtek RTS525A PCIe SD Card Reader" }, { RTSX_RTS5249, RTSX_VERSION " Realtek RTS5249 PCIe SD Card Reader" }, { RTSX_RTS5260, RTSX_VERSION " Realtek RTS5260 PCIe SD Card Reader" }, { RTSX_RTL8402, RTSX_VERSION " Realtek RTL8402 PCIe SD Card Reader" }, { RTSX_RTL8411, RTSX_VERSION " Realtek RTL8411 PCIe SD Card Reader" }, { RTSX_RTL8411B, RTSX_VERSION " Realtek RTL8411B PCIe SD Card Reader" }, }; /* See `kenv | grep smbios.system` */ static const struct rtsx_inversion_model { char *maker; char *family; char *product; } rtsx_inversion_models[] = { { "LENOVO", "ThinkPad T470p", "20J7S0PM00"}, { "LENOVO", "ThinkPad X13 Gen 1", "20UF000QRT"}, { NULL, NULL, NULL} }; static int rtsx_dma_alloc(struct rtsx_softc *sc); static void rtsx_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error); static void rtsx_dma_free(struct rtsx_softc *sc); static void rtsx_intr(void *arg); static void rtsx_handle_card_present(struct rtsx_softc *sc); static void rtsx_card_task(void *arg, int pending __unused); static bool rtsx_is_card_present(struct rtsx_softc *sc); static int rtsx_init(struct rtsx_softc *sc); static int rtsx_map_sd_drive(int index); static int rtsx_rts5227_fill_driving(struct rtsx_softc *sc); static int rtsx_rts5249_fill_driving(struct rtsx_softc *sc); static int rtsx_rts5260_fill_driving(struct rtsx_softc *sc); static int rtsx_read(struct rtsx_softc *, uint16_t, uint8_t *); static int rtsx_read_cfg(struct rtsx_softc *sc, uint8_t func, uint16_t addr, uint32_t *val); static int rtsx_write(struct rtsx_softc *sc, uint16_t addr, uint8_t mask, uint8_t val); static int rtsx_read_phy(struct rtsx_softc *sc, uint8_t addr, uint16_t *val); static int rtsx_write_phy(struct rtsx_softc *sc, uint8_t addr, uint16_t val); static int rtsx_bus_power_off(struct rtsx_softc *sc); static int rtsx_bus_power_on(struct rtsx_softc *sc); static int rtsx_set_bus_width(struct rtsx_softc *sc, enum mmc_bus_width width); static int rtsx_set_sd_timing(struct rtsx_softc *sc, enum mmc_bus_timing timing); static int rtsx_set_sd_clock(struct rtsx_softc *sc, uint32_t freq); static int rtsx_stop_sd_clock(struct rtsx_softc *sc); static int rtsx_switch_sd_clock(struct rtsx_softc *sc, uint8_t clk, uint8_t n, uint8_t div, uint8_t mcu); #ifndef MMCCAM static void rtsx_sd_change_tx_phase(struct rtsx_softc *sc, uint8_t sample_point); static void rtsx_sd_change_rx_phase(struct rtsx_softc *sc, uint8_t sample_point); static void rtsx_sd_tuning_rx_phase(struct rtsx_softc *sc, uint32_t *phase_map); static int rtsx_sd_tuning_rx_cmd(struct rtsx_softc *sc, uint8_t sample_point); static int rtsx_sd_tuning_rx_cmd_wait(struct rtsx_softc *sc, struct mmc_command *cmd); static void rtsx_sd_tuning_rx_cmd_wakeup(struct rtsx_softc *sc); static void rtsx_sd_wait_data_idle(struct rtsx_softc *sc); static uint8_t rtsx_sd_search_final_rx_phase(struct rtsx_softc *sc, uint32_t phase_map); static int rtsx_sd_get_rx_phase_len(uint32_t phase_map, int start_bit); #endif /* !MMCCAM */ #if 0 /* For led */ static int rtsx_led_enable(struct rtsx_softc *sc); static int rtsx_led_disable(struct rtsx_softc *sc); #endif /* For led */ static uint8_t rtsx_response_type(uint16_t mmc_rsp); static void rtsx_init_cmd(struct rtsx_softc *sc, struct mmc_command *cmd); static void rtsx_push_cmd(struct rtsx_softc *sc, uint8_t cmd, uint16_t reg, uint8_t mask, uint8_t data); static void rtsx_set_cmd_data_len(struct rtsx_softc *sc, uint16_t block_cnt, uint16_t byte_cnt); static void rtsx_send_cmd(struct rtsx_softc *sc); static void rtsx_ret_resp(struct rtsx_softc *sc); static void rtsx_set_resp(struct rtsx_softc *sc, struct mmc_command *cmd); static void rtsx_stop_cmd(struct rtsx_softc *sc); static void rtsx_clear_error(struct rtsx_softc *sc); static void rtsx_req_done(struct rtsx_softc *sc); static int rtsx_send_req(struct rtsx_softc *sc, struct mmc_command *cmd); static int rtsx_xfer_short(struct rtsx_softc *sc, struct mmc_command *cmd); static void rtsx_ask_ppbuf_part1(struct rtsx_softc *sc); static void rtsx_get_ppbuf_part1(struct rtsx_softc *sc); static void rtsx_get_ppbuf_part2(struct rtsx_softc *sc); static void rtsx_put_ppbuf_part1(struct rtsx_softc *sc); static void rtsx_put_ppbuf_part2(struct rtsx_softc *sc); static void rtsx_write_ppbuf(struct rtsx_softc *sc); static int rtsx_xfer(struct rtsx_softc *sc, struct mmc_command *cmd); static void rtsx_xfer_begin(struct rtsx_softc *sc); static void rtsx_xfer_start(struct rtsx_softc *sc); static void rtsx_xfer_finish(struct rtsx_softc *sc); static void rtsx_timeout(void *arg); #ifdef MMCCAM static int rtsx_get_tran_settings(device_t dev, struct ccb_trans_settings_mmc *cts); static int rtsx_set_tran_settings(device_t dev, struct ccb_trans_settings_mmc *cts); static int rtsx_cam_request(device_t dev, union ccb *ccb); #endif /* MMCCAM */ static int rtsx_read_ivar(device_t bus, device_t child, int which, uintptr_t *result); static int rtsx_write_ivar(device_t bus, device_t child, int which, uintptr_t value); static int rtsx_mmcbr_update_ios(device_t bus, device_t child __unused); static int rtsx_mmcbr_switch_vccq(device_t bus, device_t child __unused); static int rtsx_mmcbr_request(device_t bus, device_t child __unused, struct mmc_request *req); #ifndef MMCCAM static int rtsx_mmcbr_tune(device_t bus, device_t child __unused, bool hs400 __unused); static int rtsx_mmcbr_retune(device_t bus, device_t child __unused, bool reset __unused); static int rtsx_mmcbr_get_ro(device_t bus, device_t child __unused); static int rtsx_mmcbr_acquire_host(device_t bus, device_t child __unused); static int rtsx_mmcbr_release_host(device_t bus, device_t child __unused); #endif /* !MMCCAM */ static int rtsx_probe(device_t dev); static int rtsx_attach(device_t dev); static int rtsx_detach(device_t dev); static int rtsx_shutdown(device_t dev); static int rtsx_suspend(device_t dev); static int rtsx_resume(device_t dev); #define RTSX_LOCK_INIT(_sc) mtx_init(&(_sc)->rtsx_mtx, \ device_get_nameunit(sc->rtsx_dev), "rtsx", MTX_DEF) #define RTSX_LOCK(_sc) mtx_lock(&(_sc)->rtsx_mtx) #define RTSX_UNLOCK(_sc) mtx_unlock(&(_sc)->rtsx_mtx) #define RTSX_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->rtsx_mtx) #define RTSX_SDCLK_OFF 0 #define RTSX_SDCLK_250KHZ 250000 #define RTSX_SDCLK_400KHZ 400000 #define RTSX_SDCLK_25MHZ 25000000 #define RTSX_SDCLK_50MHZ 50000000 #define RTSX_SDCLK_100MHZ 100000000 #define RTSX_SDCLK_208MHZ 208000000 #define RTSX_MIN_DIV_N 80 #define RTSX_MAX_DIV_N 208 #define RTSX_MAX_DATA_BLKLEN 512 #define RTSX_DMA_ALIGN 4 #define RTSX_HOSTCMD_MAX 256 #define RTSX_DMA_CMD_BIFSIZE (sizeof(uint32_t) * RTSX_HOSTCMD_MAX) #define RTSX_DMA_DATA_BUFSIZE maxphys #define ISSET(t, f) ((t) & (f)) #define READ4(sc, reg) \ (bus_space_read_4((sc)->rtsx_mem_btag, (sc)->rtsx_mem_bhandle, (reg))) #define WRITE4(sc, reg, val) \ (bus_space_write_4((sc)->rtsx_mem_btag, (sc)->rtsx_mem_bhandle, (reg), (val))) #define RTSX_READ(sc, reg, val) \ do { \ int err = rtsx_read((sc), (reg), (val)); \ if (err) \ return (err); \ } while (0) #define RTSX_WRITE(sc, reg, val) \ do { \ int err = rtsx_write((sc), (reg), 0xff, (val)); \ if (err) \ return (err); \ } while (0) #define RTSX_CLR(sc, reg, bits) \ do { \ int err = rtsx_write((sc), (reg), (bits), 0); \ if (err) \ return (err); \ } while (0) #define RTSX_SET(sc, reg, bits) \ do { \ int err = rtsx_write((sc), (reg), (bits), 0xff);\ if (err) \ return (err); \ } while (0) #define RTSX_BITOP(sc, reg, mask, bits) \ do { \ int err = rtsx_write((sc), (reg), (mask), (bits)); \ if (err) \ return (err); \ } while (0) /* * We use two DMA buffers: a command buffer and a data buffer. * * The command buffer contains a command queue for the host controller, * which describes SD/MMC commands to run, and other parameters. The chip * runs the command queue when a special bit in the RTSX_HCBAR register is * set and signals completion with the RTSX_TRANS_OK_INT interrupt. * Each command is encoded as a 4 byte sequence containing command number * (read, write, or check a host controller register), a register address, * and a data bit-mask and value. * SD/MMC commands which do not transfer any data from/to the card only use * the command buffer. * * The data buffer is used for transfer longer than 512. Data transfer is * controlled via the RTSX_HDBAR register and completion is signalled by * the RTSX_TRANS_OK_INT interrupt. * * The chip is unable to perform DMA above 4GB. */ /* * Main commands in the usual seqence used: * * CMD0 Go idle state * CMD8 Send interface condition * CMD55 Application Command for next ACMD * ACMD41 Send Operation Conditions Register (OCR: voltage profile of the card) * CMD2 Send Card Identification (CID) Register * CMD3 Send relative address * CMD9 Send Card Specific Data (CSD) * CMD13 Send status (32 bits - bit 25: card password protected) * CMD7 Select card (before Get card SCR) * ACMD51 Send SCR (SD CARD Configuration Register - [51:48]: Bus widths supported) * CMD6 SD switch function * ACMD13 Send SD status (512 bits) * ACMD42 Set/Clear card detect * ACMD6 Set bus width * CMD19 Send tuning block * CMD12 Stop transmission * * CMD17 Read single block (<=512) * CMD18 Read multiple blocks (>512) * CMD24 Write single block (<=512) * CMD25 Write multiple blocks (>512) * * CMD52 IO R/W direct * CMD5 Send Operation Conditions */ static int rtsx_dma_alloc(struct rtsx_softc *sc) { int error = 0; error = bus_dma_tag_create(bus_get_dma_tag(sc->rtsx_dev), /* inherit from parent */ RTSX_DMA_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ RTSX_DMA_CMD_BIFSIZE, 1, /* maxsize, nsegments */ RTSX_DMA_CMD_BIFSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->rtsx_cmd_dma_tag); if (error) { device_printf(sc->rtsx_dev, "Can't create cmd parent DMA tag\n"); return (error); } error = bus_dmamem_alloc(sc->rtsx_cmd_dma_tag, /* DMA tag */ &sc->rtsx_cmd_dmamem, /* will hold the KVA pointer */ BUS_DMA_COHERENT | BUS_DMA_WAITOK | BUS_DMA_ZERO, /* flags */ &sc->rtsx_cmd_dmamap); /* DMA map */ if (error) { device_printf(sc->rtsx_dev, "Can't create DMA map for command transfer\n"); goto destroy_cmd_dma_tag; } error = bus_dmamap_load(sc->rtsx_cmd_dma_tag, /* DMA tag */ sc->rtsx_cmd_dmamap, /* DMA map */ sc->rtsx_cmd_dmamem, /* KVA pointer to be mapped */ RTSX_DMA_CMD_BIFSIZE, /* size of buffer */ rtsx_dmamap_cb, /* callback */ &sc->rtsx_cmd_buffer, /* first arg of callback */ 0); /* flags */ if (error || sc->rtsx_cmd_buffer == 0) { device_printf(sc->rtsx_dev, "Can't load DMA memory for command transfer\n"); error = (error) ? error : EFAULT; goto destroy_cmd_dmamem_alloc; } error = bus_dma_tag_create(bus_get_dma_tag(sc->rtsx_dev), /* inherit from parent */ RTSX_DMA_DATA_BUFSIZE, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ RTSX_DMA_DATA_BUFSIZE, 1, /* maxsize, nsegments */ RTSX_DMA_DATA_BUFSIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->rtsx_data_dma_tag); if (error) { device_printf(sc->rtsx_dev, "Can't create data parent DMA tag\n"); goto destroy_cmd_dmamap_load; } error = bus_dmamem_alloc(sc->rtsx_data_dma_tag, /* DMA tag */ &sc->rtsx_data_dmamem, /* will hold the KVA pointer */ BUS_DMA_WAITOK | BUS_DMA_ZERO, /* flags */ &sc->rtsx_data_dmamap); /* DMA map */ if (error) { device_printf(sc->rtsx_dev, "Can't create DMA map for data transfer\n"); goto destroy_data_dma_tag; } error = bus_dmamap_load(sc->rtsx_data_dma_tag, /* DMA tag */ sc->rtsx_data_dmamap, /* DMA map */ sc->rtsx_data_dmamem, /* KVA pointer to be mapped */ RTSX_DMA_DATA_BUFSIZE, /* size of buffer */ rtsx_dmamap_cb, /* callback */ &sc->rtsx_data_buffer, /* first arg of callback */ 0); /* flags */ if (error || sc->rtsx_data_buffer == 0) { device_printf(sc->rtsx_dev, "Can't load DMA memory for data transfer\n"); error = (error) ? error : EFAULT; goto destroy_data_dmamem_alloc; } return (error); destroy_data_dmamem_alloc: bus_dmamem_free(sc->rtsx_data_dma_tag, sc->rtsx_data_dmamem, sc->rtsx_data_dmamap); destroy_data_dma_tag: bus_dma_tag_destroy(sc->rtsx_data_dma_tag); destroy_cmd_dmamap_load: bus_dmamap_unload(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap); destroy_cmd_dmamem_alloc: bus_dmamem_free(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamem, sc->rtsx_cmd_dmamap); destroy_cmd_dma_tag: bus_dma_tag_destroy(sc->rtsx_cmd_dma_tag); return (error); } static void rtsx_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { if (error) { printf("rtsx_dmamap_cb: error %d\n", error); return; } *(bus_addr_t *)arg = segs[0].ds_addr; } static void rtsx_dma_free(struct rtsx_softc *sc) { if (sc->rtsx_cmd_dma_tag != NULL) { if (sc->rtsx_cmd_dmamap != NULL) bus_dmamap_unload(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap); if (sc->rtsx_cmd_dmamem != NULL) bus_dmamem_free(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamem, sc->rtsx_cmd_dmamap); sc->rtsx_cmd_dmamap = NULL; sc->rtsx_cmd_dmamem = NULL; sc->rtsx_cmd_buffer = 0; bus_dma_tag_destroy(sc->rtsx_cmd_dma_tag); sc->rtsx_cmd_dma_tag = NULL; } if (sc->rtsx_data_dma_tag != NULL) { if (sc->rtsx_data_dmamap != NULL) bus_dmamap_unload(sc->rtsx_data_dma_tag, sc->rtsx_data_dmamap); if (sc->rtsx_data_dmamem != NULL) bus_dmamem_free(sc->rtsx_data_dma_tag, sc->rtsx_data_dmamem, sc->rtsx_data_dmamap); sc->rtsx_data_dmamap = NULL; sc->rtsx_data_dmamem = NULL; sc->rtsx_data_buffer = 0; bus_dma_tag_destroy(sc->rtsx_data_dma_tag); sc->rtsx_data_dma_tag = NULL; } } static void rtsx_intr(void *arg) { struct rtsx_softc *sc = arg; uint32_t enabled; uint32_t status; RTSX_LOCK(sc); enabled = sc->rtsx_intr_enabled; status = READ4(sc, RTSX_BIPR); /* read Bus Interrupt Pending Register */ sc->rtsx_intr_status = status; if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "Interrupt handler - enabled: 0x%08x, status: 0x%08x\n", enabled, status); /* Ack interrupts. */ WRITE4(sc, RTSX_BIPR, status); if (((enabled & status) == 0) || status == 0xffffffff) { device_printf(sc->rtsx_dev, "Spurious interrupt - enabled: 0x%08x, status: 0x%08x\n", enabled, status); RTSX_UNLOCK(sc); return; } /* Detect write protect. */ if (status & RTSX_SD_WRITE_PROTECT) sc->rtsx_read_only = 1; else sc->rtsx_read_only = 0; /* Start task to handle SD card status change (from dwmmc.c). */ if (status & RTSX_SD_INT) { device_printf(sc->rtsx_dev, "Interrupt card inserted/removed\n"); rtsx_handle_card_present(sc); } if (sc->rtsx_req == NULL) { RTSX_UNLOCK(sc); return; } if (status & RTSX_TRANS_OK_INT) { sc->rtsx_req->cmd->error = MMC_ERR_NONE; if (sc->rtsx_intr_trans_ok != NULL) sc->rtsx_intr_trans_ok(sc); } else if (status & RTSX_TRANS_FAIL_INT) { uint8_t stat1; sc->rtsx_req->cmd->error = MMC_ERR_FAILED; if (rtsx_read(sc, RTSX_SD_STAT1, &stat1) == 0 && (stat1 & RTSX_SD_CRC_ERR)) { device_printf(sc->rtsx_dev, "CRC error\n"); sc->rtsx_req->cmd->error = MMC_ERR_BADCRC; } if (!sc->rtsx_tuning_mode) device_printf(sc->rtsx_dev, "Transfer fail - status: 0x%08x\n", status); rtsx_stop_cmd(sc); if (sc->rtsx_intr_trans_ko != NULL) sc->rtsx_intr_trans_ko(sc); } RTSX_UNLOCK(sc); } /* * Function called from the IRQ handler (from dwmmc.c). */ static void rtsx_handle_card_present(struct rtsx_softc *sc) { bool was_present; bool is_present; #ifdef MMCCAM was_present = sc->rtsx_cam_status; #else /* !MMCCAM */ was_present = sc->rtsx_mmc_dev != NULL; #endif /* MMCCAM */ is_present = rtsx_is_card_present(sc); if (is_present) device_printf(sc->rtsx_dev, "Card present\n"); else device_printf(sc->rtsx_dev, "Card absent\n"); if (!was_present && is_present) { /* * The delay is to debounce the card insert * (sometimes the card detect pin stabilizes * before the other pins have made good contact). */ taskqueue_enqueue_timeout(taskqueue_swi_giant, &sc->rtsx_card_insert_task, -hz); } else if (was_present && !is_present) { taskqueue_enqueue(taskqueue_swi_giant, &sc->rtsx_card_remove_task); } } /* * This function is called at startup. */ static void rtsx_card_task(void *arg, int pending __unused) { struct rtsx_softc *sc = arg; if (rtsx_is_card_present(sc)) { sc->rtsx_flags |= RTSX_F_CARD_PRESENT; /* Card is present, attach if necessary. */ #ifdef MMCCAM if (sc->rtsx_cam_status == 0) { #else /* !MMCCAM */ if (sc->rtsx_mmc_dev == NULL) { #endif /* MMCCAM */ if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "Card inserted\n"); sc->rtsx_read_count = sc->rtsx_write_count = 0; #ifdef MMCCAM sc->rtsx_cam_status = 1; mmc_cam_sim_discover(&sc->rtsx_mmc_sim); #else /* !MMCCAM */ RTSX_LOCK(sc); sc->rtsx_mmc_dev = device_add_child(sc->rtsx_dev, "mmc", DEVICE_UNIT_ANY); RTSX_UNLOCK(sc); if (sc->rtsx_mmc_dev == NULL) { device_printf(sc->rtsx_dev, "Adding MMC bus failed\n"); } else { device_set_ivars(sc->rtsx_mmc_dev, sc); device_probe_and_attach(sc->rtsx_mmc_dev); } #endif /* MMCCAM */ } } else { sc->rtsx_flags &= ~RTSX_F_CARD_PRESENT; /* Card isn't present, detach if necessary. */ #ifdef MMCCAM if (sc->rtsx_cam_status != 0) { #else /* !MMCCAM */ if (sc->rtsx_mmc_dev != NULL) { #endif /* MMCCAM */ if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "Card removed\n"); if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "Read count: %" PRIu64 ", write count: %" PRIu64 "\n", sc->rtsx_read_count, sc->rtsx_write_count); #ifdef MMCCAM sc->rtsx_cam_status = 0; mmc_cam_sim_discover(&sc->rtsx_mmc_sim); #else /* !MMCCAM */ if (device_delete_child(sc->rtsx_dev, sc->rtsx_mmc_dev)) device_printf(sc->rtsx_dev, "Detaching MMC bus failed\n"); sc->rtsx_mmc_dev = NULL; #endif /* MMCCAM */ } } } static bool rtsx_is_card_present(struct rtsx_softc *sc) { uint32_t status; status = READ4(sc, RTSX_BIPR); if (sc->rtsx_inversion == 0) return (status & RTSX_SD_EXIST); else return !(status & RTSX_SD_EXIST); } static int rtsx_init(struct rtsx_softc *sc) { uint8_t version; uint8_t val; int error; sc->rtsx_host.host_ocr = RTSX_SUPPORTED_VOLTAGE; sc->rtsx_host.f_min = RTSX_SDCLK_250KHZ; sc->rtsx_host.f_max = RTSX_SDCLK_208MHZ; sc->rtsx_host.caps = MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED | MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25; sc->rtsx_host.caps |= MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104; if (sc->rtsx_device_id == RTSX_RTS5209) sc->rtsx_host.caps |= MMC_CAP_8_BIT_DATA; pci_find_cap(sc->rtsx_dev, PCIY_EXPRESS, &(sc->rtsx_pcie_cap)); /* * Check IC version. */ switch (sc->rtsx_device_id) { case RTSX_RTS5229: /* Read IC version from dummy register. */ RTSX_READ(sc, RTSX_DUMMY_REG, &version); if ((version & 0x0F) == RTSX_IC_VERSION_C) sc->rtsx_flags |= RTSX_F_VERSION_C; break; case RTSX_RTS522A: /* Read IC version from dummy register. */ RTSX_READ(sc, RTSX_DUMMY_REG, &version); if ((version & 0x0F) == RTSX_IC_VERSION_A) sc->rtsx_flags |= RTSX_F_VERSION_A; break; case RTSX_RTS525A: /* Read IC version from dummy register. */ RTSX_READ(sc, RTSX_DUMMY_REG, &version); if ((version & 0x0F) == RTSX_IC_VERSION_A) sc->rtsx_flags |= RTSX_F_VERSION_A; break; case RTSX_RTL8411B: RTSX_READ(sc, RTSX_RTL8411B_PACKAGE, &version); if (version & RTSX_RTL8411B_QFN48) sc->rtsx_flags |= RTSX_F_8411B_QFN48; break; } /* * Fetch vendor settings. */ /* * Normally OEMs will set vendor setting to the config space * of Realtek card reader in BIOS stage. This statement reads * the setting and configure the internal registers according * to it, to improve card reader's compatibility condition. */ sc->rtsx_card_drive_sel = RTSX_CARD_DRIVE_DEFAULT; switch (sc->rtsx_device_id) { uint32_t reg; uint32_t reg1; uint8_t reg3; case RTSX_RTS5209: sc->rtsx_card_drive_sel = RTSX_RTS5209_CARD_DRIVE_DEFAULT; sc->rtsx_sd30_drive_sel_3v3 = RTSX_DRIVER_TYPE_D; reg = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG2, 4); if (!(reg & 0x80)) { sc->rtsx_card_drive_sel = (reg >> 8) & 0x3F; sc->rtsx_sd30_drive_sel_3v3 = reg & 0x07; } else if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) { device_printf(sc->rtsx_dev, "pci_read_config() error - reg: 0x%08x\n", reg); } if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "card_drive_sel: 0x%02x, sd30_drive_sel_3v3: 0x%02x\n", sc->rtsx_card_drive_sel, sc->rtsx_sd30_drive_sel_3v3); break; case RTSX_RTS5227: case RTSX_RTS522A: sc->rtsx_sd30_drive_sel_3v3 = RTSX_CFG_DRIVER_TYPE_B; reg = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG1, 4); if (!(reg & 0x1000000)) { sc->rtsx_card_drive_sel &= 0x3F; sc->rtsx_card_drive_sel |= ((reg >> 25) & 0x01) << 6; reg = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG2, 4); sc->rtsx_sd30_drive_sel_3v3 = (reg >> 5) & 0x03; if (reg & 0x4000) sc->rtsx_flags |= RTSX_F_REVERSE_SOCKET; } else if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) { device_printf(sc->rtsx_dev, "pci_read_config() error - reg: 0x%08x\n", reg); } if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "card_drive_sel: 0x%02x, sd30_drive_sel_3v3: 0x%02x, reverse_socket is %s\n", sc->rtsx_card_drive_sel, sc->rtsx_sd30_drive_sel_3v3, (sc->rtsx_flags & RTSX_F_REVERSE_SOCKET) ? "true" : "false"); break; case RTSX_RTS5229: sc->rtsx_sd30_drive_sel_3v3 = RTSX_DRIVER_TYPE_D; reg = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG1, 4); if (!(reg & 0x1000000)) { sc->rtsx_card_drive_sel &= 0x3F; sc->rtsx_card_drive_sel |= ((reg >> 25) & 0x01) << 6; reg = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG2, 4); sc->rtsx_sd30_drive_sel_3v3 = rtsx_map_sd_drive((reg >> 5) & 0x03); } else if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) { device_printf(sc->rtsx_dev, "pci_read_config() error - reg: 0x%08x\n", reg); } if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "card_drive_sel: 0x%02x, sd30_drive_sel_3v3: 0x%02x\n", sc->rtsx_card_drive_sel, sc->rtsx_sd30_drive_sel_3v3); break; case RTSX_RTS525A: case RTSX_RTS5249: case RTSX_RTS5260: sc->rtsx_sd30_drive_sel_3v3 = RTSX_CFG_DRIVER_TYPE_B; reg = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG1, 4); if ((reg & 0x1000000)) { sc->rtsx_card_drive_sel &= 0x3F; sc->rtsx_card_drive_sel |= ((reg >> 25) & 0x01) << 6; reg = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG2, 4); sc->rtsx_sd30_drive_sel_3v3 = (reg >> 5) & 0x03; if (reg & 0x4000) sc->rtsx_flags |= RTSX_F_REVERSE_SOCKET; } else if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) { device_printf(sc->rtsx_dev, "pci_read_config() error - reg: 0x%08x\n", reg); } if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "card_drive_sel = 0x%02x, sd30_drive_sel_3v3: 0x%02x, reverse_socket is %s\n", sc->rtsx_card_drive_sel, sc->rtsx_sd30_drive_sel_3v3, (sc->rtsx_flags & RTSX_F_REVERSE_SOCKET) ? "true" : "false"); break; case RTSX_RTL8402: case RTSX_RTL8411: sc->rtsx_card_drive_sel = RTSX_RTL8411_CARD_DRIVE_DEFAULT; sc->rtsx_sd30_drive_sel_3v3 = RTSX_DRIVER_TYPE_D; reg1 = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG1, 4); if (reg1 & 0x1000000) { sc->rtsx_card_drive_sel &= 0x3F; sc->rtsx_card_drive_sel |= ((reg1 >> 25) & 0x01) << 6; reg3 = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG3, 1); sc->rtsx_sd30_drive_sel_3v3 = (reg3 >> 5) & 0x07; } else if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) { device_printf(sc->rtsx_dev, "pci_read_config() error - reg1: 0x%08x\n", reg1); } if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "card_drive_sel: 0x%02x, sd30_drive_sel_3v3: 0x%02x\n", sc->rtsx_card_drive_sel, sc->rtsx_sd30_drive_sel_3v3); break; case RTSX_RTL8411B: sc->rtsx_card_drive_sel = RTSX_RTL8411_CARD_DRIVE_DEFAULT; sc->rtsx_sd30_drive_sel_3v3 = RTSX_DRIVER_TYPE_D; reg = pci_read_config(sc->rtsx_dev, RTSX_PCR_SETTING_REG1, 4); if (!(reg & 0x1000000)) { sc->rtsx_sd30_drive_sel_3v3 = rtsx_map_sd_drive(reg & 0x03); } else if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) { device_printf(sc->rtsx_dev, "pci_read_config() error - reg: 0x%08x\n", reg); } if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "card_drive_sel: 0x%02x, sd30_drive_sel_3v3: 0x%02x\n", sc->rtsx_card_drive_sel, sc->rtsx_sd30_drive_sel_3v3); break; } if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_init() rtsx_flags: 0x%04x\n", sc->rtsx_flags); /* Enable interrupts. */ sc->rtsx_intr_enabled = RTSX_TRANS_OK_INT_EN | RTSX_TRANS_FAIL_INT_EN | RTSX_SD_INT_EN; WRITE4(sc, RTSX_BIER, sc->rtsx_intr_enabled); /* Power on SSC clock. */ RTSX_CLR(sc, RTSX_FPDCTL, RTSX_SSC_POWER_DOWN); /* Wait SSC power stable. */ DELAY(200); /* Disable ASPM */ val = pci_read_config(sc->rtsx_dev, sc->rtsx_pcie_cap + PCIER_LINK_CTL, 1); pci_write_config(sc->rtsx_dev, sc->rtsx_pcie_cap + PCIER_LINK_CTL, val & 0xfc, 1); /* * Optimize phy. */ switch (sc->rtsx_device_id) { case RTSX_RTS5209: /* Some magic numbers from Linux driver. */ if ((error = rtsx_write_phy(sc, 0x00, 0xB966))) return (error); break; case RTSX_RTS5227: RTSX_CLR(sc, RTSX_PM_CTRL3, RTSX_D3_DELINK_MODE_EN); /* Optimize RX sensitivity. */ if ((error = rtsx_write_phy(sc, 0x00, 0xBA42))) return (error); break; case RTSX_RTS5229: /* Optimize RX sensitivity. */ if ((error = rtsx_write_phy(sc, 0x00, 0xBA42))) return (error); break; case RTSX_RTS522A: RTSX_CLR(sc, RTSX_RTS522A_PM_CTRL3, RTSX_D3_DELINK_MODE_EN); if (sc->rtsx_flags & RTSX_F_VERSION_A) { if ((error = rtsx_write_phy(sc, RTSX_PHY_RCR2, RTSX_PHY_RCR2_INIT_27S))) return (error); } if ((error = rtsx_write_phy(sc, RTSX_PHY_RCR1, RTSX_PHY_RCR1_INIT_27S))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_FLD0, RTSX_PHY_FLD0_INIT_27S))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_FLD3, RTSX_PHY_FLD3_INIT_27S))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_FLD4, RTSX_PHY_FLD4_INIT_27S))) return (error); break; case RTSX_RTS525A: if ((error = rtsx_write_phy(sc, RTSX__PHY_FLD0, RTSX__PHY_FLD0_CLK_REQ_20C | RTSX__PHY_FLD0_RX_IDLE_EN | RTSX__PHY_FLD0_BIT_ERR_RSTN | RTSX__PHY_FLD0_BER_COUNT | RTSX__PHY_FLD0_BER_TIMER | RTSX__PHY_FLD0_CHECK_EN))) return (error); if ((error = rtsx_write_phy(sc, RTSX__PHY_ANA03, RTSX__PHY_ANA03_TIMER_MAX | RTSX__PHY_ANA03_OOBS_DEB_EN | RTSX__PHY_CMU_DEBUG_EN))) return (error); if (sc->rtsx_flags & RTSX_F_VERSION_A) if ((error = rtsx_write_phy(sc, RTSX__PHY_REV0, RTSX__PHY_REV0_FILTER_OUT | RTSX__PHY_REV0_CDR_BYPASS_PFD | RTSX__PHY_REV0_CDR_RX_IDLE_BYPASS))) return (error); break; case RTSX_RTS5249: RTSX_CLR(sc, RTSX_PM_CTRL3, RTSX_D3_DELINK_MODE_EN); if ((error = rtsx_write_phy(sc, RTSX_PHY_REV, RTSX_PHY_REV_RESV | RTSX_PHY_REV_RXIDLE_LATCHED | RTSX_PHY_REV_P1_EN | RTSX_PHY_REV_RXIDLE_EN | RTSX_PHY_REV_CLKREQ_TX_EN | RTSX_PHY_REV_RX_PWST | RTSX_PHY_REV_CLKREQ_DT_1_0 | RTSX_PHY_REV_STOP_CLKRD | RTSX_PHY_REV_STOP_CLKWR))) return (error); DELAY(1000); if ((error = rtsx_write_phy(sc, RTSX_PHY_BPCR, RTSX_PHY_BPCR_IBRXSEL | RTSX_PHY_BPCR_IBTXSEL | RTSX_PHY_BPCR_IB_FILTER | RTSX_PHY_BPCR_CMIRROR_EN))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_PCR, RTSX_PHY_PCR_FORCE_CODE | RTSX_PHY_PCR_OOBS_CALI_50 | RTSX_PHY_PCR_OOBS_VCM_08 | RTSX_PHY_PCR_OOBS_SEN_90 | RTSX_PHY_PCR_RSSI_EN | RTSX_PHY_PCR_RX10K))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_RCR2, RTSX_PHY_RCR2_EMPHASE_EN | RTSX_PHY_RCR2_NADJR | RTSX_PHY_RCR2_CDR_SR_2 | RTSX_PHY_RCR2_FREQSEL_12 | RTSX_PHY_RCR2_CDR_SC_12P | RTSX_PHY_RCR2_CALIB_LATE))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_FLD4, RTSX_PHY_FLD4_FLDEN_SEL | RTSX_PHY_FLD4_REQ_REF | RTSX_PHY_FLD4_RXAMP_OFF | RTSX_PHY_FLD4_REQ_ADDA | RTSX_PHY_FLD4_BER_COUNT | RTSX_PHY_FLD4_BER_TIMER | RTSX_PHY_FLD4_BER_CHK_EN))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_RDR, RTSX_PHY_RDR_RXDSEL_1_9 | RTSX_PHY_SSC_AUTO_PWD))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_RCR1, RTSX_PHY_RCR1_ADP_TIME_4 | RTSX_PHY_RCR1_VCO_COARSE))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_FLD3, RTSX_PHY_FLD3_TIMER_4 | RTSX_PHY_FLD3_TIMER_6 | RTSX_PHY_FLD3_RXDELINK))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_TUNE, RTSX_PHY_TUNE_TUNEREF_1_0 | RTSX_PHY_TUNE_VBGSEL_1252 | RTSX_PHY_TUNE_SDBUS_33 | RTSX_PHY_TUNE_TUNED18 | RTSX_PHY_TUNE_TUNED12 | RTSX_PHY_TUNE_TUNEA12))) return (error); break; } /* Set mcu_cnt to 7 to ensure data can be sampled properly. */ RTSX_BITOP(sc, RTSX_CLK_DIV, 0x07, 0x07); /* Disable sleep mode. */ RTSX_CLR(sc, RTSX_HOST_SLEEP_STATE, RTSX_HOST_ENTER_S1 | RTSX_HOST_ENTER_S3); /* Disable card clock. */ RTSX_CLR(sc, RTSX_CARD_CLK_EN, RTSX_CARD_CLK_EN_ALL); /* Reset delink mode. */ RTSX_CLR(sc, RTSX_CHANGE_LINK_STATE, RTSX_FORCE_RST_CORE_EN | RTSX_NON_STICKY_RST_N_DBG); /* Card driving select. */ RTSX_WRITE(sc, RTSX_CARD_DRIVE_SEL, sc->rtsx_card_drive_sel); /* Enable SSC clock. */ RTSX_WRITE(sc, RTSX_SSC_CTL1, RTSX_SSC_8X_EN | RTSX_SSC_SEL_4M); RTSX_WRITE(sc, RTSX_SSC_CTL2, 0x12); /* Disable cd_pwr_save. */ RTSX_BITOP(sc, RTSX_CHANGE_LINK_STATE, 0x16, RTSX_MAC_PHY_RST_N_DBG); /* Clear Link Ready Interrupt. */ RTSX_BITOP(sc, RTSX_IRQSTAT0, RTSX_LINK_READY_INT, RTSX_LINK_READY_INT); /* Enlarge the estimation window of PERST# glitch * to reduce the chance of invalid card interrupt. */ RTSX_WRITE(sc, RTSX_PERST_GLITCH_WIDTH, 0x80); /* Set RC oscillator to 400K. */ RTSX_CLR(sc, RTSX_RCCTL, RTSX_RCCTL_F_2M); /* Enable interrupt write-clear (default is read-clear). */ RTSX_CLR(sc, RTSX_NFTS_TX_CTRL, RTSX_INT_READ_CLR); switch (sc->rtsx_device_id) { case RTSX_RTS525A: case RTSX_RTS5260: RTSX_BITOP(sc, RTSX_PM_CLK_FORCE_CTL, 1, 1); break; } /* OC power down. */ RTSX_BITOP(sc, RTSX_FPDCTL, RTSX_SD_OC_POWER_DOWN, RTSX_SD_OC_POWER_DOWN); /* Enable clk_request_n to enable clock power management */ pci_write_config(sc->rtsx_dev, sc->rtsx_pcie_cap + PCIER_LINK_CTL + 1, 1, 1); /* Enter L1 when host tx idle */ pci_write_config(sc->rtsx_dev, 0x70F, 0x5B, 1); /* * Specific extra init. */ switch (sc->rtsx_device_id) { uint16_t cap; case RTSX_RTS5209: /* Turn off LED. */ RTSX_WRITE(sc, RTSX_CARD_GPIO, 0x03); /* Reset ASPM state to default value. */ RTSX_CLR(sc, RTSX_ASPM_FORCE_CTL, RTSX_ASPM_FORCE_MASK); /* Force CLKREQ# PIN to drive 0 to request clock. */ RTSX_BITOP(sc, RTSX_PETXCFG, 0x08, 0x08); /* Configure GPIO as output. */ RTSX_WRITE(sc, RTSX_CARD_GPIO_DIR, 0x03); /* Configure driving. */ RTSX_WRITE(sc, RTSX_SD30_CMD_DRIVE_SEL, sc->rtsx_sd30_drive_sel_3v3); break; case RTSX_RTS5227: /* Configure GPIO as output. */ RTSX_BITOP(sc, RTSX_GPIO_CTL, RTSX_GPIO_LED_ON, RTSX_GPIO_LED_ON); /* Reset ASPM state to default value. */ RTSX_BITOP(sc, RTSX_ASPM_FORCE_CTL, RTSX_ASPM_FORCE_MASK, RTSX_FORCE_ASPM_NO_ASPM); /* Switch LDO3318 source from DV33 to 3V3. */ RTSX_CLR(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33); RTSX_BITOP(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33, RTSX_LDO_PWR_SEL_3V3); /* Set default OLT blink period. */ RTSX_BITOP(sc, RTSX_OLT_LED_CTL, 0x0F, RTSX_OLT_LED_PERIOD); /* Configure LTR. */ cap = pci_read_config(sc->rtsx_dev, sc->rtsx_pcie_cap + PCIER_DEVICE_CTL2, 2); if (cap & PCIEM_CTL2_LTR_ENABLE) RTSX_WRITE(sc, RTSX_LTR_CTL, 0xa3); /* Configure OBFF. */ RTSX_BITOP(sc, RTSX_OBFF_CFG, RTSX_OBFF_EN_MASK, RTSX_OBFF_ENABLE); /* Configure driving. */ if ((error = rtsx_rts5227_fill_driving(sc))) return (error); /* Configure force_clock_req. */ if (sc->rtsx_flags & RTSX_F_REVERSE_SOCKET) RTSX_BITOP(sc, RTSX_PETXCFG, 0xB8, 0xB8); else RTSX_BITOP(sc, RTSX_PETXCFG, 0xB8, 0x88); RTSX_CLR(sc, RTSX_PM_CTRL3, RTSX_D3_DELINK_MODE_EN); /*!!! Added for reboot after Windows. */ RTSX_BITOP(sc, RTSX_PM_CTRL3, RTSX_PM_WAKE_EN, RTSX_PM_WAKE_EN); break; case RTSX_RTS5229: /* Configure GPIO as output. */ RTSX_BITOP(sc, RTSX_GPIO_CTL, RTSX_GPIO_LED_ON, RTSX_GPIO_LED_ON); /* Reset ASPM state to default value. */ /* With this reset: dd if=/dev/random of=/dev/mmcsd0 encounter a timeout. */ //!!! RTSX_BITOP(sc, RTSX_ASPM_FORCE_CTL, RTSX_ASPM_FORCE_MASK, RTSX_FORCE_ASPM_NO_ASPM); /* Force CLKREQ# PIN to drive 0 to request clock. */ RTSX_BITOP(sc, RTSX_PETXCFG, 0x08, 0x08); /* Switch LDO3318 source from DV33 to card_3v3. */ RTSX_CLR(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33); RTSX_BITOP(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33, RTSX_LDO_PWR_SEL_3V3); /* Set default OLT blink period. */ RTSX_BITOP(sc, RTSX_OLT_LED_CTL, 0x0F, RTSX_OLT_LED_PERIOD); /* Configure driving. */ RTSX_WRITE(sc, RTSX_SD30_CMD_DRIVE_SEL, sc->rtsx_sd30_drive_sel_3v3); break; case RTSX_RTS522A: /* Add specific init from RTS5227. */ /* Configure GPIO as output. */ RTSX_BITOP(sc, RTSX_GPIO_CTL, RTSX_GPIO_LED_ON, RTSX_GPIO_LED_ON); /* Reset ASPM state to default value. */ RTSX_BITOP(sc, RTSX_ASPM_FORCE_CTL, RTSX_ASPM_FORCE_MASK, RTSX_FORCE_ASPM_NO_ASPM); /* Switch LDO3318 source from DV33 to 3V3. */ RTSX_CLR(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33); RTSX_BITOP(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33, RTSX_LDO_PWR_SEL_3V3); /* Set default OLT blink period. */ RTSX_BITOP(sc, RTSX_OLT_LED_CTL, 0x0F, RTSX_OLT_LED_PERIOD); /* Configure LTR. */ cap = pci_read_config(sc->rtsx_dev, sc->rtsx_pcie_cap + PCIER_DEVICE_CTL2, 2); if (cap & PCIEM_CTL2_LTR_ENABLE) RTSX_WRITE(sc, RTSX_LTR_CTL, 0xa3); /* Configure OBFF. */ RTSX_BITOP(sc, RTSX_OBFF_CFG, RTSX_OBFF_EN_MASK, RTSX_OBFF_ENABLE); /* Configure driving. */ if ((error = rtsx_rts5227_fill_driving(sc))) return (error); /* Configure force_clock_req. */ if (sc->rtsx_flags & RTSX_F_REVERSE_SOCKET) RTSX_BITOP(sc, RTSX_PETXCFG, 0xB8, 0xB8); else RTSX_BITOP(sc, RTSX_PETXCFG, 0xB8, 0x88); RTSX_CLR(sc, RTSX_RTS522A_PM_CTRL3, 0x10); /* specific for RTS522A. */ RTSX_BITOP(sc, RTSX_FUNC_FORCE_CTL, RTSX_FUNC_FORCE_UPME_XMT_DBG, RTSX_FUNC_FORCE_UPME_XMT_DBG); RTSX_BITOP(sc, RTSX_PCLK_CTL, 0x04, 0x04); RTSX_BITOP(sc, RTSX_PM_EVENT_DEBUG, RTSX_PME_DEBUG_0, RTSX_PME_DEBUG_0); RTSX_WRITE(sc, RTSX_PM_CLK_FORCE_CTL, 0x11); break; case RTSX_RTS525A: /* Add specific init from RTS5249. */ /* Rest L1SUB Config. */ RTSX_CLR(sc, RTSX_L1SUB_CONFIG3, 0xff); /* Configure GPIO as output. */ RTSX_BITOP(sc, RTSX_GPIO_CTL, RTSX_GPIO_LED_ON, RTSX_GPIO_LED_ON); /* Reset ASPM state to default value. */ RTSX_BITOP(sc, RTSX_ASPM_FORCE_CTL, RTSX_ASPM_FORCE_MASK, RTSX_FORCE_ASPM_NO_ASPM); /* Switch LDO3318 source from DV33 to 3V3. */ RTSX_CLR(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33); RTSX_BITOP(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33, RTSX_LDO_PWR_SEL_3V3); /* Set default OLT blink period. */ RTSX_BITOP(sc, RTSX_OLT_LED_CTL, 0x0F, RTSX_OLT_LED_PERIOD); /* Configure driving. */ if ((error = rtsx_rts5249_fill_driving(sc))) return (error); /* Configure force_clock_req. */ if (sc->rtsx_flags & RTSX_F_REVERSE_SOCKET) RTSX_BITOP(sc, RTSX_PETXCFG, 0xB0, 0xB0); else RTSX_BITOP(sc, RTSX_PETXCFG, 0xB0, 0x80); /* Specifc for RTS525A. */ RTSX_BITOP(sc, RTSX_PCLK_CTL, RTSX_PCLK_MODE_SEL, RTSX_PCLK_MODE_SEL); if (sc->rtsx_flags & RTSX_F_VERSION_A) { RTSX_WRITE(sc, RTSX_L1SUB_CONFIG2, RTSX_L1SUB_AUTO_CFG); RTSX_BITOP(sc, RTSX_RREF_CFG, RTSX_RREF_VBGSEL_MASK, RTSX_RREF_VBGSEL_1V25); RTSX_BITOP(sc, RTSX_LDO_VIO_CFG, RTSX_LDO_VIO_TUNE_MASK, RTSX_LDO_VIO_1V7); RTSX_BITOP(sc, RTSX_LDO_DV12S_CFG, RTSX_LDO_D12_TUNE_MASK, RTSX_LDO_D12_TUNE_DF); RTSX_BITOP(sc, RTSX_LDO_AV12S_CFG, RTSX_LDO_AV12S_TUNE_MASK, RTSX_LDO_AV12S_TUNE_DF); RTSX_BITOP(sc, RTSX_LDO_VCC_CFG0, RTSX_LDO_VCC_LMTVTH_MASK, RTSX_LDO_VCC_LMTVTH_2A); RTSX_BITOP(sc, RTSX_OOBS_CONFIG, RTSX_OOBS_AUTOK_DIS | RTSX_OOBS_VAL_MASK, 0x89); } break; case RTSX_RTS5249: /* Rest L1SUB Config. */ RTSX_CLR(sc, RTSX_L1SUB_CONFIG3, 0xff); /* Configure GPIO as output. */ RTSX_BITOP(sc, RTSX_GPIO_CTL, RTSX_GPIO_LED_ON, RTSX_GPIO_LED_ON); /* Reset ASPM state to default value. */ RTSX_BITOP(sc, RTSX_ASPM_FORCE_CTL, RTSX_ASPM_FORCE_MASK, RTSX_FORCE_ASPM_NO_ASPM); /* Switch LDO3318 source from DV33 to 3V3. */ RTSX_CLR(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33); RTSX_BITOP(sc, RTSX_LDO_PWR_SEL, RTSX_LDO_PWR_SEL_DV33, RTSX_LDO_PWR_SEL_3V3); /* Set default OLT blink period. */ RTSX_BITOP(sc, RTSX_OLT_LED_CTL, 0x0F, RTSX_OLT_LED_PERIOD); /* Configure driving. */ if ((error = rtsx_rts5249_fill_driving(sc))) return (error); /* Configure force_clock_req. */ if (sc->rtsx_flags & RTSX_F_REVERSE_SOCKET) RTSX_BITOP(sc, RTSX_PETXCFG, 0xB0, 0xB0); else RTSX_BITOP(sc, RTSX_PETXCFG, 0xB0, 0x80); break; case RTSX_RTS5260: /* Set mcu_cnt to 7 to ensure data can be sampled properly. */ RTSX_BITOP(sc, RTSX_CLK_DIV, 0x07, 0x07); RTSX_WRITE(sc, RTSX_SSC_DIV_N_0, 0x5D); /* Force no MDIO */ RTSX_WRITE(sc, RTSX_RTS5260_AUTOLOAD_CFG4, RTSX_RTS5260_MIMO_DISABLE); /* Modify SDVCC Tune Default Parameters! */ RTSX_BITOP(sc, RTSX_LDO_VCC_CFG0, RTSX_RTS5260_DVCC_TUNE_MASK, RTSX_RTS5260_DVCC_33); RTSX_BITOP(sc, RTSX_PCLK_CTL, RTSX_PCLK_MODE_SEL, RTSX_PCLK_MODE_SEL); RTSX_BITOP(sc, RTSX_L1SUB_CONFIG1, RTSX_AUX_CLK_ACTIVE_SEL_MASK, RTSX_MAC_CKSW_DONE); /* Rest L1SUB Config */ RTSX_CLR(sc, RTSX_L1SUB_CONFIG3, 0xFF); RTSX_BITOP(sc, RTSX_PM_CLK_FORCE_CTL, RTSX_CLK_PM_EN, RTSX_CLK_PM_EN); RTSX_WRITE(sc, RTSX_PWD_SUSPEND_EN, 0xFF); RTSX_BITOP(sc, RTSX_PWR_GATE_CTRL, RTSX_PWR_GATE_EN, RTSX_PWR_GATE_EN); RTSX_BITOP(sc, RTSX_REG_VREF, RTSX_PWD_SUSPND_EN, RTSX_PWD_SUSPND_EN); RTSX_BITOP(sc, RTSX_RBCTL, RTSX_U_AUTO_DMA_EN_MASK, RTSX_U_AUTO_DMA_DISABLE); if (sc->rtsx_flags & RTSX_F_REVERSE_SOCKET) RTSX_BITOP(sc, RTSX_PETXCFG, 0xB0, 0xB0); else RTSX_BITOP(sc, RTSX_PETXCFG, 0xB0, 0x80); RTSX_BITOP(sc, RTSX_OBFF_CFG, RTSX_OBFF_EN_MASK, RTSX_OBFF_DISABLE); RTSX_CLR(sc, RTSX_RTS5260_DVCC_CTRL, RTSX_RTS5260_DVCC_OCP_EN | RTSX_RTS5260_DVCC_OCP_CL_EN); /* CLKREQ# PIN will be forced to drive low. */ RTSX_BITOP(sc, RTSX_PETXCFG, RTSX_FORCE_CLKREQ_DELINK_MASK, RTSX_FORCE_CLKREQ_LOW); RTSX_CLR(sc, RTSX_RTS522A_PM_CTRL3, 0x10); break; case RTSX_RTL8402: case RTSX_RTL8411: RTSX_WRITE(sc, RTSX_SD30_CMD_DRIVE_SEL, sc->rtsx_sd30_drive_sel_3v3); RTSX_BITOP(sc, RTSX_CARD_PAD_CTL, RTSX_CD_DISABLE_MASK | RTSX_CD_AUTO_DISABLE, RTSX_CD_ENABLE); break; case RTSX_RTL8411B: if (sc->rtsx_flags & RTSX_F_8411B_QFN48) RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, 0xf5); RTSX_WRITE(sc, RTSX_SD30_CMD_DRIVE_SEL, sc->rtsx_sd30_drive_sel_3v3); /* Enable SD interrupt. */ RTSX_BITOP(sc, RTSX_CARD_PAD_CTL, RTSX_CD_DISABLE_MASK | RTSX_CD_AUTO_DISABLE, RTSX_CD_ENABLE); /* Clear hw_pfm_en to disable hardware PFM mode. */ RTSX_BITOP(sc, RTSX_FUNC_FORCE_CTL, 0x06, 0x00); break; } /*!!! Added for reboot after Windows. */ rtsx_bus_power_off(sc); rtsx_set_sd_timing(sc, bus_timing_normal); rtsx_set_sd_clock(sc, 0); /*!!! Added for reboot after Windows. */ return (0); } static int rtsx_map_sd_drive(int index) { uint8_t sd_drive[4] = { 0x01, /* Type D */ 0x02, /* Type C */ 0x05, /* Type A */ 0x03 /* Type B */ }; return (sd_drive[index]); } /* For voltage 3v3. */ static int rtsx_rts5227_fill_driving(struct rtsx_softc *sc) { u_char driving_3v3[4][3] = { {0x13, 0x13, 0x13}, {0x96, 0x96, 0x96}, {0x7F, 0x7F, 0x7F}, {0x96, 0x96, 0x96}, }; RTSX_WRITE(sc, RTSX_SD30_CLK_DRIVE_SEL, driving_3v3[sc->rtsx_sd30_drive_sel_3v3][0]); RTSX_WRITE(sc, RTSX_SD30_CMD_DRIVE_SEL, driving_3v3[sc->rtsx_sd30_drive_sel_3v3][1]); RTSX_WRITE(sc, RTSX_SD30_DAT_DRIVE_SEL, driving_3v3[sc->rtsx_sd30_drive_sel_3v3][2]); return (0); } /* For voltage 3v3. */ static int rtsx_rts5249_fill_driving(struct rtsx_softc *sc) { u_char driving_3v3[4][3] = { {0x11, 0x11, 0x18}, {0x55, 0x55, 0x5C}, {0xFF, 0xFF, 0xFF}, {0x96, 0x96, 0x96}, }; RTSX_WRITE(sc, RTSX_SD30_CLK_DRIVE_SEL, driving_3v3[sc->rtsx_sd30_drive_sel_3v3][0]); RTSX_WRITE(sc, RTSX_SD30_CMD_DRIVE_SEL, driving_3v3[sc->rtsx_sd30_drive_sel_3v3][1]); RTSX_WRITE(sc, RTSX_SD30_DAT_DRIVE_SEL, driving_3v3[sc->rtsx_sd30_drive_sel_3v3][2]); return (0); } static int rtsx_rts5260_fill_driving(struct rtsx_softc *sc) { u_char driving_3v3[4][3] = { {0x11, 0x11, 0x11}, {0x22, 0x22, 0x22}, {0x55, 0x55, 0x55}, {0x33, 0x33, 0x33}, }; RTSX_WRITE(sc, RTSX_SD30_CLK_DRIVE_SEL, driving_3v3[sc->rtsx_sd30_drive_sel_3v3][0]); RTSX_WRITE(sc, RTSX_SD30_CMD_DRIVE_SEL, driving_3v3[sc->rtsx_sd30_drive_sel_3v3][1]); RTSX_WRITE(sc, RTSX_SD30_DAT_DRIVE_SEL, driving_3v3[sc->rtsx_sd30_drive_sel_3v3][2]); return (0); } static int rtsx_read(struct rtsx_softc *sc, uint16_t addr, uint8_t *val) { int tries = 1024; uint32_t arg; uint32_t reg; arg = RTSX_HAIMR_BUSY | (uint32_t)((addr & 0x3FFF) << 16); WRITE4(sc, RTSX_HAIMR, arg); while (tries--) { reg = READ4(sc, RTSX_HAIMR); if (!(reg & RTSX_HAIMR_BUSY)) break; } *val = (reg & 0xff); if (tries > 0) { return (0); } else { device_printf(sc->rtsx_dev, "rtsx_read(0x%x) timeout\n", arg); return (ETIMEDOUT); } } static int rtsx_read_cfg(struct rtsx_softc *sc, uint8_t func, uint16_t addr, uint32_t *val) { int tries = 1024; uint8_t data0, data1, data2, data3, rwctl; RTSX_WRITE(sc, RTSX_CFGADDR0, addr); RTSX_WRITE(sc, RTSX_CFGADDR1, addr >> 8); RTSX_WRITE(sc, RTSX_CFGRWCTL, RTSX_CFG_BUSY | (func & 0x03 << 4)); while (tries--) { RTSX_READ(sc, RTSX_CFGRWCTL, &rwctl); if (!(rwctl & RTSX_CFG_BUSY)) break; } if (tries == 0) return (ETIMEDOUT); RTSX_READ(sc, RTSX_CFGDATA0, &data0); RTSX_READ(sc, RTSX_CFGDATA1, &data1); RTSX_READ(sc, RTSX_CFGDATA2, &data2); RTSX_READ(sc, RTSX_CFGDATA3, &data3); *val = (data3 << 24) | (data2 << 16) | (data1 << 8) | data0; return (0); } static int rtsx_write(struct rtsx_softc *sc, uint16_t addr, uint8_t mask, uint8_t val) { int tries = 1024; uint32_t arg; uint32_t reg; arg = RTSX_HAIMR_BUSY | RTSX_HAIMR_WRITE | (uint32_t)(((addr & 0x3FFF) << 16) | (mask << 8) | val); WRITE4(sc, RTSX_HAIMR, arg); while (tries--) { reg = READ4(sc, RTSX_HAIMR); if (!(reg & RTSX_HAIMR_BUSY)) { if (val != (reg & 0xff)) { device_printf(sc->rtsx_dev, "rtsx_write(0x%x) error reg=0x%x\n", arg, reg); return (EIO); } return (0); } } device_printf(sc->rtsx_dev, "rtsx_write(0x%x) timeout\n", arg); return (ETIMEDOUT); } static int rtsx_read_phy(struct rtsx_softc *sc, uint8_t addr, uint16_t *val) { int tries = 100000; uint8_t data0, data1, rwctl; RTSX_WRITE(sc, RTSX_PHY_ADDR, addr); RTSX_WRITE(sc, RTSX_PHY_RWCTL, RTSX_PHY_BUSY | RTSX_PHY_READ); while (tries--) { RTSX_READ(sc, RTSX_PHY_RWCTL, &rwctl); if (!(rwctl & RTSX_PHY_BUSY)) break; } if (tries == 0) return (ETIMEDOUT); RTSX_READ(sc, RTSX_PHY_DATA0, &data0); RTSX_READ(sc, RTSX_PHY_DATA1, &data1); *val = data1 << 8 | data0; return (0); } static int rtsx_write_phy(struct rtsx_softc *sc, uint8_t addr, uint16_t val) { int tries = 100000; uint8_t rwctl; RTSX_WRITE(sc, RTSX_PHY_DATA0, val); RTSX_WRITE(sc, RTSX_PHY_DATA1, val >> 8); RTSX_WRITE(sc, RTSX_PHY_ADDR, addr); RTSX_WRITE(sc, RTSX_PHY_RWCTL, RTSX_PHY_BUSY | RTSX_PHY_WRITE); while (tries--) { RTSX_READ(sc, RTSX_PHY_RWCTL, &rwctl); if (!(rwctl & RTSX_PHY_BUSY)) break; } return ((tries == 0) ? ETIMEDOUT : 0); } /* * Notice that the meaning of RTSX_PWR_GATE_CTRL changes between RTS5209 and * RTS5229. In RTS5209 it is a mask of disabled power gates, while in RTS5229 * it is a mask of *enabled* gates. */ static int rtsx_bus_power_off(struct rtsx_softc *sc) { if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_bus_power_off()\n"); /* Disable SD clock. */ RTSX_CLR(sc, RTSX_CARD_CLK_EN, RTSX_SD_CLK_EN); /* Disable SD output. */ RTSX_CLR(sc, RTSX_CARD_OE, RTSX_SD_OUTPUT_EN); /* Turn off power. */ switch (sc->rtsx_device_id) { case RTSX_RTS5209: RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK | RTSX_PMOS_STRG_MASK, RTSX_SD_PWR_OFF | RTSX_PMOS_STRG_400mA); RTSX_SET(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_OFF); break; case RTSX_RTS5227: case RTSX_RTS5229: case RTSX_RTS522A: RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK | RTSX_PMOS_STRG_MASK, RTSX_SD_PWR_OFF | RTSX_PMOS_STRG_400mA); RTSX_CLR(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK); break; case RTSX_RTS5260: rtsx_stop_cmd(sc); /* Switch vccq to 330 */ RTSX_BITOP(sc, RTSX_LDO_CONFIG2, RTSX_DV331812_VDD1, RTSX_DV331812_VDD1); RTSX_BITOP(sc, RTSX_LDO_DV18_CFG, RTSX_DV331812_MASK, RTSX_DV331812_33); RTSX_CLR(sc, RTSX_SD_PAD_CTL, RTSX_SD_IO_USING_1V8); rtsx_rts5260_fill_driving(sc); RTSX_BITOP(sc, RTSX_LDO_VCC_CFG1, RTSX_LDO_POW_SDVDD1_MASK, RTSX_LDO_POW_SDVDD1_OFF); RTSX_BITOP(sc, RTSX_LDO_CONFIG2, RTSX_DV331812_POWERON, RTSX_DV331812_POWEROFF); break; case RTSX_RTL8402: case RTSX_RTL8411: case RTSX_RTL8411B: RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_BPP_POWER_MASK, RTSX_BPP_POWER_OFF); RTSX_BITOP(sc, RTSX_LDO_CTL, RTSX_BPP_LDO_POWB, RTSX_BPP_LDO_SUSPEND); break; default: RTSX_CLR(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK); RTSX_SET(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_OFF); RTSX_CLR(sc, RTSX_CARD_PWR_CTL, RTSX_PMOS_STRG_800mA); break; } /* Disable pull control. */ switch (sc->rtsx_device_id) { case RTSX_RTS5209: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL1, RTSX_PULL_CTL_DISABLE12); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, RTSX_PULL_CTL_DISABLE12); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_DISABLE3); break; case RTSX_RTS5227: case RTSX_RTS522A: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, RTSX_PULL_CTL_DISABLE12); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_DISABLE3); break; case RTSX_RTS5229: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, RTSX_PULL_CTL_DISABLE12); if (sc->rtsx_flags & RTSX_F_VERSION_C) RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_DISABLE3_TYPE_C); else RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_DISABLE3); break; case RTSX_RTS525A: case RTSX_RTS5249: case RTSX_RTS5260: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL1, 0x66); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, RTSX_PULL_CTL_DISABLE12); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_DISABLE3); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL4, 0x55); break; case RTSX_RTL8402: case RTSX_RTL8411: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL1, 0x65); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, 0x55); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, 0x95); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL4, 0x09); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL5, 0x05); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL6, 0x04); break; case RTSX_RTL8411B: if (sc->rtsx_flags & RTSX_F_8411B_QFN48) { RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, 0x55); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, 0xf5); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL6, 0x15); } else { RTSX_WRITE(sc, RTSX_CARD_PULL_CTL1, 0x65); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, 0x55); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, 0xd5); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL4, 0x59); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL5, 0x55); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL6, 0x15); } break; } return (0); } static int rtsx_bus_power_on(struct rtsx_softc *sc) { if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_bus_power_on()\n"); /* Select SD card. */ RTSX_BITOP(sc, RTSX_CARD_SELECT, 0x07, RTSX_SD_MOD_SEL); RTSX_BITOP(sc, RTSX_CARD_SHARE_MODE, RTSX_CARD_SHARE_MASK, RTSX_CARD_SHARE_48_SD); /* Enable SD clock. */ RTSX_BITOP(sc, RTSX_CARD_CLK_EN, RTSX_SD_CLK_EN, RTSX_SD_CLK_EN); /* Enable pull control. */ switch (sc->rtsx_device_id) { case RTSX_RTS5209: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL1, RTSX_PULL_CTL_ENABLE12); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, RTSX_PULL_CTL_ENABLE12); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_ENABLE3); break; case RTSX_RTS5227: case RTSX_RTS522A: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, RTSX_PULL_CTL_ENABLE12); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_ENABLE3); break; case RTSX_RTS5229: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, RTSX_PULL_CTL_ENABLE12); if (sc->rtsx_flags & RTSX_F_VERSION_C) RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_ENABLE3_TYPE_C); else RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_ENABLE3); break; case RTSX_RTS525A: case RTSX_RTS5249: case RTSX_RTS5260: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL1, 0x66); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, RTSX_PULL_CTL_ENABLE12); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, RTSX_PULL_CTL_ENABLE3); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL4, 0xaa); break; case RTSX_RTL8402: case RTSX_RTL8411: RTSX_WRITE(sc, RTSX_CARD_PULL_CTL1, 0xaa); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, 0xaa); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, 0xa9); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL4, 0x09); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL5, 0x09); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL6, 0x04); break; case RTSX_RTL8411B: if (sc->rtsx_flags & RTSX_F_8411B_QFN48) { RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, 0xaa); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, 0xf9); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL6, 0x19); } else { RTSX_WRITE(sc, RTSX_CARD_PULL_CTL1, 0xaa); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL2, 0xaa); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL3, 0xd9); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL4, 0x59); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL5, 0x55); RTSX_WRITE(sc, RTSX_CARD_PULL_CTL6, 0x15); } break; } /* * To avoid a current peak, enable card power in two phases * with a delay in between. */ switch (sc->rtsx_device_id) { case RTSX_RTS5209: /* Partial power. */ RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK, RTSX_SD_PARTIAL_PWR_ON); RTSX_BITOP(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK, RTSX_LDO3318_VCC2); DELAY(200); /* Full power. */ RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK, RTSX_SD_PWR_ON); RTSX_BITOP(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK, RTSX_LDO3318_ON); break; case RTSX_RTS5227: case RTSX_RTS522A: /* Partial power. */ RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK, RTSX_SD_PARTIAL_PWR_ON); RTSX_BITOP(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK, RTSX_LDO3318_VCC1); DELAY(20000); /* Full power. */ RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK, RTSX_SD_PWR_ON); RTSX_BITOP(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK, RTSX_LDO3318_VCC1 | RTSX_LDO3318_VCC2); RTSX_BITOP(sc, RTSX_CARD_OE, RTSX_SD_OUTPUT_EN, RTSX_SD_OUTPUT_EN); RTSX_BITOP(sc, RTSX_CARD_OE, RTSX_MS_OUTPUT_EN, RTSX_MS_OUTPUT_EN); break; case RTSX_RTS5229: /* Partial power. */ RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK, RTSX_SD_PARTIAL_PWR_ON); RTSX_BITOP(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK, RTSX_LDO3318_VCC1); DELAY(200); /* Full power. */ RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK, RTSX_SD_PWR_ON); RTSX_BITOP(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK, RTSX_LDO3318_VCC1 | RTSX_LDO3318_VCC2); break; case RTSX_RTS525A: RTSX_BITOP(sc, RTSX_LDO_VCC_CFG1, RTSX_LDO_VCC_TUNE_MASK, RTSX_LDO_VCC_3V3); case RTSX_RTS5249: /* Partial power. */ RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK, RTSX_SD_PARTIAL_PWR_ON); RTSX_BITOP(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK, RTSX_LDO3318_VCC1); DELAY(5000); /* Full power. */ RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_SD_PWR_MASK, RTSX_SD_PWR_ON); RTSX_BITOP(sc, RTSX_PWR_GATE_CTRL, RTSX_LDO3318_PWR_MASK, RTSX_LDO3318_VCC1 | RTSX_LDO3318_VCC2); break; case RTSX_RTS5260: RTSX_BITOP(sc, RTSX_LDO_CONFIG2, RTSX_DV331812_VDD1, RTSX_DV331812_VDD1); RTSX_BITOP(sc, RTSX_LDO_VCC_CFG0, RTSX_RTS5260_DVCC_TUNE_MASK, RTSX_RTS5260_DVCC_33); RTSX_BITOP(sc, RTSX_LDO_VCC_CFG1, RTSX_LDO_POW_SDVDD1_MASK, RTSX_LDO_POW_SDVDD1_ON); RTSX_BITOP(sc, RTSX_LDO_CONFIG2, RTSX_DV331812_POWERON, RTSX_DV331812_POWERON); DELAY(20000); RTSX_BITOP(sc, RTSX_SD_CFG1, RTSX_SD_MODE_MASK | RTSX_SD_ASYNC_FIFO_NOT_RST, RTSX_SD30_MODE | RTSX_SD_ASYNC_FIFO_NOT_RST); RTSX_BITOP(sc, RTSX_CLK_CTL, RTSX_CHANGE_CLK, RTSX_CLK_LOW_FREQ); RTSX_WRITE(sc, RTSX_CARD_CLK_SOURCE, RTSX_CRC_VAR_CLK0 | RTSX_SD30_FIX_CLK | RTSX_SAMPLE_VAR_CLK1); RTSX_CLR(sc, RTSX_CLK_CTL, RTSX_CLK_LOW_FREQ); /* Initialize SD_CFG1 register */ RTSX_WRITE(sc, RTSX_SD_CFG1, RTSX_CLK_DIVIDE_128 | RTSX_SD20_MODE); RTSX_WRITE(sc, RTSX_SD_SAMPLE_POINT_CTL, RTSX_SD20_RX_POS_EDGE); RTSX_CLR(sc, RTSX_SD_PUSH_POINT_CTL, 0xff); RTSX_BITOP(sc, RTSX_CARD_STOP, RTSX_SD_STOP | RTSX_SD_CLR_ERR, RTSX_SD_STOP | RTSX_SD_CLR_ERR); /* Reset SD_CFG3 register */ RTSX_CLR(sc, RTSX_SD_CFG3, RTSX_SD30_CLK_END_EN); RTSX_CLR(sc, RTSX_REG_SD_STOP_SDCLK_CFG, RTSX_SD30_CLK_STOP_CFG_EN | RTSX_SD30_CLK_STOP_CFG0 | RTSX_SD30_CLK_STOP_CFG1); RTSX_CLR(sc, RTSX_REG_PRE_RW_MODE, RTSX_EN_INFINITE_MODE); break; case RTSX_RTL8402: case RTSX_RTL8411: case RTSX_RTL8411B: RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_BPP_POWER_MASK, RTSX_BPP_POWER_5_PERCENT_ON); RTSX_BITOP(sc, RTSX_LDO_CTL, RTSX_BPP_LDO_POWB, RTSX_BPP_LDO_SUSPEND); DELAY(150); RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_BPP_POWER_MASK, RTSX_BPP_POWER_10_PERCENT_ON); DELAY(150); RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_BPP_POWER_MASK, RTSX_BPP_POWER_15_PERCENT_ON); DELAY(150); RTSX_BITOP(sc, RTSX_CARD_PWR_CTL, RTSX_BPP_POWER_MASK, RTSX_BPP_POWER_ON); RTSX_BITOP(sc, RTSX_LDO_CTL, RTSX_BPP_LDO_POWB, RTSX_BPP_LDO_ON); break; } /* Enable SD card output. */ RTSX_WRITE(sc, RTSX_CARD_OE, RTSX_SD_OUTPUT_EN); DELAY(200); return (0); } /* * Set but width. */ static int rtsx_set_bus_width(struct rtsx_softc *sc, enum mmc_bus_width width) { uint32_t bus_width; switch (width) { case bus_width_1: bus_width = RTSX_BUS_WIDTH_1; break; case bus_width_4: bus_width = RTSX_BUS_WIDTH_4; break; case bus_width_8: bus_width = RTSX_BUS_WIDTH_8; break; default: return (MMC_ERR_INVALID); } RTSX_BITOP(sc, RTSX_SD_CFG1, RTSX_BUS_WIDTH_MASK, bus_width); if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) { char *busw[] = { "1 bit", "4 bits", "8 bits" }; device_printf(sc->rtsx_dev, "Setting bus width to %s\n", busw[bus_width]); } return (0); } static int rtsx_set_sd_timing(struct rtsx_softc *sc, enum mmc_bus_timing timing) { if (timing == bus_timing_hs && sc->rtsx_force_timing) { timing = bus_timing_uhs_sdr50; sc->rtsx_ios_timing = timing; } if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_sd_timing(%u)\n", timing); switch (timing) { case bus_timing_uhs_sdr50: case bus_timing_uhs_sdr104: sc->rtsx_double_clk = false; sc->rtsx_vpclk = true; RTSX_BITOP(sc, RTSX_SD_CFG1, 0x0c | RTSX_SD_ASYNC_FIFO_NOT_RST, RTSX_SD30_MODE | RTSX_SD_ASYNC_FIFO_NOT_RST); RTSX_BITOP(sc, RTSX_CLK_CTL, RTSX_CLK_LOW_FREQ, RTSX_CLK_LOW_FREQ); RTSX_WRITE(sc, RTSX_CARD_CLK_SOURCE, RTSX_CRC_VAR_CLK0 | RTSX_SD30_FIX_CLK | RTSX_SAMPLE_VAR_CLK1); RTSX_CLR(sc, RTSX_CLK_CTL, RTSX_CLK_LOW_FREQ); break; case bus_timing_hs: RTSX_BITOP(sc, RTSX_SD_CFG1, RTSX_SD_MODE_MASK, RTSX_SD20_MODE); RTSX_BITOP(sc, RTSX_CLK_CTL, RTSX_CLK_LOW_FREQ, RTSX_CLK_LOW_FREQ); RTSX_WRITE(sc, RTSX_CARD_CLK_SOURCE, RTSX_CRC_FIX_CLK | RTSX_SD30_VAR_CLK0 | RTSX_SAMPLE_VAR_CLK1); RTSX_CLR(sc, RTSX_CLK_CTL, RTSX_CLK_LOW_FREQ); RTSX_BITOP(sc, RTSX_SD_PUSH_POINT_CTL, RTSX_SD20_TX_SEL_MASK, RTSX_SD20_TX_14_AHEAD); RTSX_BITOP(sc, RTSX_SD_SAMPLE_POINT_CTL, RTSX_SD20_RX_SEL_MASK, RTSX_SD20_RX_14_DELAY); break; default: RTSX_BITOP(sc, RTSX_SD_CFG1, RTSX_SD_MODE_MASK, RTSX_SD20_MODE); RTSX_BITOP(sc, RTSX_CLK_CTL, RTSX_CLK_LOW_FREQ, RTSX_CLK_LOW_FREQ); RTSX_WRITE(sc, RTSX_CARD_CLK_SOURCE, RTSX_CRC_FIX_CLK | RTSX_SD30_VAR_CLK0 | RTSX_SAMPLE_VAR_CLK1); RTSX_CLR(sc, RTSX_CLK_CTL, RTSX_CLK_LOW_FREQ); RTSX_WRITE(sc, RTSX_SD_PUSH_POINT_CTL, RTSX_SD20_TX_NEG_EDGE); RTSX_BITOP(sc, RTSX_SD_SAMPLE_POINT_CTL, RTSX_SD20_RX_SEL_MASK, RTSX_SD20_RX_POS_EDGE); break; } return (0); } /* * Set or change SDCLK frequency or disable the SD clock. * Return zero on success. */ static int rtsx_set_sd_clock(struct rtsx_softc *sc, uint32_t freq) { uint8_t clk; uint8_t clk_divider, n, div, mcu; int error = 0; if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_sd_clock(%u)\n", freq); if (freq == RTSX_SDCLK_OFF) { error = rtsx_stop_sd_clock(sc); return error; } sc->rtsx_ssc_depth = RTSX_SSC_DEPTH_500K; sc->rtsx_discovery_mode = (freq <= 1000000) ? true : false; if (sc->rtsx_discovery_mode) { /* We use 250k(around) here, in discovery stage. */ clk_divider = RTSX_CLK_DIVIDE_128; freq = 30000000; } else { clk_divider = RTSX_CLK_DIVIDE_0; } RTSX_BITOP(sc, RTSX_SD_CFG1, RTSX_CLK_DIVIDE_MASK, clk_divider); freq /= 1000000; if (sc->rtsx_discovery_mode || !sc->rtsx_double_clk) clk = freq; else clk = freq * 2; switch (sc->rtsx_device_id) { case RTSX_RTL8402: case RTSX_RTL8411: case RTSX_RTL8411B: n = clk * 4 / 5 - 2; break; default: n = clk - 2; break; } if ((clk <= 2) || (n > RTSX_MAX_DIV_N)) return (MMC_ERR_INVALID); mcu = 125 / clk + 3; if (mcu > 15) mcu = 15; /* Make sure that the SSC clock div_n is not less than RTSX_MIN_DIV_N. */ div = RTSX_CLK_DIV_1; while ((n < RTSX_MIN_DIV_N) && (div < RTSX_CLK_DIV_8)) { switch (sc->rtsx_device_id) { case RTSX_RTL8402: case RTSX_RTL8411: case RTSX_RTL8411B: n = (((n + 2) * 5 / 4) * 2) * 4 / 5 - 2; break; default: n = (n + 2) * 2 - 2; break; } div++; } if (sc->rtsx_double_clk && sc->rtsx_ssc_depth > 1) sc->rtsx_ssc_depth -= 1; if (div > RTSX_CLK_DIV_1) { if (sc->rtsx_ssc_depth > (div - 1)) sc->rtsx_ssc_depth -= (div - 1); else sc->rtsx_ssc_depth = RTSX_SSC_DEPTH_4M; } /* Enable SD clock. */ error = rtsx_switch_sd_clock(sc, clk, n, div, mcu); return (error); } static int rtsx_stop_sd_clock(struct rtsx_softc *sc) { RTSX_CLR(sc, RTSX_CARD_CLK_EN, RTSX_CARD_CLK_EN_ALL); RTSX_SET(sc, RTSX_SD_BUS_STAT, RTSX_SD_CLK_FORCE_STOP); return (0); } static int rtsx_switch_sd_clock(struct rtsx_softc *sc, uint8_t clk, uint8_t n, uint8_t div, uint8_t mcu) { if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) { device_printf(sc->rtsx_dev, "rtsx_switch_sd_clock() - discovery-mode is %s, ssc_depth: %d\n", (sc->rtsx_discovery_mode) ? "true" : "false", sc->rtsx_ssc_depth); device_printf(sc->rtsx_dev, "rtsx_switch_sd_clock() - clk: %d, n: %d, div: %d, mcu: %d\n", clk, n, div, mcu); } RTSX_BITOP(sc, RTSX_CLK_CTL, RTSX_CLK_LOW_FREQ, RTSX_CLK_LOW_FREQ); RTSX_WRITE(sc, RTSX_CLK_DIV, (div << 4) | mcu); RTSX_CLR(sc, RTSX_SSC_CTL1, RTSX_RSTB); RTSX_BITOP(sc, RTSX_SSC_CTL2, RTSX_SSC_DEPTH_MASK, sc->rtsx_ssc_depth); RTSX_WRITE(sc, RTSX_SSC_DIV_N_0, n); RTSX_BITOP(sc, RTSX_SSC_CTL1, RTSX_RSTB, RTSX_RSTB); if (sc->rtsx_vpclk) { RTSX_CLR(sc, RTSX_SD_VPCLK0_CTL, RTSX_PHASE_NOT_RESET); RTSX_BITOP(sc, RTSX_SD_VPCLK0_CTL, RTSX_PHASE_NOT_RESET, RTSX_PHASE_NOT_RESET); } /* Wait SSC clock stable. */ DELAY(200); RTSX_CLR(sc, RTSX_CLK_CTL, RTSX_CLK_LOW_FREQ); return (0); } #ifndef MMCCAM static void rtsx_sd_change_tx_phase(struct rtsx_softc *sc, uint8_t sample_point) { if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_sd_change_tx_phase() - sample_point: %d\n", sample_point); rtsx_write(sc, RTSX_CLK_CTL, RTSX_CHANGE_CLK, RTSX_CHANGE_CLK); rtsx_write(sc, RTSX_SD_VPCLK0_CTL, RTSX_PHASE_SELECT_MASK, sample_point); rtsx_write(sc, RTSX_SD_VPCLK0_CTL, RTSX_PHASE_NOT_RESET, 0); rtsx_write(sc, RTSX_SD_VPCLK0_CTL, RTSX_PHASE_NOT_RESET, RTSX_PHASE_NOT_RESET); rtsx_write(sc, RTSX_CLK_CTL, RTSX_CHANGE_CLK, 0); rtsx_write(sc, RTSX_SD_CFG1, RTSX_SD_ASYNC_FIFO_NOT_RST, 0); } static void rtsx_sd_change_rx_phase(struct rtsx_softc *sc, uint8_t sample_point) { if (sc->rtsx_debug_mask & RTSX_DEBUG_TUNING) device_printf(sc->rtsx_dev, "rtsx_sd_change_rx_phase() - sample_point: %d\n", sample_point); rtsx_write(sc, RTSX_CLK_CTL, RTSX_CHANGE_CLK, RTSX_CHANGE_CLK); rtsx_write(sc, RTSX_SD_VPCLK1_CTL, RTSX_PHASE_SELECT_MASK, sample_point); rtsx_write(sc, RTSX_SD_VPCLK1_CTL, RTSX_PHASE_NOT_RESET, 0); rtsx_write(sc, RTSX_SD_VPCLK1_CTL, RTSX_PHASE_NOT_RESET, RTSX_PHASE_NOT_RESET); rtsx_write(sc, RTSX_CLK_CTL, RTSX_CHANGE_CLK, 0); rtsx_write(sc, RTSX_SD_CFG1, RTSX_SD_ASYNC_FIFO_NOT_RST, 0); } static void rtsx_sd_tuning_rx_phase(struct rtsx_softc *sc, uint32_t *phase_map) { uint32_t raw_phase_map = 0; int i; int error; for (i = 0; i < RTSX_RX_PHASE_MAX; i++) { error = rtsx_sd_tuning_rx_cmd(sc, (uint8_t)i); if (error == 0) raw_phase_map |= 1 << i; } if (phase_map != NULL) *phase_map = raw_phase_map; } static int rtsx_sd_tuning_rx_cmd(struct rtsx_softc *sc, uint8_t sample_point) { struct mmc_request req = {}; struct mmc_command cmd = {}; int error = 0; cmd.opcode = MMC_SEND_TUNING_BLOCK; cmd.arg = 0; req.cmd = &cmd; RTSX_LOCK(sc); sc->rtsx_req = &req; rtsx_sd_change_rx_phase(sc, sample_point); rtsx_write(sc, RTSX_SD_CFG3, RTSX_SD_RSP_80CLK_TIMEOUT_EN, RTSX_SD_RSP_80CLK_TIMEOUT_EN); rtsx_init_cmd(sc, &cmd); rtsx_set_cmd_data_len(sc, 1, 0x40); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CFG2, 0xff, RTSX_SD_CALCULATE_CRC7 | RTSX_SD_CHECK_CRC16 | RTSX_SD_NO_WAIT_BUSY_END | RTSX_SD_CHECK_CRC7 | RTSX_SD_RSP_LEN_6); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_TRANSFER, 0xff, RTSX_TM_AUTO_TUNING | RTSX_SD_TRANSFER_START); rtsx_push_cmd(sc, RTSX_CHECK_REG_CMD, RTSX_SD_TRANSFER, RTSX_SD_TRANSFER_END, RTSX_SD_TRANSFER_END); /* Set interrupt post processing */ sc->rtsx_intr_trans_ok = rtsx_sd_tuning_rx_cmd_wakeup; sc->rtsx_intr_trans_ko = rtsx_sd_tuning_rx_cmd_wakeup; /* Run the command queue. */ rtsx_send_cmd(sc); error = rtsx_sd_tuning_rx_cmd_wait(sc, &cmd); if (error) { if (sc->rtsx_debug_mask & RTSX_DEBUG_TUNING) device_printf(sc->rtsx_dev, "rtsx_sd_tuning_rx_cmd() - error: %d\n", error); rtsx_sd_wait_data_idle(sc); rtsx_clear_error(sc); } rtsx_write(sc, RTSX_SD_CFG3, RTSX_SD_RSP_80CLK_TIMEOUT_EN, 0); sc->rtsx_req = NULL; RTSX_UNLOCK(sc); return (error); } static int rtsx_sd_tuning_rx_cmd_wait(struct rtsx_softc *sc, struct mmc_command *cmd) { int status; int mask = RTSX_TRANS_OK_INT | RTSX_TRANS_FAIL_INT; status = sc->rtsx_intr_status & mask; while (status == 0) { if (msleep(&sc->rtsx_intr_status, &sc->rtsx_mtx, 0, "rtsxintr", sc->rtsx_timeout_cmd) == EWOULDBLOCK) { cmd->error = MMC_ERR_TIMEOUT; return (MMC_ERR_TIMEOUT); } status = sc->rtsx_intr_status & mask; } return (cmd->error); } static void rtsx_sd_tuning_rx_cmd_wakeup(struct rtsx_softc *sc) { wakeup(&sc->rtsx_intr_status); } static void rtsx_sd_wait_data_idle(struct rtsx_softc *sc) { int i; uint8_t val; for (i = 0; i < 100; i++) { rtsx_read(sc, RTSX_SD_DATA_STATE, &val); if (val & RTSX_SD_DATA_IDLE) return; DELAY(100); } } static uint8_t rtsx_sd_search_final_rx_phase(struct rtsx_softc *sc, uint32_t phase_map) { int start = 0, len = 0; int start_final = 0, len_final = 0; uint8_t final_phase = 0xff; while (start < RTSX_RX_PHASE_MAX) { len = rtsx_sd_get_rx_phase_len(phase_map, start); if (len_final < len) { start_final = start; len_final = len; } start += len ? len : 1; } final_phase = (start_final + len_final / 2) % RTSX_RX_PHASE_MAX; if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_sd_search_final_rx_phase() - phase_map: %x, start_final: %d, len_final: %d, final_phase: %d\n", phase_map, start_final, len_final, final_phase); return final_phase; } static int rtsx_sd_get_rx_phase_len(uint32_t phase_map, int start_bit) { int i; for (i = 0; i < RTSX_RX_PHASE_MAX; i++) { if ((phase_map & (1 << (start_bit + i) % RTSX_RX_PHASE_MAX)) == 0) return i; } return RTSX_RX_PHASE_MAX; } #endif /* !MMCCAM */ #if 0 /* For led */ static int rtsx_led_enable(struct rtsx_softc *sc) { switch (sc->rtsx_device_id) { case RTSX_RTS5209: RTSX_CLR(sc, RTSX_CARD_GPIO, RTSX_CARD_GPIO_LED_OFF); RTSX_WRITE(sc, RTSX_CARD_AUTO_BLINK, RTSX_LED_BLINK_EN | RTSX_LED_BLINK_SPEED); break; case RTSX_RTL8411B: RTSX_CLR(sc, RTSX_GPIO_CTL, 0x01); RTSX_WRITE(sc, RTSX_CARD_AUTO_BLINK, RTSX_LED_BLINK_EN | RTSX_LED_BLINK_SPEED); break; default: RTSX_SET(sc, RTSX_GPIO_CTL, RTSX_GPIO_LED_ON); RTSX_SET(sc, RTSX_OLT_LED_CTL, RTSX_OLT_LED_AUTOBLINK); break; } return (0); } static int rtsx_led_disable(struct rtsx_softc *sc) { switch (sc->rtsx_device_id) { case RTSX_RTS5209: RTSX_CLR(sc, RTSX_CARD_AUTO_BLINK, RTSX_LED_BLINK_EN); RTSX_WRITE(sc, RTSX_CARD_GPIO, RTSX_CARD_GPIO_LED_OFF); break; case RTSX_RTL8411B: RTSX_CLR(sc, RTSX_CARD_AUTO_BLINK, RTSX_LED_BLINK_EN); RTSX_SET(sc, RTSX_GPIO_CTL, 0x01); break; default: RTSX_CLR(sc, RTSX_OLT_LED_CTL, RTSX_OLT_LED_AUTOBLINK); RTSX_CLR(sc, RTSX_GPIO_CTL, RTSX_GPIO_LED_ON); break; } return (0); } #endif /* For led */ static uint8_t rtsx_response_type(uint16_t mmc_rsp) { int i; struct rsp_type { uint16_t mmc_rsp; uint8_t rtsx_rsp; } rsp_types[] = { { MMC_RSP_NONE, RTSX_SD_RSP_TYPE_R0 }, { MMC_RSP_R1, RTSX_SD_RSP_TYPE_R1 }, { MMC_RSP_R1B, RTSX_SD_RSP_TYPE_R1B }, { MMC_RSP_R2, RTSX_SD_RSP_TYPE_R2 }, { MMC_RSP_R3, RTSX_SD_RSP_TYPE_R3 }, { MMC_RSP_R4, RTSX_SD_RSP_TYPE_R4 }, { MMC_RSP_R5, RTSX_SD_RSP_TYPE_R5 }, { MMC_RSP_R6, RTSX_SD_RSP_TYPE_R6 }, { MMC_RSP_R7, RTSX_SD_RSP_TYPE_R7 } }; for (i = 0; i < nitems(rsp_types); i++) { if (mmc_rsp == rsp_types[i].mmc_rsp) return (rsp_types[i].rtsx_rsp); } return (0); } /* * Init command buffer with SD command index and argument. */ static void rtsx_init_cmd(struct rtsx_softc *sc, struct mmc_command *cmd) { sc->rtsx_cmd_index = 0; rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CMD0, 0xff, RTSX_SD_CMD_START | cmd->opcode); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CMD1, 0xff, cmd->arg >> 24); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CMD2, 0xff, cmd->arg >> 16); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CMD3, 0xff, cmd->arg >> 8); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CMD4, 0xff, cmd->arg); } /* * Append a properly encoded host command to the host command buffer. */ static void rtsx_push_cmd(struct rtsx_softc *sc, uint8_t cmd, uint16_t reg, uint8_t mask, uint8_t data) { KASSERT(sc->rtsx_cmd_index < RTSX_HOSTCMD_MAX, ("rtsx: Too many host commands (%d)\n", sc->rtsx_cmd_index)); uint32_t *cmd_buffer = (uint32_t *)(sc->rtsx_cmd_dmamem); cmd_buffer[sc->rtsx_cmd_index++] = htole32((uint32_t)(cmd & 0x3) << 30) | ((uint32_t)(reg & 0x3fff) << 16) | ((uint32_t)(mask) << 8) | ((uint32_t)data); } /* * Queue commands to configure data transfer size. */ static void rtsx_set_cmd_data_len(struct rtsx_softc *sc, uint16_t block_cnt, uint16_t byte_cnt) { rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_BLOCK_CNT_L, 0xff, block_cnt & 0xff); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_BLOCK_CNT_H, 0xff, block_cnt >> 8); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_BYTE_CNT_L, 0xff, byte_cnt & 0xff); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_BYTE_CNT_H, 0xff, byte_cnt >> 8); } /* * Run the command queue. */ static void rtsx_send_cmd(struct rtsx_softc *sc) { if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "rtsx_send_cmd()\n"); sc->rtsx_intr_status = 0; /* Sync command DMA buffer. */ bus_dmamap_sync(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap, BUS_DMASYNC_PREREAD); bus_dmamap_sync(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap, BUS_DMASYNC_PREWRITE); /* Tell the chip where the command buffer is and run the commands. */ WRITE4(sc, RTSX_HCBAR, (uint32_t)sc->rtsx_cmd_buffer); WRITE4(sc, RTSX_HCBCTLR, ((sc->rtsx_cmd_index * 4) & 0x00ffffff) | RTSX_START_CMD | RTSX_HW_AUTO_RSP); } /* * Stop previous command. */ static void rtsx_stop_cmd(struct rtsx_softc *sc) { /* Stop command transfer. */ WRITE4(sc, RTSX_HCBCTLR, RTSX_STOP_CMD); /* Stop DMA transfer. */ WRITE4(sc, RTSX_HDBCTLR, RTSX_STOP_DMA); switch (sc->rtsx_device_id) { case RTSX_RTS5260: rtsx_write(sc, RTSX_RTS5260_DMA_RST_CTL_0, RTSX_RTS5260_DMA_RST | RTSX_RTS5260_ADMA3_RST, RTSX_RTS5260_DMA_RST | RTSX_RTS5260_ADMA3_RST); rtsx_write(sc, RTSX_RBCTL, RTSX_RB_FLUSH, RTSX_RB_FLUSH); break; default: rtsx_write(sc, RTSX_DMACTL, RTSX_DMA_RST, RTSX_DMA_RST); rtsx_write(sc, RTSX_RBCTL, RTSX_RB_FLUSH, RTSX_RB_FLUSH); break; } } /* * Clear error. */ static void rtsx_clear_error(struct rtsx_softc *sc) { /* Clear error. */ rtsx_write(sc, RTSX_CARD_STOP, RTSX_SD_STOP | RTSX_SD_CLR_ERR, RTSX_SD_STOP | RTSX_SD_CLR_ERR); } /* * Signal end of request to mmc/mmcsd. */ static void rtsx_req_done(struct rtsx_softc *sc) { #ifdef MMCCAM union ccb *ccb; #endif /* MMCCAM */ struct mmc_request *req; req = sc->rtsx_req; if (req->cmd->error == MMC_ERR_NONE) { if (req->cmd->opcode == MMC_READ_SINGLE_BLOCK || req->cmd->opcode == MMC_READ_MULTIPLE_BLOCK) sc->rtsx_read_count++; else if (req->cmd->opcode == MMC_WRITE_BLOCK || req->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) sc->rtsx_write_count++; } else { rtsx_clear_error(sc); } callout_stop(&sc->rtsx_timeout_callout); sc->rtsx_req = NULL; #ifdef MMCCAM ccb = sc->rtsx_ccb; sc->rtsx_ccb = NULL; ccb->ccb_h.status = (req->cmd->error == 0 ? CAM_REQ_CMP : CAM_REQ_CMP_ERR); xpt_done(ccb); #else /* !MMCCAM */ req->done(req); #endif /* MMCCAM */ } /* * Send request. */ static int rtsx_send_req(struct rtsx_softc *sc, struct mmc_command *cmd) { uint8_t rsp_type; uint16_t reg; if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "rtsx_send_req() - CMD%d\n", cmd->opcode); /* Convert response type. */ rsp_type = rtsx_response_type(cmd->flags & MMC_RSP_MASK); if (rsp_type == 0) { device_printf(sc->rtsx_dev, "Unknown rsp_type: 0x%lx\n", (cmd->flags & MMC_RSP_MASK)); cmd->error = MMC_ERR_INVALID; return (MMC_ERR_INVALID); } rtsx_init_cmd(sc, cmd); /* Queue command to set response type. */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CFG2, 0xff, rsp_type); /* Use the ping-pong buffer (cmd buffer) for commands which do not transfer data. */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_CARD_DATA_SOURCE, 0x01, RTSX_PINGPONG_BUFFER); /* Queue commands to perform SD transfer. */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_TRANSFER, 0xff, RTSX_TM_CMD_RSP | RTSX_SD_TRANSFER_START); rtsx_push_cmd(sc, RTSX_CHECK_REG_CMD, RTSX_SD_TRANSFER, RTSX_SD_TRANSFER_END|RTSX_SD_STAT_IDLE, RTSX_SD_TRANSFER_END|RTSX_SD_STAT_IDLE); /* If needed queue commands to read back card status response. */ if (rsp_type == RTSX_SD_RSP_TYPE_R2) { /* Read data from ping-pong buffer. */ for (reg = RTSX_PPBUF_BASE2; reg < RTSX_PPBUF_BASE2 + 16; reg++) rtsx_push_cmd(sc, RTSX_READ_REG_CMD, reg, 0, 0); } else if (rsp_type != RTSX_SD_RSP_TYPE_R0) { /* Read data from SD_CMDx registers. */ for (reg = RTSX_SD_CMD0; reg <= RTSX_SD_CMD4; reg++) rtsx_push_cmd(sc, RTSX_READ_REG_CMD, reg, 0, 0); } rtsx_push_cmd(sc, RTSX_READ_REG_CMD, RTSX_SD_STAT1, 0, 0); /* Set transfer OK function. */ if (sc->rtsx_intr_trans_ok == NULL) sc->rtsx_intr_trans_ok = rtsx_ret_resp; /* Run the command queue. */ rtsx_send_cmd(sc); return (0); } /* * Return response of previous command (case cmd->data == NULL) and complete resquest. * This Function is called by the interrupt handler via sc->rtsx_intr_trans_ok. */ static void rtsx_ret_resp(struct rtsx_softc *sc) { struct mmc_command *cmd; cmd = sc->rtsx_req->cmd; rtsx_set_resp(sc, cmd); rtsx_req_done(sc); } /* * Set response of previous command. */ static void rtsx_set_resp(struct rtsx_softc *sc, struct mmc_command *cmd) { uint8_t rsp_type; rsp_type = rtsx_response_type(cmd->flags & MMC_RSP_MASK); /* Sync command DMA buffer. */ bus_dmamap_sync(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_sync(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap, BUS_DMASYNC_POSTWRITE); /* Copy card response into mmc response buffer. */ if (ISSET(cmd->flags, MMC_RSP_PRESENT)) { uint32_t *cmd_buffer = (uint32_t *)(sc->rtsx_cmd_dmamem); if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) { device_printf(sc->rtsx_dev, "cmd_buffer: 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", cmd_buffer[0], cmd_buffer[1], cmd_buffer[2], cmd_buffer[3], cmd_buffer[4]); } if (rsp_type == RTSX_SD_RSP_TYPE_R2) { /* First byte is CHECK_REG_CMD return value, skip it. */ unsigned char *ptr = (unsigned char *)cmd_buffer + 1; int i; /* * The controller offloads the last byte {CRC-7, end bit 1} * of response type R2. Assign dummy CRC, 0, and end bit to this * byte (ptr[16], goes into the LSB of resp[3] later). */ ptr[16] = 0x01; /* The second byte is the status of response, skip it. */ for (i = 0; i < 4; i++) cmd->resp[i] = be32dec(ptr + 1 + i * 4); } else { /* * First byte is CHECK_REG_CMD return value, second * one is the command op code -- we skip those. */ cmd->resp[0] = ((be32toh(cmd_buffer[0]) & 0x0000ffff) << 16) | ((be32toh(cmd_buffer[1]) & 0xffff0000) >> 16); } if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "cmd->resp: 0x%08x 0x%08x 0x%08x 0x%08x\n", cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]); } } /* * Use the ping-pong buffer (cmd buffer) for transfer <= 512 bytes. */ static int rtsx_xfer_short(struct rtsx_softc *sc, struct mmc_command *cmd) { int read; if (cmd->data == NULL || cmd->data->len == 0) { cmd->error = MMC_ERR_INVALID; return (MMC_ERR_INVALID); } cmd->data->xfer_len = (cmd->data->len > RTSX_MAX_DATA_BLKLEN) ? RTSX_MAX_DATA_BLKLEN : cmd->data->len; read = ISSET(cmd->data->flags, MMC_DATA_READ); if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "rtsx_xfer_short() - %s xfer: %ld bytes with block size %ld\n", read ? "Read" : "Write", (unsigned long)cmd->data->len, (unsigned long)cmd->data->xfer_len); if (cmd->data->len > 512) { device_printf(sc->rtsx_dev, "rtsx_xfer_short() - length too large: %ld > 512\n", (unsigned long)cmd->data->len); cmd->error = MMC_ERR_INVALID; return (MMC_ERR_INVALID); } if (read) { if (sc->rtsx_discovery_mode) rtsx_write(sc, RTSX_SD_CFG1, RTSX_CLK_DIVIDE_MASK, RTSX_CLK_DIVIDE_0); rtsx_init_cmd(sc, cmd); /* Queue commands to configure data transfer size. */ rtsx_set_cmd_data_len(sc, cmd->data->len / cmd->data->xfer_len, cmd->data->xfer_len); /* From Linux: rtsx_pci_sdmmc.c sd_read_data(). */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CFG2, 0xff, RTSX_SD_CALCULATE_CRC7 | RTSX_SD_CHECK_CRC16 | RTSX_SD_NO_WAIT_BUSY_END | RTSX_SD_CHECK_CRC7 | RTSX_SD_RSP_LEN_6); /* Use the ping-pong buffer (cmd buffer). */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_CARD_DATA_SOURCE, 0x01, RTSX_PINGPONG_BUFFER); /* Queue commands to perform SD transfer. */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_TRANSFER, 0xff, RTSX_TM_NORMAL_READ | RTSX_SD_TRANSFER_START); rtsx_push_cmd(sc, RTSX_CHECK_REG_CMD, RTSX_SD_TRANSFER, RTSX_SD_TRANSFER_END, RTSX_SD_TRANSFER_END); /* Set transfer OK function. */ sc->rtsx_intr_trans_ok = rtsx_ask_ppbuf_part1; /* Run the command queue. */ rtsx_send_cmd(sc); } else { /* Set transfer OK function. */ sc->rtsx_intr_trans_ok = rtsx_put_ppbuf_part1; /* Run the command queue. */ rtsx_send_req(sc, cmd); } return (0); } /* * Use the ping-pong buffer (cmd buffer) for the transfer - first part <= 256 bytes. * This Function is called by the interrupt handler via sc->rtsx_intr_trans_ok. */ static void rtsx_ask_ppbuf_part1(struct rtsx_softc *sc) { struct mmc_command *cmd; uint16_t reg = RTSX_PPBUF_BASE2; int len; int i; cmd = sc->rtsx_req->cmd; len = (cmd->data->len > RTSX_HOSTCMD_MAX) ? RTSX_HOSTCMD_MAX : cmd->data->len; sc->rtsx_cmd_index = 0; for (i = 0; i < len; i++) { rtsx_push_cmd(sc, RTSX_READ_REG_CMD, reg++, 0, 0); } /* Set transfer OK function. */ sc->rtsx_intr_trans_ok = rtsx_get_ppbuf_part1; /* Run the command queue. */ rtsx_send_cmd(sc); } /* * Get the data from the ping-pong buffer (cmd buffer) - first part <= 256 bytes. * This Function is called by the interrupt handler via sc->rtsx_intr_trans_ok. */ static void rtsx_get_ppbuf_part1(struct rtsx_softc *sc) { struct mmc_command *cmd; uint8_t *ptr; int len; cmd = sc->rtsx_req->cmd; ptr = cmd->data->data; len = (cmd->data->len > RTSX_HOSTCMD_MAX) ? RTSX_HOSTCMD_MAX : cmd->data->len; /* Sync command DMA buffer. */ bus_dmamap_sync(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_sync(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap, BUS_DMASYNC_POSTWRITE); memcpy(ptr, sc->rtsx_cmd_dmamem, len); len = (cmd->data->len > RTSX_HOSTCMD_MAX) ? cmd->data->len - RTSX_HOSTCMD_MAX : 0; /* Use the ping-pong buffer (cmd buffer) for the transfer - second part > 256 bytes. */ if (len > 0) { uint16_t reg = RTSX_PPBUF_BASE2 + RTSX_HOSTCMD_MAX; int i; sc->rtsx_cmd_index = 0; for (i = 0; i < len; i++) { rtsx_push_cmd(sc, RTSX_READ_REG_CMD, reg++, 0, 0); } /* Set transfer OK function. */ sc->rtsx_intr_trans_ok = rtsx_get_ppbuf_part2; /* Run the command queue. */ rtsx_send_cmd(sc); } else { if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD && cmd->opcode == ACMD_SEND_SCR) { uint8_t *ptr = cmd->data->data; device_printf(sc->rtsx_dev, "SCR: 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7]); } if (sc->rtsx_discovery_mode) rtsx_write(sc, RTSX_SD_CFG1, RTSX_CLK_DIVIDE_MASK, RTSX_CLK_DIVIDE_128); rtsx_req_done(sc); } } /* * Get the data from the ping-pong buffer (cmd buffer) - second part > 256 bytes. * This Function is called by the interrupt handler via sc->rtsx_intr_trans_ok. */ static void rtsx_get_ppbuf_part2(struct rtsx_softc *sc) { struct mmc_command *cmd; uint8_t *ptr; int len; cmd = sc->rtsx_req->cmd; ptr = cmd->data->data; ptr += RTSX_HOSTCMD_MAX; len = cmd->data->len - RTSX_HOSTCMD_MAX; /* Sync command DMA buffer. */ bus_dmamap_sync(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_sync(sc->rtsx_cmd_dma_tag, sc->rtsx_cmd_dmamap, BUS_DMASYNC_POSTWRITE); memcpy(ptr, sc->rtsx_cmd_dmamem, len); if (sc->rtsx_discovery_mode) rtsx_write(sc, RTSX_SD_CFG1, RTSX_CLK_DIVIDE_MASK, RTSX_CLK_DIVIDE_128); rtsx_req_done(sc); } /* * Use the ping-pong buffer (cmd buffer) for transfer - first part <= 256 bytes. * This Function is called by the interrupt handler via sc->rtsx_intr_trans_ok. */ static void rtsx_put_ppbuf_part1(struct rtsx_softc *sc) { struct mmc_command *cmd; uint16_t reg = RTSX_PPBUF_BASE2; uint8_t *ptr; int len; int i; cmd = sc->rtsx_req->cmd; ptr = cmd->data->data; len = (cmd->data->len > RTSX_HOSTCMD_MAX) ? RTSX_HOSTCMD_MAX : cmd->data->len; rtsx_set_resp(sc, cmd); sc->rtsx_cmd_index = 0; for (i = 0; i < len; i++) { rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, reg++, 0xff, *ptr); ptr++; } /* Set transfer OK function. */ if (cmd->data->len > RTSX_HOSTCMD_MAX) sc->rtsx_intr_trans_ok = rtsx_put_ppbuf_part2; else sc->rtsx_intr_trans_ok = rtsx_write_ppbuf; /* Run the command queue. */ rtsx_send_cmd(sc); } /* * Use the ping-pong buffer (cmd buffer) for transfer - second part > 256 bytes. * This Function is called by the interrupt handler via sc->rtsx_intr_trans_ok. */ static void rtsx_put_ppbuf_part2(struct rtsx_softc *sc) { struct mmc_command *cmd; uint16_t reg = RTSX_PPBUF_BASE2 + RTSX_HOSTCMD_MAX; uint8_t *ptr; int len; int i; cmd = sc->rtsx_req->cmd; ptr = cmd->data->data; ptr += RTSX_HOSTCMD_MAX; len = cmd->data->len - RTSX_HOSTCMD_MAX; sc->rtsx_cmd_index = 0; for (i = 0; i < len; i++) { rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, reg++, 0xff, *ptr); ptr++; } /* Set transfer OK function. */ sc->rtsx_intr_trans_ok = rtsx_write_ppbuf; /* Run the command queue. */ rtsx_send_cmd(sc); } /* * Write the data previously given via the ping-pong buffer on the card. * This Function is called by the interrupt handler via sc->rtsx_intr_trans_ok. */ static void rtsx_write_ppbuf(struct rtsx_softc *sc) { struct mmc_command *cmd; cmd = sc->rtsx_req->cmd; sc->rtsx_cmd_index = 0; /* Queue commands to configure data transfer size. */ rtsx_set_cmd_data_len(sc, cmd->data->len / cmd->data->xfer_len, cmd->data->xfer_len); /* From Linux: rtsx_pci_sdmmc.c sd_write_data(). */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CFG2, 0xff, RTSX_SD_CALCULATE_CRC7 | RTSX_SD_CHECK_CRC16 | RTSX_SD_NO_WAIT_BUSY_END | RTSX_SD_CHECK_CRC7 | RTSX_SD_RSP_LEN_0); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_TRANSFER, 0xff, RTSX_TM_AUTO_WRITE3 | RTSX_SD_TRANSFER_START); rtsx_push_cmd(sc, RTSX_CHECK_REG_CMD, RTSX_SD_TRANSFER, RTSX_SD_TRANSFER_END, RTSX_SD_TRANSFER_END); /* Set transfer OK function. */ sc->rtsx_intr_trans_ok = rtsx_req_done; /* Run the command queue. */ rtsx_send_cmd(sc); } /* * Use the data buffer for transfer > 512 bytes. */ static int rtsx_xfer(struct rtsx_softc *sc, struct mmc_command *cmd) { int read = ISSET(cmd->data->flags, MMC_DATA_READ); cmd->data->xfer_len = (cmd->data->len > RTSX_MAX_DATA_BLKLEN) ? RTSX_MAX_DATA_BLKLEN : cmd->data->len; if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "rtsx_xfer() - %s xfer: %ld bytes with block size %ld\n", read ? "Read" : "Write", (unsigned long)cmd->data->len, (unsigned long)cmd->data->xfer_len); if (cmd->data->len > RTSX_DMA_DATA_BUFSIZE) { device_printf(sc->rtsx_dev, "rtsx_xfer() length too large: %ld > %ld\n", (unsigned long)cmd->data->len, RTSX_DMA_DATA_BUFSIZE); cmd->error = MMC_ERR_INVALID; return (MMC_ERR_INVALID); } if (!read) { /* Set transfer OK function. */ sc->rtsx_intr_trans_ok = rtsx_xfer_begin; /* Run the command queue. */ rtsx_send_req(sc, cmd); } else { rtsx_xfer_start(sc); } return (0); } /* * Get request response and start dma data transfer (write command). * This Function is called by the interrupt handler via sc->rtsx_intr_trans_ok. */ static void rtsx_xfer_begin(struct rtsx_softc *sc) { struct mmc_command *cmd; cmd = sc->rtsx_req->cmd; if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "rtsx_xfer_begin() - CMD%d\n", cmd->opcode); rtsx_set_resp(sc, cmd); rtsx_xfer_start(sc); } /* * Start dma data transfer. */ static void rtsx_xfer_start(struct rtsx_softc *sc) { struct mmc_command *cmd; int read; uint8_t cfg2; int dma_dir; int tmode; cmd = sc->rtsx_req->cmd; read = ISSET(cmd->data->flags, MMC_DATA_READ); /* Configure DMA transfer mode parameters. */ if (cmd->opcode == MMC_READ_MULTIPLE_BLOCK) cfg2 = RTSX_SD_CHECK_CRC16 | RTSX_SD_NO_WAIT_BUSY_END | RTSX_SD_RSP_LEN_6; else cfg2 = RTSX_SD_CHECK_CRC16 | RTSX_SD_NO_WAIT_BUSY_END | RTSX_SD_RSP_LEN_0; if (read) { dma_dir = RTSX_DMA_DIR_FROM_CARD; /* * Use transfer mode AUTO_READ1, which assume we not * already send the read command and don't need to send * CMD 12 manually after read. */ tmode = RTSX_TM_AUTO_READ1; cfg2 |= RTSX_SD_CALCULATE_CRC7 | RTSX_SD_CHECK_CRC7; rtsx_init_cmd(sc, cmd); } else { dma_dir = RTSX_DMA_DIR_TO_CARD; /* * Use transfer mode AUTO_WRITE3, wich assumes we've already * sent the write command and gotten the response, and will * send CMD 12 manually after writing. */ tmode = RTSX_TM_AUTO_WRITE3; cfg2 |= RTSX_SD_NO_CALCULATE_CRC7 | RTSX_SD_NO_CHECK_CRC7; sc->rtsx_cmd_index = 0; } /* Queue commands to configure data transfer size. */ rtsx_set_cmd_data_len(sc, cmd->data->len / cmd->data->xfer_len, cmd->data->xfer_len); /* Configure DMA controller. */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_IRQSTAT0, RTSX_DMA_DONE_INT, RTSX_DMA_DONE_INT); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_DMATC3, 0xff, cmd->data->len >> 24); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_DMATC2, 0xff, cmd->data->len >> 16); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_DMATC1, 0xff, cmd->data->len >> 8); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_DMATC0, 0xff, cmd->data->len); rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_DMACTL, RTSX_DMA_EN | RTSX_DMA_DIR | RTSX_DMA_PACK_SIZE_MASK, RTSX_DMA_EN | dma_dir | RTSX_DMA_512); /* Use the DMA ring buffer for commands which transfer data. */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_CARD_DATA_SOURCE, 0x01, RTSX_RING_BUFFER); /* Queue command to set response type. */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_CFG2, 0xff, cfg2); /* Queue commands to perform SD transfer. */ rtsx_push_cmd(sc, RTSX_WRITE_REG_CMD, RTSX_SD_TRANSFER, 0xff, tmode | RTSX_SD_TRANSFER_START); rtsx_push_cmd(sc, RTSX_CHECK_REG_CMD, RTSX_SD_TRANSFER, RTSX_SD_TRANSFER_END, RTSX_SD_TRANSFER_END); /* Run the command queue. */ rtsx_send_cmd(sc); if (!read) memcpy(sc->rtsx_data_dmamem, cmd->data->data, cmd->data->len); /* Sync data DMA buffer. */ bus_dmamap_sync(sc->rtsx_data_dma_tag, sc->rtsx_data_dmamap, BUS_DMASYNC_PREREAD); bus_dmamap_sync(sc->rtsx_data_dma_tag, sc->rtsx_data_dmamap, BUS_DMASYNC_PREWRITE); /* Set transfer OK function. */ sc->rtsx_intr_trans_ok = rtsx_xfer_finish; /* Tell the chip where the data buffer is and run the transfer. */ WRITE4(sc, RTSX_HDBAR, sc->rtsx_data_buffer); WRITE4(sc, RTSX_HDBCTLR, RTSX_TRIG_DMA | (read ? RTSX_DMA_READ : 0) | (cmd->data->len & 0x00ffffff)); } /* * Finish dma data transfer. * This Function is called by the interrupt handler via sc->rtsx_intr_trans_ok. */ static void rtsx_xfer_finish(struct rtsx_softc *sc) { struct mmc_command *cmd; int read; cmd = sc->rtsx_req->cmd; if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "rtsx_xfer_finish() - CMD%d\n", cmd->opcode); read = ISSET(cmd->data->flags, MMC_DATA_READ); /* Sync data DMA buffer. */ bus_dmamap_sync(sc->rtsx_data_dma_tag, sc->rtsx_data_dmamap, BUS_DMASYNC_POSTREAD); bus_dmamap_sync(sc->rtsx_data_dma_tag, sc->rtsx_data_dmamap, BUS_DMASYNC_POSTWRITE); if (read) { memcpy(cmd->data->data, sc->rtsx_data_dmamem, cmd->data->len); rtsx_req_done(sc); } else { /* Send CMD12 after AUTO_WRITE3 (see mmcsd_rw() in mmcsd.c) */ /* and complete request. */ sc->rtsx_intr_trans_ok = NULL; rtsx_send_req(sc, sc->rtsx_req->stop); } } /* * Manage request timeout. */ static void rtsx_timeout(void *arg) { struct rtsx_softc *sc; sc = (struct rtsx_softc *)arg; if (sc->rtsx_req != NULL) { device_printf(sc->rtsx_dev, "Controller timeout for CMD%u\n", sc->rtsx_req->cmd->opcode); sc->rtsx_req->cmd->error = MMC_ERR_TIMEOUT; rtsx_stop_cmd(sc); rtsx_req_done(sc); } else { device_printf(sc->rtsx_dev, "Controller timeout!\n"); } } #ifdef MMCCAM static int rtsx_get_tran_settings(device_t dev, struct ccb_trans_settings_mmc *cts) { struct rtsx_softc *sc; sc = device_get_softc(dev); cts->host_ocr = sc->rtsx_host.host_ocr; cts->host_f_min = sc->rtsx_host.f_min; cts->host_f_max = sc->rtsx_host.f_max; cts->host_caps = sc->rtsx_host.caps; cts->host_max_data = RTSX_DMA_DATA_BUFSIZE / MMC_SECTOR_SIZE; memcpy(&cts->ios, &sc->rtsx_host.ios, sizeof(struct mmc_ios)); return (0); } /* * Apply settings and return status accordingly. */ static int rtsx_set_tran_settings(device_t dev, struct ccb_trans_settings_mmc *cts) { struct rtsx_softc *sc; struct mmc_ios *ios; struct mmc_ios *new_ios; sc = device_get_softc(dev); ios = &sc->rtsx_host.ios; new_ios = &cts->ios; /* Update only requested fields */ if (cts->ios_valid & MMC_CLK) { ios->clock = new_ios->clock; sc->rtsx_ios_clock = -1; /* To be updated by rtsx_mmcbr_update_ios(). */ if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_tran_settings() - clock: %u\n", ios->clock); } if (cts->ios_valid & MMC_VDD) { ios->vdd = new_ios->vdd; if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_tran_settings() - vdd: %d\n", ios->vdd); } if (cts->ios_valid & MMC_CS) { ios->chip_select = new_ios->chip_select; if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_tran_settings() - chip_select: %d\n", ios->chip_select); } if (cts->ios_valid & MMC_BW) { ios->bus_width = new_ios->bus_width; sc->rtsx_ios_bus_width = -1; /* To be updated by rtsx_mmcbr_update_ios(). */ if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_tran_settings() - bus width: %d\n", ios->bus_width); } if (cts->ios_valid & MMC_PM) { ios->power_mode = new_ios->power_mode; sc->rtsx_ios_power_mode = -1; /* To be updated by rtsx_mmcbr_update_ios(). */ if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_tran_settings() - power mode: %d\n", ios->power_mode); } if (cts->ios_valid & MMC_BT) { ios->timing = new_ios->timing; sc->rtsx_ios_timing = -1; /* To be updated by rtsx_mmcbr_update_ios(). */ if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_tran_settings() - timing: %d\n", ios->timing); } if (cts->ios_valid & MMC_BM) { ios->bus_mode = new_ios->bus_mode; if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_tran_settings() - bus mode: %d\n", ios->bus_mode); } #if __FreeBSD_version >= 1300000 if (cts->ios_valid & MMC_VCCQ) { ios->vccq = new_ios->vccq; sc->rtsx_ios_vccq = -1; /* To be updated by rtsx_mmcbr_update_ios(). */ if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_set_tran_settings() - vccq: %d\n", ios->vccq); } #endif /* __FreeBSD_version >= 1300000 */ if (rtsx_mmcbr_update_ios(sc->rtsx_dev, NULL) == 0) return (CAM_REQ_CMP); else return (CAM_REQ_CMP_ERR); } /* * Build a request and run it. */ static int rtsx_cam_request(device_t dev, union ccb *ccb) { struct rtsx_softc *sc; sc = device_get_softc(dev); RTSX_LOCK(sc); if (sc->rtsx_ccb != NULL) { RTSX_UNLOCK(sc); return (CAM_BUSY); } sc->rtsx_ccb = ccb; sc->rtsx_cam_req.cmd = &ccb->mmcio.cmd; sc->rtsx_cam_req.stop = &ccb->mmcio.stop; RTSX_UNLOCK(sc); rtsx_mmcbr_request(sc->rtsx_dev, NULL, &sc->rtsx_cam_req); return (0); } #endif /* MMCCAM */ static int rtsx_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct rtsx_softc *sc; sc = device_get_softc(bus); switch (which) { case MMCBR_IVAR_BUS_MODE: /* ivar 0 - 1 = opendrain, 2 = pushpull */ *result = sc->rtsx_host.ios.bus_mode; break; case MMCBR_IVAR_BUS_WIDTH: /* ivar 1 - 0 = 1b 2 = 4b, 3 = 8b */ *result = sc->rtsx_host.ios.bus_width; break; case MMCBR_IVAR_CHIP_SELECT: /* ivar 2 - O = dontcare, 1 = cs_high, 2 = cs_low */ *result = sc->rtsx_host.ios.chip_select; break; case MMCBR_IVAR_CLOCK: /* ivar 3 - clock in Hz */ *result = sc->rtsx_host.ios.clock; break; case MMCBR_IVAR_F_MIN: /* ivar 4 */ *result = sc->rtsx_host.f_min; break; case MMCBR_IVAR_F_MAX: /* ivar 5 */ *result = sc->rtsx_host.f_max; break; case MMCBR_IVAR_HOST_OCR: /* ivar 6 - host operation conditions register */ *result = sc->rtsx_host.host_ocr; break; case MMCBR_IVAR_MODE: /* ivar 7 - 0 = mode_mmc, 1 = mode_sd */ *result = sc->rtsx_host.mode; break; case MMCBR_IVAR_OCR: /* ivar 8 - operation conditions register */ *result = sc->rtsx_host.ocr; break; case MMCBR_IVAR_POWER_MODE: /* ivar 9 - 0 = off, 1 = up, 2 = on */ *result = sc->rtsx_host.ios.power_mode; break; case MMCBR_IVAR_VDD: /* ivar 11 - voltage power pin */ *result = sc->rtsx_host.ios.vdd; break; case MMCBR_IVAR_VCCQ: /* ivar 12 - signaling: 0 = 1.20V, 1 = 1.80V, 2 = 3.30V */ *result = sc->rtsx_host.ios.vccq; break; case MMCBR_IVAR_CAPS: /* ivar 13 */ *result = sc->rtsx_host.caps; break; case MMCBR_IVAR_TIMING: /* ivar 14 - 0 = normal, 1 = timing_hs, ... */ *result = sc->rtsx_host.ios.timing; break; case MMCBR_IVAR_MAX_DATA: /* ivar 15 */ *result = RTSX_DMA_DATA_BUFSIZE / MMC_SECTOR_SIZE; break; case MMCBR_IVAR_RETUNE_REQ: /* ivar 10 */ case MMCBR_IVAR_MAX_BUSY_TIMEOUT: /* ivar 16 */ default: return (EINVAL); } if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(bus, "Read ivar #%d, value %#x / #%d\n", which, *(int *)result, *(int *)result); return (0); } static int rtsx_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct rtsx_softc *sc; sc = device_get_softc(bus); if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(bus, "Write ivar #%d, value %#x / #%d\n", which, (int)value, (int)value); switch (which) { case MMCBR_IVAR_BUS_MODE: /* ivar 0 - 1 = opendrain, 2 = pushpull */ sc->rtsx_host.ios.bus_mode = value; break; case MMCBR_IVAR_BUS_WIDTH: /* ivar 1 - 0 = 1b 2 = 4b, 3 = 8b */ sc->rtsx_host.ios.bus_width = value; sc->rtsx_ios_bus_width = -1; /* To be updated on next rtsx_mmcbr_update_ios(). */ break; case MMCBR_IVAR_CHIP_SELECT: /* ivar 2 - O = dontcare, 1 = cs_high, 2 = cs_low */ sc->rtsx_host.ios.chip_select = value; break; case MMCBR_IVAR_CLOCK: /* ivar 3 - clock in Hz */ sc->rtsx_host.ios.clock = value; sc->rtsx_ios_clock = -1; /* To be updated on next rtsx_mmcbr_update_ios(). */ break; case MMCBR_IVAR_MODE: /* ivar 7 - 0 = mode_mmc, 1 = mode_sd */ sc->rtsx_host.mode = value; break; case MMCBR_IVAR_OCR: /* ivar 8 - operation conditions register */ sc->rtsx_host.ocr = value; break; case MMCBR_IVAR_POWER_MODE: /* ivar 9 - 0 = off, 1 = up, 2 = on */ sc->rtsx_host.ios.power_mode = value; sc->rtsx_ios_power_mode = -1; /* To be updated on next rtsx_mmcbr_update_ios(). */ break; case MMCBR_IVAR_VDD: /* ivar 11 - voltage power pin */ sc->rtsx_host.ios.vdd = value; break; case MMCBR_IVAR_VCCQ: /* ivar 12 - signaling: 0 = 1.20V, 1 = 1.80V, 2 = 3.30V */ sc->rtsx_host.ios.vccq = value; sc->rtsx_ios_vccq = value; /* rtsx_mmcbr_switch_vccq() will be called by mmc.c (MMCCAM undef). */ break; case MMCBR_IVAR_TIMING: /* ivar 14 - 0 = normal, 1 = timing_hs, ... */ sc->rtsx_host.ios.timing = value; sc->rtsx_ios_timing = -1; /* To be updated on next rtsx_mmcbr_update_ios(). */ break; /* These are read-only. */ case MMCBR_IVAR_F_MIN: /* ivar 4 */ case MMCBR_IVAR_F_MAX: /* ivar 5 */ case MMCBR_IVAR_HOST_OCR: /* ivar 6 - host operation conditions register */ case MMCBR_IVAR_RETUNE_REQ: /* ivar 10 */ case MMCBR_IVAR_CAPS: /* ivar 13 */ case MMCBR_IVAR_MAX_DATA: /* ivar 15 */ case MMCBR_IVAR_MAX_BUSY_TIMEOUT: /* ivar 16 */ default: return (EINVAL); } return (0); } static int rtsx_mmcbr_update_ios(device_t bus, device_t child__unused) { struct rtsx_softc *sc; struct mmc_ios *ios; int error; sc = device_get_softc(bus); ios = &sc->rtsx_host.ios; if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(bus, "rtsx_mmcbr_update_ios()\n"); /* if MMCBR_IVAR_BUS_WIDTH updated. */ if (sc->rtsx_ios_bus_width < 0) { sc->rtsx_ios_bus_width = ios->bus_width; if ((error = rtsx_set_bus_width(sc, ios->bus_width))) return (error); } /* if MMCBR_IVAR_POWER_MODE updated. */ if (sc->rtsx_ios_power_mode < 0) { sc->rtsx_ios_power_mode = ios->power_mode; switch (ios->power_mode) { case power_off: if ((error = rtsx_bus_power_off(sc))) return (error); break; case power_up: if ((error = rtsx_bus_power_on(sc))) return (error); break; case power_on: if ((error = rtsx_bus_power_on(sc))) return (error); break; } } sc->rtsx_double_clk = true; sc->rtsx_vpclk = false; /* if MMCBR_IVAR_TIMING updated. */ if (sc->rtsx_ios_timing < 0) { sc->rtsx_ios_timing = ios->timing; if ((error = rtsx_set_sd_timing(sc, ios->timing))) return (error); } /* if MMCBR_IVAR_CLOCK updated, must be after rtsx_set_sd_timing() */ if (sc->rtsx_ios_clock < 0) { sc->rtsx_ios_clock = ios->clock; if ((error = rtsx_set_sd_clock(sc, ios->clock))) return (error); } /* if MMCCAM and vccq updated */ if (sc->rtsx_ios_vccq < 0) { sc->rtsx_ios_vccq = ios->vccq; if ((error = rtsx_mmcbr_switch_vccq(sc->rtsx_dev, NULL))) return (error); } return (0); } /* * Set output stage logic power voltage. */ static int rtsx_mmcbr_switch_vccq(device_t bus, device_t child __unused) { struct rtsx_softc *sc; int vccq = 0; int error; sc = device_get_softc(bus); switch (sc->rtsx_host.ios.vccq) { case vccq_120: vccq = 120; break; case vccq_180: vccq = 180; break; case vccq_330: vccq = 330; break; }; /* It seems it is always vccq_330. */ if (vccq == 330) { switch (sc->rtsx_device_id) { uint16_t val; case RTSX_RTS5227: if ((error = rtsx_write_phy(sc, 0x08, 0x4FE4))) return (error); if ((error = rtsx_rts5227_fill_driving(sc))) return (error); break; case RTSX_RTS5209: case RTSX_RTS5229: RTSX_BITOP(sc, RTSX_SD30_CMD_DRIVE_SEL, RTSX_SD30_DRIVE_SEL_MASK, sc->rtsx_sd30_drive_sel_3v3); if ((error = rtsx_write_phy(sc, 0x08, 0x4FE4))) return (error); break; case RTSX_RTS522A: if ((error = rtsx_write_phy(sc, 0x08, 0x57E4))) return (error); if ((error = rtsx_rts5227_fill_driving(sc))) return (error); break; case RTSX_RTS525A: RTSX_BITOP(sc, RTSX_LDO_CONFIG2, RTSX_LDO_D3318_MASK, RTSX_LDO_D3318_33V); RTSX_BITOP(sc, RTSX_SD_PAD_CTL, RTSX_SD_IO_USING_1V8, 0); if ((error = rtsx_rts5249_fill_driving(sc))) return (error); break; case RTSX_RTS5249: if ((error = rtsx_read_phy(sc, RTSX_PHY_TUNE, &val))) return (error); if ((error = rtsx_write_phy(sc, RTSX_PHY_TUNE, (val & RTSX_PHY_TUNE_VOLTAGE_MASK) | RTSX_PHY_TUNE_VOLTAGE_3V3))) return (error); if ((error = rtsx_rts5249_fill_driving(sc))) return (error); break; case RTSX_RTS5260: RTSX_BITOP(sc, RTSX_LDO_CONFIG2, RTSX_DV331812_VDD1, RTSX_DV331812_VDD1); RTSX_BITOP(sc, RTSX_LDO_DV18_CFG, RTSX_DV331812_MASK, RTSX_DV331812_33); RTSX_CLR(sc, RTSX_SD_PAD_CTL, RTSX_SD_IO_USING_1V8); if ((error = rtsx_rts5260_fill_driving(sc))) return (error); break; case RTSX_RTL8402: RTSX_BITOP(sc, RTSX_SD30_CMD_DRIVE_SEL, RTSX_SD30_DRIVE_SEL_MASK, sc->rtsx_sd30_drive_sel_3v3); RTSX_BITOP(sc, RTSX_LDO_CTL, (RTSX_BPP_ASIC_MASK << RTSX_BPP_SHIFT_8402) | RTSX_BPP_PAD_MASK, (RTSX_BPP_ASIC_3V3 << RTSX_BPP_SHIFT_8402) | RTSX_BPP_PAD_3V3); break; case RTSX_RTL8411: case RTSX_RTL8411B: RTSX_BITOP(sc, RTSX_SD30_CMD_DRIVE_SEL, RTSX_SD30_DRIVE_SEL_MASK, sc->rtsx_sd30_drive_sel_3v3); RTSX_BITOP(sc, RTSX_LDO_CTL, (RTSX_BPP_ASIC_MASK << RTSX_BPP_SHIFT_8411) | RTSX_BPP_PAD_MASK, (RTSX_BPP_ASIC_3V3 << RTSX_BPP_SHIFT_8411) | RTSX_BPP_PAD_3V3); break; } DELAY(300); } if (sc->rtsx_debug_mask & (RTSX_DEBUG_BASIC | RTSX_TRACE_SD_CMD)) device_printf(sc->rtsx_dev, "rtsx_mmcbr_switch_vccq(%d)\n", vccq); return (0); } #ifndef MMCCAM /* * Tune card if bus_timing_uhs_sdr50. */ static int rtsx_mmcbr_tune(device_t bus, device_t child __unused, bool hs400) { struct rtsx_softc *sc; uint32_t raw_phase_map[RTSX_RX_TUNING_CNT] = {0}; uint32_t phase_map; uint8_t final_phase; int i; sc = device_get_softc(bus); if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_mmcbr_tune() - hs400 is %s\n", (hs400) ? "true" : "false"); if (sc->rtsx_ios_timing != bus_timing_uhs_sdr50) return (0); sc->rtsx_tuning_mode = true; switch (sc->rtsx_device_id) { case RTSX_RTS5209: case RTSX_RTS5227: rtsx_sd_change_tx_phase(sc, 27); break; case RTSX_RTS522A: rtsx_sd_change_tx_phase(sc, 20); break; case RTSX_RTS5229: rtsx_sd_change_tx_phase(sc, 27); break; case RTSX_RTS525A: case RTSX_RTS5249: rtsx_sd_change_tx_phase(sc, 29); break; case RTSX_RTL8402: case RTSX_RTL8411: case RTSX_RTL8411B: rtsx_sd_change_tx_phase(sc, 7); break; } /* trying rx tuning for bus_timing_uhs_sdr50. */ for (i = 0; i < RTSX_RX_TUNING_CNT; i++) { rtsx_sd_tuning_rx_phase(sc, &(raw_phase_map[i])); if (raw_phase_map[i] == 0) break; } phase_map = 0xffffffff; for (i = 0; i < RTSX_RX_TUNING_CNT; i++) { if (sc->rtsx_debug_mask & (RTSX_DEBUG_BASIC | RTSX_DEBUG_TUNING)) device_printf(sc->rtsx_dev, "rtsx_mmcbr_tune() - RX raw_phase_map[%d]: 0x%08x\n", i, raw_phase_map[i]); phase_map &= raw_phase_map[i]; } if (sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(sc->rtsx_dev, "rtsx_mmcbr_tune() - RX phase_map: 0x%08x\n", phase_map); if (phase_map) { final_phase = rtsx_sd_search_final_rx_phase(sc, phase_map); if (final_phase != 0xff) { rtsx_sd_change_rx_phase(sc, final_phase); } } sc->rtsx_tuning_mode = false; return (0); } static int rtsx_mmcbr_retune(device_t bus, device_t child __unused, bool reset __unused) { struct rtsx_softc *sc; sc = device_get_softc(bus); if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "rtsx_mmcbr_retune()\n"); return (0); } #endif /* !MMCCAM */ static int rtsx_mmcbr_request(device_t bus, device_t child __unused, struct mmc_request *req) { struct rtsx_softc *sc; struct mmc_command *cmd; int timeout; int error; sc = device_get_softc(bus); RTSX_LOCK(sc); if (sc->rtsx_req != NULL) { RTSX_UNLOCK(sc); return (EBUSY); } sc->rtsx_req = req; cmd = req->cmd; cmd->error = error = MMC_ERR_NONE; sc->rtsx_intr_status = 0; sc->rtsx_intr_trans_ok = NULL; sc->rtsx_intr_trans_ko = rtsx_req_done; if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(sc->rtsx_dev, "rtsx_mmcbr_request(CMD%u arg %#x, flags %#x, dlen %u, dflags %#x)\n", cmd->opcode, cmd->arg, cmd->flags, cmd->data != NULL ? (unsigned int)cmd->data->len : 0, cmd->data != NULL ? cmd->data->flags : 0); /* Check if card present. */ if (!ISSET(sc->rtsx_flags, RTSX_F_CARD_PRESENT)) { cmd->error = error = MMC_ERR_FAILED; goto end; } /* Refuse SDIO probe if the chip doesn't support SDIO. */ if (cmd->opcode == IO_SEND_OP_COND && !ISSET(sc->rtsx_flags, RTSX_F_SDIO_SUPPORT)) { cmd->error = error = MMC_ERR_INVALID; goto end; } /* Return MMC_ERR_TIMEOUT for SD_IO_RW_DIRECT and IO_SEND_OP_COND. */ if (cmd->opcode == SD_IO_RW_DIRECT || cmd->opcode == IO_SEND_OP_COND) { cmd->error = error = MMC_ERR_TIMEOUT; goto end; } /* Select SD card. */ RTSX_BITOP(sc, RTSX_CARD_SELECT, 0x07, RTSX_SD_MOD_SEL); RTSX_BITOP(sc, RTSX_CARD_SHARE_MODE, RTSX_CARD_SHARE_MASK, RTSX_CARD_SHARE_48_SD); if (cmd->data == NULL) { DELAY(200); timeout = sc->rtsx_timeout_cmd; error = rtsx_send_req(sc, cmd); } else if (cmd->data->len <= 512) { timeout = sc->rtsx_timeout_io; error = rtsx_xfer_short(sc, cmd); } else { timeout = sc->rtsx_timeout_io; error = rtsx_xfer(sc, cmd); } end: if (error == MMC_ERR_NONE) { callout_reset(&sc->rtsx_timeout_callout, timeout * hz, rtsx_timeout, sc); } else { rtsx_req_done(sc); } RTSX_UNLOCK(sc); return (error); } #ifndef MMCCAM static int rtsx_mmcbr_get_ro(device_t bus, device_t child __unused) { struct rtsx_softc *sc; sc = device_get_softc(bus); if (sc->rtsx_inversion == 0) return (sc->rtsx_read_only); else return !(sc->rtsx_read_only); } static int rtsx_mmcbr_acquire_host(device_t bus, device_t child __unused) { struct rtsx_softc *sc; sc = device_get_softc(bus); if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(bus, "rtsx_mmcbr_acquire_host()\n"); RTSX_LOCK(sc); while (sc->rtsx_bus_busy) msleep(&sc->rtsx_bus_busy, &sc->rtsx_mtx, 0, "rtsxah", 0); sc->rtsx_bus_busy++; RTSX_UNLOCK(sc); return (0); } static int rtsx_mmcbr_release_host(device_t bus, device_t child __unused) { struct rtsx_softc *sc; sc = device_get_softc(bus); if (sc->rtsx_debug_mask & RTSX_TRACE_SD_CMD) device_printf(bus, "rtsx_mmcbr_release_host()\n"); RTSX_LOCK(sc); sc->rtsx_bus_busy--; wakeup(&sc->rtsx_bus_busy); RTSX_UNLOCK(sc); return (0); } #endif /* !MMCCAM */ /* * * PCI Support Functions * */ /* * Compare the device ID (chip) of this device against the IDs that this driver * supports. If there is a match, set the description and return success. */ static int rtsx_probe(device_t dev) { uint16_t vendor_id; uint16_t device_id; int i; vendor_id = pci_get_vendor(dev); device_id = pci_get_device(dev); if (vendor_id != RTSX_REALTEK) return (ENXIO); for (i = 0; i < nitems(rtsx_ids); i++) { if (rtsx_ids[i].device_id == device_id) { device_set_desc(dev, rtsx_ids[i].desc); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } /* * Attach function is only called if the probe is successful. */ static int rtsx_attach(device_t dev) { struct rtsx_softc *sc = device_get_softc(dev); uint16_t vendor_id; uint16_t device_id; struct sysctl_ctx_list *ctx; struct sysctl_oid_list *tree; int msi_count = 1; uint32_t sdio_cfg; int error; char *maker; char *family; char *product; int i; vendor_id = pci_get_vendor(dev); device_id = pci_get_device(dev); if (bootverbose) device_printf(dev, "Attach - Vendor ID: 0x%x - Device ID: 0x%x\n", vendor_id, device_id); sc->rtsx_dev = dev; sc->rtsx_device_id = device_id; sc->rtsx_req = NULL; sc->rtsx_timeout_cmd = 1; sc->rtsx_timeout_io = 10; sc->rtsx_read_only = 0; sc->rtsx_inversion = 0; sc->rtsx_force_timing = 0; sc->rtsx_debug_mask = 0; sc->rtsx_read_count = 0; sc->rtsx_write_count = 0; maker = kern_getenv("smbios.system.maker"); family = kern_getenv("smbios.system.family"); product = kern_getenv("smbios.system.product"); for (i = 0; rtsx_inversion_models[i].maker != NULL; i++) { if (strcmp(rtsx_inversion_models[i].maker, maker) == 0 && strcmp(rtsx_inversion_models[i].family, family) == 0 && strcmp(rtsx_inversion_models[i].product, product) == 0) { device_printf(dev, "Inversion activated for %s/%s/%s, see BUG in rtsx(4)\n", maker, family, product); device_printf(dev, "If a card is detected without an SD card present," " add dev.rtsx.0.inversion=0 in loader.conf(5)\n"); sc->rtsx_inversion = 1; break; } } RTSX_LOCK_INIT(sc); ctx = device_get_sysctl_ctx(dev); tree = SYSCTL_CHILDREN(device_get_sysctl_tree(dev)); SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "timeout_io", CTLFLAG_RW, &sc->rtsx_timeout_io, 0, "Request timeout for I/O commands in seconds"); SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "timeout_cmd", CTLFLAG_RW, &sc->rtsx_timeout_cmd, 0, "Request timeout for setup commands in seconds"); SYSCTL_ADD_U8(ctx, tree, OID_AUTO, "read_only", CTLFLAG_RD, &sc->rtsx_read_only, 0, "Card is write protected"); SYSCTL_ADD_U8(ctx, tree, OID_AUTO, "inversion", CTLFLAG_RWTUN, &sc->rtsx_inversion, 0, "Inversion of card detection and read only status"); SYSCTL_ADD_U8(ctx, tree, OID_AUTO, "force_timing", CTLFLAG_RW, &sc->rtsx_force_timing, 0, "Force bus_timing_uhs_sdr50"); SYSCTL_ADD_U8(ctx, tree, OID_AUTO, "debug_mask", CTLFLAG_RWTUN, &sc->rtsx_debug_mask, 0, "debugging mask, see rtsx(4)"); SYSCTL_ADD_U64(ctx, tree, OID_AUTO, "read_count", CTLFLAG_RD | CTLFLAG_STATS, &sc->rtsx_read_count, 0, "Count of read operations"); SYSCTL_ADD_U64(ctx, tree, OID_AUTO, "write_count", CTLFLAG_RD | CTLFLAG_STATS, &sc->rtsx_write_count, 0, "Count of write operations"); if (bootverbose || sc->rtsx_debug_mask & RTSX_DEBUG_BASIC) device_printf(dev, "We are running with inversion: %d\n", sc->rtsx_inversion); /* Allocate IRQ. */ sc->rtsx_irq_res_id = 0; if (pci_alloc_msi(dev, &msi_count) == 0) sc->rtsx_irq_res_id = 1; sc->rtsx_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->rtsx_irq_res_id, RF_ACTIVE | (sc->rtsx_irq_res_id != 0 ? 0 : RF_SHAREABLE)); if (sc->rtsx_irq_res == NULL) { device_printf(dev, "Can't allocate IRQ resources for %d\n", sc->rtsx_irq_res_id); pci_release_msi(dev); return (ENXIO); } callout_init_mtx(&sc->rtsx_timeout_callout, &sc->rtsx_mtx, 0); /* Allocate memory resource. */ if (sc->rtsx_device_id == RTSX_RTS525A) sc->rtsx_mem_res_id = PCIR_BAR(1); else sc->rtsx_mem_res_id = PCIR_BAR(0); sc->rtsx_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rtsx_mem_res_id, RF_ACTIVE); if (sc->rtsx_mem_res == NULL) { device_printf(dev, "Can't allocate memory resource for %d\n", sc->rtsx_mem_res_id); goto destroy_rtsx_irq_res; } if (bootverbose) device_printf(dev, "rtsx_irq_res_id: %d, rtsx_mem_res_id: %d\n", sc->rtsx_irq_res_id, sc->rtsx_mem_res_id); sc->rtsx_mem_btag = rman_get_bustag(sc->rtsx_mem_res); sc->rtsx_mem_bhandle = rman_get_bushandle(sc->rtsx_mem_res); TIMEOUT_TASK_INIT(taskqueue_swi_giant, &sc->rtsx_card_insert_task, 0, rtsx_card_task, sc); TASK_INIT(&sc->rtsx_card_remove_task, 0, rtsx_card_task, sc); /* Allocate two DMA buffers: a command buffer and a data buffer. */ error = rtsx_dma_alloc(sc); if (error) goto destroy_rtsx_irq_res; /* Activate the interrupt. */ error = bus_setup_intr(dev, sc->rtsx_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, rtsx_intr, sc, &sc->rtsx_irq_cookie); if (error) { device_printf(dev, "Can't set up irq [0x%x]!\n", error); goto destroy_rtsx_mem_res; } pci_enable_busmaster(dev); if (rtsx_read_cfg(sc, 0, RTSX_SDIOCFG_REG, &sdio_cfg) == 0) { if ((sdio_cfg & RTSX_SDIOCFG_SDIO_ONLY) || (sdio_cfg & RTSX_SDIOCFG_HAVE_SDIO)) sc->rtsx_flags |= RTSX_F_SDIO_SUPPORT; } #ifdef MMCCAM sc->rtsx_ccb = NULL; sc->rtsx_cam_status = 0; SYSCTL_ADD_U8(ctx, tree, OID_AUTO, "cam_status", CTLFLAG_RD, &sc->rtsx_cam_status, 0, "driver cam card present"); if (mmc_cam_sim_alloc(dev, "rtsx_mmc", &sc->rtsx_mmc_sim) != 0) { device_printf(dev, "Can't allocate CAM SIM\n"); goto destroy_rtsx_irq; } #endif /* MMCCAM */ /* Initialize device. */ error = rtsx_init(sc); if (error) { device_printf(dev, "Error %d during rtsx_init()\n", error); goto destroy_rtsx_irq; } /* * Schedule a card detection as we won't get an interrupt * if the card is inserted when we attach. We wait a quarter * of a second to allow for a "spontaneous" interrupt which may * change the card presence state. This delay avoid a panic * on some configuration (e.g. Lenovo T540p). */ DELAY(250000); if (rtsx_is_card_present(sc)) device_printf(sc->rtsx_dev, "A card is detected\n"); else device_printf(sc->rtsx_dev, "No card is detected\n"); rtsx_card_task(sc, 0); if (bootverbose) device_printf(dev, "Device attached\n"); return (0); destroy_rtsx_irq: bus_teardown_intr(dev, sc->rtsx_irq_res, sc->rtsx_irq_cookie); destroy_rtsx_mem_res: bus_release_resource(dev, SYS_RES_MEMORY, sc->rtsx_mem_res_id, sc->rtsx_mem_res); rtsx_dma_free(sc); destroy_rtsx_irq_res: callout_drain(&sc->rtsx_timeout_callout); bus_release_resource(dev, SYS_RES_IRQ, sc->rtsx_irq_res_id, sc->rtsx_irq_res); pci_release_msi(dev); RTSX_LOCK_DESTROY(sc); return (ENXIO); } static int rtsx_detach(device_t dev) { struct rtsx_softc *sc = device_get_softc(dev); int error; if (bootverbose) device_printf(dev, "Detach - Vendor ID: 0x%x - Device ID: 0x%x\n", pci_get_vendor(dev), sc->rtsx_device_id); /* Disable interrupts. */ sc->rtsx_intr_enabled = 0; WRITE4(sc, RTSX_BIER, sc->rtsx_intr_enabled); /* Stop device. */ - error = device_delete_children(sc->rtsx_dev); - sc->rtsx_mmc_dev = NULL; + error = bus_generic_detach(sc->rtsx_dev); if (error) return (error); + sc->rtsx_mmc_dev = NULL; taskqueue_drain_timeout(taskqueue_swi_giant, &sc->rtsx_card_insert_task); taskqueue_drain(taskqueue_swi_giant, &sc->rtsx_card_remove_task); /* Teardown the state in our softc created in our attach routine. */ rtsx_dma_free(sc); if (sc->rtsx_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->rtsx_mem_res_id, sc->rtsx_mem_res); if (sc->rtsx_irq_cookie != NULL) bus_teardown_intr(dev, sc->rtsx_irq_res, sc->rtsx_irq_cookie); if (sc->rtsx_irq_res != NULL) { callout_drain(&sc->rtsx_timeout_callout); bus_release_resource(dev, SYS_RES_IRQ, sc->rtsx_irq_res_id, sc->rtsx_irq_res); pci_release_msi(dev); } RTSX_LOCK_DESTROY(sc); #ifdef MMCCAM mmc_cam_sim_free(&sc->rtsx_mmc_sim); #endif /* MMCCAM */ return (0); } static int rtsx_shutdown(device_t dev) { if (bootverbose) device_printf(dev, "Shutdown\n"); return (0); } /* * Device suspend routine. */ static int rtsx_suspend(device_t dev) { struct rtsx_softc *sc = device_get_softc(dev); device_printf(dev, "Suspend\n"); #ifdef MMCCAM if (sc->rtsx_ccb != NULL) { device_printf(dev, "Request in progress: CMD%u, rtsr_intr_status: 0x%08x\n", sc->rtsx_ccb->mmcio.cmd.opcode, sc->rtsx_intr_status); } #else /* !MMCCAM */ if (sc->rtsx_req != NULL) { device_printf(dev, "Request in progress: CMD%u, rtsr_intr_status: 0x%08x\n", sc->rtsx_req->cmd->opcode, sc->rtsx_intr_status); } #endif /* MMCCAM */ bus_generic_suspend(dev); return (0); } /* * Device resume routine. */ static int rtsx_resume(device_t dev) { device_printf(dev, "Resume\n"); rtsx_init(device_get_softc(dev)); bus_generic_resume(dev); return (0); } static device_method_t rtsx_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rtsx_probe), DEVMETHOD(device_attach, rtsx_attach), DEVMETHOD(device_detach, rtsx_detach), DEVMETHOD(device_shutdown, rtsx_shutdown), DEVMETHOD(device_suspend, rtsx_suspend), DEVMETHOD(device_resume, rtsx_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, rtsx_read_ivar), DEVMETHOD(bus_write_ivar, rtsx_write_ivar), #ifndef MMCCAM /* MMC bridge interface */ DEVMETHOD(mmcbr_update_ios, rtsx_mmcbr_update_ios), DEVMETHOD(mmcbr_switch_vccq, rtsx_mmcbr_switch_vccq), DEVMETHOD(mmcbr_tune, rtsx_mmcbr_tune), DEVMETHOD(mmcbr_retune, rtsx_mmcbr_retune), DEVMETHOD(mmcbr_request, rtsx_mmcbr_request), DEVMETHOD(mmcbr_get_ro, rtsx_mmcbr_get_ro), DEVMETHOD(mmcbr_acquire_host, rtsx_mmcbr_acquire_host), DEVMETHOD(mmcbr_release_host, rtsx_mmcbr_release_host), #endif /* !MMCCAM */ #ifdef MMCCAM /* MMCCAM interface */ DEVMETHOD(mmc_sim_get_tran_settings, rtsx_get_tran_settings), DEVMETHOD(mmc_sim_set_tran_settings, rtsx_set_tran_settings), DEVMETHOD(mmc_sim_cam_request, rtsx_cam_request), #endif /* MMCCAM */ DEVMETHOD_END }; DEFINE_CLASS_0(rtsx, rtsx_driver, rtsx_methods, sizeof(struct rtsx_softc)); DRIVER_MODULE(rtsx, pci, rtsx_driver, NULL, NULL); /* For Plug and Play */ MODULE_PNP_INFO("U16:device;D:#;T:vendor=0x10ec", pci, rtsx, rtsx_ids, nitems(rtsx_ids)); #ifndef MMCCAM MMC_DECLARE_BRIDGE(rtsx); #endif /* !MMCCAM */ diff --git a/sys/dev/siis/siis.c b/sys/dev/siis/siis.c index 4cc78ed57323..df11b36ec844 100644 --- a/sys/dev/siis/siis.c +++ b/sys/dev/siis/siis.c @@ -1,1984 +1,1987 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2009 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "siis.h" #include #include #include #include #include /* local prototypes */ static int siis_setup_interrupt(device_t dev); static void siis_intr(void *data); static int siis_suspend(device_t dev); static int siis_resume(device_t dev); static int siis_ch_init(device_t dev); static int siis_ch_deinit(device_t dev); static int siis_ch_suspend(device_t dev); static int siis_ch_resume(device_t dev); static void siis_ch_intr_locked(void *data); static void siis_ch_intr(void *data); static void siis_ch_led(void *priv, int onoff); static void siis_begin_transaction(device_t dev, union ccb *ccb); static void siis_dmasetprd(void *arg, bus_dma_segment_t *segs, int nsegs, int error); static void siis_execute_transaction(struct siis_slot *slot); static void siis_timeout(void *arg); static void siis_end_transaction(struct siis_slot *slot, enum siis_err_type et); static int siis_setup_fis(device_t dev, struct siis_cmd *ctp, union ccb *ccb, int tag); static void siis_dmainit(device_t dev); static void siis_dmasetupc_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error); static void siis_dmafini(device_t dev); static void siis_slotsalloc(device_t dev); static void siis_slotsfree(device_t dev); static void siis_reset(device_t dev); static void siis_portinit(device_t dev); static int siis_wait_ready(device_t dev, int t); static int siis_sata_connect(struct siis_channel *ch); static void siis_issue_recovery(device_t dev); static void siis_process_read_log(device_t dev, union ccb *ccb); static void siis_process_request_sense(device_t dev, union ccb *ccb); static void siisaction(struct cam_sim *sim, union ccb *ccb); static void siispoll(struct cam_sim *sim); static MALLOC_DEFINE(M_SIIS, "SIIS driver", "SIIS driver data buffers"); static struct { uint32_t id; const char *name; int ports; int quirks; #define SIIS_Q_SNTF 1 #define SIIS_Q_NOMSI 2 } siis_ids[] = { {0x31241095, "SiI3124", 4, 0}, {0x31248086, "SiI3124", 4, 0}, {0x31321095, "SiI3132", 2, SIIS_Q_SNTF|SIIS_Q_NOMSI}, {0x02421095, "SiI3132", 2, SIIS_Q_SNTF|SIIS_Q_NOMSI}, {0x02441095, "SiI3132", 2, SIIS_Q_SNTF|SIIS_Q_NOMSI}, {0x31311095, "SiI3131", 1, SIIS_Q_SNTF|SIIS_Q_NOMSI}, {0x35311095, "SiI3531", 1, SIIS_Q_SNTF|SIIS_Q_NOMSI}, {0, NULL, 0, 0} }; #define recovery_type spriv_field0 #define RECOVERY_NONE 0 #define RECOVERY_READ_LOG 1 #define RECOVERY_REQUEST_SENSE 2 #define recovery_slot spriv_field1 static int siis_probe(device_t dev) { int i; uint32_t devid = pci_get_devid(dev); for (i = 0; siis_ids[i].id != 0; i++) { if (siis_ids[i].id == devid) { device_set_descf(dev, "%s SATA controller", siis_ids[i].name); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int siis_attach(device_t dev) { struct siis_controller *ctlr = device_get_softc(dev); uint32_t devid = pci_get_devid(dev); device_t child; int error, i, unit; ctlr->dev = dev; for (i = 0; siis_ids[i].id != 0; i++) { if (siis_ids[i].id == devid) break; } ctlr->quirks = siis_ids[i].quirks; /* Global memory */ ctlr->r_grid = PCIR_BAR(0); if (!(ctlr->r_gmem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &ctlr->r_grid, RF_ACTIVE))) return (ENXIO); ctlr->gctl = ATA_INL(ctlr->r_gmem, SIIS_GCTL); /* Channels memory */ ctlr->r_rid = PCIR_BAR(2); if (!(ctlr->r_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &ctlr->r_rid, RF_ACTIVE))) return (ENXIO); /* Setup our own memory management for channels. */ ctlr->sc_iomem.rm_start = rman_get_start(ctlr->r_mem); ctlr->sc_iomem.rm_end = rman_get_end(ctlr->r_mem); ctlr->sc_iomem.rm_type = RMAN_ARRAY; ctlr->sc_iomem.rm_descr = "I/O memory addresses"; if ((error = rman_init(&ctlr->sc_iomem)) != 0) { bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_grid, ctlr->r_gmem); return (error); } if ((error = rman_manage_region(&ctlr->sc_iomem, rman_get_start(ctlr->r_mem), rman_get_end(ctlr->r_mem))) != 0) { bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_grid, ctlr->r_gmem); rman_fini(&ctlr->sc_iomem); return (error); } pci_enable_busmaster(dev); /* Reset controller */ siis_resume(dev); /* Number of HW channels */ ctlr->channels = siis_ids[i].ports; /* Setup interrupts. */ if (siis_setup_interrupt(dev)) { bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_grid, ctlr->r_gmem); rman_fini(&ctlr->sc_iomem); return ENXIO; } /* Attach all channels on this controller */ for (unit = 0; unit < ctlr->channels; unit++) { child = device_add_child(dev, "siisch", DEVICE_UNIT_ANY); if (child == NULL) device_printf(dev, "failed to add channel device\n"); else device_set_ivars(child, (void *)(intptr_t)unit); } bus_attach_children(dev); return 0; } static int siis_detach(device_t dev) { struct siis_controller *ctlr = device_get_softc(dev); + int error; /* Detach & delete all children */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); /* Free interrupts. */ if (ctlr->irq.r_irq) { bus_teardown_intr(dev, ctlr->irq.r_irq, ctlr->irq.handle); bus_release_resource(dev, SYS_RES_IRQ, ctlr->irq.r_irq_rid, ctlr->irq.r_irq); } pci_release_msi(dev); /* Free memory. */ rman_fini(&ctlr->sc_iomem); bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_grid, ctlr->r_gmem); return (0); } static int siis_suspend(device_t dev) { struct siis_controller *ctlr = device_get_softc(dev); bus_generic_suspend(dev); /* Put controller into reset state. */ ctlr->gctl |= SIIS_GCTL_GRESET; ATA_OUTL(ctlr->r_gmem, SIIS_GCTL, ctlr->gctl); return 0; } static int siis_resume(device_t dev) { struct siis_controller *ctlr = device_get_softc(dev); /* Set PCIe max read request size to at least 1024 bytes */ if (pci_get_max_read_req(dev) < 1024) pci_set_max_read_req(dev, 1024); /* Put controller into reset state. */ ctlr->gctl |= SIIS_GCTL_GRESET; ATA_OUTL(ctlr->r_gmem, SIIS_GCTL, ctlr->gctl); DELAY(10000); /* Get controller out of reset state and enable port interrupts. */ ctlr->gctl &= ~(SIIS_GCTL_GRESET | SIIS_GCTL_I2C_IE); ctlr->gctl |= 0x0000000f; ATA_OUTL(ctlr->r_gmem, SIIS_GCTL, ctlr->gctl); return (bus_generic_resume(dev)); } static int siis_setup_interrupt(device_t dev) { struct siis_controller *ctlr = device_get_softc(dev); int msi = ctlr->quirks & SIIS_Q_NOMSI ? 0 : 1; /* Process hints. */ resource_int_value(device_get_name(dev), device_get_unit(dev), "msi", &msi); if (msi < 0) msi = 0; else if (msi > 0) msi = min(1, pci_msi_count(dev)); /* Allocate MSI if needed/present. */ if (msi && pci_alloc_msi(dev, &msi) != 0) msi = 0; /* Allocate all IRQs. */ ctlr->irq.r_irq_rid = msi ? 1 : 0; if (!(ctlr->irq.r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &ctlr->irq.r_irq_rid, RF_SHAREABLE | RF_ACTIVE))) { device_printf(dev, "unable to map interrupt\n"); return ENXIO; } if ((bus_setup_intr(dev, ctlr->irq.r_irq, ATA_INTR_FLAGS, NULL, siis_intr, ctlr, &ctlr->irq.handle))) { /* SOS XXX release r_irq */ device_printf(dev, "unable to setup interrupt\n"); return ENXIO; } return (0); } /* * Common case interrupt handler. */ static void siis_intr(void *data) { struct siis_controller *ctlr = (struct siis_controller *)data; u_int32_t is; void *arg; int unit; is = ATA_INL(ctlr->r_gmem, SIIS_IS); for (unit = 0; unit < ctlr->channels; unit++) { if ((is & SIIS_IS_PORT(unit)) != 0 && (arg = ctlr->interrupt[unit].argument)) { ctlr->interrupt[unit].function(arg); } } /* Acknowledge interrupt, if MSI enabled. */ if (ctlr->irq.r_irq_rid) { ATA_OUTL(ctlr->r_gmem, SIIS_GCTL, ctlr->gctl | SIIS_GCTL_MSIACK); } } static struct resource * siis_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct siis_controller *ctlr = device_get_softc(dev); int unit = ((struct siis_channel *)device_get_softc(child))->unit; struct resource *res = NULL; int offset = unit << 13; rman_res_t st; switch (type) { case SYS_RES_MEMORY: st = rman_get_start(ctlr->r_mem); res = rman_reserve_resource(&ctlr->sc_iomem, st + offset, st + offset + 0x2000, 0x2000, RF_ACTIVE, child); if (res) { bus_space_handle_t bsh; bus_space_tag_t bst; bsh = rman_get_bushandle(ctlr->r_mem); bst = rman_get_bustag(ctlr->r_mem); bus_space_subregion(bst, bsh, offset, 0x2000, &bsh); rman_set_bushandle(res, bsh); rman_set_bustag(res, bst); } break; case SYS_RES_IRQ: if (*rid == ATA_IRQ_RID) res = ctlr->irq.r_irq; break; } return (res); } static int siis_release_resource(device_t dev, device_t child, struct resource *r) { switch (rman_get_type(r)) { case SYS_RES_MEMORY: rman_release_resource(r); return (0); case SYS_RES_IRQ: if (rman_get_rid(r) != ATA_IRQ_RID) return ENOENT; return (0); } return (EINVAL); } static int siis_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *function, void *argument, void **cookiep) { struct siis_controller *ctlr = device_get_softc(dev); int unit = (intptr_t)device_get_ivars(child); if (filter != NULL) { printf("siis.c: we cannot use a filter here\n"); return (EINVAL); } ctlr->interrupt[unit].function = function; ctlr->interrupt[unit].argument = argument; return (0); } static int siis_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { struct siis_controller *ctlr = device_get_softc(dev); int unit = (intptr_t)device_get_ivars(child); ctlr->interrupt[unit].function = NULL; ctlr->interrupt[unit].argument = NULL; return (0); } static int siis_print_child(device_t dev, device_t child) { int retval; retval = bus_print_child_header(dev, child); retval += printf(" at channel %d", (int)(intptr_t)device_get_ivars(child)); retval += bus_print_child_footer(dev, child); return (retval); } static int siis_child_location(device_t dev, device_t child, struct sbuf *sb) { sbuf_printf(sb, "channel=%d", (int)(intptr_t)device_get_ivars(child)); return (0); } static bus_dma_tag_t siis_get_dma_tag(device_t bus, device_t child) { return (bus_get_dma_tag(bus)); } static device_method_t siis_methods[] = { DEVMETHOD(device_probe, siis_probe), DEVMETHOD(device_attach, siis_attach), DEVMETHOD(device_detach, siis_detach), DEVMETHOD(device_suspend, siis_suspend), DEVMETHOD(device_resume, siis_resume), DEVMETHOD(bus_print_child, siis_print_child), DEVMETHOD(bus_alloc_resource, siis_alloc_resource), DEVMETHOD(bus_release_resource, siis_release_resource), DEVMETHOD(bus_setup_intr, siis_setup_intr), DEVMETHOD(bus_teardown_intr,siis_teardown_intr), DEVMETHOD(bus_child_location, siis_child_location), DEVMETHOD(bus_get_dma_tag, siis_get_dma_tag), { 0, 0 } }; static driver_t siis_driver = { "siis", siis_methods, sizeof(struct siis_controller) }; DRIVER_MODULE(siis, pci, siis_driver, 0, 0); MODULE_VERSION(siis, 1); MODULE_DEPEND(siis, cam, 1, 1, 1); static int siis_ch_probe(device_t dev) { device_set_desc(dev, "SIIS channel"); return (BUS_PROBE_DEFAULT); } static int siis_ch_attach(device_t dev) { struct siis_controller *ctlr = device_get_softc(device_get_parent(dev)); struct siis_channel *ch = device_get_softc(dev); struct cam_devq *devq; int rid, error, i, sata_rev = 0; ch->dev = dev; ch->unit = (intptr_t)device_get_ivars(dev); ch->quirks = ctlr->quirks; ch->pm_level = 0; resource_int_value(device_get_name(dev), device_get_unit(dev), "pm_level", &ch->pm_level); resource_int_value(device_get_name(dev), device_get_unit(dev), "sata_rev", &sata_rev); for (i = 0; i < 16; i++) { ch->user[i].revision = sata_rev; ch->user[i].mode = 0; ch->user[i].bytecount = 8192; ch->user[i].tags = SIIS_MAX_SLOTS; ch->curr[i] = ch->user[i]; if (ch->pm_level) ch->user[i].caps = CTS_SATA_CAPS_H_PMREQ; ch->user[i].caps |= CTS_SATA_CAPS_H_AN; } mtx_init(&ch->mtx, "SIIS channel lock", NULL, MTX_DEF); rid = ch->unit; if (!(ch->r_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE))) return (ENXIO); siis_dmainit(dev); siis_slotsalloc(dev); siis_ch_init(dev); mtx_lock(&ch->mtx); rid = ATA_IRQ_RID; if (!(ch->r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE))) { device_printf(dev, "Unable to map interrupt\n"); error = ENXIO; goto err0; } if ((bus_setup_intr(dev, ch->r_irq, ATA_INTR_FLAGS, NULL, siis_ch_intr_locked, dev, &ch->ih))) { device_printf(dev, "Unable to setup interrupt\n"); error = ENXIO; goto err1; } /* Create the device queue for our SIM. */ devq = cam_simq_alloc(SIIS_MAX_SLOTS); if (devq == NULL) { device_printf(dev, "Unable to allocate simq\n"); error = ENOMEM; goto err1; } /* Construct SIM entry */ ch->sim = cam_sim_alloc(siisaction, siispoll, "siisch", ch, device_get_unit(dev), &ch->mtx, 2, SIIS_MAX_SLOTS, devq); if (ch->sim == NULL) { cam_simq_free(devq); device_printf(dev, "unable to allocate sim\n"); error = ENOMEM; goto err1; } if (xpt_bus_register(ch->sim, dev, 0) != CAM_SUCCESS) { device_printf(dev, "unable to register xpt bus\n"); error = ENXIO; goto err2; } if (xpt_create_path(&ch->path, /*periph*/NULL, cam_sim_path(ch->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { device_printf(dev, "unable to create path\n"); error = ENXIO; goto err3; } mtx_unlock(&ch->mtx); ch->led = led_create(siis_ch_led, dev, device_get_nameunit(dev)); return (0); err3: xpt_bus_deregister(cam_sim_path(ch->sim)); err2: cam_sim_free(ch->sim, /*free_devq*/TRUE); err1: bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq); err0: bus_release_resource(dev, SYS_RES_MEMORY, ch->unit, ch->r_mem); mtx_unlock(&ch->mtx); mtx_destroy(&ch->mtx); return (error); } static int siis_ch_detach(device_t dev) { struct siis_channel *ch = device_get_softc(dev); led_destroy(ch->led); mtx_lock(&ch->mtx); xpt_async(AC_LOST_DEVICE, ch->path, NULL); xpt_free_path(ch->path); xpt_bus_deregister(cam_sim_path(ch->sim)); cam_sim_free(ch->sim, /*free_devq*/TRUE); mtx_unlock(&ch->mtx); bus_teardown_intr(dev, ch->r_irq, ch->ih); bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq); siis_ch_deinit(dev); siis_slotsfree(dev); siis_dmafini(dev); bus_release_resource(dev, SYS_RES_MEMORY, ch->unit, ch->r_mem); mtx_destroy(&ch->mtx); return (0); } static int siis_ch_init(device_t dev) { struct siis_channel *ch = device_get_softc(dev); /* Get port out of reset state. */ ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_PORT_RESET); ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_32BIT); if (ch->pm_present) ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_PME); else ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_PME); /* Enable port interrupts */ ATA_OUTL(ch->r_mem, SIIS_P_IESET, SIIS_P_IX_ENABLED); return (0); } static int siis_ch_deinit(device_t dev) { struct siis_channel *ch = device_get_softc(dev); /* Put port into reset state. */ ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_PORT_RESET); return (0); } static int siis_ch_suspend(device_t dev) { struct siis_channel *ch = device_get_softc(dev); mtx_lock(&ch->mtx); xpt_freeze_simq(ch->sim, 1); while (ch->oslots) msleep(ch, &ch->mtx, PRIBIO, "siissusp", hz/100); siis_ch_deinit(dev); mtx_unlock(&ch->mtx); return (0); } static int siis_ch_resume(device_t dev) { struct siis_channel *ch = device_get_softc(dev); mtx_lock(&ch->mtx); siis_ch_init(dev); siis_reset(dev); xpt_release_simq(ch->sim, TRUE); mtx_unlock(&ch->mtx); return (0); } static device_method_t siisch_methods[] = { DEVMETHOD(device_probe, siis_ch_probe), DEVMETHOD(device_attach, siis_ch_attach), DEVMETHOD(device_detach, siis_ch_detach), DEVMETHOD(device_suspend, siis_ch_suspend), DEVMETHOD(device_resume, siis_ch_resume), { 0, 0 } }; static driver_t siisch_driver = { "siisch", siisch_methods, sizeof(struct siis_channel) }; DRIVER_MODULE(siisch, siis, siisch_driver, 0, 0); static void siis_ch_led(void *priv, int onoff) { device_t dev; struct siis_channel *ch; dev = (device_t)priv; ch = device_get_softc(dev); if (onoff == 0) ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_LED_ON); else ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_LED_ON); } struct siis_dc_cb_args { bus_addr_t maddr; int error; }; static void siis_dmainit(device_t dev) { struct siis_channel *ch = device_get_softc(dev); struct siis_dc_cb_args dcba; /* Command area. */ if (bus_dma_tag_create(bus_get_dma_tag(dev), 1024, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, SIIS_WORK_SIZE, 1, SIIS_WORK_SIZE, 0, NULL, NULL, &ch->dma.work_tag)) goto error; if (bus_dmamem_alloc(ch->dma.work_tag, (void **)&ch->dma.work, 0, &ch->dma.work_map)) goto error; if (bus_dmamap_load(ch->dma.work_tag, ch->dma.work_map, ch->dma.work, SIIS_WORK_SIZE, siis_dmasetupc_cb, &dcba, 0) || dcba.error) { bus_dmamem_free(ch->dma.work_tag, ch->dma.work, ch->dma.work_map); goto error; } ch->dma.work_bus = dcba.maddr; /* Data area. */ if (bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, SIIS_SG_ENTRIES * PAGE_SIZE, SIIS_SG_ENTRIES, 0xFFFFFFFF, 0, busdma_lock_mutex, &ch->mtx, &ch->dma.data_tag)) { goto error; } return; error: device_printf(dev, "WARNING - DMA initialization failed\n"); siis_dmafini(dev); } static void siis_dmasetupc_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error) { struct siis_dc_cb_args *dcba = (struct siis_dc_cb_args *)xsc; if (!(dcba->error = error)) dcba->maddr = segs[0].ds_addr; } static void siis_dmafini(device_t dev) { struct siis_channel *ch = device_get_softc(dev); if (ch->dma.data_tag) { bus_dma_tag_destroy(ch->dma.data_tag); ch->dma.data_tag = NULL; } if (ch->dma.work_bus) { bus_dmamap_unload(ch->dma.work_tag, ch->dma.work_map); bus_dmamem_free(ch->dma.work_tag, ch->dma.work, ch->dma.work_map); ch->dma.work_bus = 0; ch->dma.work_map = NULL; ch->dma.work = NULL; } if (ch->dma.work_tag) { bus_dma_tag_destroy(ch->dma.work_tag); ch->dma.work_tag = NULL; } } static void siis_slotsalloc(device_t dev) { struct siis_channel *ch = device_get_softc(dev); int i; /* Alloc and setup command/dma slots */ bzero(ch->slot, sizeof(ch->slot)); for (i = 0; i < SIIS_MAX_SLOTS; i++) { struct siis_slot *slot = &ch->slot[i]; slot->dev = dev; slot->slot = i; slot->state = SIIS_SLOT_EMPTY; slot->prb_offset = SIIS_PRB_SIZE * i; slot->ccb = NULL; callout_init_mtx(&slot->timeout, &ch->mtx, 0); if (bus_dmamap_create(ch->dma.data_tag, 0, &slot->dma.data_map)) device_printf(ch->dev, "FAILURE - create data_map\n"); } } static void siis_slotsfree(device_t dev) { struct siis_channel *ch = device_get_softc(dev); int i; /* Free all dma slots */ for (i = 0; i < SIIS_MAX_SLOTS; i++) { struct siis_slot *slot = &ch->slot[i]; callout_drain(&slot->timeout); if (slot->dma.data_map) { bus_dmamap_destroy(ch->dma.data_tag, slot->dma.data_map); slot->dma.data_map = NULL; } } } static void siis_notify_events(device_t dev) { struct siis_channel *ch = device_get_softc(dev); struct cam_path *dpath; u_int32_t status; int i; if (ch->quirks & SIIS_Q_SNTF) { status = ATA_INL(ch->r_mem, SIIS_P_SNTF); ATA_OUTL(ch->r_mem, SIIS_P_SNTF, status); } else { /* * Without SNTF we have no idea which device sent notification. * If PMP is connected, assume it, else - device. */ status = (ch->pm_present) ? 0x8000 : 0x0001; } if (bootverbose) device_printf(dev, "SNTF 0x%04x\n", status); for (i = 0; i < 16; i++) { if ((status & (1 << i)) == 0) continue; if (xpt_create_path(&dpath, NULL, xpt_path_path_id(ch->path), i, 0) == CAM_REQ_CMP) { xpt_async(AC_SCSI_AEN, dpath, NULL); xpt_free_path(dpath); } } } static void siis_phy_check_events(device_t dev) { struct siis_channel *ch = device_get_softc(dev); /* If we have a connection event, deal with it */ if (ch->pm_level == 0) { u_int32_t status = ATA_INL(ch->r_mem, SIIS_P_SSTS); union ccb *ccb; if (bootverbose) { if (((status & ATA_SS_DET_MASK) == ATA_SS_DET_PHY_ONLINE) && ((status & ATA_SS_SPD_MASK) != ATA_SS_SPD_NO_SPEED) && ((status & ATA_SS_IPM_MASK) == ATA_SS_IPM_ACTIVE)) { device_printf(dev, "CONNECT requested\n"); } else device_printf(dev, "DISCONNECT requested\n"); } siis_reset(dev); if ((ccb = xpt_alloc_ccb_nowait()) == NULL) return; if (xpt_create_path(&ccb->ccb_h.path, NULL, cam_sim_path(ch->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_free_ccb(ccb); return; } xpt_rescan(ccb); } } static void siis_ch_intr_locked(void *data) { device_t dev = (device_t)data; struct siis_channel *ch = device_get_softc(dev); mtx_lock(&ch->mtx); siis_ch_intr(data); mtx_unlock(&ch->mtx); } static void siis_ch_intr(void *data) { device_t dev = (device_t)data; struct siis_channel *ch = device_get_softc(dev); uint32_t istatus, sstatus, ctx, estatus, ok; enum siis_err_type et; int i, ccs, port, tslots; mtx_assert(&ch->mtx, MA_OWNED); /* Read command statuses. */ sstatus = ATA_INL(ch->r_mem, SIIS_P_SS); ok = ch->rslots & ~sstatus; /* Complete all successful commands. */ for (i = 0; i < SIIS_MAX_SLOTS; i++) { if ((ok >> i) & 1) siis_end_transaction(&ch->slot[i], SIIS_ERR_NONE); } /* Do we have any other events? */ if ((sstatus & SIIS_P_SS_ATTN) == 0) return; /* Read and clear interrupt statuses. */ istatus = ATA_INL(ch->r_mem, SIIS_P_IS) & (0xFFFF & ~SIIS_P_IX_COMMCOMP); ATA_OUTL(ch->r_mem, SIIS_P_IS, istatus); /* Process PHY events */ if (istatus & SIIS_P_IX_PHYRDYCHG) siis_phy_check_events(dev); /* Process NOTIFY events */ if (istatus & SIIS_P_IX_SDBN) siis_notify_events(dev); /* Process command errors */ if (istatus & SIIS_P_IX_COMMERR) { estatus = ATA_INL(ch->r_mem, SIIS_P_CMDERR); ctx = ATA_INL(ch->r_mem, SIIS_P_CTX); ccs = (ctx & SIIS_P_CTX_SLOT) >> SIIS_P_CTX_SLOT_SHIFT; port = (ctx & SIIS_P_CTX_PMP) >> SIIS_P_CTX_PMP_SHIFT; //device_printf(dev, "%s ERROR ss %08x is %08x rs %08x es %d act %d port %d serr %08x\n", // __func__, sstatus, istatus, ch->rslots, estatus, ccs, port, // ATA_INL(ch->r_mem, SIIS_P_SERR)); if (!ch->recoverycmd && !ch->recovery) { xpt_freeze_simq(ch->sim, ch->numrslots); ch->recovery = 1; } if (ch->frozen) { union ccb *fccb = ch->frozen; ch->frozen = NULL; fccb->ccb_h.status &= ~CAM_STATUS_MASK; fccb->ccb_h.status |= CAM_REQUEUE_REQ | CAM_RELEASE_SIMQ; if (!(fccb->ccb_h.status & CAM_DEV_QFRZN)) { xpt_freeze_devq(fccb->ccb_h.path, 1); fccb->ccb_h.status |= CAM_DEV_QFRZN; } xpt_done(fccb); } if (estatus == SIIS_P_CMDERR_DEV || estatus == SIIS_P_CMDERR_SDB || estatus == SIIS_P_CMDERR_DATAFIS) { tslots = ch->numtslots[port]; for (i = 0; i < SIIS_MAX_SLOTS; i++) { /* XXX: requests in loading state. */ if (((ch->rslots >> i) & 1) == 0) continue; if (ch->slot[i].ccb->ccb_h.target_id != port) continue; if (tslots == 0) { /* Untagged operation. */ if (i == ccs) et = SIIS_ERR_TFE; else et = SIIS_ERR_INNOCENT; } else { /* Tagged operation. */ et = SIIS_ERR_NCQ; } siis_end_transaction(&ch->slot[i], et); } /* * We can't reinit port if there are some other * commands active, use resume to complete them. */ if (ch->rslots != 0 && !ch->recoverycmd) ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_RESUME); } else { if (estatus == SIIS_P_CMDERR_SENDFIS || estatus == SIIS_P_CMDERR_INCSTATE || estatus == SIIS_P_CMDERR_PPE || estatus == SIIS_P_CMDERR_SERVICE) { et = SIIS_ERR_SATA; } else et = SIIS_ERR_INVALID; for (i = 0; i < SIIS_MAX_SLOTS; i++) { /* XXX: requests in loading state. */ if (((ch->rslots >> i) & 1) == 0) continue; siis_end_transaction(&ch->slot[i], et); } } } } /* Must be called with channel locked. */ static int siis_check_collision(device_t dev, union ccb *ccb) { struct siis_channel *ch = device_get_softc(dev); mtx_assert(&ch->mtx, MA_OWNED); if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) { /* Tagged command while we have no supported tag free. */ if (((~ch->oslots) & (0x7fffffff >> (31 - ch->curr[ccb->ccb_h.target_id].tags))) == 0) return (1); } if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & (CAM_ATAIO_CONTROL | CAM_ATAIO_NEEDRESULT))) { /* Atomic command while anything active. */ if (ch->numrslots != 0) return (1); } /* We have some atomic command running. */ if (ch->aslots != 0) return (1); return (0); } /* Must be called with channel locked. */ static void siis_begin_transaction(device_t dev, union ccb *ccb) { struct siis_channel *ch = device_get_softc(dev); struct siis_slot *slot; int tag, tags; mtx_assert(&ch->mtx, MA_OWNED); /* Choose empty slot. */ tags = SIIS_MAX_SLOTS; if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) tags = ch->curr[ccb->ccb_h.target_id].tags; tag = fls((~ch->oslots) & (0x7fffffff >> (31 - tags))) - 1; /* Occupy chosen slot. */ slot = &ch->slot[tag]; slot->ccb = ccb; /* Update channel stats. */ ch->oslots |= (1 << slot->slot); ch->numrslots++; if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) { ch->numtslots[ccb->ccb_h.target_id]++; } if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & (CAM_ATAIO_CONTROL | CAM_ATAIO_NEEDRESULT))) ch->aslots |= (1 << slot->slot); slot->dma.nsegs = 0; /* If request moves data, setup and load SG list */ if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { slot->state = SIIS_SLOT_LOADING; bus_dmamap_load_ccb(ch->dma.data_tag, slot->dma.data_map, ccb, siis_dmasetprd, slot, 0); } else siis_execute_transaction(slot); } /* Locked by busdma engine. */ static void siis_dmasetprd(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { struct siis_slot *slot = arg; struct siis_channel *ch = device_get_softc(slot->dev); struct siis_cmd *ctp; struct siis_dma_prd *prd; int i; mtx_assert(&ch->mtx, MA_OWNED); if (error) { device_printf(slot->dev, "DMA load error\n"); if (!ch->recoverycmd) xpt_freeze_simq(ch->sim, 1); siis_end_transaction(slot, SIIS_ERR_INVALID); return; } KASSERT(nsegs <= SIIS_SG_ENTRIES, ("too many DMA segment entries\n")); slot->dma.nsegs = nsegs; if (nsegs != 0) { /* Get a piece of the workspace for this request */ ctp = (struct siis_cmd *)(ch->dma.work + slot->prb_offset); /* Fill S/G table */ if (slot->ccb->ccb_h.func_code == XPT_ATA_IO) prd = &ctp->u.ata.prd[0]; else prd = &ctp->u.atapi.prd[0]; for (i = 0; i < nsegs; i++) { prd[i].dba = htole64(segs[i].ds_addr); prd[i].dbc = htole32(segs[i].ds_len); prd[i].control = 0; } prd[nsegs - 1].control = htole32(SIIS_PRD_TRM); bus_dmamap_sync(ch->dma.data_tag, slot->dma.data_map, ((slot->ccb->ccb_h.flags & CAM_DIR_IN) ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE)); } siis_execute_transaction(slot); } /* Must be called with channel locked. */ static void siis_execute_transaction(struct siis_slot *slot) { device_t dev = slot->dev; struct siis_channel *ch = device_get_softc(dev); struct siis_cmd *ctp; union ccb *ccb = slot->ccb; u_int64_t prb_bus; mtx_assert(&ch->mtx, MA_OWNED); /* Get a piece of the workspace for this request */ ctp = (struct siis_cmd *)(ch->dma.work + slot->prb_offset); ctp->control = 0; ctp->protocol_override = 0; ctp->transfer_count = 0; /* Special handling for Soft Reset command. */ if (ccb->ccb_h.func_code == XPT_ATA_IO) { if (ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL) { ctp->control |= htole16(SIIS_PRB_SOFT_RESET); } else { ctp->control |= htole16(SIIS_PRB_PROTOCOL_OVERRIDE); if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) { ctp->protocol_override |= htole16(SIIS_PRB_PROTO_NCQ); } if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { ctp->protocol_override |= htole16(SIIS_PRB_PROTO_READ); } else if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) { ctp->protocol_override |= htole16(SIIS_PRB_PROTO_WRITE); } } } else if (ccb->ccb_h.func_code == XPT_SCSI_IO) { if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) ctp->control |= htole16(SIIS_PRB_PACKET_READ); else if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) ctp->control |= htole16(SIIS_PRB_PACKET_WRITE); } /* Special handling for Soft Reset command. */ if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL) && (ccb->ataio.cmd.control & ATA_A_RESET)) { /* Kick controller into sane state */ siis_portinit(dev); } /* Setup the FIS for this request */ if (!siis_setup_fis(dev, ctp, ccb, slot->slot)) { device_printf(ch->dev, "Setting up SATA FIS failed\n"); if (!ch->recoverycmd) xpt_freeze_simq(ch->sim, 1); siis_end_transaction(slot, SIIS_ERR_INVALID); return; } bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, BUS_DMASYNC_PREWRITE); /* Issue command to the controller. */ slot->state = SIIS_SLOT_RUNNING; ch->rslots |= (1 << slot->slot); prb_bus = ch->dma.work_bus + slot->prb_offset; ATA_OUTL(ch->r_mem, SIIS_P_CACTL(slot->slot), prb_bus); ATA_OUTL(ch->r_mem, SIIS_P_CACTH(slot->slot), prb_bus >> 32); /* Start command execution timeout */ callout_reset_sbt(&slot->timeout, SBT_1MS * ccb->ccb_h.timeout, 0, siis_timeout, slot, 0); return; } /* Must be called with channel locked. */ static void siis_process_timeout(device_t dev) { struct siis_channel *ch = device_get_softc(dev); int i; mtx_assert(&ch->mtx, MA_OWNED); if (!ch->recoverycmd && !ch->recovery) { xpt_freeze_simq(ch->sim, ch->numrslots); ch->recovery = 1; } /* Handle the rest of commands. */ for (i = 0; i < SIIS_MAX_SLOTS; i++) { /* Do we have a running request on slot? */ if (ch->slot[i].state < SIIS_SLOT_RUNNING) continue; siis_end_transaction(&ch->slot[i], SIIS_ERR_TIMEOUT); } } /* Must be called with channel locked. */ static void siis_rearm_timeout(device_t dev) { struct siis_channel *ch = device_get_softc(dev); int i; mtx_assert(&ch->mtx, MA_OWNED); for (i = 0; i < SIIS_MAX_SLOTS; i++) { struct siis_slot *slot = &ch->slot[i]; /* Do we have a running request on slot? */ if (slot->state < SIIS_SLOT_RUNNING) continue; if ((ch->toslots & (1 << i)) == 0) continue; callout_reset_sbt(&slot->timeout, SBT_1MS * slot->ccb->ccb_h.timeout, 0, siis_timeout, slot, 0); } } /* Locked by callout mechanism. */ static void siis_timeout(void *arg) { struct siis_slot *slot = arg; device_t dev = slot->dev; struct siis_channel *ch = device_get_softc(dev); union ccb *ccb = slot->ccb; mtx_assert(&ch->mtx, MA_OWNED); /* Check for stale timeout. */ if (slot->state < SIIS_SLOT_RUNNING) return; /* Handle soft-reset timeouts without doing hard-reset. */ if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL) && (ccb->ataio.cmd.control & ATA_A_RESET)) { xpt_freeze_simq(ch->sim, ch->numrslots); siis_end_transaction(slot, SIIS_ERR_TFE); return; } device_printf(dev, "Timeout on slot %d\n", slot->slot); device_printf(dev, "%s is %08x ss %08x rs %08x es %08x sts %08x serr %08x\n", __func__, ATA_INL(ch->r_mem, SIIS_P_IS), ATA_INL(ch->r_mem, SIIS_P_SS), ch->rslots, ATA_INL(ch->r_mem, SIIS_P_CMDERR), ATA_INL(ch->r_mem, SIIS_P_STS), ATA_INL(ch->r_mem, SIIS_P_SERR)); if (ch->toslots == 0) xpt_freeze_simq(ch->sim, 1); ch->toslots |= (1 << slot->slot); if ((ch->rslots & ~ch->toslots) == 0) siis_process_timeout(dev); else device_printf(dev, " ... waiting for slots %08x\n", ch->rslots & ~ch->toslots); } /* Must be called with channel locked. */ static void siis_end_transaction(struct siis_slot *slot, enum siis_err_type et) { device_t dev = slot->dev; struct siis_channel *ch = device_get_softc(dev); union ccb *ccb = slot->ccb; int lastto; mtx_assert(&ch->mtx, MA_OWNED); bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, BUS_DMASYNC_POSTWRITE); /* Read result registers to the result struct * May be incorrect if several commands finished same time, * so read only when sure or have to. */ if (ccb->ccb_h.func_code == XPT_ATA_IO) { struct ata_res *res = &ccb->ataio.res; if ((et == SIIS_ERR_TFE) || (ccb->ataio.cmd.flags & CAM_ATAIO_NEEDRESULT)) { int offs = SIIS_P_LRAM_SLOT(slot->slot) + 8; res->status = ATA_INB(ch->r_mem, offs + 2); res->error = ATA_INB(ch->r_mem, offs + 3); res->lba_low = ATA_INB(ch->r_mem, offs + 4); res->lba_mid = ATA_INB(ch->r_mem, offs + 5); res->lba_high = ATA_INB(ch->r_mem, offs + 6); res->device = ATA_INB(ch->r_mem, offs + 7); res->lba_low_exp = ATA_INB(ch->r_mem, offs + 8); res->lba_mid_exp = ATA_INB(ch->r_mem, offs + 9); res->lba_high_exp = ATA_INB(ch->r_mem, offs + 10); res->sector_count = ATA_INB(ch->r_mem, offs + 12); res->sector_count_exp = ATA_INB(ch->r_mem, offs + 13); } else bzero(res, sizeof(*res)); if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN && ch->numrslots == 1) { ccb->ataio.resid = ccb->ataio.dxfer_len - ATA_INL(ch->r_mem, SIIS_P_LRAM_SLOT(slot->slot) + 4); } } else { if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN && ch->numrslots == 1) { ccb->csio.resid = ccb->csio.dxfer_len - ATA_INL(ch->r_mem, SIIS_P_LRAM_SLOT(slot->slot) + 4); } } if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) { bus_dmamap_sync(ch->dma.data_tag, slot->dma.data_map, (ccb->ccb_h.flags & CAM_DIR_IN) ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(ch->dma.data_tag, slot->dma.data_map); } /* Set proper result status. */ if (et != SIIS_ERR_NONE || ch->recovery) { ch->eslots |= (1 << slot->slot); ccb->ccb_h.status |= CAM_RELEASE_SIMQ; } /* In case of error, freeze device for proper recovery. */ if (et != SIIS_ERR_NONE && (!ch->recoverycmd) && !(ccb->ccb_h.status & CAM_DEV_QFRZN)) { xpt_freeze_devq(ccb->ccb_h.path, 1); ccb->ccb_h.status |= CAM_DEV_QFRZN; } ccb->ccb_h.status &= ~CAM_STATUS_MASK; switch (et) { case SIIS_ERR_NONE: ccb->ccb_h.status |= CAM_REQ_CMP; if (ccb->ccb_h.func_code == XPT_SCSI_IO) ccb->csio.scsi_status = SCSI_STATUS_OK; break; case SIIS_ERR_INVALID: ch->fatalerr = 1; ccb->ccb_h.status |= CAM_REQ_INVALID; break; case SIIS_ERR_INNOCENT: ccb->ccb_h.status |= CAM_REQUEUE_REQ; break; case SIIS_ERR_TFE: case SIIS_ERR_NCQ: if (ccb->ccb_h.func_code == XPT_SCSI_IO) { ccb->ccb_h.status |= CAM_SCSI_STATUS_ERROR; ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; } else { ccb->ccb_h.status |= CAM_ATA_STATUS_ERROR; } break; case SIIS_ERR_SATA: ch->fatalerr = 1; ccb->ccb_h.status |= CAM_UNCOR_PARITY; break; case SIIS_ERR_TIMEOUT: ch->fatalerr = 1; ccb->ccb_h.status |= CAM_CMD_TIMEOUT; break; default: ccb->ccb_h.status |= CAM_REQ_CMP_ERR; } /* Free slot. */ ch->oslots &= ~(1 << slot->slot); ch->rslots &= ~(1 << slot->slot); ch->aslots &= ~(1 << slot->slot); slot->state = SIIS_SLOT_EMPTY; slot->ccb = NULL; /* Update channel stats. */ ch->numrslots--; if ((ccb->ccb_h.func_code == XPT_ATA_IO) && (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) { ch->numtslots[ccb->ccb_h.target_id]--; } /* Cancel timeout state if request completed normally. */ if (et != SIIS_ERR_TIMEOUT) { lastto = (ch->toslots == (1 << slot->slot)); ch->toslots &= ~(1 << slot->slot); if (lastto) xpt_release_simq(ch->sim, TRUE); } /* If it was our READ LOG command - process it. */ if (ccb->ccb_h.recovery_type == RECOVERY_READ_LOG) { siis_process_read_log(dev, ccb); /* If it was our REQUEST SENSE command - process it. */ } else if (ccb->ccb_h.recovery_type == RECOVERY_REQUEST_SENSE) { siis_process_request_sense(dev, ccb); /* If it was NCQ or ATAPI command error, put result on hold. */ } else if (et == SIIS_ERR_NCQ || ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR && (ccb->ccb_h.flags & CAM_DIS_AUTOSENSE) == 0)) { ch->hold[slot->slot] = ccb; ch->numhslots++; } else xpt_done(ccb); /* If we have no other active commands, ... */ if (ch->rslots == 0) { /* if there were timeouts or fatal error - reset port. */ if (ch->toslots != 0 || ch->fatalerr) { siis_reset(dev); } else { /* if we have slots in error, we can reinit port. */ if (ch->eslots != 0) siis_portinit(dev); /* if there commands on hold, we can do recovery. */ if (!ch->recoverycmd && ch->numhslots) siis_issue_recovery(dev); } /* If all the reset of commands are in timeout - abort them. */ } else if ((ch->rslots & ~ch->toslots) == 0 && et != SIIS_ERR_TIMEOUT) siis_rearm_timeout(dev); /* Unfreeze frozen command. */ if (ch->frozen && !siis_check_collision(dev, ch->frozen)) { union ccb *fccb = ch->frozen; ch->frozen = NULL; siis_begin_transaction(dev, fccb); xpt_release_simq(ch->sim, TRUE); } } static void siis_issue_recovery(device_t dev) { struct siis_channel *ch = device_get_softc(dev); union ccb *ccb; struct ccb_ataio *ataio; struct ccb_scsiio *csio; int i; /* Find some held command. */ for (i = 0; i < SIIS_MAX_SLOTS; i++) { if (ch->hold[i]) break; } if (i == SIIS_MAX_SLOTS) return; ccb = xpt_alloc_ccb_nowait(); if (ccb == NULL) { device_printf(dev, "Unable to allocate recovery command\n"); completeall: /* We can't do anything -- complete held commands. */ for (i = 0; i < SIIS_MAX_SLOTS; i++) { if (ch->hold[i] == NULL) continue; ch->hold[i]->ccb_h.status &= ~CAM_STATUS_MASK; ch->hold[i]->ccb_h.status |= CAM_RESRC_UNAVAIL; xpt_done(ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; } siis_reset(dev); return; } xpt_setup_ccb(&ccb->ccb_h, ch->hold[i]->ccb_h.path, ch->hold[i]->ccb_h.pinfo.priority); if (ch->hold[i]->ccb_h.func_code == XPT_ATA_IO) { /* READ LOG */ ccb->ccb_h.recovery_type = RECOVERY_READ_LOG; ccb->ccb_h.func_code = XPT_ATA_IO; ccb->ccb_h.flags = CAM_DIR_IN; ccb->ccb_h.timeout = 1000; /* 1s should be enough. */ ataio = &ccb->ataio; ataio->data_ptr = malloc(512, M_SIIS, M_NOWAIT); if (ataio->data_ptr == NULL) { xpt_free_ccb(ccb); device_printf(dev, "Unable to allocate memory for READ LOG command\n"); goto completeall; } ataio->dxfer_len = 512; bzero(&ataio->cmd, sizeof(ataio->cmd)); ataio->cmd.flags = CAM_ATAIO_48BIT; ataio->cmd.command = 0x2F; /* READ LOG EXT */ ataio->cmd.sector_count = 1; ataio->cmd.sector_count_exp = 0; ataio->cmd.lba_low = 0x10; ataio->cmd.lba_mid = 0; ataio->cmd.lba_mid_exp = 0; } else { /* REQUEST SENSE */ ccb->ccb_h.recovery_type = RECOVERY_REQUEST_SENSE; ccb->ccb_h.recovery_slot = i; ccb->ccb_h.func_code = XPT_SCSI_IO; ccb->ccb_h.flags = CAM_DIR_IN; ccb->ccb_h.status = 0; ccb->ccb_h.timeout = 1000; /* 1s should be enough. */ csio = &ccb->csio; csio->data_ptr = (void *)&ch->hold[i]->csio.sense_data; csio->dxfer_len = ch->hold[i]->csio.sense_len; csio->cdb_len = 6; bzero(&csio->cdb_io, sizeof(csio->cdb_io)); csio->cdb_io.cdb_bytes[0] = 0x03; csio->cdb_io.cdb_bytes[4] = csio->dxfer_len; } ch->recoverycmd = 1; siis_begin_transaction(dev, ccb); } static void siis_process_read_log(device_t dev, union ccb *ccb) { struct siis_channel *ch = device_get_softc(dev); uint8_t *data; struct ata_res *res; int i; ch->recoverycmd = 0; data = ccb->ataio.data_ptr; if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP && (data[0] & 0x80) == 0) { for (i = 0; i < SIIS_MAX_SLOTS; i++) { if (!ch->hold[i]) continue; if (ch->hold[i]->ccb_h.target_id != ccb->ccb_h.target_id) continue; if ((data[0] & 0x1F) == i) { res = &ch->hold[i]->ataio.res; res->status = data[2]; res->error = data[3]; res->lba_low = data[4]; res->lba_mid = data[5]; res->lba_high = data[6]; res->device = data[7]; res->lba_low_exp = data[8]; res->lba_mid_exp = data[9]; res->lba_high_exp = data[10]; res->sector_count = data[12]; res->sector_count_exp = data[13]; } else { ch->hold[i]->ccb_h.status &= ~CAM_STATUS_MASK; ch->hold[i]->ccb_h.status |= CAM_REQUEUE_REQ; } xpt_done(ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; } } else { if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) device_printf(dev, "Error while READ LOG EXT\n"); else if ((data[0] & 0x80) == 0) { device_printf(dev, "Non-queued command error in READ LOG EXT\n"); } for (i = 0; i < SIIS_MAX_SLOTS; i++) { if (!ch->hold[i]) continue; if (ch->hold[i]->ccb_h.target_id != ccb->ccb_h.target_id) continue; xpt_done(ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; } } free(ccb->ataio.data_ptr, M_SIIS); xpt_free_ccb(ccb); } static void siis_process_request_sense(device_t dev, union ccb *ccb) { struct siis_channel *ch = device_get_softc(dev); int i; ch->recoverycmd = 0; i = ccb->ccb_h.recovery_slot; if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { ch->hold[i]->ccb_h.status |= CAM_AUTOSNS_VALID; } else { ch->hold[i]->ccb_h.status &= ~CAM_STATUS_MASK; ch->hold[i]->ccb_h.status |= CAM_AUTOSENSE_FAIL; } xpt_done(ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; xpt_free_ccb(ccb); } static void siis_portinit(device_t dev) { struct siis_channel *ch = device_get_softc(dev); int i; ch->eslots = 0; ch->recovery = 0; ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_RESUME); for (i = 0; i < 16; i++) { ATA_OUTL(ch->r_mem, SIIS_P_PMPSTS(i), 0), ATA_OUTL(ch->r_mem, SIIS_P_PMPQACT(i), 0); } ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_PORT_INIT); siis_wait_ready(dev, 1000); } static int siis_devreset(device_t dev) { struct siis_channel *ch = device_get_softc(dev); int timeout = 0; uint32_t val; ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_DEV_RESET); while (((val = ATA_INL(ch->r_mem, SIIS_P_STS)) & SIIS_P_CTL_DEV_RESET) != 0) { DELAY(100); if (timeout++ > 1000) { device_printf(dev, "device reset stuck " "(timeout 100ms) status = %08x\n", val); return (EBUSY); } } return (0); } static int siis_wait_ready(device_t dev, int t) { struct siis_channel *ch = device_get_softc(dev); int timeout = 0; uint32_t val; while (((val = ATA_INL(ch->r_mem, SIIS_P_STS)) & SIIS_P_CTL_READY) == 0) { DELAY(1000); if (timeout++ > t) { device_printf(dev, "port is not ready (timeout %dms) " "status = %08x\n", t, val); return (EBUSY); } } return (0); } static void siis_reset(device_t dev) { struct siis_channel *ch = device_get_softc(dev); int i, retry = 0, sata_rev; uint32_t val; xpt_freeze_simq(ch->sim, 1); if (bootverbose) device_printf(dev, "SIIS reset...\n"); if (!ch->recoverycmd && !ch->recovery) xpt_freeze_simq(ch->sim, ch->numrslots); /* Requeue frozen command. */ if (ch->frozen) { union ccb *fccb = ch->frozen; ch->frozen = NULL; fccb->ccb_h.status &= ~CAM_STATUS_MASK; fccb->ccb_h.status |= CAM_REQUEUE_REQ | CAM_RELEASE_SIMQ; if (!(fccb->ccb_h.status & CAM_DEV_QFRZN)) { xpt_freeze_devq(fccb->ccb_h.path, 1); fccb->ccb_h.status |= CAM_DEV_QFRZN; } xpt_done(fccb); } /* Requeue all running commands. */ for (i = 0; i < SIIS_MAX_SLOTS; i++) { /* Do we have a running request on slot? */ if (ch->slot[i].state < SIIS_SLOT_RUNNING) continue; /* XXX; Commands in loading state. */ siis_end_transaction(&ch->slot[i], SIIS_ERR_INNOCENT); } /* Finish all held commands as-is. */ for (i = 0; i < SIIS_MAX_SLOTS; i++) { if (!ch->hold[i]) continue; xpt_done(ch->hold[i]); ch->hold[i] = NULL; ch->numhslots--; } if (ch->toslots != 0) xpt_release_simq(ch->sim, TRUE); ch->eslots = 0; ch->recovery = 0; ch->toslots = 0; ch->fatalerr = 0; /* Disable port interrupts */ ATA_OUTL(ch->r_mem, SIIS_P_IECLR, 0x0000FFFF); /* Set speed limit. */ sata_rev = ch->user[ch->pm_present ? 15 : 0].revision; if (sata_rev == 1) val = ATA_SC_SPD_SPEED_GEN1; else if (sata_rev == 2) val = ATA_SC_SPD_SPEED_GEN2; else if (sata_rev == 3) val = ATA_SC_SPD_SPEED_GEN3; else val = 0; ATA_OUTL(ch->r_mem, SIIS_P_SCTL, ATA_SC_DET_IDLE | val | ((ch->pm_level > 0) ? 0 : (ATA_SC_IPM_DIS_PARTIAL | ATA_SC_IPM_DIS_SLUMBER))); retry: siis_devreset(dev); /* Reset and reconnect PHY, */ if (!siis_sata_connect(ch)) { ch->devices = 0; /* Enable port interrupts */ ATA_OUTL(ch->r_mem, SIIS_P_IESET, SIIS_P_IX_ENABLED); if (bootverbose) device_printf(dev, "SIIS reset done: phy reset found no device\n"); /* Tell the XPT about the event */ xpt_async(AC_BUS_RESET, ch->path, NULL); xpt_release_simq(ch->sim, TRUE); return; } /* Wait for port ready status. */ if (siis_wait_ready(dev, 1000)) { device_printf(dev, "port ready timeout\n"); if (!retry) { device_printf(dev, "trying full port reset ...\n"); /* Get port to the reset state. */ ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_PORT_RESET); DELAY(10000); /* Get port out of reset state. */ ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_PORT_RESET); ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_32BIT); if (ch->pm_present) ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_PME); else ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_PME); siis_wait_ready(dev, 5000); retry = 1; goto retry; } } ch->devices = 1; /* Enable port interrupts */ ATA_OUTL(ch->r_mem, SIIS_P_IS, 0xFFFFFFFF); ATA_OUTL(ch->r_mem, SIIS_P_IESET, SIIS_P_IX_ENABLED); if (bootverbose) device_printf(dev, "SIIS reset done: devices=%08x\n", ch->devices); /* Tell the XPT about the event */ xpt_async(AC_BUS_RESET, ch->path, NULL); xpt_release_simq(ch->sim, TRUE); } static int siis_setup_fis(device_t dev, struct siis_cmd *ctp, union ccb *ccb, int tag) { struct siis_channel *ch = device_get_softc(dev); u_int8_t *fis = &ctp->fis[0]; bzero(fis, 24); fis[0] = 0x27; /* host to device */ fis[1] = (ccb->ccb_h.target_id & 0x0f); if (ccb->ccb_h.func_code == XPT_SCSI_IO) { fis[1] |= 0x80; fis[2] = ATA_PACKET_CMD; if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE && ch->curr[ccb->ccb_h.target_id].mode >= ATA_DMA) fis[3] = ATA_F_DMA; else { fis[5] = ccb->csio.dxfer_len; fis[6] = ccb->csio.dxfer_len >> 8; } fis[7] = ATA_D_LBA; fis[15] = ATA_A_4BIT; bzero(ctp->u.atapi.ccb, 16); bcopy((ccb->ccb_h.flags & CAM_CDB_POINTER) ? ccb->csio.cdb_io.cdb_ptr : ccb->csio.cdb_io.cdb_bytes, ctp->u.atapi.ccb, ccb->csio.cdb_len); } else if ((ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL) == 0) { fis[1] |= 0x80; fis[2] = ccb->ataio.cmd.command; fis[3] = ccb->ataio.cmd.features; fis[4] = ccb->ataio.cmd.lba_low; fis[5] = ccb->ataio.cmd.lba_mid; fis[6] = ccb->ataio.cmd.lba_high; fis[7] = ccb->ataio.cmd.device; fis[8] = ccb->ataio.cmd.lba_low_exp; fis[9] = ccb->ataio.cmd.lba_mid_exp; fis[10] = ccb->ataio.cmd.lba_high_exp; fis[11] = ccb->ataio.cmd.features_exp; fis[12] = ccb->ataio.cmd.sector_count; if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) { fis[12] &= 0x07; fis[12] |= tag << 3; } fis[13] = ccb->ataio.cmd.sector_count_exp; if (ccb->ataio.ata_flags & ATA_FLAG_ICC) fis[14] = ccb->ataio.icc; fis[15] = ATA_A_4BIT; if (ccb->ataio.ata_flags & ATA_FLAG_AUX) { fis[16] = ccb->ataio.aux & 0xff; fis[17] = (ccb->ataio.aux >> 8) & 0xff; fis[18] = (ccb->ataio.aux >> 16) & 0xff; fis[19] = (ccb->ataio.aux >> 24) & 0xff; } } else { /* Soft reset. */ } return (20); } static int siis_sata_connect(struct siis_channel *ch) { u_int32_t status; int timeout, found = 0; /* Wait up to 100ms for "connect well" */ for (timeout = 0; timeout < 1000 ; timeout++) { status = ATA_INL(ch->r_mem, SIIS_P_SSTS); if ((status & ATA_SS_DET_MASK) != ATA_SS_DET_NO_DEVICE) found = 1; if (((status & ATA_SS_DET_MASK) == ATA_SS_DET_PHY_ONLINE) && ((status & ATA_SS_SPD_MASK) != ATA_SS_SPD_NO_SPEED) && ((status & ATA_SS_IPM_MASK) == ATA_SS_IPM_ACTIVE)) break; if ((status & ATA_SS_DET_MASK) == ATA_SS_DET_PHY_OFFLINE) { if (bootverbose) { device_printf(ch->dev, "SATA offline status=%08x\n", status); } return (0); } if (found == 0 && timeout >= 100) break; DELAY(100); } if (timeout >= 1000 || !found) { if (bootverbose) { device_printf(ch->dev, "SATA connect timeout time=%dus status=%08x\n", timeout * 100, status); } return (0); } if (bootverbose) { device_printf(ch->dev, "SATA connect time=%dus status=%08x\n", timeout * 100, status); } /* Clear SATA error register */ ATA_OUTL(ch->r_mem, SIIS_P_SERR, 0xffffffff); return (1); } static int siis_check_ids(device_t dev, union ccb *ccb) { if (ccb->ccb_h.target_id > 15) { ccb->ccb_h.status = CAM_TID_INVALID; xpt_done(ccb); return (-1); } if (ccb->ccb_h.target_lun != 0) { ccb->ccb_h.status = CAM_LUN_INVALID; xpt_done(ccb); return (-1); } return (0); } static void siisaction(struct cam_sim *sim, union ccb *ccb) { device_t dev, parent; struct siis_channel *ch; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("siisaction func_code=%x\n", ccb->ccb_h.func_code)); ch = (struct siis_channel *)cam_sim_softc(sim); dev = ch->dev; mtx_assert(&ch->mtx, MA_OWNED); switch (ccb->ccb_h.func_code) { /* Common cases first */ case XPT_ATA_IO: /* Execute the requested I/O operation */ case XPT_SCSI_IO: if (siis_check_ids(dev, ccb)) return; if (ch->devices == 0 || (ch->pm_present == 0 && ccb->ccb_h.target_id > 0 && ccb->ccb_h.target_id < 15)) { ccb->ccb_h.status = CAM_SEL_TIMEOUT; break; } ccb->ccb_h.recovery_type = RECOVERY_NONE; /* Check for command collision. */ if (siis_check_collision(dev, ccb)) { /* Freeze command. */ ch->frozen = ccb; /* We have only one frozen slot, so freeze simq also. */ xpt_freeze_simq(ch->sim, 1); return; } siis_begin_transaction(dev, ccb); return; case XPT_ABORT: /* Abort the specified CCB */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; break; case XPT_SET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; struct siis_device *d; if (siis_check_ids(dev, ccb)) return; if (cts->type == CTS_TYPE_CURRENT_SETTINGS) d = &ch->curr[ccb->ccb_h.target_id]; else d = &ch->user[ccb->ccb_h.target_id]; if (cts->xport_specific.sata.valid & CTS_SATA_VALID_REVISION) d->revision = cts->xport_specific.sata.revision; if (cts->xport_specific.sata.valid & CTS_SATA_VALID_MODE) d->mode = cts->xport_specific.sata.mode; if (cts->xport_specific.sata.valid & CTS_SATA_VALID_BYTECOUNT) d->bytecount = min(8192, cts->xport_specific.sata.bytecount); if (cts->xport_specific.sata.valid & CTS_SATA_VALID_TAGS) d->tags = min(SIIS_MAX_SLOTS, cts->xport_specific.sata.tags); if (cts->xport_specific.sata.valid & CTS_SATA_VALID_PM) { ch->pm_present = cts->xport_specific.sata.pm_present; if (ch->pm_present) ATA_OUTL(ch->r_mem, SIIS_P_CTLSET, SIIS_P_CTL_PME); else ATA_OUTL(ch->r_mem, SIIS_P_CTLCLR, SIIS_P_CTL_PME); } if (cts->xport_specific.sata.valid & CTS_SATA_VALID_TAGS) d->atapi = cts->xport_specific.sata.atapi; if (cts->xport_specific.sata.valid & CTS_SATA_VALID_CAPS) d->caps = cts->xport_specific.sata.caps; ccb->ccb_h.status = CAM_REQ_CMP; break; } case XPT_GET_TRAN_SETTINGS: /* Get default/user set transfer settings for the target */ { struct ccb_trans_settings *cts = &ccb->cts; struct siis_device *d; uint32_t status; if (siis_check_ids(dev, ccb)) return; if (cts->type == CTS_TYPE_CURRENT_SETTINGS) d = &ch->curr[ccb->ccb_h.target_id]; else d = &ch->user[ccb->ccb_h.target_id]; cts->protocol = PROTO_UNSPECIFIED; cts->protocol_version = PROTO_VERSION_UNSPECIFIED; cts->transport = XPORT_SATA; cts->transport_version = XPORT_VERSION_UNSPECIFIED; cts->proto_specific.valid = 0; cts->xport_specific.sata.valid = 0; if (cts->type == CTS_TYPE_CURRENT_SETTINGS && (ccb->ccb_h.target_id == 15 || (ccb->ccb_h.target_id == 0 && !ch->pm_present))) { status = ATA_INL(ch->r_mem, SIIS_P_SSTS) & ATA_SS_SPD_MASK; if (status & 0x0f0) { cts->xport_specific.sata.revision = (status & 0x0f0) >> 4; cts->xport_specific.sata.valid |= CTS_SATA_VALID_REVISION; } cts->xport_specific.sata.caps = d->caps & CTS_SATA_CAPS_D; if (ch->pm_level) cts->xport_specific.sata.caps |= CTS_SATA_CAPS_H_PMREQ; cts->xport_specific.sata.caps |= CTS_SATA_CAPS_H_AN; cts->xport_specific.sata.caps &= ch->user[ccb->ccb_h.target_id].caps; cts->xport_specific.sata.valid |= CTS_SATA_VALID_CAPS; } else { cts->xport_specific.sata.revision = d->revision; cts->xport_specific.sata.valid |= CTS_SATA_VALID_REVISION; cts->xport_specific.sata.caps = d->caps; if (cts->type == CTS_TYPE_CURRENT_SETTINGS && (ch->quirks & SIIS_Q_SNTF) == 0) cts->xport_specific.sata.caps &= ~CTS_SATA_CAPS_H_AN; cts->xport_specific.sata.valid |= CTS_SATA_VALID_CAPS; } cts->xport_specific.sata.mode = d->mode; cts->xport_specific.sata.valid |= CTS_SATA_VALID_MODE; cts->xport_specific.sata.bytecount = d->bytecount; cts->xport_specific.sata.valid |= CTS_SATA_VALID_BYTECOUNT; cts->xport_specific.sata.pm_present = ch->pm_present; cts->xport_specific.sata.valid |= CTS_SATA_VALID_PM; cts->xport_specific.sata.tags = d->tags; cts->xport_specific.sata.valid |= CTS_SATA_VALID_TAGS; cts->xport_specific.sata.atapi = d->atapi; cts->xport_specific.sata.valid |= CTS_SATA_VALID_ATAPI; ccb->ccb_h.status = CAM_REQ_CMP; break; } case XPT_RESET_BUS: /* Reset the specified SCSI bus */ case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */ siis_reset(dev); ccb->ccb_h.status = CAM_REQ_CMP; break; case XPT_TERM_IO: /* Terminate the I/O process */ /* XXX Implement */ ccb->ccb_h.status = CAM_REQ_INVALID; break; case XPT_PATH_INQ: /* Path routing inquiry */ { struct ccb_pathinq *cpi = &ccb->cpi; parent = device_get_parent(dev); cpi->version_num = 1; /* XXX??? */ cpi->hba_inquiry = PI_SDTR_ABLE | PI_TAG_ABLE; cpi->hba_inquiry |= PI_SATAPM; cpi->target_sprt = 0; cpi->hba_misc = PIM_SEQSCAN | PIM_UNMAPPED | PIM_ATA_EXT; cpi->hba_eng_cnt = 0; cpi->max_target = 15; cpi->max_lun = 0; cpi->initiator_id = 0; cpi->bus_id = cam_sim_bus(sim); cpi->base_transfer_speed = 150000; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "SIIS", HBA_IDLEN); strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->transport = XPORT_SATA; cpi->transport_version = XPORT_VERSION_UNSPECIFIED; cpi->protocol = PROTO_ATA; cpi->protocol_version = PROTO_VERSION_UNSPECIFIED; cpi->maxio = maxphys; cpi->hba_vendor = pci_get_vendor(parent); cpi->hba_device = pci_get_device(parent); cpi->hba_subvendor = pci_get_subvendor(parent); cpi->hba_subdevice = pci_get_subdevice(parent); cpi->ccb_h.status = CAM_REQ_CMP; break; } default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); } static void siispoll(struct cam_sim *sim) { struct siis_channel *ch = (struct siis_channel *)cam_sim_softc(sim); siis_ch_intr(ch->dev); } diff --git a/sys/dev/sound/pci/hda/hdaa.c b/sys/dev/sound/pci/hda/hdaa.c index 2fab3ae014d1..51d6e024225f 100644 --- a/sys/dev/sound/pci/hda/hdaa.c +++ b/sys/dev/sound/pci/hda/hdaa.c @@ -1,7145 +1,7145 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 Stephane E. Potvin * Copyright (c) 2006 Ariff Abdullah * Copyright (c) 2008-2012 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Intel High Definition Audio (Audio function) driver for FreeBSD. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include #include #include #include "mixer_if.h" #define hdaa_lock(devinfo) snd_mtxlock((devinfo)->lock) #define hdaa_unlock(devinfo) snd_mtxunlock((devinfo)->lock) #define hdaa_lockassert(devinfo) snd_mtxassert((devinfo)->lock) static const struct { const char *key; uint32_t value; } hdaa_quirks_tab[] = { { "softpcmvol", HDAA_QUIRK_SOFTPCMVOL }, { "fixedrate", HDAA_QUIRK_FIXEDRATE }, { "forcestereo", HDAA_QUIRK_FORCESTEREO }, { "eapdinv", HDAA_QUIRK_EAPDINV }, { "senseinv", HDAA_QUIRK_SENSEINV }, { "ivref50", HDAA_QUIRK_IVREF50 }, { "ivref80", HDAA_QUIRK_IVREF80 }, { "ivref100", HDAA_QUIRK_IVREF100 }, { "ovref50", HDAA_QUIRK_OVREF50 }, { "ovref80", HDAA_QUIRK_OVREF80 }, { "ovref100", HDAA_QUIRK_OVREF100 }, { "ivref", HDAA_QUIRK_IVREF }, { "ovref", HDAA_QUIRK_OVREF }, { "vref", HDAA_QUIRK_VREF }, }; #define HDA_PARSE_MAXDEPTH 10 MALLOC_DEFINE(M_HDAA, "hdaa", "HDA Audio"); static const char *HDA_COLORS[16] = {"Unknown", "Black", "Grey", "Blue", "Green", "Red", "Orange", "Yellow", "Purple", "Pink", "Res.A", "Res.B", "Res.C", "Res.D", "White", "Other"}; static const char *HDA_DEVS[16] = {"Line-out", "Speaker", "Headphones", "CD", "SPDIF-out", "Digital-out", "Modem-line", "Modem-handset", "Line-in", "AUX", "Mic", "Telephony", "SPDIF-in", "Digital-in", "Res.E", "Other"}; static const char *HDA_CONNS[4] = {"Jack", "None", "Fixed", "Both"}; static const char *HDA_CONNECTORS[16] = { "Unknown", "1/8", "1/4", "ATAPI", "RCA", "Optical", "Digital", "Analog", "DIN", "XLR", "RJ-11", "Combo", "0xc", "0xd", "0xe", "Other" }; static const char *HDA_LOCS[64] = { "0x00", "Rear", "Front", "Left", "Right", "Top", "Bottom", "Rear-panel", "Drive-bay", "0x09", "0x0a", "0x0b", "0x0c", "0x0d", "0x0e", "0x0f", "Internal", "0x11", "0x12", "0x13", "0x14", "0x15", "0x16", "Riser", "0x18", "Onboard", "0x1a", "0x1b", "0x1c", "0x1d", "0x1e", "0x1f", "External", "Ext-Rear", "Ext-Front", "Ext-Left", "Ext-Right", "Ext-Top", "Ext-Bottom", "0x07", "0x28", "0x29", "0x2a", "0x2b", "0x2c", "0x2d", "0x2e", "0x2f", "Other", "0x31", "0x32", "0x33", "0x34", "0x35", "Other-Bott", "Lid-In", "Lid-Out", "0x39", "0x3a", "0x3b", "0x3c", "0x3d", "0x3e", "0x3f" }; static const char *HDA_GPIO_ACTIONS[8] = { "keep", "set", "clear", "disable", "input", "0x05", "0x06", "0x07"}; static const char *HDA_HDMI_CODING_TYPES[18] = { "undefined", "LPCM", "AC-3", "MPEG1", "MP3", "MPEG2", "AAC-LC", "DTS", "ATRAC", "DSD", "E-AC-3", "DTS-HD", "MLP", "DST", "WMAPro", "HE-AAC", "HE-AACv2", "MPEG-Surround" }; /* Default */ static uint32_t hdaa_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps hdaa_caps = {48000, 48000, hdaa_fmt, 0}; static const struct { uint32_t rate; int valid; uint16_t base; uint16_t mul; uint16_t div; } hda_rate_tab[] = { { 8000, 1, 0x0000, 0x0000, 0x0500 }, /* (48000 * 1) / 6 */ { 9600, 0, 0x0000, 0x0000, 0x0400 }, /* (48000 * 1) / 5 */ { 12000, 0, 0x0000, 0x0000, 0x0300 }, /* (48000 * 1) / 4 */ { 16000, 1, 0x0000, 0x0000, 0x0200 }, /* (48000 * 1) / 3 */ { 18000, 0, 0x0000, 0x1000, 0x0700 }, /* (48000 * 3) / 8 */ { 19200, 0, 0x0000, 0x0800, 0x0400 }, /* (48000 * 2) / 5 */ { 24000, 0, 0x0000, 0x0000, 0x0100 }, /* (48000 * 1) / 2 */ { 28800, 0, 0x0000, 0x1000, 0x0400 }, /* (48000 * 3) / 5 */ { 32000, 1, 0x0000, 0x0800, 0x0200 }, /* (48000 * 2) / 3 */ { 36000, 0, 0x0000, 0x1000, 0x0300 }, /* (48000 * 3) / 4 */ { 38400, 0, 0x0000, 0x1800, 0x0400 }, /* (48000 * 4) / 5 */ { 48000, 1, 0x0000, 0x0000, 0x0000 }, /* (48000 * 1) / 1 */ { 64000, 0, 0x0000, 0x1800, 0x0200 }, /* (48000 * 4) / 3 */ { 72000, 0, 0x0000, 0x1000, 0x0100 }, /* (48000 * 3) / 2 */ { 96000, 1, 0x0000, 0x0800, 0x0000 }, /* (48000 * 2) / 1 */ { 144000, 0, 0x0000, 0x1000, 0x0000 }, /* (48000 * 3) / 1 */ { 192000, 1, 0x0000, 0x1800, 0x0000 }, /* (48000 * 4) / 1 */ { 8820, 0, 0x4000, 0x0000, 0x0400 }, /* (44100 * 1) / 5 */ { 11025, 1, 0x4000, 0x0000, 0x0300 }, /* (44100 * 1) / 4 */ { 12600, 0, 0x4000, 0x0800, 0x0600 }, /* (44100 * 2) / 7 */ { 14700, 0, 0x4000, 0x0000, 0x0200 }, /* (44100 * 1) / 3 */ { 17640, 0, 0x4000, 0x0800, 0x0400 }, /* (44100 * 2) / 5 */ { 18900, 0, 0x4000, 0x1000, 0x0600 }, /* (44100 * 3) / 7 */ { 22050, 1, 0x4000, 0x0000, 0x0100 }, /* (44100 * 1) / 2 */ { 25200, 0, 0x4000, 0x1800, 0x0600 }, /* (44100 * 4) / 7 */ { 26460, 0, 0x4000, 0x1000, 0x0400 }, /* (44100 * 3) / 5 */ { 29400, 0, 0x4000, 0x0800, 0x0200 }, /* (44100 * 2) / 3 */ { 33075, 0, 0x4000, 0x1000, 0x0300 }, /* (44100 * 3) / 4 */ { 35280, 0, 0x4000, 0x1800, 0x0400 }, /* (44100 * 4) / 5 */ { 44100, 1, 0x4000, 0x0000, 0x0000 }, /* (44100 * 1) / 1 */ { 58800, 0, 0x4000, 0x1800, 0x0200 }, /* (44100 * 4) / 3 */ { 66150, 0, 0x4000, 0x1000, 0x0100 }, /* (44100 * 3) / 2 */ { 88200, 1, 0x4000, 0x0800, 0x0000 }, /* (44100 * 2) / 1 */ { 132300, 0, 0x4000, 0x1000, 0x0000 }, /* (44100 * 3) / 1 */ { 176400, 1, 0x4000, 0x1800, 0x0000 }, /* (44100 * 4) / 1 */ }; #define HDA_RATE_TAB_LEN (sizeof(hda_rate_tab) / sizeof(hda_rate_tab[0])) const static char *ossnames[] = SOUND_DEVICE_NAMES; /**************************************************************************** * Function prototypes ****************************************************************************/ static int hdaa_pcmchannel_setup(struct hdaa_chan *); static void hdaa_widget_connection_select(struct hdaa_widget *, uint8_t); static void hdaa_audio_ctl_amp_set(struct hdaa_audio_ctl *, uint32_t, int, int); static struct hdaa_audio_ctl *hdaa_audio_ctl_amp_get(struct hdaa_devinfo *, nid_t, int, int, int); static void hdaa_audio_ctl_amp_set_internal(struct hdaa_devinfo *, nid_t, int, int, int, int, int, int); static void hdaa_dump_pin_config(struct hdaa_widget *w, uint32_t conf); static char * hdaa_audio_ctl_ossmixer_mask2allname(uint32_t mask, char *buf, size_t len) { int i, first = 1; bzero(buf, len); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (mask & (1 << i)) { if (first == 0) strlcat(buf, ", ", len); strlcat(buf, ossnames[i], len); first = 0; } } return (buf); } static struct hdaa_audio_ctl * hdaa_audio_ctl_each(struct hdaa_devinfo *devinfo, int *index) { if (devinfo == NULL || index == NULL || devinfo->ctl == NULL || devinfo->ctlcnt < 1 || *index < 0 || *index >= devinfo->ctlcnt) return (NULL); return (&devinfo->ctl[(*index)++]); } static struct hdaa_audio_ctl * hdaa_audio_ctl_amp_get(struct hdaa_devinfo *devinfo, nid_t nid, int dir, int index, int cnt) { struct hdaa_audio_ctl *ctl; int i, found = 0; if (devinfo == NULL || devinfo->ctl == NULL) return (NULL); i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0) continue; if (ctl->widget->nid != nid) continue; if (dir && ctl->ndir != dir) continue; if (index >= 0 && ctl->ndir == HDAA_CTL_IN && ctl->dir == ctl->ndir && ctl->index != index) continue; found++; if (found == cnt || cnt <= 0) return (ctl); } return (NULL); } static const struct matrix { struct pcmchan_matrix m; int analog; } matrixes[] = { { SND_CHN_MATRIX_MAP_1_0, 1 }, { SND_CHN_MATRIX_MAP_2_0, 1 }, { SND_CHN_MATRIX_MAP_2_1, 0 }, { SND_CHN_MATRIX_MAP_3_0, 0 }, { SND_CHN_MATRIX_MAP_3_1, 0 }, { SND_CHN_MATRIX_MAP_4_0, 1 }, { SND_CHN_MATRIX_MAP_4_1, 0 }, { SND_CHN_MATRIX_MAP_5_0, 0 }, { SND_CHN_MATRIX_MAP_5_1, 1 }, { SND_CHN_MATRIX_MAP_6_0, 0 }, { SND_CHN_MATRIX_MAP_6_1, 0 }, { SND_CHN_MATRIX_MAP_7_0, 0 }, { SND_CHN_MATRIX_MAP_7_1, 1 }, }; static const char *channel_names[] = SND_CHN_T_NAMES; /* * Connected channels change handler. */ static void hdaa_channels_handler(struct hdaa_audio_as *as) { struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo; struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_chan *ch = &devinfo->chans[as->chans[0]]; struct hdaa_widget *w; uint8_t *eld; int total, sub, assume, channels; size_t i; uint16_t cpins, upins, tpins; cpins = upins = 0; eld = NULL; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; w = hdaa_widget_get(devinfo, as->pins[i]); if (w == NULL) continue; if (w->wclass.pin.connected == 1) cpins |= (1 << i); else if (w->wclass.pin.connected != 0) upins |= (1 << i); if (w->eld != NULL && w->eld_len >= 8) eld = w->eld; } tpins = cpins | upins; if (as->hpredir >= 0) tpins &= 0x7fff; if (tpins == 0) tpins = as->pinset; total = sub = assume = channels = 0; if (eld) { /* Map CEA speakers to sound(4) channels. */ if (eld[7] & 0x01) /* Front Left/Right */ channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR; if (eld[7] & 0x02) /* Low Frequency Effect */ channels |= SND_CHN_T_MASK_LF; if (eld[7] & 0x04) /* Front Center */ channels |= SND_CHN_T_MASK_FC; if (eld[7] & 0x08) { /* Rear Left/Right */ /* If we have both RLR and RLRC, report RLR as side. */ if (eld[7] & 0x40) /* Rear Left/Right Center */ channels |= SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR; else channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; } if (eld[7] & 0x10) /* Rear center */ channels |= SND_CHN_T_MASK_BC; if (eld[7] & 0x20) /* Front Left/Right Center */ channels |= SND_CHN_T_MASK_FLC | SND_CHN_T_MASK_FRC; if (eld[7] & 0x40) /* Rear Left/Right Center */ channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; } else if (as->pinset != 0 && (tpins & 0xffe0) == 0) { /* Map UAA speakers to sound(4) channels. */ if (tpins & 0x0001) channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR; if (tpins & 0x0002) channels |= SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF; if (tpins & 0x0004) channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; if (tpins & 0x0008) channels |= SND_CHN_T_MASK_FLC | SND_CHN_T_MASK_FRC; if (tpins & 0x0010) { /* If there is no back pin, report side as back. */ if ((as->pinset & 0x0004) == 0) channels |= SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR; else channels |= SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR; } } else if (as->mixed) { /* Mixed assoc can be only stereo or theoretically mono. */ if (ch->channels == 1) channels |= SND_CHN_T_MASK_FC; else channels |= SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR; } if (channels) { /* We have some usable channels info. */ HDA_BOOTVERBOSE( device_printf(pdevinfo->dev, "%s channel set is: ", as->dir == HDAA_CTL_OUT ? "Playback" : "Recording"); for (i = 0; i < SND_CHN_T_MAX; i++) if (channels & (1 << i)) printf("%s, ", channel_names[i]); printf("\n"); ); /* Look for maximal fitting matrix. */ for (i = 0; i < nitems(matrixes); i++) { if (as->pinset != 0 && matrixes[i].analog == 0) continue; if ((matrixes[i].m.mask & ~channels) == 0) { total = matrixes[i].m.channels; sub = matrixes[i].m.ext; } } } if (total == 0) { assume = 1; total = ch->channels; sub = (total == 6 || total == 8) ? 1 : 0; } HDA_BOOTVERBOSE( device_printf(pdevinfo->dev, "%s channel matrix is: %s%d.%d (%s)\n", as->dir == HDAA_CTL_OUT ? "Playback" : "Recording", assume ? "unknown, assuming " : "", total - sub, sub, cpins != 0 ? "connected" : (upins != 0 ? "unknown" : "disconnected")); ); } /* * Headphones redirection change handler. */ static void hdaa_hpredir_handler(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; struct hdaa_audio_as *as = &devinfo->as[w->bindas]; struct hdaa_widget *w1; struct hdaa_audio_ctl *ctl; uint32_t val; int j, connected = w->wclass.pin.connected; HDA_BOOTVERBOSE( device_printf((as->pdevinfo && as->pdevinfo->dev) ? as->pdevinfo->dev : devinfo->dev, "Redirect output to: %s\n", connected ? "headphones": "main"); ); /* (Un)Mute headphone pin. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, -1, 1); if (ctl != NULL && ctl->mute) { /* If pin has muter - use it. */ val = connected ? 0 : 1; if (val != ctl->forcemute) { ctl->forcemute = val; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_DEFAULT, HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT); } } else { /* If there is no muter - disable pin output. */ if (connected) val = w->wclass.pin.ctrl | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; else val = w->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val != w->wclass.pin.ctrl) { w->wclass.pin.ctrl = val; hda_command(devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, w->nid, w->wclass.pin.ctrl)); } } /* (Un)Mute other pins. */ for (j = 0; j < 15; j++) { if (as->pins[j] <= 0) continue; ctl = hdaa_audio_ctl_amp_get(devinfo, as->pins[j], HDAA_CTL_IN, -1, 1); if (ctl != NULL && ctl->mute) { /* If pin has muter - use it. */ val = connected ? 1 : 0; if (val == ctl->forcemute) continue; ctl->forcemute = val; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_DEFAULT, HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT); continue; } /* If there is no muter - disable pin output. */ w1 = hdaa_widget_get(devinfo, as->pins[j]); if (w1 != NULL) { if (connected) val = w1->wclass.pin.ctrl & ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; else val = w1->wclass.pin.ctrl | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (val != w1->wclass.pin.ctrl) { w1->wclass.pin.ctrl = val; hda_command(devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, w1->nid, w1->wclass.pin.ctrl)); } } } } /* * Recording source change handler. */ static void hdaa_autorecsrc_handler(struct hdaa_audio_as *as, struct hdaa_widget *w) { struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo; struct hdaa_devinfo *devinfo; struct hdaa_widget *w1; int i, mask, fullmask, prio, bestprio; char buf[128]; if (!as->mixed || pdevinfo == NULL || pdevinfo->mixer == NULL) return; /* Don't touch anything if we asked not to. */ if (pdevinfo->autorecsrc == 0 || (pdevinfo->autorecsrc == 1 && w != NULL)) return; /* Don't touch anything if "mix" or "speaker" selected. */ if (pdevinfo->recsrc & (SOUND_MASK_IMIX | SOUND_MASK_SPEAKER)) return; /* Don't touch anything if several selected. */ if (ffs(pdevinfo->recsrc) != fls(pdevinfo->recsrc)) return; devinfo = pdevinfo->devinfo; mask = fullmask = 0; bestprio = 0; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; w1 = hdaa_widget_get(devinfo, as->pins[i]); if (w1 == NULL || w1->enable == 0) continue; if (w1->wclass.pin.connected == 0) continue; prio = (w1->wclass.pin.connected == 1) ? 2 : 1; if (prio < bestprio) continue; if (prio > bestprio) { mask = 0; bestprio = prio; } mask |= (1 << w1->ossdev); fullmask |= (1 << w1->ossdev); } if (mask == 0) return; /* Prefer newly connected input. */ if (w != NULL && (mask & (1 << w->ossdev))) mask = (1 << w->ossdev); /* Prefer previously selected input */ if (mask & pdevinfo->recsrc) mask &= pdevinfo->recsrc; /* Prefer mic. */ if (mask & SOUND_MASK_MIC) mask = SOUND_MASK_MIC; /* Prefer monitor (2nd mic). */ if (mask & SOUND_MASK_MONITOR) mask = SOUND_MASK_MONITOR; /* Just take first one. */ mask = (1 << (ffs(mask) - 1)); HDA_BOOTVERBOSE( hdaa_audio_ctl_ossmixer_mask2allname(mask, buf, sizeof(buf)); device_printf(pdevinfo->dev, "Automatically set rec source to: %s\n", buf); ); hdaa_unlock(devinfo); mix_setrecsrc(pdevinfo->mixer, mask); hdaa_lock(devinfo); } /* * Jack presence detection event handler. */ static void hdaa_presence_handler(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; struct hdaa_audio_as *as; uint32_t res; int connected, old; if (w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) return; if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 || (HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) return; res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); connected = (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) != 0; if (devinfo->quirks & HDAA_QUIRK_SENSEINV) connected = !connected; old = w->wclass.pin.connected; if (connected == old) return; w->wclass.pin.connected = connected; HDA_BOOTVERBOSE( if (connected || old != 2) { device_printf(devinfo->dev, "Pin sense: nid=%d sense=0x%08x (%sconnected)\n", w->nid, res, !connected ? "dis" : ""); } ); as = &devinfo->as[w->bindas]; if (as->hpredir >= 0 && as->pins[15] == w->nid) hdaa_hpredir_handler(w); if (as->dir == HDAA_CTL_IN && old != 2) hdaa_autorecsrc_handler(as, w); if (old != 2) hdaa_channels_handler(as); } /* * Callback for poll based presence detection. */ static void hdaa_jack_poll_callback(void *arg) { struct hdaa_devinfo *devinfo = arg; struct hdaa_widget *w; int i; hdaa_lock(devinfo); if (devinfo->poll_ival == 0) { hdaa_unlock(devinfo); return; } for (i = 0; i < devinfo->ascnt; i++) { if (devinfo->as[i].hpredir < 0) continue; w = hdaa_widget_get(devinfo, devinfo->as[i].pins[15]); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; hdaa_presence_handler(w); } callout_reset(&devinfo->poll_jack, devinfo->poll_ival, hdaa_jack_poll_callback, devinfo); hdaa_unlock(devinfo); } static void hdaa_eld_dump(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; device_t dev = devinfo->dev; uint8_t *sad; int mnl, i, sadc, fmt; if (w->eld == NULL || w->eld_len < 4) return; device_printf(dev, "ELD nid=%d: ELD_Ver=%u Baseline_ELD_Len=%u\n", w->nid, w->eld[0] >> 3, w->eld[2]); if ((w->eld[0] >> 3) != 0x02) return; mnl = w->eld[4] & 0x1f; device_printf(dev, "ELD nid=%d: CEA_EDID_Ver=%u MNL=%u\n", w->nid, w->eld[4] >> 5, mnl); sadc = w->eld[5] >> 4; device_printf(dev, "ELD nid=%d: SAD_Count=%u Conn_Type=%u S_AI=%u HDCP=%u\n", w->nid, sadc, (w->eld[5] >> 2) & 0x3, (w->eld[5] >> 1) & 0x1, w->eld[5] & 0x1); device_printf(dev, "ELD nid=%d: Aud_Synch_Delay=%ums\n", w->nid, w->eld[6] * 2); device_printf(dev, "ELD nid=%d: Channels=0x%b\n", w->nid, w->eld[7], "\020\07RLRC\06FLRC\05RC\04RLR\03FC\02LFE\01FLR"); device_printf(dev, "ELD nid=%d: Port_ID=0x%02x%02x%02x%02x%02x%02x%02x%02x\n", w->nid, w->eld[8], w->eld[9], w->eld[10], w->eld[11], w->eld[12], w->eld[13], w->eld[14], w->eld[15]); device_printf(dev, "ELD nid=%d: Manufacturer_Name=0x%02x%02x\n", w->nid, w->eld[16], w->eld[17]); device_printf(dev, "ELD nid=%d: Product_Code=0x%02x%02x\n", w->nid, w->eld[18], w->eld[19]); device_printf(dev, "ELD nid=%d: Monitor_Name_String='%.*s'\n", w->nid, mnl, &w->eld[20]); for (i = 0; i < sadc; i++) { sad = &w->eld[20 + mnl + i * 3]; fmt = (sad[0] >> 3) & 0x0f; if (fmt == HDA_HDMI_CODING_TYPE_REF_CTX) { fmt = (sad[2] >> 3) & 0x1f; if (fmt < 1 || fmt > 3) fmt = 0; else fmt += 14; } device_printf(dev, "ELD nid=%d: %s %dch freqs=0x%b", w->nid, HDA_HDMI_CODING_TYPES[fmt], (sad[0] & 0x07) + 1, sad[1], "\020\007192\006176\00596\00488\00348\00244\00132"); switch (fmt) { case HDA_HDMI_CODING_TYPE_LPCM: printf(" sizes=0x%b", sad[2] & 0x07, "\020\00324\00220\00116"); break; case HDA_HDMI_CODING_TYPE_AC3: case HDA_HDMI_CODING_TYPE_MPEG1: case HDA_HDMI_CODING_TYPE_MP3: case HDA_HDMI_CODING_TYPE_MPEG2: case HDA_HDMI_CODING_TYPE_AACLC: case HDA_HDMI_CODING_TYPE_DTS: case HDA_HDMI_CODING_TYPE_ATRAC: printf(" max_bitrate=%d", sad[2] * 8000); break; case HDA_HDMI_CODING_TYPE_WMAPRO: printf(" profile=%d", sad[2] & 0x07); break; } printf("\n"); } } static void hdaa_eld_handler(struct hdaa_widget *w) { struct hdaa_devinfo *devinfo = w->devinfo; uint32_t res; int i; if (w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) return; if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 || (HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) return; res = hda_command(devinfo->dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); if ((w->eld != 0) == ((res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) != 0)) return; if (w->eld != NULL) { w->eld_len = 0; free(w->eld, M_HDAA); w->eld = NULL; } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Pin sense: nid=%d sense=0x%08x " "(%sconnected, ELD %svalid)\n", w->nid, res, (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) ? "" : "dis", (res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) ? "" : "in"); ); if ((res & HDA_CMD_GET_PIN_SENSE_ELD_VALID) == 0) return; res = hda_command(devinfo->dev, HDA_CMD_GET_HDMI_DIP_SIZE(0, w->nid, 0x08)); if (res == HDA_INVALID) return; w->eld_len = res & 0xff; if (w->eld_len != 0) w->eld = malloc(w->eld_len, M_HDAA, M_ZERO | M_NOWAIT); if (w->eld == NULL) { w->eld_len = 0; return; } for (i = 0; i < w->eld_len; i++) { res = hda_command(devinfo->dev, HDA_CMD_GET_HDMI_ELDD(0, w->nid, i)); if (res & 0x80000000) w->eld[i] = res & 0xff; } HDA_BOOTVERBOSE( hdaa_eld_dump(w); ); hdaa_channels_handler(&devinfo->as[w->bindas]); } /* * Pin sense initializer. */ static void hdaa_sense_init(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as; struct hdaa_widget *w; int i, poll = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) { if (w->unsol < 0) w->unsol = HDAC_UNSOL_ALLOC( device_get_parent(devinfo->dev), devinfo->dev, w->nid); hda_command(devinfo->dev, HDA_CMD_SET_UNSOLICITED_RESPONSE(0, w->nid, HDA_CMD_SET_UNSOLICITED_RESPONSE_ENABLE | w->unsol)); } as = &devinfo->as[w->bindas]; if (as->hpredir >= 0 && as->pins[15] == w->nid) { if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(w->wclass.pin.cap) == 0 || (HDA_CONFIG_DEFAULTCONF_MISC(w->wclass.pin.config) & 1) != 0) { device_printf(devinfo->dev, "No presence detection support at nid %d\n", w->nid); } else { if (w->unsol < 0) poll = 1; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Headphones redirection for " "association %d nid=%d using %s.\n", w->bindas, w->nid, (w->unsol < 0) ? "polling" : "unsolicited responses"); ); } } hdaa_presence_handler(w); if (!HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) && !HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap)) continue; hdaa_eld_handler(w); } if (poll) { callout_reset(&devinfo->poll_jack, 1, hdaa_jack_poll_callback, devinfo); } } static void hdaa_sense_deinit(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; callout_stop(&devinfo->poll_jack); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->unsol < 0) continue; hda_command(devinfo->dev, HDA_CMD_SET_UNSOLICITED_RESPONSE(0, w->nid, 0)); HDAC_UNSOL_FREE( device_get_parent(devinfo->dev), devinfo->dev, w->unsol); w->unsol = -1; } } uint32_t hdaa_widget_pin_patch(uint32_t config, const char *str) { char buf[256]; char *key, *value, *rest, *bad; int ival, i; strlcpy(buf, str, sizeof(buf)); rest = buf; while ((key = strsep(&rest, "=")) != NULL) { value = strsep(&rest, " \t"); if (value == NULL) break; ival = strtol(value, &bad, 10); if (strcmp(key, "seq") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_SEQUENCE_MASK; config |= ((ival << HDA_CONFIG_DEFAULTCONF_SEQUENCE_SHIFT) & HDA_CONFIG_DEFAULTCONF_SEQUENCE_MASK); } else if (strcmp(key, "as") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK; config |= ((ival << HDA_CONFIG_DEFAULTCONF_ASSOCIATION_SHIFT) & HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK); } else if (strcmp(key, "misc") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_MISC_MASK; config |= ((ival << HDA_CONFIG_DEFAULTCONF_MISC_SHIFT) & HDA_CONFIG_DEFAULTCONF_MISC_MASK); } else if (strcmp(key, "color") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_COLOR_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT) & HDA_CONFIG_DEFAULTCONF_COLOR_MASK); } for (i = 0; i < 16; i++) { if (strcasecmp(HDA_COLORS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT); break; } } } else if (strcmp(key, "ctype") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_SHIFT) & HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_MASK); } for (i = 0; i < 16; i++) { if (strcasecmp(HDA_CONNECTORS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_SHIFT); break; } } } else if (strcmp(key, "device") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT) & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK); continue; } for (i = 0; i < 16; i++) { if (strcasecmp(HDA_DEVS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT); break; } } } else if (strcmp(key, "loc") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_LOCATION_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_LOCATION_SHIFT) & HDA_CONFIG_DEFAULTCONF_LOCATION_MASK); continue; } for (i = 0; i < 64; i++) { if (strcasecmp(HDA_LOCS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_LOCATION_SHIFT); break; } } } else if (strcmp(key, "conn") == 0) { config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK; if (bad[0] == 0) { config |= ((ival << HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT) & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); continue; } for (i = 0; i < 4; i++) { if (strcasecmp(HDA_CONNS[i], value) == 0) { config |= (i << HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT); break; } } } } return (config); } uint32_t hdaa_gpio_patch(uint32_t gpio, const char *str) { char buf[256]; char *key, *value, *rest; int ikey, i; strlcpy(buf, str, sizeof(buf)); rest = buf; while ((key = strsep(&rest, "=")) != NULL) { value = strsep(&rest, " \t"); if (value == NULL) break; ikey = strtol(key, NULL, 10); if (ikey < 0 || ikey > 7) continue; for (i = 0; i < 7; i++) { if (strcasecmp(HDA_GPIO_ACTIONS[i], value) == 0) { gpio &= ~HDAA_GPIO_MASK(ikey); gpio |= i << HDAA_GPIO_SHIFT(ikey); break; } } } return (gpio); } static void hdaa_local_patch_pin(struct hdaa_widget *w) { device_t dev = w->devinfo->dev; const char *res = NULL; uint32_t config, orig; char buf[32]; config = orig = w->wclass.pin.config; snprintf(buf, sizeof(buf), "cad%u.nid%u.config", hda_get_codec_id(dev), w->nid); if (resource_string_value(device_get_name( device_get_parent(device_get_parent(dev))), device_get_unit(device_get_parent(device_get_parent(dev))), buf, &res) == 0) { if (strncmp(res, "0x", 2) == 0) { config = strtol(res + 2, NULL, 16); } else { config = hdaa_widget_pin_patch(config, res); } } snprintf(buf, sizeof(buf), "nid%u.config", w->nid); if (resource_string_value(device_get_name(dev), device_get_unit(dev), buf, &res) == 0) { if (strncmp(res, "0x", 2) == 0) { config = strtol(res + 2, NULL, 16); } else { config = hdaa_widget_pin_patch(config, res); } } HDA_BOOTVERBOSE( if (config != orig) device_printf(w->devinfo->dev, "Patching pin config nid=%u 0x%08x -> 0x%08x\n", w->nid, orig, config); ); w->wclass.pin.newconf = w->wclass.pin.config = config; } static void hdaa_dump_audio_formats_sb(struct sbuf *sb, uint32_t fcap, uint32_t pcmcap) { uint32_t cap; cap = fcap; if (cap != 0) { sbuf_printf(sb, " Stream cap: 0x%08x", cap); if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) sbuf_printf(sb, " AC3"); if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) sbuf_printf(sb, " FLOAT32"); if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) sbuf_printf(sb, " PCM"); sbuf_printf(sb, "\n"); } cap = pcmcap; if (cap != 0) { sbuf_printf(sb, " PCM cap: 0x%08x", cap); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) sbuf_printf(sb, " 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) sbuf_printf(sb, " 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) sbuf_printf(sb, " 20"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) sbuf_printf(sb, " 24"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) sbuf_printf(sb, " 32"); sbuf_printf(sb, " bits,"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) sbuf_printf(sb, " 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) sbuf_printf(sb, " 11"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) sbuf_printf(sb, " 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) sbuf_printf(sb, " 22"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) sbuf_printf(sb, " 32"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) sbuf_printf(sb, " 44"); sbuf_printf(sb, " 48"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) sbuf_printf(sb, " 88"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) sbuf_printf(sb, " 96"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) sbuf_printf(sb, " 176"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) sbuf_printf(sb, " 192"); sbuf_printf(sb, " KHz\n"); } } static void hdaa_dump_pin_sb(struct sbuf *sb, struct hdaa_widget *w) { uint32_t pincap, conf; pincap = w->wclass.pin.cap; sbuf_printf(sb, " Pin cap: 0x%08x", pincap); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap)) sbuf_printf(sb, " ISC"); if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) sbuf_printf(sb, " TRQD"); if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) sbuf_printf(sb, " PDC"); if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) sbuf_printf(sb, " HP"); if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) sbuf_printf(sb, " OUT"); if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) sbuf_printf(sb, " IN"); if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap)) sbuf_printf(sb, " BAL"); if (HDA_PARAM_PIN_CAP_HDMI(pincap)) sbuf_printf(sb, " HDMI"); if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) { sbuf_printf(sb, " VREF["); if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) sbuf_printf(sb, " 50"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) sbuf_printf(sb, " 80"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) sbuf_printf(sb, " 100"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap)) sbuf_printf(sb, " GROUND"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap)) sbuf_printf(sb, " HIZ"); sbuf_printf(sb, " ]"); } if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) sbuf_printf(sb, " EAPD"); if (HDA_PARAM_PIN_CAP_DP(pincap)) sbuf_printf(sb, " DP"); if (HDA_PARAM_PIN_CAP_HBR(pincap)) sbuf_printf(sb, " HBR"); sbuf_printf(sb, "\n"); conf = w->wclass.pin.config; sbuf_printf(sb, " Pin config: 0x%08x", conf); sbuf_printf(sb, " as=%d seq=%d " "device=%s conn=%s ctype=%s loc=%s color=%s misc=%d\n", HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf), HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf), HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)], HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)], HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)], HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)], HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)], HDA_CONFIG_DEFAULTCONF_MISC(conf)); sbuf_printf(sb, " Pin control: 0x%08x", w->wclass.pin.ctrl); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE) sbuf_printf(sb, " HP"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE) sbuf_printf(sb, " IN"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE) sbuf_printf(sb, " OUT"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) == 0x03) sbuf_printf(sb, " HBR"); else if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) sbuf_printf(sb, " EPTs"); } else { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) sbuf_printf(sb, " VREFs"); } sbuf_printf(sb, "\n"); } static void hdaa_dump_amp_sb(struct sbuf *sb, uint32_t cap, const char *banner) { int offset, size, step; offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap); sbuf_printf(sb, " %s amp: 0x%08x " "mute=%d step=%d size=%d offset=%d (%+d/%+ddB)\n", banner, cap, HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap), step, size, offset, ((0 - offset) * (size + 1)) / 4, ((step - offset) * (size + 1)) / 4); } static int hdaa_sysctl_caps(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo; struct hdaa_widget *w, *cw; struct sbuf sb; char buf[64]; int error, j; w = (struct hdaa_widget *)oidp->oid_arg1; devinfo = w->devinfo; sbuf_new_for_sysctl(&sb, NULL, 256, req); sbuf_printf(&sb, "%s%s\n", w->name, (w->enable == 0) ? " [DISABLED]" : ""); sbuf_printf(&sb, " Widget cap: 0x%08x", w->param.widget_cap); if (w->param.widget_cap & 0x0ee1) { if (HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP(w->param.widget_cap)) sbuf_printf(&sb, " LRSWAP"); if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(w->param.widget_cap)) sbuf_printf(&sb, " PWR"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) sbuf_printf(&sb, " DIGITAL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) sbuf_printf(&sb, " UNSOL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(w->param.widget_cap)) sbuf_printf(&sb, " PROC"); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) sbuf_printf(&sb, " STRIPE(x%d)", 1 << (fls(w->wclass.conv.stripecap) - 1)); j = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (j == 1) sbuf_printf(&sb, " STEREO"); else if (j > 1) sbuf_printf(&sb, " %dCH", j + 1); } sbuf_printf(&sb, "\n"); if (w->bindas != -1) { sbuf_printf(&sb, " Association: %d (0x%04x)\n", w->bindas, w->bindseqmask); } if (w->ossmask != 0 || w->ossdev >= 0) { sbuf_printf(&sb, " OSS: %s", hdaa_audio_ctl_ossmixer_mask2allname(w->ossmask, buf, sizeof(buf))); if (w->ossdev >= 0) sbuf_printf(&sb, " (%s)", ossnames[w->ossdev]); sbuf_printf(&sb, "\n"); } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdaa_dump_audio_formats_sb(&sb, w->param.supp_stream_formats, w->param.supp_pcm_size_rate); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) hdaa_dump_pin_sb(&sb, w); if (w->param.eapdbtl != HDA_INVALID) { sbuf_printf(&sb, " EAPD: 0x%08x%s%s%s\n", w->param.eapdbtl, (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_LR_SWAP) ? " LRSWAP" : "", (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD) ? " EAPD" : "", (w->param.eapdbtl & HDA_CMD_SET_EAPD_BTL_ENABLE_BTL) ? " BTL" : ""); } if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) && w->param.outamp_cap != 0) hdaa_dump_amp_sb(&sb, w->param.outamp_cap, "Output"); if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) && w->param.inamp_cap != 0) hdaa_dump_amp_sb(&sb, w->param.inamp_cap, " Input"); if (w->nconns > 0) sbuf_printf(&sb, " Connections: %d\n", w->nconns); for (j = 0; j < w->nconns; j++) { cw = hdaa_widget_get(devinfo, w->conns[j]); sbuf_printf(&sb, " + %s<- nid=%d [%s]", (w->connsenable[j] == 0)?"[DISABLED] ":"", w->conns[j], (cw == NULL) ? "GHOST!" : cw->name); if (cw == NULL) sbuf_printf(&sb, " [UNKNOWN]"); else if (cw->enable == 0) sbuf_printf(&sb, " [DISABLED]"); if (w->nconns > 1 && w->selconn == j && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) sbuf_printf(&sb, " (selected)"); sbuf_printf(&sb, "\n"); } error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } static int hdaa_sysctl_config(SYSCTL_HANDLER_ARGS) { char buf[256]; int error; uint32_t conf; conf = *(uint32_t *)oidp->oid_arg1; snprintf(buf, sizeof(buf), "0x%08x as=%d seq=%d " "device=%s conn=%s ctype=%s loc=%s color=%s misc=%d", conf, HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf), HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf), HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)], HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)], HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)], HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)], HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)], HDA_CONFIG_DEFAULTCONF_MISC(conf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) conf = strtol(buf + 2, NULL, 16); else conf = hdaa_widget_pin_patch(conf, buf); *(uint32_t *)oidp->oid_arg1 = conf; return (0); } static void hdaa_config_fetch(const char *str, uint32_t *on, uint32_t *off) { size_t k; int i = 0, j, len, inv; for (;;) { while (str[i] != '\0' && (str[i] == ',' || isspace(str[i]) != 0)) i++; if (str[i] == '\0') return; j = i; while (str[j] != '\0' && !(str[j] == ',' || isspace(str[j]) != 0)) j++; len = j - i; if (len > 2 && strncmp(str + i, "no", 2) == 0) inv = 2; else inv = 0; for (k = 0; len > inv && k < nitems(hdaa_quirks_tab); k++) { if (strncmp(str + i + inv, hdaa_quirks_tab[k].key, len - inv) != 0) continue; if (len - inv != strlen(hdaa_quirks_tab[k].key)) continue; if (inv == 0) { *on |= hdaa_quirks_tab[k].value; *off &= ~hdaa_quirks_tab[k].value; } else { *off |= hdaa_quirks_tab[k].value; *on &= ~hdaa_quirks_tab[k].value; } break; } i = j; } } static int hdaa_sysctl_quirks(SYSCTL_HANDLER_ARGS) { char buf[256]; int error, n = 0; size_t i; uint32_t quirks, quirks_off; quirks = *(uint32_t *)oidp->oid_arg1; buf[0] = 0; for (i = 0; i < nitems(hdaa_quirks_tab); i++) { if ((quirks & hdaa_quirks_tab[i].value) != 0) n += snprintf(buf + n, sizeof(buf) - n, "%s%s", n != 0 ? "," : "", hdaa_quirks_tab[i].key); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) quirks = strtol(buf + 2, NULL, 16); else { quirks = 0; hdaa_config_fetch(buf, &quirks, &quirks_off); } *(uint32_t *)oidp->oid_arg1 = quirks; return (0); } static void hdaa_local_patch(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; const char *res = NULL; uint32_t quirks_on = 0, quirks_off = 0, x; int i; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) hdaa_local_patch_pin(w); } if (resource_string_value(device_get_name(devinfo->dev), device_get_unit(devinfo->dev), "config", &res) == 0) { if (res != NULL && strlen(res) > 0) hdaa_config_fetch(res, &quirks_on, &quirks_off); devinfo->quirks |= quirks_on; devinfo->quirks &= ~quirks_off; } if (devinfo->newquirks == -1) devinfo->newquirks = devinfo->quirks; else devinfo->quirks = devinfo->newquirks; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, "Config options: 0x%08x\n", devinfo->quirks); ); if (resource_string_value(device_get_name(devinfo->dev), device_get_unit(devinfo->dev), "gpio_config", &res) == 0) { if (strncmp(res, "0x", 2) == 0) { devinfo->gpio = strtol(res + 2, NULL, 16); } else { devinfo->gpio = hdaa_gpio_patch(devinfo->gpio, res); } } if (devinfo->newgpio == -1) devinfo->newgpio = devinfo->gpio; else devinfo->gpio = devinfo->newgpio; if (devinfo->newgpo == -1) devinfo->newgpo = devinfo->gpo; else devinfo->gpo = devinfo->newgpo; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, "GPIO config options:"); for (i = 0; i < 7; i++) { x = (devinfo->gpio & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i); if (x != 0) printf(" %d=%s", i, HDA_GPIO_ACTIONS[x]); } printf("\n"); ); } static void hdaa_widget_connection_parse(struct hdaa_widget *w) { uint32_t res; int i, j, max, ents, entnum; nid_t nid = w->nid; nid_t cnid, addcnid, prevcnid; w->nconns = 0; res = hda_command(w->devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_CONN_LIST_LENGTH)); ents = HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH(res); if (ents < 1) return; entnum = HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM(res) ? 2 : 4; max = (sizeof(w->conns) / sizeof(w->conns[0])) - 1; prevcnid = 0; #define CONN_RMASK(e) (1 << ((32 / (e)) - 1)) #define CONN_NMASK(e) (CONN_RMASK(e) - 1) #define CONN_RESVAL(r, e, n) ((r) >> ((32 / (e)) * (n))) #define CONN_RANGE(r, e, n) (CONN_RESVAL(r, e, n) & CONN_RMASK(e)) #define CONN_CNID(r, e, n) (CONN_RESVAL(r, e, n) & CONN_NMASK(e)) for (i = 0; i < ents; i += entnum) { res = hda_command(w->devinfo->dev, HDA_CMD_GET_CONN_LIST_ENTRY(0, nid, i)); for (j = 0; j < entnum; j++) { cnid = CONN_CNID(res, entnum, j); if (cnid == 0) { if (w->nconns < ents) device_printf(w->devinfo->dev, "WARNING: nid=%d has zero cnid " "entnum=%d j=%d index=%d " "entries=%d found=%d res=0x%08x\n", nid, entnum, j, i, ents, w->nconns, res); else goto getconns_out; } if (cnid < w->devinfo->startnode || cnid >= w->devinfo->endnode) { HDA_BOOTVERBOSE( device_printf(w->devinfo->dev, "WARNING: nid=%d has cnid outside " "of the AFG range j=%d " "entnum=%d index=%d res=0x%08x\n", nid, j, entnum, i, res); ); } if (CONN_RANGE(res, entnum, j) == 0) addcnid = cnid; else if (prevcnid == 0 || prevcnid >= cnid) { device_printf(w->devinfo->dev, "WARNING: Invalid child range " "nid=%d index=%d j=%d entnum=%d " "prevcnid=%d cnid=%d res=0x%08x\n", nid, i, j, entnum, prevcnid, cnid, res); addcnid = cnid; } else addcnid = prevcnid + 1; while (addcnid <= cnid) { if (w->nconns > max) { device_printf(w->devinfo->dev, "Adding %d (nid=%d): " "Max connection reached! max=%d\n", addcnid, nid, max + 1); goto getconns_out; } w->connsenable[w->nconns] = 1; w->conns[w->nconns++] = addcnid++; } prevcnid = cnid; } } getconns_out: return; } static void hdaa_widget_parse(struct hdaa_widget *w) { device_t dev = w->devinfo->dev; uint32_t wcap, cap; nid_t nid = w->nid; char buf[64]; w->param.widget_cap = wcap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_AUDIO_WIDGET_CAP)); w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(wcap); hdaa_widget_connection_parse(w); if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(wcap)) { if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) w->param.outamp_cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_OUTPUT_AMP_CAP)); else w->param.outamp_cap = w->devinfo->outamp_cap; } else w->param.outamp_cap = 0; if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(wcap)) { if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) w->param.inamp_cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_INPUT_AMP_CAP)); else w->param.inamp_cap = w->devinfo->inamp_cap; } else w->param.inamp_cap = 0; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { if (HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR(wcap)) { cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_STREAM_FORMATS)); w->param.supp_stream_formats = (cap != 0) ? cap : w->devinfo->supp_stream_formats; cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE)); w->param.supp_pcm_size_rate = (cap != 0) ? cap : w->devinfo->supp_pcm_size_rate; } else { w->param.supp_stream_formats = w->devinfo->supp_stream_formats; w->param.supp_pcm_size_rate = w->devinfo->supp_pcm_size_rate; } if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) { w->wclass.conv.stripecap = hda_command(dev, HDA_CMD_GET_STRIPE_CONTROL(0, w->nid)) >> 20; } else w->wclass.conv.stripecap = 1; } else { w->param.supp_stream_formats = 0; w->param.supp_pcm_size_rate = 0; } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { w->wclass.pin.original = w->wclass.pin.newconf = w->wclass.pin.config = hda_command(dev, HDA_CMD_GET_CONFIGURATION_DEFAULT(0, w->nid)); w->wclass.pin.cap = hda_command(dev, HDA_CMD_GET_PARAMETER(0, w->nid, HDA_PARAM_PIN_CAP)); w->wclass.pin.ctrl = hda_command(dev, HDA_CMD_GET_PIN_WIDGET_CTRL(0, nid)); w->wclass.pin.connected = 2; if (HDA_PARAM_PIN_CAP_EAPD_CAP(w->wclass.pin.cap)) { w->param.eapdbtl = hda_command(dev, HDA_CMD_GET_EAPD_BTL_ENABLE(0, nid)); w->param.eapdbtl &= 0x7; w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; } else w->param.eapdbtl = HDA_INVALID; } w->unsol = -1; hdaa_unlock(w->devinfo); snprintf(buf, sizeof(buf), "nid%d", w->nid); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, buf, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, w, 0, hdaa_sysctl_caps, "A", "Node capabilities"); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { snprintf(buf, sizeof(buf), "nid%d_config", w->nid); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, buf, CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, &w->wclass.pin.newconf, 0, hdaa_sysctl_config, "A", "Current pin configuration"); snprintf(buf, sizeof(buf), "nid%d_original", w->nid); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, buf, CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, &w->wclass.pin.original, 0, hdaa_sysctl_config, "A", "Original pin configuration"); } hdaa_lock(w->devinfo); } static void hdaa_widget_postprocess(struct hdaa_widget *w) { const char *typestr; w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(w->param.widget_cap); switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: typestr = "audio output"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: typestr = "audio input"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: typestr = "audio mixer"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: typestr = "audio selector"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: typestr = "pin"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET: typestr = "power widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET: typestr = "volume widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET: typestr = "beep widget"; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VENDOR_WIDGET: typestr = "vendor widget"; break; default: typestr = "unknown type"; break; } strlcpy(w->name, typestr, sizeof(w->name)); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { uint32_t config; const char *devstr; int conn, color; config = w->wclass.pin.config; devstr = HDA_DEVS[(config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) >> HDA_CONFIG_DEFAULTCONF_DEVICE_SHIFT]; conn = (config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) >> HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_SHIFT; color = (config & HDA_CONFIG_DEFAULTCONF_COLOR_MASK) >> HDA_CONFIG_DEFAULTCONF_COLOR_SHIFT; strlcat(w->name, ": ", sizeof(w->name)); strlcat(w->name, devstr, sizeof(w->name)); strlcat(w->name, " (", sizeof(w->name)); if (conn == 0 && color != 0 && color != 15) { strlcat(w->name, HDA_COLORS[color], sizeof(w->name)); strlcat(w->name, " ", sizeof(w->name)); } strlcat(w->name, HDA_CONNS[conn], sizeof(w->name)); strlcat(w->name, ")", sizeof(w->name)); } } struct hdaa_widget * hdaa_widget_get(struct hdaa_devinfo *devinfo, nid_t nid) { if (devinfo == NULL || devinfo->widget == NULL || nid < devinfo->startnode || nid >= devinfo->endnode) return (NULL); return (&devinfo->widget[nid - devinfo->startnode]); } static void hdaa_audio_ctl_amp_set_internal(struct hdaa_devinfo *devinfo, nid_t nid, int index, int lmute, int rmute, int left, int right, int dir) { uint16_t v = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, "Setting amplifier nid=%d index=%d %s mute=%d/%d vol=%d/%d\n", nid,index,dir ? "in" : "out",lmute,rmute,left,right); ); if (left != right || lmute != rmute) { v = (1 << (15 - dir)) | (1 << 13) | (index << 8) | (lmute << 7) | left; hda_command(devinfo->dev, HDA_CMD_SET_AMP_GAIN_MUTE(0, nid, v)); v = (1 << (15 - dir)) | (1 << 12) | (index << 8) | (rmute << 7) | right; } else v = (1 << (15 - dir)) | (3 << 12) | (index << 8) | (lmute << 7) | left; hda_command(devinfo->dev, HDA_CMD_SET_AMP_GAIN_MUTE(0, nid, v)); } static void hdaa_audio_ctl_amp_set(struct hdaa_audio_ctl *ctl, uint32_t mute, int left, int right) { nid_t nid; int lmute, rmute; nid = ctl->widget->nid; /* Save new values if valid. */ if (mute != HDAA_AMP_MUTE_DEFAULT) ctl->muted = mute; if (left != HDAA_AMP_VOL_DEFAULT) ctl->left = left; if (right != HDAA_AMP_VOL_DEFAULT) ctl->right = right; /* Prepare effective values */ if (ctl->forcemute) { lmute = 1; rmute = 1; left = 0; right = 0; } else { lmute = HDAA_AMP_LEFT_MUTED(ctl->muted); rmute = HDAA_AMP_RIGHT_MUTED(ctl->muted); left = ctl->left; right = ctl->right; } /* Apply effective values */ if (ctl->dir & HDAA_CTL_OUT) hdaa_audio_ctl_amp_set_internal(ctl->widget->devinfo, nid, ctl->index, lmute, rmute, left, right, 0); if (ctl->dir & HDAA_CTL_IN) hdaa_audio_ctl_amp_set_internal(ctl->widget->devinfo, nid, ctl->index, lmute, rmute, left, right, 1); } static void hdaa_widget_connection_select(struct hdaa_widget *w, uint8_t index) { if (w == NULL || w->nconns < 1 || index > (w->nconns - 1)) return; HDA_BOOTHVERBOSE( device_printf(w->devinfo->dev, "Setting selector nid=%d index=%d\n", w->nid, index); ); hda_command(w->devinfo->dev, HDA_CMD_SET_CONNECTION_SELECT_CONTROL(0, w->nid, index)); w->selconn = index; } /**************************************************************************** * Device Methods ****************************************************************************/ static void * hdaa_channel_init(kobj_t obj, void *data, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct hdaa_chan *ch = data; struct hdaa_pcm_devinfo *pdevinfo = ch->pdevinfo; struct hdaa_devinfo *devinfo = pdevinfo->devinfo; hdaa_lock(devinfo); if (devinfo->quirks & HDAA_QUIRK_FIXEDRATE) { ch->caps.minspeed = ch->caps.maxspeed = 48000; ch->pcmrates[0] = 48000; ch->pcmrates[1] = 0; } ch->dir = dir; ch->b = b; ch->c = c; ch->blksz = pdevinfo->chan_size / pdevinfo->chan_blkcnt; ch->blkcnt = pdevinfo->chan_blkcnt; hdaa_unlock(devinfo); if (sndbuf_alloc(ch->b, bus_get_dma_tag(devinfo->dev), hda_get_dma_nocache(devinfo->dev) ? BUS_DMA_NOCACHE : BUS_DMA_COHERENT, pdevinfo->chan_size) != 0) return (NULL); return (ch); } static int hdaa_channel_setformat(kobj_t obj, void *data, uint32_t format) { struct hdaa_chan *ch = data; int i; for (i = 0; ch->caps.fmtlist[i] != 0; i++) { if (format == ch->caps.fmtlist[i]) { ch->fmt = format; return (0); } } return (EINVAL); } static uint32_t hdaa_channel_setspeed(kobj_t obj, void *data, uint32_t speed) { struct hdaa_chan *ch = data; uint32_t spd = 0, threshold; int i; /* First look for equal or multiple frequency. */ for (i = 0; ch->pcmrates[i] != 0; i++) { spd = ch->pcmrates[i]; if (speed != 0 && spd / speed * speed == spd) { ch->spd = spd; return (spd); } } /* If no match, just find nearest. */ for (i = 0; ch->pcmrates[i] != 0; i++) { spd = ch->pcmrates[i]; threshold = spd + ((ch->pcmrates[i + 1] != 0) ? ((ch->pcmrates[i + 1] - spd) >> 1) : 0); if (speed < threshold) break; } ch->spd = spd; return (spd); } static uint16_t hdaa_stream_format(struct hdaa_chan *ch) { int i; uint16_t fmt; fmt = 0; if (ch->fmt & AFMT_S16_LE) fmt |= ch->bit16 << 4; else if (ch->fmt & AFMT_S32_LE) fmt |= ch->bit32 << 4; else fmt |= 1 << 4; for (i = 0; i < HDA_RATE_TAB_LEN; i++) { if (hda_rate_tab[i].valid && ch->spd == hda_rate_tab[i].rate) { fmt |= hda_rate_tab[i].base; fmt |= hda_rate_tab[i].mul; fmt |= hda_rate_tab[i].div; break; } } fmt |= (AFMT_CHANNEL(ch->fmt) - 1); return (fmt); } static int hdaa_allowed_stripes(uint16_t fmt) { static const int bits[8] = { 8, 16, 20, 24, 32, 32, 32, 32 }; int size; size = bits[(fmt >> 4) & 0x03]; size *= (fmt & 0x0f) + 1; size *= ((fmt >> 11) & 0x07) + 1; return (0xffffffffU >> (32 - fls(size / 8))); } static void hdaa_audio_setup(struct hdaa_chan *ch) { struct hdaa_audio_as *as = &ch->devinfo->as[ch->as]; struct hdaa_widget *w, *wp; int i, j, k, chn, cchn, totalchn, totalextchn, c; uint16_t fmt, dfmt; /* Mapping channel pairs to codec pins/converters. */ const static uint16_t convmap[2][5] = /* 1.0 2.0 4.0 5.1 7.1 */ {{ 0x0010, 0x0001, 0x0201, 0x0231, 0x4231 }, /* no dup. */ { 0x0010, 0x0001, 0x2201, 0x2231, 0x4231 }}; /* side dup. */ /* Mapping formats to HDMI channel allocations. */ const static uint8_t hdmica[2][8] = /* 1 2 3 4 5 6 7 8 */ {{ 0x02, 0x00, 0x04, 0x08, 0x0a, 0x0e, 0x12, 0x12 }, /* x.0 */ { 0x01, 0x03, 0x01, 0x03, 0x09, 0x0b, 0x0f, 0x13 }}; /* x.1 */ /* Mapping formats to HDMI channels order. */ const static uint32_t hdmich[2][8] = /* 1 / 5 2 / 6 3 / 7 4 / 8 */ {{ 0xFFFF0F00, 0xFFFFFF10, 0xFFF2FF10, 0xFF32FF10, 0xFF324F10, 0xF5324F10, 0x54326F10, 0x54326F10 }, /* x.0 */ { 0xFFFFF000, 0xFFFF0100, 0xFFFFF210, 0xFFFF2310, 0xFF32F410, 0xFF324510, 0xF6324510, 0x76325410 }}; /* x.1 */ int convmapid = -1; nid_t nid; uint8_t csum; totalchn = AFMT_CHANNEL(ch->fmt); totalextchn = AFMT_EXTCHANNEL(ch->fmt); HDA_BOOTHVERBOSE( device_printf(ch->pdevinfo->dev, "PCMDIR_%s: Stream setup fmt=%08x (%d.%d) speed=%d\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->fmt, totalchn - totalextchn, totalextchn, ch->spd); ); fmt = hdaa_stream_format(ch); /* Set channels to I/O converters mapping for known speaker setups. */ if ((as->pinset == 0x0007 || as->pinset == 0x0013) || /* Standard 5.1 */ (as->pinset == 0x0017)) /* Standard 7.1 */ convmapid = (ch->dir == PCMDIR_PLAY); dfmt = HDA_CMD_SET_DIGITAL_CONV_FMT1_DIGEN; if (ch->fmt & AFMT_AC3) dfmt |= HDA_CMD_SET_DIGITAL_CONV_FMT1_NAUDIO; chn = 0; for (i = 0; ch->io[i] != -1; i++) { w = hdaa_widget_get(ch->devinfo, ch->io[i]); if (w == NULL) continue; /* If HP redirection is enabled, but failed to use same DAC, make last DAC to duplicate first one. */ if (as->fakeredir && i == (as->pincnt - 1)) { c = (ch->sid << 4); } else { /* Map channels to I/O converters, if set. */ if (convmapid >= 0) chn = (((convmap[convmapid][totalchn / 2] >> i * 4) & 0xf) - 1) * 2; if (chn < 0 || chn >= totalchn) { c = 0; } else { c = (ch->sid << 4) | chn; } } hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_FMT(0, ch->io[i], fmt)); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { hda_command(ch->devinfo->dev, HDA_CMD_SET_DIGITAL_CONV_FMT1(0, ch->io[i], dfmt)); } hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_STREAM_CHAN(0, ch->io[i], c)); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) { hda_command(ch->devinfo->dev, HDA_CMD_SET_STRIPE_CONTROL(0, w->nid, ch->stripectl)); } cchn = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (cchn > 1 && chn < totalchn) { cchn = min(cchn, totalchn - chn - 1); hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_CHAN_COUNT(0, ch->io[i], cchn)); } HDA_BOOTHVERBOSE( device_printf(ch->pdevinfo->dev, "PCMDIR_%s: Stream setup nid=%d: " "fmt=0x%04x, dfmt=0x%04x, chan=0x%04x, " "chan_count=0x%02x, stripe=%d\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ch->io[i], fmt, dfmt, c, cchn, ch->stripectl); ); for (j = 0; j < 16; j++) { if (as->dacs[ch->asindex][j] != ch->io[i]) continue; nid = as->pins[j]; wp = hdaa_widget_get(ch->devinfo, nid); if (wp == NULL) continue; if (!HDA_PARAM_PIN_CAP_DP(wp->wclass.pin.cap) && !HDA_PARAM_PIN_CAP_HDMI(wp->wclass.pin.cap)) continue; /* Set channel mapping. */ for (k = 0; k < 8; k++) { hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_CHAN_SLOT(0, nid, (((hdmich[totalextchn == 0 ? 0 : 1][totalchn - 1] >> (k * 4)) & 0xf) << 4) | k)); } /* * Enable High Bit Rate (HBR) Encoded Packet Type * (EPT), if supported and needed (8ch data). */ if (HDA_PARAM_PIN_CAP_HDMI(wp->wclass.pin.cap) && HDA_PARAM_PIN_CAP_HBR(wp->wclass.pin.cap)) { wp->wclass.pin.ctrl &= ~HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK; if ((ch->fmt & AFMT_AC3) && (cchn == 7)) wp->wclass.pin.ctrl |= 0x03; hda_command(ch->devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, nid, wp->wclass.pin.ctrl)); } /* Stop audio infoframe transmission. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_XMIT(0, nid, 0x00)); /* Clear audio infoframe buffer. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); for (k = 0; k < 32; k++) hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00)); /* Write HDMI/DisplayPort audio infoframe. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); if (w->eld != NULL && w->eld_len >= 6 && ((w->eld[5] >> 2) & 0x3) == 1) { /* DisplayPort */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x84)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x1b)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x44)); } else { /* HDMI */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x84)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x01)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x0a)); csum = 0; csum -= 0x84 + 0x01 + 0x0a + (totalchn - 1) + hdmica[totalextchn == 0 ? 0 : 1][totalchn - 1]; hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, csum)); } hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, totalchn - 1)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_DATA(0, nid, hdmica[totalextchn == 0 ? 0 : 1][totalchn - 1])); /* Start audio infoframe transmission. */ hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_INDEX(0, nid, 0x00)); hda_command(ch->devinfo->dev, HDA_CMD_SET_HDMI_DIP_XMIT(0, nid, 0xc0)); } chn += cchn + 1; } } /* * Greatest Common Divisor. */ static unsigned gcd(unsigned a, unsigned b) { u_int c; while (b != 0) { c = a; a = b; b = (c % b); } return (a); } /* * Least Common Multiple. */ static unsigned lcm(unsigned a, unsigned b) { return ((a * b) / gcd(a, b)); } static int hdaa_channel_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct hdaa_chan *ch = data; blksz -= blksz % lcm(HDA_DMA_ALIGNMENT, sndbuf_getalign(ch->b)); if (blksz > (sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN)) blksz = sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN; if (blksz < HDA_BLK_MIN) blksz = HDA_BLK_MIN; if (blkcnt > HDA_BDL_MAX) blkcnt = HDA_BDL_MAX; if (blkcnt < HDA_BDL_MIN) blkcnt = HDA_BDL_MIN; while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->b)) { if ((blkcnt >> 1) >= HDA_BDL_MIN) blkcnt >>= 1; else if ((blksz >> 1) >= HDA_BLK_MIN) blksz >>= 1; else break; } if ((sndbuf_getblksz(ch->b) != blksz || sndbuf_getblkcnt(ch->b) != blkcnt) && sndbuf_resize(ch->b, blkcnt, blksz) != 0) device_printf(ch->devinfo->dev, "%s: failed blksz=%u blkcnt=%u\n", __func__, blksz, blkcnt); ch->blksz = sndbuf_getblksz(ch->b); ch->blkcnt = sndbuf_getblkcnt(ch->b); return (0); } static uint32_t hdaa_channel_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct hdaa_chan *ch = data; hdaa_channel_setfragments(obj, data, blksz, ch->pdevinfo->chan_blkcnt); return (ch->blksz); } static void hdaa_channel_stop(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; struct hdaa_widget *w; int i; if ((ch->flags & HDAA_CHN_RUNNING) == 0) return; ch->flags &= ~HDAA_CHN_RUNNING; HDAC_STREAM_STOP(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); for (i = 0; ch->io[i] != -1; i++) { w = hdaa_widget_get(ch->devinfo, ch->io[i]); if (w == NULL) continue; if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { hda_command(devinfo->dev, HDA_CMD_SET_DIGITAL_CONV_FMT1(0, ch->io[i], 0)); } hda_command(devinfo->dev, HDA_CMD_SET_CONV_STREAM_CHAN(0, ch->io[i], 0)); } HDAC_STREAM_FREE(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); } static int hdaa_channel_start(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; uint32_t fmt; fmt = hdaa_stream_format(ch); ch->stripectl = fls(ch->stripecap & hdaa_allowed_stripes(fmt) & hda_get_stripes_mask(devinfo->dev)) - 1; ch->sid = HDAC_STREAM_ALLOC(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, fmt, ch->stripectl, &ch->dmapos); if (ch->sid <= 0) return (EBUSY); hdaa_audio_setup(ch); HDAC_STREAM_RESET(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); HDAC_STREAM_START(device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid, sndbuf_getbufaddr(ch->b), ch->blksz, ch->blkcnt); ch->flags |= HDAA_CHN_RUNNING; return (0); } static int hdaa_channel_trigger(kobj_t obj, void *data, int go) { struct hdaa_chan *ch = data; int error = 0; if (!PCMTRIG_COMMON(go)) return (0); hdaa_lock(ch->devinfo); switch (go) { case PCMTRIG_START: error = hdaa_channel_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: hdaa_channel_stop(ch); break; default: break; } hdaa_unlock(ch->devinfo); return (error); } static uint32_t hdaa_channel_getptr(kobj_t obj, void *data) { struct hdaa_chan *ch = data; struct hdaa_devinfo *devinfo = ch->devinfo; uint32_t ptr; hdaa_lock(devinfo); if (ch->dmapos != NULL) { ptr = *(ch->dmapos); } else { ptr = HDAC_STREAM_GETPTR( device_get_parent(devinfo->dev), devinfo->dev, ch->dir == PCMDIR_PLAY ? 1 : 0, ch->sid); } hdaa_unlock(devinfo); /* * Round to available space and force 128 bytes alignment. */ ptr %= ch->blksz * ch->blkcnt; ptr &= HDA_BLK_ALIGN; return (ptr); } static struct pcmchan_caps * hdaa_channel_getcaps(kobj_t obj, void *data) { return (&((struct hdaa_chan *)data)->caps); } static kobj_method_t hdaa_channel_methods[] = { KOBJMETHOD(channel_init, hdaa_channel_init), KOBJMETHOD(channel_setformat, hdaa_channel_setformat), KOBJMETHOD(channel_setspeed, hdaa_channel_setspeed), KOBJMETHOD(channel_setblocksize, hdaa_channel_setblocksize), KOBJMETHOD(channel_setfragments, hdaa_channel_setfragments), KOBJMETHOD(channel_trigger, hdaa_channel_trigger), KOBJMETHOD(channel_getptr, hdaa_channel_getptr), KOBJMETHOD(channel_getcaps, hdaa_channel_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(hdaa_channel); static int hdaa_audio_ctl_ossmixer_init(struct snd_mixer *m) { struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; uint32_t mask, recmask; int i, j; hdaa_lock(devinfo); pdevinfo->mixer = m; /* Make sure that in case of soft volume it won't stay muted. */ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { pdevinfo->left[i] = 100; pdevinfo->right[i] = 100; } /* Declare volume controls assigned to this association. */ mask = pdevinfo->ossmask; if (pdevinfo->playas >= 0) { /* Declate EAPD as ogain control. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->param.eapdbtl == HDA_INVALID || w->bindas != pdevinfo->playas) continue; mask |= SOUND_MASK_OGAIN; break; } /* Declare soft PCM volume if needed. */ if ((mask & SOUND_MASK_PCM) == 0 || (devinfo->quirks & HDAA_QUIRK_SOFTPCMVOL) || pdevinfo->minamp[SOUND_MIXER_PCM] == pdevinfo->maxamp[SOUND_MIXER_PCM]) { mask |= SOUND_MASK_PCM; pcm_setflags(pdevinfo->dev, pcm_getflags(pdevinfo->dev) | SD_F_SOFTPCMVOL); HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Forcing Soft PCM volume\n"); ); } /* Declare master volume if needed. */ if ((mask & SOUND_MASK_VOLUME) == 0) { mask |= SOUND_MASK_VOLUME; mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Forcing master volume with PCM\n"); ); } } /* Declare record sources available to this association. */ recmask = 0; if (pdevinfo->recas >= 0) { for (i = 0; i < 16; i++) { if (devinfo->as[pdevinfo->recas].dacs[0][i] < 0) continue; w = hdaa_widget_get(devinfo, devinfo->as[pdevinfo->recas].dacs[0][i]); if (w == NULL || w->enable == 0) continue; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (cw->bindas != pdevinfo->recas && cw->bindas != -2) continue; recmask |= cw->ossmask; } } } recmask &= (1 << SOUND_MIXER_NRDEVICES) - 1; mask &= (1 << SOUND_MIXER_NRDEVICES) - 1; pdevinfo->ossmask = mask; mix_setrecdevs(m, recmask); mix_setdevs(m, mask); hdaa_unlock(devinfo); return (0); } /* * Update amplification per pdevinfo per ossdev, calculate summary coefficient * and write it to codec, update *left and *right to reflect remaining error. */ static void hdaa_audio_ctl_dev_set(struct hdaa_audio_ctl *ctl, int ossdev, int mute, int *left, int *right) { int i, zleft, zright, sleft, sright, smute, lval, rval; ctl->devleft[ossdev] = *left; ctl->devright[ossdev] = *right; ctl->devmute[ossdev] = mute; smute = sleft = sright = zleft = zright = 0; for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { sleft += ctl->devleft[i]; sright += ctl->devright[i]; smute |= ctl->devmute[i]; if (i == ossdev) continue; zleft += ctl->devleft[i]; zright += ctl->devright[i]; } lval = QDB2VAL(ctl, sleft); rval = QDB2VAL(ctl, sright); hdaa_audio_ctl_amp_set(ctl, smute, lval, rval); *left -= VAL2QDB(ctl, lval) - VAL2QDB(ctl, QDB2VAL(ctl, zleft)); *right -= VAL2QDB(ctl, rval) - VAL2QDB(ctl, QDB2VAL(ctl, zright)); } /* * Trace signal from source, setting volumes on the way. */ static void hdaa_audio_ctl_source_volume(struct hdaa_pcm_devinfo *pdevinfo, int ossdev, nid_t nid, int index, int mute, int left, int right, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, conns = 0; if (depth > HDA_PARSE_MAXDEPTH) return; w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return; /* Count number of active inputs. */ if (depth > 0) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; conns++; } } /* If this is not a first step - use input mixer. Pins have common input ctl so care must be taken. */ if (depth > 0 && (conns == 1 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, index, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right); } /* If widget has own ossdev - not traverse it. It will be traversed on its own. */ if (w->ossdev >= 0 && depth > 0) return; /* We must not traverse pin */ if ((w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) && depth > 0) return; /* * If signals mixed, we can't assign controls farther. * Ignore this on depth zero. Caller must knows why. */ if (conns > 1 && (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER || w->selconn != index)) return; ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right); for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) { hdaa_audio_ctl_source_volume(pdevinfo, ossdev, wc->nid, j, mute, left, right, depth + 1); } } } return; } /* * Trace signal from destination, setting volumes on the way. */ static void hdaa_audio_ctl_dest_volume(struct hdaa_pcm_devinfo *pdevinfo, int ossdev, nid_t nid, int index, int mute, int left, int right, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, consumers, cleft, cright; if (depth > HDA_PARSE_MAXDEPTH) return; w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return; if (depth > 0) { /* If this node produce output for several consumers, we can't touch it. */ consumers = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) consumers++; } } /* The only exception is if real HP redirection is configured and this is a duplication point. XXX: Actually exception is not completely correct. XXX: Duplication point check is not perfect. */ if ((consumers == 2 && (w->bindas < 0 || as[w->bindas].hpredir < 0 || as[w->bindas].fakeredir || (w->bindseqmask & (1 << 15)) == 0)) || consumers > 2) return; /* Else use it's output mixer. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &left, &right); } /* We must not traverse pin */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && depth > 0) return; for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; if (index >= 0 && i != index) continue; cleft = left; cright = right; ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, i, 1); if (ctl) hdaa_audio_ctl_dev_set(ctl, ossdev, mute, &cleft, &cright); hdaa_audio_ctl_dest_volume(pdevinfo, ossdev, w->conns[i], -1, mute, cleft, cright, depth + 1); } } /* * Set volumes for the specified pdevinfo and ossdev. */ static void hdaa_audio_ctl_dev_volume(struct hdaa_pcm_devinfo *pdevinfo, unsigned dev) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; uint32_t mute; int lvol, rvol; int i, j; mute = 0; if (pdevinfo->left[dev] == 0) { mute |= HDAA_AMP_MUTE_LEFT; lvol = -4000; } else lvol = ((pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) * pdevinfo->left[dev] + 50) / 100 + pdevinfo->minamp[dev]; if (pdevinfo->right[dev] == 0) { mute |= HDAA_AMP_MUTE_RIGHT; rvol = -4000; } else rvol = ((pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) * pdevinfo->right[dev] + 50) / 100 + pdevinfo->minamp[dev]; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas < 0) { if (pdevinfo->index != 0) continue; } else { if (w->bindas != pdevinfo->playas && w->bindas != pdevinfo->recas) continue; } if (dev == SOUND_MIXER_RECLEV && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); continue; } if (dev == SOUND_MIXER_VOLUME && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && devinfo->as[w->bindas].dir == HDAA_CTL_OUT) { hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); continue; } if (dev == SOUND_MIXER_IGAIN && w->pflags & HDAA_ADC_MONITOR) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (cw->bindas == -1) continue; if (cw->bindas >= 0 && devinfo->as[cw->bindas].dir != HDAA_CTL_IN) continue; hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, j, mute, lvol, rvol, 0); } continue; } if (w->ossdev != dev) continue; hdaa_audio_ctl_source_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); if (dev == SOUND_MIXER_IMIX && (w->pflags & HDAA_IMIX_AS_DST)) hdaa_audio_ctl_dest_volume(pdevinfo, dev, w->nid, -1, mute, lvol, rvol, 0); } } /* * OSS Mixer set method. */ static int hdaa_audio_ctl_ossmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; int i; hdaa_lock(devinfo); /* Save new values. */ pdevinfo->left[dev] = left; pdevinfo->right[dev] = right; /* 'ogain' is the special case implemented with EAPD. */ if (dev == SOUND_MIXER_OGAIN) { uint32_t orig; w = NULL; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->param.eapdbtl == HDA_INVALID) continue; break; } if (i >= devinfo->endnode) { hdaa_unlock(devinfo); return (-1); } orig = w->param.eapdbtl; if (left == 0) w->param.eapdbtl &= ~HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; else w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; if (orig != w->param.eapdbtl) { uint32_t val; val = w->param.eapdbtl; if (devinfo->quirks & HDAA_QUIRK_EAPDINV) val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; hda_command(devinfo->dev, HDA_CMD_SET_EAPD_BTL_ENABLE(0, w->nid, val)); } hdaa_unlock(devinfo); return (left | (left << 8)); } /* Recalculate all controls related to this OSS device. */ hdaa_audio_ctl_dev_volume(pdevinfo, dev); hdaa_unlock(devinfo); return (left | (right << 8)); } /* * Set mixer settings to our own default values: * +20dB for mics, -10dB for analog vol, mute for igain, 0dB for others. */ static void hdaa_audio_ctl_set_defaults(struct hdaa_pcm_devinfo *pdevinfo) { int amp, vol, dev; for (dev = 0; dev < SOUND_MIXER_NRDEVICES; dev++) { if ((pdevinfo->ossmask & (1 << dev)) == 0) continue; /* If the value was overridden, leave it as is. */ if (resource_int_value(device_get_name(pdevinfo->dev), device_get_unit(pdevinfo->dev), ossnames[dev], &vol) == 0) continue; vol = -1; if (dev == SOUND_MIXER_OGAIN) vol = 100; else if (dev == SOUND_MIXER_IGAIN) vol = 0; else if (dev == SOUND_MIXER_MIC || dev == SOUND_MIXER_MONITOR) amp = 20 * 4; /* +20dB */ else if (dev == SOUND_MIXER_VOLUME && !pdevinfo->digital) amp = -10 * 4; /* -10dB */ else amp = 0; if (vol < 0 && (pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) <= 0) { vol = 100; } else if (vol < 0) { vol = ((amp - pdevinfo->minamp[dev]) * 100 + (pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]) / 2) / (pdevinfo->maxamp[dev] - pdevinfo->minamp[dev]); vol = imin(imax(vol, 1), 100); } mix_set(pdevinfo->mixer, dev, vol, vol); } } /* * Recursively commutate specified record source. */ static uint32_t hdaa_audio_ctl_recsel_comm(struct hdaa_pcm_devinfo *pdevinfo, uint32_t src, nid_t nid, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; char buf[64]; int i, muted; uint32_t res = 0; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[i]); if (cw == NULL || cw->enable == 0 || cw->bindas == -1) continue; /* Call recursively to trace signal to it's source if needed. */ if ((src & cw->ossmask) != 0) { if (cw->ossdev < 0) { res |= hdaa_audio_ctl_recsel_comm(pdevinfo, src, w->conns[i], depth + 1); } else { res |= cw->ossmask; } } /* We have two special cases: mixers and others (selectors). */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, i, 1); if (ctl == NULL) continue; /* If we have input control on this node mute them * according to requested sources. */ muted = (src & cw->ossmask) ? 0 : 1; if (muted != ctl->forcemute) { ctl->forcemute = muted; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_DEFAULT, HDAA_AMP_VOL_DEFAULT, HDAA_AMP_VOL_DEFAULT); } HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Recsel (%s): nid %d source %d %s\n", hdaa_audio_ctl_ossmixer_mask2allname( src, buf, sizeof(buf)), nid, i, muted?"mute":"unmute"); ); } else { if (w->nconns == 1) break; if ((src & cw->ossmask) == 0) continue; /* If we found requested source - select it and exit. */ hdaa_widget_connection_select(w, i); HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "Recsel (%s): nid %d source %d select\n", hdaa_audio_ctl_ossmixer_mask2allname( src, buf, sizeof(buf)), nid, i); ); break; } } return (res); } static uint32_t hdaa_audio_ctl_ossmixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct hdaa_pcm_devinfo *pdevinfo = mix_getdevinfo(m); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; struct hdaa_audio_as *as; struct hdaa_audio_ctl *ctl; struct hdaa_chan *ch; int i, j; uint32_t ret = 0xffffffff; hdaa_lock(devinfo); if (pdevinfo->recas < 0) { hdaa_unlock(devinfo); return (0); } as = &devinfo->as[pdevinfo->recas]; /* For non-mixed associations we always recording everything. */ if (!as->mixed) { hdaa_unlock(devinfo); return (mix_getrecdevs(m)); } /* Commutate requested recsrc for each ADC. */ for (j = 0; j < as->num_chans; j++) { ch = &devinfo->chans[as->chans[j]]; for (i = 0; ch->io[i] >= 0; i++) { w = hdaa_widget_get(devinfo, ch->io[i]); if (w == NULL || w->enable == 0) continue; ret &= hdaa_audio_ctl_recsel_comm(pdevinfo, src, ch->io[i], 0); } } if (ret == 0xffffffff) ret = 0; /* * Some controls could be shared. Reset volumes for controls * related to previously chosen devices, as they may no longer * affect the signal. */ i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || !(ctl->ossmask & pdevinfo->recsrc)) continue; if (!((pdevinfo->playas >= 0 && ctl->widget->bindas == pdevinfo->playas) || (pdevinfo->recas >= 0 && ctl->widget->bindas == pdevinfo->recas) || (pdevinfo->index == 0 && ctl->widget->bindas == -2))) continue; for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { if (pdevinfo->recsrc & (1 << j)) { ctl->devleft[j] = 0; ctl->devright[j] = 0; ctl->devmute[j] = 0; } } } /* * Some controls could be shared. Set volumes for controls * related to devices selected both previously and now. */ for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { if ((ret | pdevinfo->recsrc) & (1 << j)) hdaa_audio_ctl_dev_volume(pdevinfo, j); } pdevinfo->recsrc = ret; hdaa_unlock(devinfo); return (ret); } static kobj_method_t hdaa_audio_ctl_ossmixer_methods[] = { KOBJMETHOD(mixer_init, hdaa_audio_ctl_ossmixer_init), KOBJMETHOD(mixer_set, hdaa_audio_ctl_ossmixer_set), KOBJMETHOD(mixer_setrecsrc, hdaa_audio_ctl_ossmixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(hdaa_audio_ctl_ossmixer); static void hdaa_dump_gpi(struct hdaa_devinfo *devinfo) { device_t dev = devinfo->dev; int i; uint32_t data, wake, unsol, sticky; if (HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap) > 0) { data = hda_command(dev, HDA_CMD_GET_GPI_DATA(0, devinfo->nid)); wake = hda_command(dev, HDA_CMD_GET_GPI_WAKE_ENABLE_MASK(0, devinfo->nid)); unsol = hda_command(dev, HDA_CMD_GET_GPI_UNSOLICITED_ENABLE_MASK(0, devinfo->nid)); sticky = hda_command(dev, HDA_CMD_GET_GPI_STICKY_MASK(0, devinfo->nid)); for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap); i++) { device_printf(dev, " GPI%d:%s%s%s state=%d", i, (sticky & (1 << i)) ? " sticky" : "", (unsol & (1 << i)) ? " unsol" : "", (wake & (1 << i)) ? " wake" : "", (data >> i) & 1); } } } static void hdaa_dump_gpio(struct hdaa_devinfo *devinfo) { device_t dev = devinfo->dev; int i; uint32_t data, dir, enable, wake, unsol, sticky; if (HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap) > 0) { data = hda_command(dev, HDA_CMD_GET_GPIO_DATA(0, devinfo->nid)); enable = hda_command(dev, HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid)); dir = hda_command(dev, HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid)); wake = hda_command(dev, HDA_CMD_GET_GPIO_WAKE_ENABLE_MASK(0, devinfo->nid)); unsol = hda_command(dev, HDA_CMD_GET_GPIO_UNSOLICITED_ENABLE_MASK(0, devinfo->nid)); sticky = hda_command(dev, HDA_CMD_GET_GPIO_STICKY_MASK(0, devinfo->nid)); for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); i++) { device_printf(dev, " GPIO%d: ", i); if ((enable & (1 << i)) == 0) { printf("disabled\n"); continue; } if ((dir & (1 << i)) == 0) { printf("input%s%s%s", (sticky & (1 << i)) ? " sticky" : "", (unsol & (1 << i)) ? " unsol" : "", (wake & (1 << i)) ? " wake" : ""); } else printf("output"); printf(" state=%d\n", (data >> i) & 1); } } } static void hdaa_dump_gpo(struct hdaa_devinfo *devinfo) { device_t dev = devinfo->dev; int i; uint32_t data; if (HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap) > 0) { data = hda_command(dev, HDA_CMD_GET_GPO_DATA(0, devinfo->nid)); for (i = 0; i < HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); i++) { device_printf(dev, " GPO%d: state=%d", i, (data >> i) & 1); } } } static void hdaa_audio_parse(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; uint32_t res; int i; nid_t nid; nid = devinfo->nid; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_GPIO_COUNT)); devinfo->gpio_cap = res; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "NumGPIO=%d NumGPO=%d " "NumGPI=%d GPIWake=%d GPIUnsol=%d\n", HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->gpio_cap)); hdaa_dump_gpi(devinfo); hdaa_dump_gpio(devinfo); hdaa_dump_gpo(devinfo); ); res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_STREAM_FORMATS)); devinfo->supp_stream_formats = res; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE)); devinfo->supp_pcm_size_rate = res; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_OUTPUT_AMP_CAP)); devinfo->outamp_cap = res; res = hda_command(devinfo->dev, HDA_CMD_GET_PARAMETER(0, nid, HDA_PARAM_INPUT_AMP_CAP)); devinfo->inamp_cap = res; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) device_printf(devinfo->dev, "Ghost widget! nid=%d!\n", i); else { w->devinfo = devinfo; w->nid = i; w->enable = 1; w->selconn = -1; w->pflags = 0; w->ossdev = -1; w->bindas = -1; w->param.eapdbtl = HDA_INVALID; hdaa_widget_parse(w); } } } static void hdaa_audio_postprocess(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; hdaa_widget_postprocess(w); } } static void hdaa_audio_ctl_parse(struct hdaa_devinfo *devinfo) { struct hdaa_audio_ctl *ctls; struct hdaa_widget *w, *cw; int i, j, cnt, max, ocap, icap; int mute, offset, step, size; /* XXX This is redundant */ max = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->param.outamp_cap != 0) max++; if (w->param.inamp_cap != 0) { switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: for (j = 0; j < w->nconns; j++) { cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; max++; } break; default: max++; break; } } } devinfo->ctlcnt = max; if (max < 1) return; ctls = malloc(sizeof(*ctls) * max, M_HDAA, M_ZERO | M_NOWAIT); if (ctls == NULL) { /* Blekh! */ device_printf(devinfo->dev, "unable to allocate ctls!\n"); devinfo->ctlcnt = 0; return; } cnt = 0; for (i = devinfo->startnode; cnt < max && i < devinfo->endnode; i++) { if (cnt >= max) { device_printf(devinfo->dev, "%s: Ctl overflow!\n", __func__); break; } w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; ocap = w->param.outamp_cap; icap = w->param.inamp_cap; if (ocap != 0) { mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(ocap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(ocap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(ocap); offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(ocap); /*if (offset > step) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "BUGGY outamp: nid=%d " "[offset=%d > step=%d]\n", w->nid, offset, step); ); offset = step; }*/ ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) ctls[cnt].ndir = HDAA_CTL_IN; else ctls[cnt].ndir = HDAA_CTL_OUT; ctls[cnt++].dir = HDAA_CTL_OUT; } if (icap != 0) { mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(icap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(icap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(icap); offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(icap); /*if (offset > step) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "BUGGY inamp: nid=%d " "[offset=%d > step=%d]\n", w->nid, offset, step); ); offset = step; }*/ switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: for (j = 0; j < w->nconns; j++) { if (cnt >= max) { device_printf(devinfo->dev, "%s: Ctl overflow!\n", __func__); break; } cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].childwidget = cw; ctls[cnt].index = j; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; ctls[cnt].ndir = HDAA_CTL_IN; ctls[cnt++].dir = HDAA_CTL_IN; } break; default: if (cnt >= max) { device_printf(devinfo->dev, "%s: Ctl overflow!\n", __func__); break; } ctls[cnt].enable = 1; ctls[cnt].widget = w; ctls[cnt].mute = mute; ctls[cnt].step = step; ctls[cnt].size = size; ctls[cnt].offset = offset; ctls[cnt].left = offset; ctls[cnt].right = offset; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) ctls[cnt].ndir = HDAA_CTL_OUT; else ctls[cnt].ndir = HDAA_CTL_IN; ctls[cnt++].dir = HDAA_CTL_IN; break; } } } devinfo->ctl = ctls; } static void hdaa_audio_as_parse(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as; struct hdaa_widget *w; int i, j, cnt, max, type, dir, assoc, seq, first, hpredir; /* Count present associations */ max = 0; for (j = 1; j < 16; j++) { for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (HDA_CONFIG_DEFAULTCONF_ASSOCIATION(w->wclass.pin.config) != j) continue; max++; if (j != 15) /* There could be many 1-pin assocs #15 */ break; } } devinfo->ascnt = max; if (max < 1) return; as = malloc(sizeof(*as) * max, M_HDAA, M_ZERO | M_NOWAIT); if (as == NULL) { /* Blekh! */ device_printf(devinfo->dev, "unable to allocate assocs!\n"); devinfo->ascnt = 0; return; } for (i = 0; i < max; i++) { as[i].hpredir = -1; as[i].digital = 0; as[i].num_chans = 1; as[i].location = -1; } /* Scan associations skipping as=0. */ cnt = 0; for (j = 1; j < 16 && cnt < max; j++) { first = 16; hpredir = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; assoc = HDA_CONFIG_DEFAULTCONF_ASSOCIATION(w->wclass.pin.config); seq = HDA_CONFIG_DEFAULTCONF_SEQUENCE(w->wclass.pin.config); if (assoc != j) { continue; } KASSERT(cnt < max, ("%s: Associations owerflow (%d of %d)", __func__, cnt, max)); type = w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; /* Get pin direction. */ if (type == HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT || type == HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER || type == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT || type == HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT || type == HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT) dir = HDAA_CTL_OUT; else dir = HDAA_CTL_IN; /* If this is a first pin - create new association. */ if (as[cnt].pincnt == 0) { as[cnt].enable = 1; as[cnt].index = j; as[cnt].dir = dir; } if (seq < first) first = seq; /* Check association correctness. */ if (as[cnt].pins[seq] != 0) { device_printf(devinfo->dev, "%s: Duplicate pin %d (%d) " "in association %d! Disabling association.\n", __func__, seq, w->nid, j); as[cnt].enable = 0; } if (dir != as[cnt].dir) { device_printf(devinfo->dev, "%s: Pin %d has wrong " "direction for association %d! Disabling " "association.\n", __func__, w->nid, j); as[cnt].enable = 0; } if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { as[cnt].digital |= 0x1; if (HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap)) as[cnt].digital |= 0x2; if (HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap)) as[cnt].digital |= 0x4; } if (as[cnt].location == -1) { as[cnt].location = HDA_CONFIG_DEFAULTCONF_LOCATION(w->wclass.pin.config); } else if (as[cnt].location != HDA_CONFIG_DEFAULTCONF_LOCATION(w->wclass.pin.config)) { as[cnt].location = -2; } /* Headphones with seq=15 may mean redirection. */ if (type == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT && seq == 15) hpredir = 1; as[cnt].pins[seq] = w->nid; as[cnt].pincnt++; /* Association 15 is a multiple unassociated pins. */ if (j == 15) cnt++; } if (j != 15 && as[cnt].pincnt > 0) { if (hpredir && as[cnt].pincnt > 1) as[cnt].hpredir = first; cnt++; } } for (i = 0; i < max; i++) { if (as[i].dir == HDAA_CTL_IN && (as[i].pincnt == 1 || as[i].pins[14] > 0 || as[i].pins[15] > 0)) as[i].mixed = 1; } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "%d associations found:\n", max); for (i = 0; i < max; i++) { device_printf(devinfo->dev, "Association %d (%d) %s%s:\n", i, as[i].index, (as[i].dir == HDAA_CTL_IN)?"in":"out", as[i].enable?"":" (disabled)"); for (j = 0; j < 16; j++) { if (as[i].pins[j] == 0) continue; device_printf(devinfo->dev, " Pin nid=%d seq=%d\n", as[i].pins[j], j); } } ); devinfo->as = as; } /* * Trace path from DAC to pin. */ static nid_t hdaa_audio_trace_dac(struct hdaa_devinfo *devinfo, int as, int seq, nid_t nid, int dupseq, int min, int only, int depth) { struct hdaa_widget *w; int i, im = -1; nid_t m = 0, ret; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*stracing via nid %d\n", depth + 1, "", w->nid); } ); /* Use only unused widgets */ if (w->bindas >= 0 && w->bindas != as) { HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*snid %d busy by association %d\n", depth + 1, "", w->nid, w->bindas); } ); return (0); } if (dupseq < 0) { if (w->bindseqmask != 0) { HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*snid %d busy by seqmask %x\n", depth + 1, "", w->nid, w->bindseqmask); } ); return (0); } } else { /* If this is headphones - allow duplicate first pin. */ if (w->bindseqmask != 0 && (w->bindseqmask & (1 << dupseq)) == 0) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by seqmask %x\n", depth + 1, "", w->nid, w->bindseqmask); ); return (0); } } switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: /* Do not traverse input. AD1988 has digital monitor for which we are not ready. */ break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: /* If we are tracing HP take only dac of first pin. */ if ((only == 0 || only == w->nid) && (w->nid >= min) && (dupseq < 0 || w->nid == devinfo->as[as].dacs[0][dupseq])) m = w->nid; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (depth > 0) break; /* Fall */ default: /* Find reachable DACs with smallest nid respecting constraints. */ for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; if (w->selconn != -1 && w->selconn != i) continue; if ((ret = hdaa_audio_trace_dac(devinfo, as, seq, w->conns[i], dupseq, min, only, depth + 1)) != 0) { if (m == 0 || ret < m) { m = ret; im = i; } if (only || dupseq >= 0) break; } } if (im >= 0 && only && ((w->nconns > 1 && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) w->selconn = im; break; } if (m && only) { w->bindas = as; w->bindseqmask |= (1 << seq); } HDA_BOOTHVERBOSE( if (!only) { device_printf(devinfo->dev, " %*snid %d returned %d\n", depth + 1, "", w->nid, m); } ); return (m); } /* * Trace path from widget to ADC. */ static nid_t hdaa_audio_trace_adc(struct hdaa_devinfo *devinfo, int as, int seq, nid_t nid, int mixed, int min, int only, int depth, int *length, int onlylength) { struct hdaa_widget *w, *wc; int i, j, im, lm = HDA_PARSE_MAXDEPTH; nid_t m = 0, ret; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*stracing via nid %d\n", depth + 1, "", w->nid); ); /* Use only unused widgets */ if (w->bindas >= 0 && w->bindas != as) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by association %d\n", depth + 1, "", w->nid, w->bindas); ); return (0); } if (!mixed && w->bindseqmask != 0) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by seqmask %x\n", depth + 1, "", w->nid, w->bindseqmask); ); return (0); } switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: if ((only == 0 || only == w->nid) && (w->nid >= min) && (onlylength == 0 || onlylength == depth)) { m = w->nid; *length = depth; } break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (depth > 0) break; /* Fall */ default: /* Try to find reachable ADCs with specified nid. */ for (j = devinfo->startnode; j < devinfo->endnode; j++) { wc = hdaa_widget_get(devinfo, j); if (wc == NULL || wc->enable == 0) continue; im = -1; for (i = 0; i < wc->nconns; i++) { if (wc->connsenable[i] == 0) continue; if (wc->conns[i] != nid) continue; if ((ret = hdaa_audio_trace_adc(devinfo, as, seq, j, mixed, min, only, depth + 1, length, onlylength)) != 0) { if (m == 0 || ret < m || (ret == m && *length < lm)) { m = ret; im = i; lm = *length; } else *length = lm; if (only) break; } } if (im >= 0 && only && ((wc->nconns > 1 && wc->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) || wc->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) wc->selconn = im; } break; } if (m && only) { w->bindas = as; w->bindseqmask |= (1 << seq); } HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d returned %d\n", depth + 1, "", w->nid, m); ); return (m); } /* * Erase trace path of the specified association. */ static void hdaa_audio_undo_trace(struct hdaa_devinfo *devinfo, int as, int seq) { struct hdaa_widget *w; int i; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas == as) { if (seq >= 0) { w->bindseqmask &= ~(1 << seq); if (w->bindseqmask == 0) { w->bindas = -1; w->selconn = -1; } } else { w->bindas = -1; w->bindseqmask = 0; w->selconn = -1; } } } } /* * Trace association path from DAC to output */ static int hdaa_audio_trace_as_out(struct hdaa_devinfo *devinfo, int as, int seq) { struct hdaa_audio_as *ases = devinfo->as; int i, hpredir; nid_t min, res; /* Find next pin */ for (i = seq; i < 16 && ases[as].pins[i] == 0; i++) ; /* Check if there is no any left. If so - we succeeded. */ if (i == 16) return (1); hpredir = (i == 15 && ases[as].fakeredir == 0)?ases[as].hpredir:-1; min = 0; do { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing pin %d with min nid %d", ases[as].pins[i], min); if (hpredir >= 0) printf(" and hpredir %d", hpredir); printf("\n"); ); /* Trace this pin taking min nid into account. */ res = hdaa_audio_trace_dac(devinfo, as, i, ases[as].pins[i], hpredir, min, 0, 0); if (res == 0) { /* If we failed - return to previous and redo it. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Unable to trace pin %d seq %d with min " "nid %d", ases[as].pins[i], i, min); if (hpredir >= 0) printf(" and hpredir %d", hpredir); printf("\n"); ); return (0); } HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Pin %d traced to DAC %d", ases[as].pins[i], res); if (hpredir >= 0) printf(" and hpredir %d", hpredir); if (ases[as].fakeredir) printf(" with fake redirection"); printf("\n"); ); /* Trace again to mark the path */ hdaa_audio_trace_dac(devinfo, as, i, ases[as].pins[i], hpredir, min, res, 0); ases[as].dacs[0][i] = res; /* We succeeded, so call next. */ if (hdaa_audio_trace_as_out(devinfo, as, i + 1)) return (1); /* If next failed, we should retry with next min */ hdaa_audio_undo_trace(devinfo, as, i); ases[as].dacs[0][i] = 0; min = res + 1; } while (1); } /* * Check equivalency of two DACs. */ static int hdaa_audio_dacs_equal(struct hdaa_widget *w1, struct hdaa_widget *w2) { struct hdaa_devinfo *devinfo = w1->devinfo; struct hdaa_widget *w3; int i, j, c1, c2; if (memcmp(&w1->param, &w2->param, sizeof(w1->param))) return (0); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w3 = hdaa_widget_get(devinfo, i); if (w3 == NULL || w3->enable == 0) continue; if (w3->bindas != w1->bindas) continue; if (w3->nconns == 0) continue; c1 = c2 = -1; for (j = 0; j < w3->nconns; j++) { if (w3->connsenable[j] == 0) continue; if (w3->conns[j] == w1->nid) c1 = j; if (w3->conns[j] == w2->nid) c2 = j; } if (c1 < 0) continue; if (c2 < 0) return (0); if (w3->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) return (0); } return (1); } /* * Check equivalency of two ADCs. */ static int hdaa_audio_adcs_equal(struct hdaa_widget *w1, struct hdaa_widget *w2) { struct hdaa_devinfo *devinfo = w1->devinfo; struct hdaa_widget *w3, *w4; int i; if (memcmp(&w1->param, &w2->param, sizeof(w1->param))) return (0); if (w1->nconns != 1 || w2->nconns != 1) return (0); if (w1->conns[0] == w2->conns[0]) return (1); w3 = hdaa_widget_get(devinfo, w1->conns[0]); if (w3 == NULL || w3->enable == 0) return (0); w4 = hdaa_widget_get(devinfo, w2->conns[0]); if (w4 == NULL || w4->enable == 0) return (0); if (w3->bindas == w4->bindas && w3->bindseqmask == w4->bindseqmask) return (1); if (w4->bindas >= 0) return (0); if (w3->type != w4->type) return (0); if (memcmp(&w3->param, &w4->param, sizeof(w3->param))) return (0); if (w3->nconns != w4->nconns) return (0); for (i = 0; i < w3->nconns; i++) { if (w3->conns[i] != w4->conns[i]) return (0); } return (1); } /* * Look for equivalent DAC/ADC to implement second channel. */ static void hdaa_audio_adddac(struct hdaa_devinfo *devinfo, int asid) { struct hdaa_audio_as *as = &devinfo->as[asid]; struct hdaa_widget *w1, *w2; int i, pos; nid_t nid1, nid2; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Looking for additional %sC " "for association %d (%d)\n", (as->dir == HDAA_CTL_OUT) ? "DA" : "AD", asid, as->index); ); /* Find the existing DAC position and return if found more the one. */ pos = -1; for (i = 0; i < 16; i++) { if (as->dacs[0][i] <= 0) continue; if (pos >= 0 && as->dacs[0][i] != as->dacs[0][pos]) return; pos = i; } nid1 = as->dacs[0][pos]; w1 = hdaa_widget_get(devinfo, nid1); w2 = NULL; for (nid2 = devinfo->startnode; nid2 < devinfo->endnode; nid2++) { w2 = hdaa_widget_get(devinfo, nid2); if (w2 == NULL || w2->enable == 0) continue; if (w2->bindas >= 0) continue; if (w1->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) { if (w2->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) continue; if (hdaa_audio_dacs_equal(w1, w2)) break; } else { if (w2->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (hdaa_audio_adcs_equal(w1, w2)) break; } } if (nid2 >= devinfo->endnode) return; w2->bindas = w1->bindas; w2->bindseqmask = w1->bindseqmask; if (w1->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " ADC %d considered equal to ADC %d\n", nid2, nid1); ); w1 = hdaa_widget_get(devinfo, w1->conns[0]); w2 = hdaa_widget_get(devinfo, w2->conns[0]); w2->bindas = w1->bindas; w2->bindseqmask = w1->bindseqmask; } else { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " DAC %d considered equal to DAC %d\n", nid2, nid1); ); } for (i = 0; i < 16; i++) { if (as->dacs[0][i] <= 0) continue; as->dacs[as->num_chans][i] = nid2; } as->num_chans++; } /* * Trace association path from input to ADC */ static int hdaa_audio_trace_as_in(struct hdaa_devinfo *devinfo, int as) { struct hdaa_audio_as *ases = devinfo->as; struct hdaa_widget *w; int i, j, k, length; for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (w->bindas >= 0 && w->bindas != as) continue; /* Find next pin */ for (i = 0; i < 16; i++) { if (ases[as].pins[i] == 0) continue; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing pin %d to ADC %d\n", ases[as].pins[i], j); ); /* Trace this pin taking goal into account. */ if (hdaa_audio_trace_adc(devinfo, as, i, ases[as].pins[i], 1, 0, j, 0, &length, 0) == 0) { /* If we failed - return to previous and redo it. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Unable to trace pin %d to ADC %d, undo traces\n", ases[as].pins[i], j); ); hdaa_audio_undo_trace(devinfo, as, -1); for (k = 0; k < 16; k++) ases[as].dacs[0][k] = 0; break; } HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Pin %d traced to ADC %d\n", ases[as].pins[i], j); ); ases[as].dacs[0][i] = j; } if (i == 16) return (1); } return (0); } /* * Trace association path from input to multiple ADCs */ static int hdaa_audio_trace_as_in_mch(struct hdaa_devinfo *devinfo, int as, int seq) { struct hdaa_audio_as *ases = devinfo->as; int i, length; nid_t min, res; /* Find next pin */ for (i = seq; i < 16 && ases[as].pins[i] == 0; i++) ; /* Check if there is no any left. If so - we succeeded. */ if (i == 16) return (1); min = 0; do { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing pin %d with min nid %d", ases[as].pins[i], min); printf("\n"); ); /* Trace this pin taking min nid into account. */ res = hdaa_audio_trace_adc(devinfo, as, i, ases[as].pins[i], 0, min, 0, 0, &length, 0); if (res == 0) { /* If we failed - return to previous and redo it. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Unable to trace pin %d seq %d with min " "nid %d", ases[as].pins[i], i, min); printf("\n"); ); return (0); } HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Pin %d traced to ADC %d\n", ases[as].pins[i], res); ); /* Trace again to mark the path */ hdaa_audio_trace_adc(devinfo, as, i, ases[as].pins[i], 0, min, res, 0, &length, length); ases[as].dacs[0][i] = res; /* We succeeded, so call next. */ if (hdaa_audio_trace_as_in_mch(devinfo, as, i + 1)) return (1); /* If next failed, we should retry with next min */ hdaa_audio_undo_trace(devinfo, as, i); ases[as].dacs[0][i] = 0; min = res + 1; } while (1); } /* * Trace input monitor path from mixer to output association. */ static int hdaa_audio_trace_to_out(struct hdaa_devinfo *devinfo, nid_t nid, int depth) { struct hdaa_audio_as *ases = devinfo->as; struct hdaa_widget *w, *wc; int i, j; nid_t res = 0; if (depth > HDA_PARSE_MAXDEPTH) return (0); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (0); HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*stracing via nid %d\n", depth + 1, "", w->nid); ); /* Use only unused widgets */ if (depth > 0 && w->bindas != -1) { if (w->bindas < 0 || ases[w->bindas].dir == HDAA_CTL_OUT) { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d found output association %d\n", depth + 1, "", w->nid, w->bindas); ); if (w->bindas >= 0) w->pflags |= HDAA_ADC_MONITOR; return (1); } else { HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d busy by input association %d\n", depth + 1, "", w->nid, w->bindas); ); return (0); } } switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: /* Do not traverse input. AD1988 has digital monitor for which we are not ready. */ break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (depth > 0) break; /* Fall */ default: /* Try to find reachable ADCs with specified nid. */ for (j = devinfo->startnode; j < devinfo->endnode; j++) { wc = hdaa_widget_get(devinfo, j); if (wc == NULL || wc->enable == 0) continue; for (i = 0; i < wc->nconns; i++) { if (wc->connsenable[i] == 0) continue; if (wc->conns[i] != nid) continue; if (hdaa_audio_trace_to_out(devinfo, j, depth + 1) != 0) { res = 1; if (wc->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && wc->selconn == -1) wc->selconn = i; } } } break; } if (res && w->bindas == -1) w->bindas = -2; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " %*snid %d returned %d\n", depth + 1, "", w->nid, res); ); return (res); } /* * Trace extra associations (beeper, monitor) */ static void hdaa_audio_trace_as_extra(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; int j; /* Input monitor */ /* Find mixer associated with input, but supplying signal for output associations. Hope it will be input monitor. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing input monitor\n"); ); for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; if (w->bindas < 0 || as[w->bindas].dir != HDAA_CTL_IN) continue; HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Tracing nid %d to out\n", j); ); if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " nid %d is input monitor\n", w->nid); ); w->ossdev = SOUND_MIXER_IMIX; } } /* Other inputs monitor */ /* Find input pins supplying signal for output associations. Hope it will be input monitoring. */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing other input monitors\n"); ); for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->bindas < 0 || as[w->bindas].dir != HDAA_CTL_IN) continue; HDA_BOOTVERBOSE( device_printf(devinfo->dev, " Tracing nid %d to out\n", j); ); if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " nid %d is input monitor\n", w->nid); ); } } /* Beeper */ HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing beeper\n"); ); for (j = devinfo->startnode; j < devinfo->endnode; j++) { w = hdaa_widget_get(devinfo, j); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET) continue; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Tracing nid %d to out\n", j); ); if (hdaa_audio_trace_to_out(devinfo, w->nid, 0)) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, " nid %d traced to out\n", j); ); } w->bindas = -2; } } /* * Bind assotiations to PCM channels */ static void hdaa_audio_bind_as(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; int i, j, cnt = 0, free; for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable) cnt += as[j].num_chans; } if (devinfo->num_chans == 0) { devinfo->chans = malloc(sizeof(struct hdaa_chan) * cnt, M_HDAA, M_ZERO | M_NOWAIT); if (devinfo->chans == NULL) { device_printf(devinfo->dev, "Channels memory allocation failed!\n"); return; } } else { devinfo->chans = (struct hdaa_chan *)realloc(devinfo->chans, sizeof(struct hdaa_chan) * (devinfo->num_chans + cnt), M_HDAA, M_ZERO | M_NOWAIT); if (devinfo->chans == NULL) { devinfo->num_chans = 0; device_printf(devinfo->dev, "Channels memory allocation failed!\n"); return; } /* Fixup relative pointers after realloc */ for (j = 0; j < devinfo->num_chans; j++) devinfo->chans[j].caps.fmtlist = devinfo->chans[j].fmtlist; } free = devinfo->num_chans; devinfo->num_chans += cnt; for (j = free; j < free + cnt; j++) { devinfo->chans[j].devinfo = devinfo; devinfo->chans[j].as = -1; } /* Assign associations in order of their numbers, */ for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable == 0) continue; for (i = 0; i < as[j].num_chans; i++) { devinfo->chans[free].as = j; devinfo->chans[free].asindex = i; devinfo->chans[free].dir = (as[j].dir == HDAA_CTL_IN) ? PCMDIR_REC : PCMDIR_PLAY; hdaa_pcmchannel_setup(&devinfo->chans[free]); as[j].chans[i] = free; free++; } } } static void hdaa_audio_disable_nonaudio(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; /* Disable power and volume widgets. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d due to it's" " non-audio type.\n", w->nid); ); } } } static void hdaa_audio_disable_useless(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; int done, found, i, j, k; /* Disable useless pins. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling pin nid %d due" " to None connectivity.\n", w->nid); ); } else if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK) == 0) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling unassociated" " pin nid %d.\n", w->nid); ); } } } do { done = 1; /* Disable and mute controls for disabled widgets. */ i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0) continue; if (ctl->widget->enable == 0 || (ctl->childwidget != NULL && ctl->childwidget->enable == 0)) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; if (ctl->ndir == HDAA_CTL_IN) ctl->widget->connsenable[ctl->index] = 0; done = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling ctl %d nid %d cnid %d due" " to disabled widget.\n", i, ctl->widget->nid, (ctl->childwidget != NULL)? ctl->childwidget->nid:-1); ); } } /* Disable useless widgets. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; /* Disable inputs with disabled child widgets. */ for (j = 0; j < w->nconns; j++) { if (w->connsenable[j]) { cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) { w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d connection %d due" " to disabled child widget.\n", i, j); ); } } } if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; /* Disable mixers and selectors without inputs. */ found = 0; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j]) { found = 1; break; } } if (found == 0) { w->enable = 0; done = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d due to all it's" " inputs disabled.\n", w->nid); ); } /* Disable nodes without consumers. */ if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; found = 0; for (k = devinfo->startnode; k < devinfo->endnode; k++) { cw = hdaa_widget_get(devinfo, k); if (cw == NULL || cw->enable == 0) continue; for (j = 0; j < cw->nconns; j++) { if (cw->connsenable[j] && cw->conns[j] == i) { found = 1; break; } } } if (found == 0) { w->enable = 0; done = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling nid %d due to all it's" " consumers disabled.\n", w->nid); ); } } } while (done == 0); } static void hdaa_audio_disable_unas(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; int i, j, k; /* Disable unassosiated widgets. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas == -1) { w->enable = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling unassociated nid %d.\n", w->nid); ); } } /* Disable input connections on input pin and * output on output. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->bindas < 0) continue; if (as[w->bindas].dir == HDAA_CTL_IN) { for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling connection to input pin " "nid %d conn %d.\n", i, j); ); } ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, -1, 1); if (ctl && ctl->enable) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; } } else { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl && ctl->enable) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; } for (k = devinfo->startnode; k < devinfo->endnode; k++) { cw = hdaa_widget_get(devinfo, k); if (cw == NULL || cw->enable == 0) continue; for (j = 0; j < cw->nconns; j++) { if (cw->connsenable[j] && cw->conns[j] == i) { cw->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling connection from output pin " "nid %d conn %d cnid %d.\n", k, j, i); ); if (cw->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && cw->nconns > 1) continue; ctl = hdaa_audio_ctl_amp_get(devinfo, k, HDAA_CTL_IN, j, 1); if (ctl && ctl->enable) { ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; } } } } } } } static void hdaa_audio_disable_notselected(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; int i, j; /* On playback path we can safely disable all unseleted inputs. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->nconns <= 1) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; if (w->bindas < 0 || as[w->bindas].dir == HDAA_CTL_IN) continue; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; if (w->selconn < 0 || w->selconn == j) continue; w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling unselected connection " "nid %d conn %d.\n", i, j); ); } } } static void hdaa_audio_disable_crossas(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *ases = devinfo->as; struct hdaa_widget *w, *cw; struct hdaa_audio_ctl *ctl; int i, j; /* Disable crossassociatement and unwanted crosschannel connections. */ /* ... using selectors */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->nconns <= 1) continue; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) continue; /* Allow any -> mix */ if (w->bindas == -2) continue; for (j = 0; j < w->nconns; j++) { if (w->connsenable[j] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || w->enable == 0) continue; /* Allow mix -> out. */ if (cw->bindas == -2 && w->bindas >= 0 && ases[w->bindas].dir == HDAA_CTL_OUT) continue; /* Allow mix -> mixed-in. */ if (cw->bindas == -2 && w->bindas >= 0 && ases[w->bindas].mixed) continue; /* Allow in -> mix. */ if ((w->pflags & HDAA_ADC_MONITOR) && cw->bindas >= 0 && ases[cw->bindas].dir == HDAA_CTL_IN) continue; /* Allow if have common as/seqs. */ if (w->bindas == cw->bindas && (w->bindseqmask & cw->bindseqmask) != 0) continue; w->connsenable[j] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling crossassociatement connection " "nid %d conn %d cnid %d.\n", i, j, cw->nid); ); } } /* ... using controls */ i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->childwidget == NULL) continue; /* Allow any -> mix */ if (ctl->widget->bindas == -2) continue; /* Allow mix -> out. */ if (ctl->childwidget->bindas == -2 && ctl->widget->bindas >= 0 && ases[ctl->widget->bindas].dir == HDAA_CTL_OUT) continue; /* Allow mix -> mixed-in. */ if (ctl->childwidget->bindas == -2 && ctl->widget->bindas >= 0 && ases[ctl->widget->bindas].mixed) continue; /* Allow in -> mix. */ if ((ctl->widget->pflags & HDAA_ADC_MONITOR) && ctl->childwidget->bindas >= 0 && ases[ctl->childwidget->bindas].dir == HDAA_CTL_IN) continue; /* Allow if have common as/seqs. */ if (ctl->widget->bindas == ctl->childwidget->bindas && (ctl->widget->bindseqmask & ctl->childwidget->bindseqmask) != 0) continue; ctl->forcemute = 1; ctl->muted = HDAA_AMP_MUTE_ALL; ctl->left = 0; ctl->right = 0; ctl->enable = 0; if (ctl->ndir == HDAA_CTL_IN) ctl->widget->connsenable[ctl->index] = 0; HDA_BOOTHVERBOSE( device_printf(devinfo->dev, " Disabling crossassociatement connection " "ctl %d nid %d cnid %d.\n", i, ctl->widget->nid, ctl->childwidget->nid); ); } } /* * Find controls to control amplification for source and calculate possible * amplification range. */ static int hdaa_audio_ctl_source_amp(struct hdaa_devinfo *devinfo, nid_t nid, int index, int ossdev, int ctlable, int depth, int *minamp, int *maxamp) { struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, conns = 0, tminamp, tmaxamp, cminamp, cmaxamp, found = 0; if (depth > HDA_PARSE_MAXDEPTH) return (found); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (found); /* Count number of active inputs. */ if (depth > 0) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; conns++; } } /* If this is not a first step - use input mixer. Pins have common input ctl so care must be taken. */ if (depth > 0 && ctlable && (conns == 1 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX)) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, index, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { *minamp += MINQDB(ctl); *maxamp += MAXQDB(ctl); } } } /* If widget has own ossdev - not traverse it. It will be traversed on its own. */ if (w->ossdev >= 0 && depth > 0) return (found); /* We must not traverse pin */ if ((w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) && depth > 0) return (found); /* record that this widget exports such signal, */ w->ossmask |= (1 << ossdev); /* * If signals mixed, we can't assign controls farther. * Ignore this on depth zero. Caller must knows why. */ if (conns > 1 && w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) ctlable = 0; if (ctlable) { ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { *minamp += MINQDB(ctl); *maxamp += MAXQDB(ctl); } } } cminamp = cmaxamp = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) { tminamp = tmaxamp = 0; found += hdaa_audio_ctl_source_amp(devinfo, wc->nid, j, ossdev, ctlable, depth + 1, &tminamp, &tmaxamp); if (cminamp == 0 && cmaxamp == 0) { cminamp = tminamp; cmaxamp = tmaxamp; } else if (tminamp != tmaxamp) { cminamp = imax(cminamp, tminamp); cmaxamp = imin(cmaxamp, tmaxamp); } } } } if (*minamp == *maxamp && cminamp < cmaxamp) { *minamp += cminamp; *maxamp += cmaxamp; } return (found); } /* * Find controls to control amplification for destination and calculate * possible amplification range. */ static int hdaa_audio_ctl_dest_amp(struct hdaa_devinfo *devinfo, nid_t nid, int index, int ossdev, int depth, int *minamp, int *maxamp) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *wc; struct hdaa_audio_ctl *ctl; int i, j, consumers, tminamp, tmaxamp, cminamp, cmaxamp, found = 0; if (depth > HDA_PARSE_MAXDEPTH) return (found); w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return (found); if (depth > 0) { /* If this node produce output for several consumers, we can't touch it. */ consumers = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { wc = hdaa_widget_get(devinfo, i); if (wc == NULL || wc->enable == 0) continue; for (j = 0; j < wc->nconns; j++) { if (wc->connsenable[j] && wc->conns[j] == nid) consumers++; } } /* The only exception is if real HP redirection is configured and this is a duplication point. XXX: Actually exception is not completely correct. XXX: Duplication point check is not perfect. */ if ((consumers == 2 && (w->bindas < 0 || as[w->bindas].hpredir < 0 || as[w->bindas].fakeredir || (w->bindseqmask & (1 << 15)) == 0)) || consumers > 2) return (found); /* Else use it's output mixer. */ ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_OUT, -1, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { *minamp += MINQDB(ctl); *maxamp += MAXQDB(ctl); } } } /* We must not traverse pin */ if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && depth > 0) return (found); cminamp = cmaxamp = 0; for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; if (index >= 0 && i != index) continue; tminamp = tmaxamp = 0; ctl = hdaa_audio_ctl_amp_get(devinfo, w->nid, HDAA_CTL_IN, i, 1); if (ctl) { ctl->ossmask |= (1 << ossdev); found++; if (*minamp == *maxamp) { tminamp += MINQDB(ctl); tmaxamp += MAXQDB(ctl); } } found += hdaa_audio_ctl_dest_amp(devinfo, w->conns[i], -1, ossdev, depth + 1, &tminamp, &tmaxamp); if (cminamp == 0 && cmaxamp == 0) { cminamp = tminamp; cmaxamp = tmaxamp; } else if (tminamp != tmaxamp) { cminamp = imax(cminamp, tminamp); cmaxamp = imin(cmaxamp, tmaxamp); } } if (*minamp == *maxamp && cminamp < cmaxamp) { *minamp += cminamp; *maxamp += cmaxamp; } return (found); } /* * Assign OSS names to sound sources */ static void hdaa_audio_assign_names(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; int i, j; int type = -1, use, used = 0; static const int types[7][13] = { { SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2, SOUND_MIXER_LINE3, -1 }, /* line */ { SOUND_MIXER_MONITOR, SOUND_MIXER_MIC, -1 }, /* int mic */ { SOUND_MIXER_MIC, SOUND_MIXER_MONITOR, -1 }, /* ext mic */ { SOUND_MIXER_CD, -1 }, /* cd */ { SOUND_MIXER_SPEAKER, -1 }, /* speaker */ { SOUND_MIXER_DIGITAL1, SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3, -1 }, /* digital */ { SOUND_MIXER_LINE, SOUND_MIXER_LINE1, SOUND_MIXER_LINE2, SOUND_MIXER_LINE3, SOUND_MIXER_PHONEIN, SOUND_MIXER_PHONEOUT, SOUND_MIXER_VIDEO, SOUND_MIXER_RADIO, SOUND_MIXER_DIGITAL1, SOUND_MIXER_DIGITAL2, SOUND_MIXER_DIGITAL3, SOUND_MIXER_MONITOR, -1 } /* others */ }; /* Surely known names */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->bindas == -1) continue; use = -1; switch (w->type) { case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: if (as[w->bindas].dir == HDAA_CTL_OUT) break; type = -1; switch (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) { case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN: type = 0; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: if ((w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK) break; type = 1; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_CD: type = 3; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER: type = 4; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_IN: case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_IN: type = 5; break; } if (type == -1) break; j = 0; while (types[type][j] >= 0 && (used & (1 << types[type][j])) != 0) { j++; } if (types[type][j] >= 0) use = types[type][j]; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: use = SOUND_MIXER_PCM; break; case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET: use = SOUND_MIXER_SPEAKER; break; default: break; } if (use >= 0) { w->ossdev = use; used |= (1 << use); } } /* Semi-known names */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->ossdev >= 0) continue; if (w->bindas == -1) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (as[w->bindas].dir == HDAA_CTL_OUT) continue; type = -1; switch (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) { case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT: case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER: case HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT: case HDA_CONFIG_DEFAULTCONF_DEVICE_AUX: type = 0; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: type = 2; break; case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT: case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT: type = 5; break; } if (type == -1) break; j = 0; while (types[type][j] >= 0 && (used & (1 << types[type][j])) != 0) { j++; } if (types[type][j] >= 0) { w->ossdev = types[type][j]; used |= (1 << types[type][j]); } } /* Others */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->ossdev >= 0) continue; if (w->bindas == -1) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (as[w->bindas].dir == HDAA_CTL_OUT) continue; j = 0; while (types[6][j] >= 0 && (used & (1 << types[6][j])) != 0) { j++; } if (types[6][j] >= 0) { w->ossdev = types[6][j]; used |= (1 << types[6][j]); } } } static void hdaa_audio_build_tree(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; int j, res; /* Trace all associations in order of their numbers. */ for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable == 0) continue; HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Tracing association %d (%d)\n", j, as[j].index); ); if (as[j].dir == HDAA_CTL_OUT) { retry: res = hdaa_audio_trace_as_out(devinfo, j, 0); if (res == 0 && as[j].hpredir >= 0 && as[j].fakeredir == 0) { /* If CODEC can't do analog HP redirection try to make it using one more DAC. */ as[j].fakeredir = 1; goto retry; } } else if (as[j].mixed) res = hdaa_audio_trace_as_in(devinfo, j); else res = hdaa_audio_trace_as_in_mch(devinfo, j, 0); if (res) { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Association %d (%d) trace succeeded\n", j, as[j].index); ); } else { HDA_BOOTVERBOSE( device_printf(devinfo->dev, "Association %d (%d) trace failed\n", j, as[j].index); ); as[j].enable = 0; } } /* Look for additional DACs/ADCs. */ for (j = 0; j < devinfo->ascnt; j++) { if (as[j].enable == 0) continue; hdaa_audio_adddac(devinfo, j); } /* Trace mixer and beeper pseudo associations. */ hdaa_audio_trace_as_extra(devinfo); } /* * Store in pdevinfo new data about whether and how we can control signal * for OSS device to/from specified widget. */ static void hdaa_adjust_amp(struct hdaa_widget *w, int ossdev, int found, int minamp, int maxamp) { struct hdaa_devinfo *devinfo = w->devinfo; struct hdaa_pcm_devinfo *pdevinfo; if (w->bindas >= 0) pdevinfo = devinfo->as[w->bindas].pdevinfo; else pdevinfo = &devinfo->devs[0]; if (found) pdevinfo->ossmask |= (1 << ossdev); if (minamp == 0 && maxamp == 0) return; if (pdevinfo->minamp[ossdev] == 0 && pdevinfo->maxamp[ossdev] == 0) { pdevinfo->minamp[ossdev] = minamp; pdevinfo->maxamp[ossdev] = maxamp; } else { pdevinfo->minamp[ossdev] = imax(pdevinfo->minamp[ossdev], minamp); pdevinfo->maxamp[ossdev] = imin(pdevinfo->maxamp[ossdev], maxamp); } } /* * Trace signals from/to all possible sources/destionstions to find possible * recording sources, OSS device control ranges and to assign controls. */ static void hdaa_audio_assign_mixers(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w, *cw; int i, j, minamp, maxamp, found; /* Assign mixers to the tree. */ for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; minamp = maxamp = 0; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET || (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && as[w->bindas].dir == HDAA_CTL_IN)) { if (w->ossdev < 0) continue; found = hdaa_audio_ctl_source_amp(devinfo, w->nid, -1, w->ossdev, 1, 0, &minamp, &maxamp); hdaa_adjust_amp(w, w->ossdev, found, minamp, maxamp); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, SOUND_MIXER_RECLEV, 0, &minamp, &maxamp); hdaa_adjust_amp(w, SOUND_MIXER_RECLEV, found, minamp, maxamp); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && as[w->bindas].dir == HDAA_CTL_OUT) { found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, SOUND_MIXER_VOLUME, 0, &minamp, &maxamp); hdaa_adjust_amp(w, SOUND_MIXER_VOLUME, found, minamp, maxamp); } if (w->ossdev == SOUND_MIXER_IMIX) { minamp = maxamp = 0; found = hdaa_audio_ctl_source_amp(devinfo, w->nid, -1, w->ossdev, 1, 0, &minamp, &maxamp); if (minamp == maxamp) { /* If we are unable to control input monitor as source - try to control it as destination. */ found += hdaa_audio_ctl_dest_amp(devinfo, w->nid, -1, w->ossdev, 0, &minamp, &maxamp); w->pflags |= HDAA_IMIX_AS_DST; } hdaa_adjust_amp(w, w->ossdev, found, minamp, maxamp); } if (w->pflags & HDAA_ADC_MONITOR) { for (j = 0; j < w->nconns; j++) { if (!w->connsenable[j]) continue; cw = hdaa_widget_get(devinfo, w->conns[j]); if (cw == NULL || cw->enable == 0) continue; if (cw->bindas == -1) continue; if (cw->bindas >= 0 && as[cw->bindas].dir != HDAA_CTL_IN) continue; minamp = maxamp = 0; found = hdaa_audio_ctl_dest_amp(devinfo, w->nid, j, SOUND_MIXER_IGAIN, 0, &minamp, &maxamp); hdaa_adjust_amp(w, SOUND_MIXER_IGAIN, found, minamp, maxamp); } } } } static void hdaa_audio_prepare_pin_ctrl(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; uint32_t pincap; int i; for (i = 0; i < devinfo->nodecnt; i++) { w = &devinfo->widget[i]; if (w == NULL) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && w->waspin == 0) continue; pincap = w->wclass.pin.cap; /* Disable everything. */ if (devinfo->init_clear) { w->wclass.pin.ctrl &= ~( HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE | HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK); } if (w->enable == 0) { /* Pin is unused so left it disabled. */ continue; } else if (w->waspin) { /* Enable input for beeper input. */ w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; } else if (w->bindas < 0 || as[w->bindas].enable == 0) { /* Pin is unused so left it disabled. */ continue; } else if (as[w->bindas].dir == HDAA_CTL_IN) { /* Input pin, configure for input. */ if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; if ((devinfo->quirks & HDAA_QUIRK_IVREF100) && HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); else if ((devinfo->quirks & HDAA_QUIRK_IVREF80) && HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); else if ((devinfo->quirks & HDAA_QUIRK_IVREF50) && HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); } else { /* Output pin, configure for output. */ if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap) && (w->wclass.pin.config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE; if ((devinfo->quirks & HDAA_QUIRK_OVREF100) && HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); else if ((devinfo->quirks & HDAA_QUIRK_OVREF80) && HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); else if ((devinfo->quirks & HDAA_QUIRK_OVREF50) && HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); } } } static void hdaa_audio_ctl_commit(struct hdaa_devinfo *devinfo) { struct hdaa_audio_ctl *ctl; int i, z; i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->ossmask != 0) { /* Mute disabled and mixer controllable controls. * Last will be initialized by mixer_init(). * This expected to reduce click on startup. */ hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_ALL, 0, 0); continue; } /* Init fixed controls to 0dB amplification. */ z = ctl->offset; if (z > ctl->step) z = ctl->step; hdaa_audio_ctl_amp_set(ctl, HDAA_AMP_MUTE_NONE, z, z); } } static void hdaa_gpio_commit(struct hdaa_devinfo *devinfo) { uint32_t gdata, gmask, gdir; int i, numgpio; numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); if (devinfo->gpio != 0 && numgpio != 0) { gdata = hda_command(devinfo->dev, HDA_CMD_GET_GPIO_DATA(0, devinfo->nid)); gmask = hda_command(devinfo->dev, HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid)); gdir = hda_command(devinfo->dev, HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid)); for (i = 0; i < numgpio; i++) { if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_SET(i)) { gdata |= (1 << i); gmask |= (1 << i); gdir |= (1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_CLEAR(i)) { gdata &= ~(1 << i); gmask |= (1 << i); gdir |= (1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_DISABLE(i)) { gmask &= ~(1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_INPUT(i)) { gmask |= (1 << i); gdir &= ~(1 << i); } } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "GPIO commit\n"); ); hda_command(devinfo->dev, HDA_CMD_SET_GPIO_ENABLE_MASK(0, devinfo->nid, gmask)); hda_command(devinfo->dev, HDA_CMD_SET_GPIO_DIRECTION(0, devinfo->nid, gdir)); hda_command(devinfo->dev, HDA_CMD_SET_GPIO_DATA(0, devinfo->nid, gdata)); HDA_BOOTVERBOSE( hdaa_dump_gpio(devinfo); ); } } static void hdaa_gpo_commit(struct hdaa_devinfo *devinfo) { uint32_t gdata; int i, numgpo; numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); if (devinfo->gpo != 0 && numgpo != 0) { gdata = hda_command(devinfo->dev, HDA_CMD_GET_GPO_DATA(0, devinfo->nid)); for (i = 0; i < numgpo; i++) { if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_SET(i)) { gdata |= (1 << i); } else if ((devinfo->gpio & HDAA_GPIO_MASK(i)) == HDAA_GPIO_CLEAR(i)) { gdata &= ~(1 << i); } } HDA_BOOTVERBOSE( device_printf(devinfo->dev, "GPO commit\n"); ); hda_command(devinfo->dev, HDA_CMD_SET_GPO_DATA(0, devinfo->nid, gdata)); HDA_BOOTVERBOSE( hdaa_dump_gpo(devinfo); ); } } static void hdaa_audio_commit(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; /* Commit controls. */ hdaa_audio_ctl_commit(devinfo); /* Commit selectors, pins and EAPD. */ for (i = 0; i < devinfo->nodecnt; i++) { w = &devinfo->widget[i]; if (w == NULL) continue; if (w->selconn == -1) w->selconn = 0; if (w->nconns > 0) hdaa_widget_connection_select(w, w->selconn); if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) { hda_command(devinfo->dev, HDA_CMD_SET_PIN_WIDGET_CTRL(0, w->nid, w->wclass.pin.ctrl)); } if (w->param.eapdbtl != HDA_INVALID) { uint32_t val; val = w->param.eapdbtl; if (devinfo->quirks & HDAA_QUIRK_EAPDINV) val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; hda_command(devinfo->dev, HDA_CMD_SET_EAPD_BTL_ENABLE(0, w->nid, val)); } } hdaa_gpio_commit(devinfo); hdaa_gpo_commit(devinfo); } static void hdaa_powerup(struct hdaa_devinfo *devinfo) { int i; hda_command(devinfo->dev, HDA_CMD_SET_POWER_STATE(0, devinfo->nid, HDA_CMD_POWER_STATE_D0)); DELAY(100); for (i = devinfo->startnode; i < devinfo->endnode; i++) { hda_command(devinfo->dev, HDA_CMD_SET_POWER_STATE(0, i, HDA_CMD_POWER_STATE_D0)); } DELAY(1000); } static int hdaa_pcmchannel_setup(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; struct hdaa_audio_as *as = devinfo->as; struct hdaa_widget *w; uint32_t cap, fmtcap, pcmcap; int i, j, ret, channels, onlystereo; uint16_t pinset; ch->caps = hdaa_caps; ch->caps.fmtlist = ch->fmtlist; ch->bit16 = 1; ch->bit32 = 0; ch->pcmrates[0] = 48000; ch->pcmrates[1] = 0; ch->stripecap = 0xff; ret = 0; channels = 0; onlystereo = 1; pinset = 0; fmtcap = devinfo->supp_stream_formats; pcmcap = devinfo->supp_pcm_size_rate; for (i = 0; i < 16; i++) { /* Check as is correct */ if (ch->as < 0) break; /* Cound only present DACs */ if (as[ch->as].dacs[ch->asindex][i] <= 0) continue; /* Ignore duplicates */ for (j = 0; j < ret; j++) { if (ch->io[j] == as[ch->as].dacs[ch->asindex][i]) break; } if (j < ret) continue; w = hdaa_widget_get(devinfo, as[ch->as].dacs[ch->asindex][i]); if (w == NULL || w->enable == 0) continue; cap = w->param.supp_stream_formats; if (!HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap) && !HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) continue; /* Many CODECs does not declare AC3 support on SPDIF. I don't beleave that they doesn't support it! */ if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) cap |= HDA_PARAM_SUPP_STREAM_FORMATS_AC3_MASK; if (ret == 0) { fmtcap = cap; pcmcap = w->param.supp_pcm_size_rate; } else { fmtcap &= cap; pcmcap &= w->param.supp_pcm_size_rate; } ch->io[ret++] = as[ch->as].dacs[ch->asindex][i]; ch->stripecap &= w->wclass.conv.stripecap; /* Do not count redirection pin/dac channels. */ if (i == 15 && as[ch->as].hpredir >= 0) continue; channels += HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap) + 1; if (HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap) != 1) onlystereo = 0; pinset |= (1 << i); } ch->io[ret] = -1; ch->channels = channels; if (as[ch->as].fakeredir) ret--; /* Standard speaks only about stereo pins and playback, ... */ if ((!onlystereo) || as[ch->as].mixed) pinset = 0; /* ..., but there it gives us info about speakers layout. */ as[ch->as].pinset = pinset; ch->supp_stream_formats = fmtcap; ch->supp_pcm_size_rate = pcmcap; /* * 8bit = 0 * 16bit = 1 * 20bit = 2 * 24bit = 3 * 32bit = 4 */ if (ret > 0) { i = 0; if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(fmtcap)) { if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(pcmcap)) ch->bit16 = 1; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(pcmcap)) ch->bit16 = 0; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(pcmcap)) ch->bit32 = 3; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(pcmcap)) ch->bit32 = 2; else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(pcmcap)) ch->bit32 = 4; if (!(devinfo->quirks & HDAA_QUIRK_FORCESTEREO)) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 1, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 1, 0); } if (channels >= 2) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 2, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 2, 0); } if (channels >= 3 && !onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 3, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 3, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 3, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 3, 1); } if (channels >= 4) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 4, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 4, 0); if (!onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 4, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 4, 1); } } if (channels >= 5 && !onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 5, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 5, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 5, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 5, 1); } if (channels >= 6) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 6, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 6, 1); if (!onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 6, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 6, 0); } } if (channels >= 7 && !onlystereo) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 7, 0); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 7, 0); ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 7, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 7, 1); } if (channels >= 8) { ch->fmtlist[i++] = SND_FORMAT(AFMT_S16_LE, 8, 1); if (ch->bit32) ch->fmtlist[i++] = SND_FORMAT(AFMT_S32_LE, 8, 1); } } if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(fmtcap)) { ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 2, 0); if (channels >= 8) { ch->fmtlist[i++] = SND_FORMAT(AFMT_AC3, 8, 1); } } ch->fmtlist[i] = 0; i = 0; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(pcmcap)) ch->pcmrates[i++] = 8000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(pcmcap)) ch->pcmrates[i++] = 11025; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(pcmcap)) ch->pcmrates[i++] = 16000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(pcmcap)) ch->pcmrates[i++] = 22050; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(pcmcap)) ch->pcmrates[i++] = 32000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(pcmcap)) ch->pcmrates[i++] = 44100; /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ(pcmcap)) */ ch->pcmrates[i++] = 48000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(pcmcap)) ch->pcmrates[i++] = 88200; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(pcmcap)) ch->pcmrates[i++] = 96000; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(pcmcap)) ch->pcmrates[i++] = 176400; if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(pcmcap)) ch->pcmrates[i++] = 192000; /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ(pcmcap)) */ ch->pcmrates[i] = 0; if (i > 0) { ch->caps.minspeed = ch->pcmrates[0]; ch->caps.maxspeed = ch->pcmrates[i - 1]; } } return (ret); } static void hdaa_prepare_pcms(struct hdaa_devinfo *devinfo) { struct hdaa_audio_as *as = devinfo->as; int i, j, k, apdev = 0, ardev = 0, dpdev = 0, drdev = 0; for (i = 0; i < devinfo->ascnt; i++) { if (as[i].enable == 0) continue; if (as[i].dir == HDAA_CTL_IN) { if (as[i].digital) drdev++; else ardev++; } else { if (as[i].digital) dpdev++; else apdev++; } } devinfo->num_devs = max(ardev, apdev) + max(drdev, dpdev); devinfo->devs = malloc(devinfo->num_devs * sizeof(struct hdaa_pcm_devinfo), M_HDAA, M_ZERO | M_NOWAIT); if (devinfo->devs == NULL) { device_printf(devinfo->dev, "Unable to allocate memory for devices\n"); return; } for (i = 0; i < devinfo->num_devs; i++) { devinfo->devs[i].index = i; devinfo->devs[i].devinfo = devinfo; devinfo->devs[i].playas = -1; devinfo->devs[i].recas = -1; devinfo->devs[i].digital = 255; } for (i = 0; i < devinfo->ascnt; i++) { if (as[i].enable == 0) continue; for (j = 0; j < devinfo->num_devs; j++) { if (devinfo->devs[j].digital != 255 && (!devinfo->devs[j].digital) != (!as[i].digital)) continue; if (as[i].dir == HDAA_CTL_IN) { if (devinfo->devs[j].recas >= 0) continue; devinfo->devs[j].recas = i; } else { if (devinfo->devs[j].playas >= 0) continue; devinfo->devs[j].playas = i; } as[i].pdevinfo = &devinfo->devs[j]; for (k = 0; k < as[i].num_chans; k++) { devinfo->chans[as[i].chans[k]].pdevinfo = &devinfo->devs[j]; } devinfo->devs[j].digital = as[i].digital; break; } } } static void hdaa_create_pcms(struct hdaa_devinfo *devinfo) { int i; for (i = 0; i < devinfo->num_devs; i++) { struct hdaa_pcm_devinfo *pdevinfo = &devinfo->devs[i]; pdevinfo->dev = device_add_child(devinfo->dev, "pcm", DEVICE_UNIT_ANY); device_set_ivars(pdevinfo->dev, (void *)pdevinfo); } } static void hdaa_dump_ctls(struct hdaa_pcm_devinfo *pdevinfo, const char *banner, uint32_t flag) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_ctl *ctl; char buf[64]; int i, j, printed = 0; if (flag == 0) { flag = ~(SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_CD | SOUND_MASK_LINE | SOUND_MASK_RECLEV | SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IGAIN | SOUND_MASK_OGAIN | SOUND_MASK_IMIX | SOUND_MASK_MONITOR); } for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) { if ((flag & (1 << j)) == 0) continue; i = 0; printed = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { if (ctl->enable == 0 || ctl->widget->enable == 0) continue; if (!((pdevinfo->playas >= 0 && ctl->widget->bindas == pdevinfo->playas) || (pdevinfo->recas >= 0 && ctl->widget->bindas == pdevinfo->recas) || (ctl->widget->bindas == -2 && pdevinfo->index == 0))) continue; if ((ctl->ossmask & (1 << j)) == 0) continue; if (printed == 0) { if (banner != NULL) { device_printf(pdevinfo->dev, "%s", banner); } else { device_printf(pdevinfo->dev, "Unknown Ctl"); } printf(" (OSS: %s)", hdaa_audio_ctl_ossmixer_mask2allname(1 << j, buf, sizeof(buf))); if (pdevinfo->ossmask & (1 << j)) { printf(": %+d/%+ddB\n", pdevinfo->minamp[j] / 4, pdevinfo->maxamp[j] / 4); } else printf("\n"); printed = 1; } device_printf(pdevinfo->dev, " +- ctl %2d (nid %3d %s", i, ctl->widget->nid, (ctl->ndir == HDAA_CTL_IN)?"in ":"out"); if (ctl->ndir == HDAA_CTL_IN && ctl->ndir == ctl->dir) printf(" %2d): ", ctl->index); else printf("): "); if (ctl->step > 0) { printf("%+d/%+ddB (%d steps)%s\n", MINQDB(ctl) / 4, MAXQDB(ctl) / 4, ctl->step + 1, ctl->mute?" + mute":""); } else printf("%s\n", ctl->mute?"mute":""); } } if (printed) device_printf(pdevinfo->dev, "\n"); } static void hdaa_dump_audio_formats(device_t dev, uint32_t fcap, uint32_t pcmcap) { uint32_t cap; cap = fcap; if (cap != 0) { device_printf(dev, " Stream cap: 0x%08x", cap); if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) printf(" AC3"); if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) printf(" FLOAT32"); if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) printf(" PCM"); printf("\n"); } cap = pcmcap; if (cap != 0) { device_printf(dev, " PCM cap: 0x%08x", cap); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) printf(" 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) printf(" 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) printf(" 20"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) printf(" 24"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) printf(" 32"); printf(" bits,"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) printf(" 8"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) printf(" 11"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) printf(" 16"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) printf(" 22"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) printf(" 32"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) printf(" 44"); printf(" 48"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) printf(" 88"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) printf(" 96"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) printf(" 176"); if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) printf(" 192"); printf(" KHz\n"); } } static void hdaa_dump_pin(struct hdaa_widget *w) { uint32_t pincap; pincap = w->wclass.pin.cap; device_printf(w->devinfo->dev, " Pin cap: 0x%08x", pincap); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap)) printf(" ISC"); if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) printf(" TRQD"); if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) printf(" PDC"); if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) printf(" HP"); if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) printf(" OUT"); if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) printf(" IN"); if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap)) printf(" BAL"); if (HDA_PARAM_PIN_CAP_HDMI(pincap)) printf(" HDMI"); if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) { printf(" VREF["); if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) printf(" 50"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) printf(" 80"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) printf(" 100"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap)) printf(" GROUND"); if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap)) printf(" HIZ"); printf(" ]"); } if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) printf(" EAPD"); if (HDA_PARAM_PIN_CAP_DP(pincap)) printf(" DP"); if (HDA_PARAM_PIN_CAP_HBR(pincap)) printf(" HBR"); printf("\n"); device_printf(w->devinfo->dev, " Pin config: 0x%08x\n", w->wclass.pin.config); device_printf(w->devinfo->dev, " Pin control: 0x%08x", w->wclass.pin.ctrl); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE) printf(" HP"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE) printf(" IN"); if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE) printf(" OUT"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) == 0x03) printf(" HBR"); else if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) printf(" EPTs"); } else { if ((w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) != 0) printf(" VREFs"); } printf("\n"); } static void hdaa_dump_pin_config(struct hdaa_widget *w, uint32_t conf) { device_printf(w->devinfo->dev, "%2d %08x %-2d %-2d " "%-13s %-5s %-7s %-10s %-7s %d%s\n", w->nid, conf, HDA_CONFIG_DEFAULTCONF_ASSOCIATION(conf), HDA_CONFIG_DEFAULTCONF_SEQUENCE(conf), HDA_DEVS[HDA_CONFIG_DEFAULTCONF_DEVICE(conf)], HDA_CONNS[HDA_CONFIG_DEFAULTCONF_CONNECTIVITY(conf)], HDA_CONNECTORS[HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE(conf)], HDA_LOCS[HDA_CONFIG_DEFAULTCONF_LOCATION(conf)], HDA_COLORS[HDA_CONFIG_DEFAULTCONF_COLOR(conf)], HDA_CONFIG_DEFAULTCONF_MISC(conf), (w->enable == 0)?" DISA":""); } static void hdaa_dump_pin_configs(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w; int i; device_printf(devinfo->dev, "nid 0x as seq " "device conn jack loc color misc\n"); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; hdaa_dump_pin_config(w, w->wclass.pin.config); } } static void hdaa_dump_amp(device_t dev, uint32_t cap, const char *banner) { int offset, size, step; offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap); size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap); step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap); device_printf(dev, " %s amp: 0x%08x " "mute=%d step=%d size=%d offset=%d (%+d/%+ddB)\n", banner, cap, HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap), step, size, offset, ((0 - offset) * (size + 1)) / 4, ((step - offset) * (size + 1)) / 4); } static void hdaa_dump_nodes(struct hdaa_devinfo *devinfo) { struct hdaa_widget *w, *cw; char buf[64]; int i, j; device_printf(devinfo->dev, "\n"); device_printf(devinfo->dev, "Default parameters:\n"); hdaa_dump_audio_formats(devinfo->dev, devinfo->supp_stream_formats, devinfo->supp_pcm_size_rate); hdaa_dump_amp(devinfo->dev, devinfo->inamp_cap, " Input"); hdaa_dump_amp(devinfo->dev, devinfo->outamp_cap, "Output"); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) { device_printf(devinfo->dev, "Ghost widget nid=%d\n", i); continue; } device_printf(devinfo->dev, "\n"); device_printf(devinfo->dev, " nid: %d%s\n", w->nid, (w->enable == 0) ? " [DISABLED]" : ""); device_printf(devinfo->dev, " Name: %s\n", w->name); device_printf(devinfo->dev, " Widget cap: 0x%08x", w->param.widget_cap); if (w->param.widget_cap & 0x0ee1) { if (HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP(w->param.widget_cap)) printf(" LRSWAP"); if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(w->param.widget_cap)) printf(" PWR"); if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) printf(" DIGITAL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) printf(" UNSOL"); if (HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(w->param.widget_cap)) printf(" PROC"); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) printf(" STRIPE(x%d)", 1 << (fls(w->wclass.conv.stripecap) - 1)); j = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (j == 1) printf(" STEREO"); else if (j > 1) printf(" %dCH", j + 1); } printf("\n"); if (w->bindas != -1) { device_printf(devinfo->dev, " Association: %d (0x%04x)\n", w->bindas, w->bindseqmask); } if (w->ossmask != 0 || w->ossdev >= 0) { device_printf(devinfo->dev, " OSS: %s", hdaa_audio_ctl_ossmixer_mask2allname(w->ossmask, buf, sizeof(buf))); if (w->ossdev >= 0) printf(" (%s)", ossnames[w->ossdev]); printf("\n"); } if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { hdaa_dump_audio_formats(devinfo->dev, w->param.supp_stream_formats, w->param.supp_pcm_size_rate); } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || w->waspin) hdaa_dump_pin(w); if (w->param.eapdbtl != HDA_INVALID) device_printf(devinfo->dev, " EAPD: 0x%08x\n", w->param.eapdbtl); if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) && w->param.outamp_cap != 0) hdaa_dump_amp(devinfo->dev, w->param.outamp_cap, "Output"); if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) && w->param.inamp_cap != 0) hdaa_dump_amp(devinfo->dev, w->param.inamp_cap, " Input"); if (w->nconns > 0) device_printf(devinfo->dev, " Connections: %d\n", w->nconns); for (j = 0; j < w->nconns; j++) { cw = hdaa_widget_get(devinfo, w->conns[j]); device_printf(devinfo->dev, " + %s<- nid=%d [%s]", (w->connsenable[j] == 0)?"[DISABLED] ":"", w->conns[j], (cw == NULL) ? "GHOST!" : cw->name); if (cw == NULL) printf(" [UNKNOWN]"); else if (cw->enable == 0) printf(" [DISABLED]"); if (w->nconns > 1 && w->selconn == j && w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) printf(" (selected)"); printf("\n"); } } } static void hdaa_dump_dst_nid(struct hdaa_pcm_devinfo *pdevinfo, nid_t nid, int depth) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w, *cw; char buf[64]; int i; if (depth > HDA_PARSE_MAXDEPTH) return; w = hdaa_widget_get(devinfo, nid); if (w == NULL || w->enable == 0) return; if (depth == 0) device_printf(pdevinfo->dev, "%*s", 4, ""); else device_printf(pdevinfo->dev, "%*s + <- ", 4 + (depth - 1) * 7, ""); printf("nid=%d [%s]", w->nid, w->name); if (depth > 0) { if (w->ossmask == 0) { printf("\n"); return; } printf(" [src: %s]", hdaa_audio_ctl_ossmixer_mask2allname( w->ossmask, buf, sizeof(buf))); if (w->ossdev >= 0) { printf("\n"); return; } } printf("\n"); for (i = 0; i < w->nconns; i++) { if (w->connsenable[i] == 0) continue; cw = hdaa_widget_get(devinfo, w->conns[i]); if (cw == NULL || cw->enable == 0 || cw->bindas == -1) continue; hdaa_dump_dst_nid(pdevinfo, w->conns[i], depth + 1); } } static void hdaa_dump_dac(struct hdaa_pcm_devinfo *pdevinfo) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_as *as; struct hdaa_widget *w; nid_t *nids; int chid, i; if (pdevinfo->playas < 0) return; device_printf(pdevinfo->dev, "Playback:\n"); chid = devinfo->as[pdevinfo->playas].chans[0]; hdaa_dump_audio_formats(pdevinfo->dev, devinfo->chans[chid].supp_stream_formats, devinfo->chans[chid].supp_pcm_size_rate); for (i = 0; i < devinfo->as[pdevinfo->playas].num_chans; i++) { chid = devinfo->as[pdevinfo->playas].chans[i]; device_printf(pdevinfo->dev, " DAC:"); for (nids = devinfo->chans[chid].io; *nids != -1; nids++) printf(" %d", *nids); printf("\n"); } as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; w = hdaa_widget_get(devinfo, as->pins[i]); if (w == NULL || w->enable == 0) continue; device_printf(pdevinfo->dev, "\n"); hdaa_dump_dst_nid(pdevinfo, as->pins[i], 0); } device_printf(pdevinfo->dev, "\n"); } static void hdaa_dump_adc(struct hdaa_pcm_devinfo *pdevinfo) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; nid_t *nids; int chid, i; if (pdevinfo->recas < 0) return; device_printf(pdevinfo->dev, "Record:\n"); chid = devinfo->as[pdevinfo->recas].chans[0]; hdaa_dump_audio_formats(pdevinfo->dev, devinfo->chans[chid].supp_stream_formats, devinfo->chans[chid].supp_pcm_size_rate); for (i = 0; i < devinfo->as[pdevinfo->recas].num_chans; i++) { chid = devinfo->as[pdevinfo->recas].chans[i]; device_printf(pdevinfo->dev, " ADC:"); for (nids = devinfo->chans[chid].io; *nids != -1; nids++) printf(" %d", *nids); printf("\n"); } for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) continue; if (w->bindas != pdevinfo->recas) continue; device_printf(pdevinfo->dev, "\n"); hdaa_dump_dst_nid(pdevinfo, i, 0); } device_printf(pdevinfo->dev, "\n"); } static void hdaa_dump_mix(struct hdaa_pcm_devinfo *pdevinfo) { struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_widget *w; int i; int printed = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0) continue; if (w->ossdev != SOUND_MIXER_IMIX) continue; if (w->bindas != pdevinfo->recas) continue; if (printed == 0) { printed = 1; device_printf(pdevinfo->dev, "Input Mix:\n"); } device_printf(pdevinfo->dev, "\n"); hdaa_dump_dst_nid(pdevinfo, i, 0); } if (printed) device_printf(pdevinfo->dev, "\n"); } static void hdaa_pindump(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_widget *w; uint32_t res, pincap, delay; int i; device_printf(dev, "Dumping AFG pins:\n"); device_printf(dev, "nid 0x as seq " "device conn jack loc color misc\n"); for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; hdaa_dump_pin_config(w, w->wclass.pin.config); pincap = w->wclass.pin.cap; device_printf(dev, " Caps: %2s %3s %2s %4s %4s", HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)?"IN":"", HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)?"OUT":"", HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)?"HP":"", HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)?"EAPD":"", HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)?"VREF":""); if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap) || HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) { if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) { delay = 0; hda_command(dev, HDA_CMD_SET_PIN_SENSE(0, w->nid, 0)); do { res = hda_command(dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); if (res != 0x7fffffff && res != 0xffffffff) break; DELAY(10); } while (++delay < 10000); } else { delay = 0; res = hda_command(dev, HDA_CMD_GET_PIN_SENSE(0, w->nid)); } printf(" Sense: 0x%08x (%sconnected%s)", res, (res & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT) ? "" : "dis", (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap) && (res & HDA_CMD_GET_PIN_SENSE_ELD_VALID)) ? ", ELD valid" : ""); if (delay > 0) printf(" delay %dus", delay * 10); } printf("\n"); } device_printf(dev, "NumGPIO=%d NumGPO=%d NumGPI=%d GPIWake=%d GPIUnsol=%d\n", HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->gpio_cap), HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->gpio_cap)); hdaa_dump_gpi(devinfo); hdaa_dump_gpio(devinfo); hdaa_dump_gpo(devinfo); } static void hdaa_configure(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_audio_ctl *ctl; int i; HDA_BOOTHVERBOSE( device_printf(dev, "Applying built-in patches...\n"); ); hdaa_patch(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Applying local patches...\n"); ); hdaa_local_patch(devinfo); hdaa_audio_postprocess(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Parsing Ctls...\n"); ); hdaa_audio_ctl_parse(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling nonaudio...\n"); ); hdaa_audio_disable_nonaudio(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling useless...\n"); ); hdaa_audio_disable_useless(devinfo); HDA_BOOTVERBOSE( device_printf(dev, "Patched pins configuration:\n"); hdaa_dump_pin_configs(devinfo); ); HDA_BOOTHVERBOSE( device_printf(dev, "Parsing pin associations...\n"); ); hdaa_audio_as_parse(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Building AFG tree...\n"); ); hdaa_audio_build_tree(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling unassociated " "widgets...\n"); ); hdaa_audio_disable_unas(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling nonselected " "inputs...\n"); ); hdaa_audio_disable_notselected(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling useless...\n"); ); hdaa_audio_disable_useless(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling " "crossassociatement connections...\n"); ); hdaa_audio_disable_crossas(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Disabling useless...\n"); ); hdaa_audio_disable_useless(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Binding associations to channels...\n"); ); hdaa_audio_bind_as(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Assigning names to signal sources...\n"); ); hdaa_audio_assign_names(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Preparing PCM devices...\n"); ); hdaa_prepare_pcms(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Assigning mixers to the tree...\n"); ); hdaa_audio_assign_mixers(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Preparing pin controls...\n"); ); hdaa_audio_prepare_pin_ctrl(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "AFG commit...\n"); ); hdaa_audio_commit(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Applying direct built-in patches...\n"); ); hdaa_patch_direct(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Pin sense init...\n"); ); hdaa_sense_init(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Creating PCM devices...\n"); ); hdaa_create_pcms(devinfo); HDA_BOOTVERBOSE( if (devinfo->quirks != 0) { device_printf(dev, "FG config/quirks:"); for (i = 0; i < nitems(hdaa_quirks_tab); i++) { if ((devinfo->quirks & hdaa_quirks_tab[i].value) == hdaa_quirks_tab[i].value) printf(" %s", hdaa_quirks_tab[i].key); } printf("\n"); } ); HDA_BOOTHVERBOSE( device_printf(dev, "\n"); device_printf(dev, "+-----------+\n"); device_printf(dev, "| HDA NODES |\n"); device_printf(dev, "+-----------+\n"); hdaa_dump_nodes(devinfo); device_printf(dev, "\n"); device_printf(dev, "+----------------+\n"); device_printf(dev, "| HDA AMPLIFIERS |\n"); device_printf(dev, "+----------------+\n"); device_printf(dev, "\n"); i = 0; while ((ctl = hdaa_audio_ctl_each(devinfo, &i)) != NULL) { device_printf(dev, "%3d: nid %3d %s (%s) index %d", i, (ctl->widget != NULL) ? ctl->widget->nid : -1, (ctl->ndir == HDAA_CTL_IN)?"in ":"out", (ctl->dir == HDAA_CTL_IN)?"in ":"out", ctl->index); if (ctl->childwidget != NULL) printf(" cnid %3d", ctl->childwidget->nid); else printf(" "); printf(" ossmask=0x%08x\n", ctl->ossmask); device_printf(dev, " mute: %d step: %3d size: %3d off: %3d%s\n", ctl->mute, ctl->step, ctl->size, ctl->offset, (ctl->enable == 0) ? " [DISABLED]" : ((ctl->ossmask == 0) ? " [UNUSED]" : "")); } device_printf(dev, "\n"); ); } static void hdaa_unconfigure(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_widget *w; int i, j; HDA_BOOTHVERBOSE( device_printf(dev, "Pin sense deinit...\n"); ); hdaa_sense_deinit(devinfo); free(devinfo->ctl, M_HDAA); devinfo->ctl = NULL; devinfo->ctlcnt = 0; free(devinfo->as, M_HDAA); devinfo->as = NULL; devinfo->ascnt = 0; free(devinfo->devs, M_HDAA); devinfo->devs = NULL; devinfo->num_devs = 0; free(devinfo->chans, M_HDAA); devinfo->chans = NULL; devinfo->num_chans = 0; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL) continue; w->enable = 1; w->selconn = -1; w->pflags = 0; w->bindas = -1; w->bindseqmask = 0; w->ossdev = -1; w->ossmask = 0; for (j = 0; j < w->nconns; j++) w->connsenable[j] = 1; if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) w->wclass.pin.config = w->wclass.pin.newconf; if (w->eld != NULL) { w->eld_len = 0; free(w->eld, M_HDAA); w->eld = NULL; } } } static int hdaa_sysctl_gpi_state(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; device_t dev = devinfo->dev; char buf[256]; int n = 0, i, numgpi; uint32_t data = 0; buf[0] = 0; hdaa_lock(devinfo); numgpi = HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->gpio_cap); if (numgpi > 0) { data = hda_command(dev, HDA_CMD_GET_GPI_DATA(0, devinfo->nid)); } hdaa_unlock(devinfo); for (i = 0; i < numgpi; i++) { n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%d", n != 0 ? " " : "", i, ((data >> i) & 1)); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdaa_sysctl_gpio_state(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; device_t dev = devinfo->dev; char buf[256]; int n = 0, i, numgpio; uint32_t data = 0, enable = 0, dir = 0; buf[0] = 0; hdaa_lock(devinfo); numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); if (numgpio > 0) { data = hda_command(dev, HDA_CMD_GET_GPIO_DATA(0, devinfo->nid)); enable = hda_command(dev, HDA_CMD_GET_GPIO_ENABLE_MASK(0, devinfo->nid)); dir = hda_command(dev, HDA_CMD_GET_GPIO_DIRECTION(0, devinfo->nid)); } hdaa_unlock(devinfo); for (i = 0; i < numgpio; i++) { n += snprintf(buf + n, sizeof(buf) - n, "%s%d=", n != 0 ? " " : "", i); if ((enable & (1 << i)) == 0) { n += snprintf(buf + n, sizeof(buf) - n, "disabled"); continue; } n += snprintf(buf + n, sizeof(buf) - n, "%sput(%d)", ((dir >> i) & 1) ? "out" : "in", ((data >> i) & 1)); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdaa_sysctl_gpio_config(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; char buf[256]; int error, n = 0, i, numgpio; uint32_t gpio, x; gpio = devinfo->newgpio; numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->gpio_cap); buf[0] = 0; for (i = 0; i < numgpio; i++) { x = (gpio & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i); n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%s", n != 0 ? " " : "", i, HDA_GPIO_ACTIONS[x]); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) gpio = strtol(buf + 2, NULL, 16); else gpio = hdaa_gpio_patch(gpio, buf); hdaa_lock(devinfo); devinfo->newgpio = devinfo->gpio = gpio; hdaa_gpio_commit(devinfo); hdaa_unlock(devinfo); return (0); } static int hdaa_sysctl_gpo_state(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; device_t dev = devinfo->dev; char buf[256]; int n = 0, i, numgpo; uint32_t data = 0; buf[0] = 0; hdaa_lock(devinfo); numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); if (numgpo > 0) { data = hda_command(dev, HDA_CMD_GET_GPO_DATA(0, devinfo->nid)); } hdaa_unlock(devinfo); for (i = 0; i < numgpo; i++) { n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%d", n != 0 ? " " : "", i, ((data >> i) & 1)); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdaa_sysctl_gpo_config(SYSCTL_HANDLER_ARGS) { struct hdaa_devinfo *devinfo = oidp->oid_arg1; char buf[256]; int error, n = 0, i, numgpo; uint32_t gpo, x; gpo = devinfo->newgpo; numgpo = HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->gpio_cap); buf[0] = 0; for (i = 0; i < numgpo; i++) { x = (gpo & HDAA_GPIO_MASK(i)) >> HDAA_GPIO_SHIFT(i); n += snprintf(buf + n, sizeof(buf) - n, "%s%d=%s", n != 0 ? " " : "", i, HDA_GPIO_ACTIONS[x]); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strncmp(buf, "0x", 2) == 0) gpo = strtol(buf + 2, NULL, 16); else gpo = hdaa_gpio_patch(gpo, buf); hdaa_lock(devinfo); devinfo->newgpo = devinfo->gpo = gpo; hdaa_gpo_commit(devinfo); hdaa_unlock(devinfo); return (0); } static int hdaa_sysctl_reconfig(SYSCTL_HANDLER_ARGS) { device_t dev; struct hdaa_devinfo *devinfo; int error, val; dev = oidp->oid_arg1; devinfo = device_get_softc(dev); if (devinfo == NULL) return (EINVAL); val = 0; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL || val == 0) return (error); HDA_BOOTHVERBOSE( device_printf(dev, "Reconfiguration...\n"); ); bus_topo_lock(); if ((error = device_delete_children(dev)) != 0) { bus_topo_unlock(); return (error); } hdaa_lock(devinfo); hdaa_unconfigure(dev); hdaa_configure(dev); hdaa_unlock(devinfo); bus_attach_children(dev); HDA_BOOTHVERBOSE( device_printf(dev, "Reconfiguration done\n"); ); bus_topo_unlock(); return (0); } static int hdaa_suspend(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); int i; HDA_BOOTHVERBOSE( device_printf(dev, "Suspend...\n"); ); hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Stop streams...\n"); ); for (i = 0; i < devinfo->num_chans; i++) { if (devinfo->chans[i].flags & HDAA_CHN_RUNNING) { devinfo->chans[i].flags |= HDAA_CHN_SUSPEND; hdaa_channel_stop(&devinfo->chans[i]); } } HDA_BOOTHVERBOSE( device_printf(dev, "Power down FG" " nid=%d to the D3 state...\n", devinfo->nid); ); hda_command(devinfo->dev, HDA_CMD_SET_POWER_STATE(0, devinfo->nid, HDA_CMD_POWER_STATE_D3)); callout_stop(&devinfo->poll_jack); hdaa_unlock(devinfo); callout_drain(&devinfo->poll_jack); HDA_BOOTHVERBOSE( device_printf(dev, "Suspend done\n"); ); return (0); } static int hdaa_resume(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); int i; HDA_BOOTHVERBOSE( device_printf(dev, "Resume...\n"); ); hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Power up audio FG nid=%d...\n", devinfo->nid); ); hdaa_powerup(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "AFG commit...\n"); ); hdaa_audio_commit(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Applying direct built-in patches...\n"); ); hdaa_patch_direct(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Pin sense init...\n"); ); hdaa_sense_init(devinfo); hdaa_unlock(devinfo); for (i = 0; i < devinfo->num_devs; i++) { struct hdaa_pcm_devinfo *pdevinfo = &devinfo->devs[i]; HDA_BOOTHVERBOSE( device_printf(pdevinfo->dev, "OSS mixer reinitialization...\n"); ); if (mixer_reinit(pdevinfo->dev) == -1) device_printf(pdevinfo->dev, "unable to reinitialize the mixer\n"); } hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Start streams...\n"); ); for (i = 0; i < devinfo->num_chans; i++) { if (devinfo->chans[i].flags & HDAA_CHN_SUSPEND) { devinfo->chans[i].flags &= ~HDAA_CHN_SUSPEND; hdaa_channel_start(&devinfo->chans[i]); } } hdaa_unlock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Resume done\n"); ); return (0); } static int hdaa_probe(device_t dev) { const char *pdesc; if (hda_get_node_type(dev) != HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO) return (ENXIO); pdesc = device_get_desc(device_get_parent(dev)); device_set_descf(dev, "%.*s Audio Function Group", (int)(strlen(pdesc) - 10), pdesc); return (BUS_PROBE_DEFAULT); } static int hdaa_attach(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); uint32_t res; nid_t nid = hda_get_node_id(dev); devinfo->dev = dev; devinfo->lock = HDAC_GET_MTX(device_get_parent(dev), dev); devinfo->nid = nid; devinfo->newquirks = -1; devinfo->newgpio = -1; devinfo->newgpo = -1; callout_init(&devinfo->poll_jack, 1); devinfo->poll_ival = hz; hdaa_lock(devinfo); res = hda_command(dev, HDA_CMD_GET_PARAMETER(0 , nid, HDA_PARAM_SUB_NODE_COUNT)); hdaa_unlock(devinfo); devinfo->nodecnt = HDA_PARAM_SUB_NODE_COUNT_TOTAL(res); devinfo->startnode = HDA_PARAM_SUB_NODE_COUNT_START(res); devinfo->endnode = devinfo->startnode + devinfo->nodecnt; HDA_BOOTVERBOSE( device_printf(dev, "Subsystem ID: 0x%08x\n", hda_get_subsystem_id(dev)); ); HDA_BOOTHVERBOSE( device_printf(dev, "Audio Function Group at nid=%d: %d subnodes %d-%d\n", nid, devinfo->nodecnt, devinfo->startnode, devinfo->endnode - 1); ); if (devinfo->nodecnt > 0) devinfo->widget = malloc(sizeof(*(devinfo->widget)) * devinfo->nodecnt, M_HDAA, M_WAITOK | M_ZERO); else devinfo->widget = NULL; hdaa_lock(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Powering up...\n"); ); hdaa_powerup(devinfo); HDA_BOOTHVERBOSE( device_printf(dev, "Parsing audio FG...\n"); ); hdaa_audio_parse(devinfo); HDA_BOOTVERBOSE( device_printf(dev, "Original pins configuration:\n"); hdaa_dump_pin_configs(devinfo); ); hdaa_configure(dev); hdaa_unlock(devinfo); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, &devinfo->newquirks, 0, hdaa_sysctl_quirks, "A", "Configuration options"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpi_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpi_state, "A", "GPI state"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpio_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpio_state, "A", "GPIO state"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpio_config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpio_config, "A", "GPIO configuration"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpo_state", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpo_state, "A", "GPO state"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "gpo_config", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, devinfo, 0, hdaa_sysctl_gpo_config, "A", "GPO configuration"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "reconfig", CTLTYPE_INT | CTLFLAG_RW, dev, 0, hdaa_sysctl_reconfig, "I", "Reprocess configuration"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "init_clear", CTLFLAG_RW, &devinfo->init_clear, 1,"Clear initial pin widget configuration"); bus_attach_children(dev); return (0); } static int hdaa_detach(device_t dev) { struct hdaa_devinfo *devinfo = device_get_softc(dev); int error; - if ((error = device_delete_children(dev)) != 0) + if ((error = bus_generic_detach(dev)) != 0) return (error); hdaa_lock(devinfo); hdaa_unconfigure(dev); devinfo->poll_ival = 0; callout_stop(&devinfo->poll_jack); hdaa_unlock(devinfo); callout_drain(&devinfo->poll_jack); free(devinfo->widget, M_HDAA); return (0); } static int hdaa_print_child(device_t dev, device_t child) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(child); struct hdaa_audio_as *as; int retval, first = 1, i; retval = bus_print_child_header(dev, child); retval += printf(" at nid "); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; retval += printf("%s%d", first ? "" : ",", as->pins[i]); first = 0; } } if (pdevinfo->recas >= 0) { if (pdevinfo->playas >= 0) { retval += printf(" and "); first = 1; } as = &devinfo->as[pdevinfo->recas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; retval += printf("%s%d", first ? "" : ",", as->pins[i]); first = 0; } } retval += bus_print_child_footer(dev, child); return (retval); } static int hdaa_child_location(device_t dev, device_t child, struct sbuf *sb) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(child); struct hdaa_audio_as *as; int first = 1, i; sbuf_printf(sb, "nid="); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; sbuf_printf(sb, "%s%d", first ? "" : ",", as->pins[i]); first = 0; } } if (pdevinfo->recas >= 0) { as = &devinfo->as[pdevinfo->recas]; for (i = 0; i < 16; i++) { if (as->pins[i] <= 0) continue; sbuf_printf(sb, "%s%d", first ? "" : ",", as->pins[i]); first = 0; } } return (0); } static void hdaa_stream_intr(device_t dev, int dir, int stream) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_chan *ch; int i; for (i = 0; i < devinfo->num_chans; i++) { ch = &devinfo->chans[i]; if (!(ch->flags & HDAA_CHN_RUNNING)) continue; if (ch->dir == ((dir == 1) ? PCMDIR_PLAY : PCMDIR_REC) && ch->sid == stream) { hdaa_unlock(devinfo); chn_intr(ch->c); hdaa_lock(devinfo); } } } static void hdaa_unsol_intr(device_t dev, uint32_t resp) { struct hdaa_devinfo *devinfo = device_get_softc(dev); struct hdaa_widget *w; int i, tag, flags; HDA_BOOTHVERBOSE( device_printf(dev, "Unsolicited response %08x\n", resp); ); tag = resp >> 26; for (i = devinfo->startnode; i < devinfo->endnode; i++) { w = hdaa_widget_get(devinfo, i); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; if (w->unsol != tag) continue; if (HDA_PARAM_PIN_CAP_DP(w->wclass.pin.cap) || HDA_PARAM_PIN_CAP_HDMI(w->wclass.pin.cap)) flags = resp & 0x03; else flags = 0x01; if (flags & 0x01) hdaa_presence_handler(w); if (flags & 0x02) hdaa_eld_handler(w); } } static device_method_t hdaa_methods[] = { /* device interface */ DEVMETHOD(device_probe, hdaa_probe), DEVMETHOD(device_attach, hdaa_attach), DEVMETHOD(device_detach, hdaa_detach), DEVMETHOD(device_suspend, hdaa_suspend), DEVMETHOD(device_resume, hdaa_resume), /* Bus interface */ DEVMETHOD(bus_print_child, hdaa_print_child), DEVMETHOD(bus_child_location, hdaa_child_location), DEVMETHOD(hdac_stream_intr, hdaa_stream_intr), DEVMETHOD(hdac_unsol_intr, hdaa_unsol_intr), DEVMETHOD(hdac_pindump, hdaa_pindump), DEVMETHOD_END }; static driver_t hdaa_driver = { "hdaa", hdaa_methods, sizeof(struct hdaa_devinfo), }; DRIVER_MODULE(snd_hda, hdacc, hdaa_driver, NULL, NULL); static void hdaa_chan_formula(struct hdaa_devinfo *devinfo, int asid, char *buf, int buflen) { struct hdaa_audio_as *as; int c; as = &devinfo->as[asid]; c = devinfo->chans[as->chans[0]].channels; if (c == 1) snprintf(buf, buflen, "mono"); else if (c == 2) { if (as->hpredir < 0) buf[0] = 0; else snprintf(buf, buflen, "2.0"); } else if (as->pinset == 0x0003) snprintf(buf, buflen, "3.1"); else if (as->pinset == 0x0005 || as->pinset == 0x0011) snprintf(buf, buflen, "4.0"); else if (as->pinset == 0x0007 || as->pinset == 0x0013) snprintf(buf, buflen, "5.1"); else if (as->pinset == 0x0017) snprintf(buf, buflen, "7.1"); else snprintf(buf, buflen, "%dch", c); if (as->hpredir >= 0) strlcat(buf, "+HP", buflen); } static int hdaa_chan_type(struct hdaa_devinfo *devinfo, int asid) { struct hdaa_audio_as *as; struct hdaa_widget *w; int i, t = -1, t1; as = &devinfo->as[asid]; for (i = 0; i < 16; i++) { w = hdaa_widget_get(devinfo, as->pins[i]); if (w == NULL || w->enable == 0 || w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) continue; t1 = HDA_CONFIG_DEFAULTCONF_DEVICE(w->wclass.pin.config); if (t == -1) t = t1; else if (t != t1) { t = -2; break; } } return (t); } static int hdaa_sysctl_32bit(SYSCTL_HANDLER_ARGS) { struct hdaa_audio_as *as = (struct hdaa_audio_as *)oidp->oid_arg1; struct hdaa_pcm_devinfo *pdevinfo = as->pdevinfo; struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_chan *ch; int error, val, i; uint32_t pcmcap; ch = &devinfo->chans[as->chans[0]]; val = (ch->bit32 == 4) ? 32 : ((ch->bit32 == 3) ? 24 : ((ch->bit32 == 2) ? 20 : 0)); error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL) return (error); pcmcap = ch->supp_pcm_size_rate; if (val == 32 && HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(pcmcap)) ch->bit32 = 4; else if (val == 24 && HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(pcmcap)) ch->bit32 = 3; else if (val == 20 && HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(pcmcap)) ch->bit32 = 2; else return (EINVAL); for (i = 1; i < as->num_chans; i++) devinfo->chans[as->chans[i]].bit32 = ch->bit32; return (0); } static int hdaa_pcm_probe(device_t dev) { struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(dev); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; const char *pdesc; char chans1[8], chans2[8]; int loc1, loc2, t1, t2; if (pdevinfo->playas >= 0) loc1 = devinfo->as[pdevinfo->playas].location; else loc1 = devinfo->as[pdevinfo->recas].location; if (pdevinfo->recas >= 0) loc2 = devinfo->as[pdevinfo->recas].location; else loc2 = loc1; if (loc1 != loc2) loc1 = -2; if (loc1 >= 0 && HDA_LOCS[loc1][0] == '0') loc1 = -2; chans1[0] = 0; chans2[0] = 0; t1 = t2 = -1; if (pdevinfo->playas >= 0) { hdaa_chan_formula(devinfo, pdevinfo->playas, chans1, sizeof(chans1)); t1 = hdaa_chan_type(devinfo, pdevinfo->playas); } if (pdevinfo->recas >= 0) { hdaa_chan_formula(devinfo, pdevinfo->recas, chans2, sizeof(chans2)); t2 = hdaa_chan_type(devinfo, pdevinfo->recas); } if (chans1[0] != 0 || chans2[0] != 0) { if (chans1[0] == 0 && pdevinfo->playas >= 0) snprintf(chans1, sizeof(chans1), "2.0"); else if (chans2[0] == 0 && pdevinfo->recas >= 0) snprintf(chans2, sizeof(chans2), "2.0"); if (strcmp(chans1, chans2) == 0) chans2[0] = 0; } if (t1 == -1) t1 = t2; else if (t2 == -1) t2 = t1; if (t1 != t2) t1 = -2; if (pdevinfo->digital) t1 = -2; pdesc = device_get_desc(device_get_parent(dev)); device_set_descf(dev, "%.*s (%s%s%s%s%s%s%s%s%s)", (int)(strlen(pdesc) - 21), pdesc, loc1 >= 0 ? HDA_LOCS[loc1] : "", loc1 >= 0 ? " " : "", (pdevinfo->digital == 0x7)?"HDMI/DP": ((pdevinfo->digital == 0x5)?"DisplayPort": ((pdevinfo->digital == 0x3)?"HDMI": ((pdevinfo->digital)?"Digital":"Analog"))), chans1[0] ? " " : "", chans1, chans2[0] ? "/" : "", chans2, t1 >= 0 ? " " : "", t1 >= 0 ? HDA_DEVS[t1] : ""); return (BUS_PROBE_SPECIFIC); } static int hdaa_pcm_attach(device_t dev) { struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(dev); struct hdaa_devinfo *devinfo = pdevinfo->devinfo; struct hdaa_audio_as *as; struct snddev_info *d; char status[SND_STATUSLEN]; int i; pdevinfo->chan_size = pcm_getbuffersize(dev, HDA_BUFSZ_MIN, HDA_BUFSZ_DEFAULT, HDA_BUFSZ_MAX); HDA_BOOTVERBOSE( hdaa_dump_dac(pdevinfo); hdaa_dump_adc(pdevinfo); hdaa_dump_mix(pdevinfo); hdaa_dump_ctls(pdevinfo, "Master Volume", SOUND_MASK_VOLUME); hdaa_dump_ctls(pdevinfo, "PCM Volume", SOUND_MASK_PCM); hdaa_dump_ctls(pdevinfo, "CD Volume", SOUND_MASK_CD); hdaa_dump_ctls(pdevinfo, "Microphone Volume", SOUND_MASK_MIC); hdaa_dump_ctls(pdevinfo, "Microphone2 Volume", SOUND_MASK_MONITOR); hdaa_dump_ctls(pdevinfo, "Line-in Volume", SOUND_MASK_LINE); hdaa_dump_ctls(pdevinfo, "Speaker/Beep Volume", SOUND_MASK_SPEAKER); hdaa_dump_ctls(pdevinfo, "Recording Level", SOUND_MASK_RECLEV); hdaa_dump_ctls(pdevinfo, "Input Mix Level", SOUND_MASK_IMIX); hdaa_dump_ctls(pdevinfo, "Input Monitoring Level", SOUND_MASK_IGAIN); hdaa_dump_ctls(pdevinfo, NULL, 0); ); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { i &= HDA_BLK_ALIGN; if (i < HDA_BLK_MIN) i = HDA_BLK_MIN; pdevinfo->chan_blkcnt = pdevinfo->chan_size / i; i = 0; while (pdevinfo->chan_blkcnt >> i) i++; pdevinfo->chan_blkcnt = 1 << (i - 1); if (pdevinfo->chan_blkcnt < HDA_BDL_MIN) pdevinfo->chan_blkcnt = HDA_BDL_MIN; else if (pdevinfo->chan_blkcnt > HDA_BDL_MAX) pdevinfo->chan_blkcnt = HDA_BDL_MAX; } else pdevinfo->chan_blkcnt = HDA_BDL_DEFAULT; /* * We don't register interrupt handler with snd_setup_intr * in pcm device. Mark pcm device as MPSAFE manually. */ pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); HDA_BOOTHVERBOSE( device_printf(dev, "OSS mixer initialization...\n"); ); if (mixer_init(dev, &hdaa_audio_ctl_ossmixer_class, pdevinfo) != 0) device_printf(dev, "Can't register mixer\n"); HDA_BOOTHVERBOSE( device_printf(dev, "Registering PCM channels...\n"); ); pcm_init(dev, pdevinfo); pdevinfo->registered++; d = device_get_softc(dev); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; for (i = 0; i < as->num_chans; i++) pcm_addchan(dev, PCMDIR_PLAY, &hdaa_channel_class, &devinfo->chans[as->chans[i]]); SYSCTL_ADD_PROC(&d->play_sysctl_ctx, SYSCTL_CHILDREN(d->play_sysctl_tree), OID_AUTO, "32bit", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, as, sizeof(as), hdaa_sysctl_32bit, "I", "Resolution of 32bit samples (20/24/32bit)"); } if (pdevinfo->recas >= 0) { as = &devinfo->as[pdevinfo->recas]; for (i = 0; i < as->num_chans; i++) pcm_addchan(dev, PCMDIR_REC, &hdaa_channel_class, &devinfo->chans[as->chans[i]]); SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "32bit", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, as, sizeof(as), hdaa_sysctl_32bit, "I", "Resolution of 32bit samples (20/24/32bit)"); pdevinfo->autorecsrc = 2; resource_int_value(device_get_name(dev), device_get_unit(dev), "rec.autosrc", &pdevinfo->autorecsrc); SYSCTL_ADD_INT(&d->rec_sysctl_ctx, SYSCTL_CHILDREN(d->rec_sysctl_tree), OID_AUTO, "autosrc", CTLFLAG_RW, &pdevinfo->autorecsrc, 0, "Automatic recording source selection"); } if (pdevinfo->mixer != NULL) { hdaa_audio_ctl_set_defaults(pdevinfo); hdaa_lock(devinfo); if (pdevinfo->playas >= 0) { as = &devinfo->as[pdevinfo->playas]; hdaa_channels_handler(as); } if (pdevinfo->recas >= 0) { as = &devinfo->as[pdevinfo->recas]; hdaa_autorecsrc_handler(as, NULL); hdaa_channels_handler(as); } hdaa_unlock(devinfo); } snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); return (pcm_register(dev, status)); } static int hdaa_pcm_detach(device_t dev) { struct hdaa_pcm_devinfo *pdevinfo = (struct hdaa_pcm_devinfo *)device_get_ivars(dev); int err; if (pdevinfo->registered > 0) { err = pcm_unregister(dev); if (err != 0) return (err); } return (0); } static device_method_t hdaa_pcm_methods[] = { /* device interface */ DEVMETHOD(device_probe, hdaa_pcm_probe), DEVMETHOD(device_attach, hdaa_pcm_attach), DEVMETHOD(device_detach, hdaa_pcm_detach), DEVMETHOD_END }; static driver_t hdaa_pcm_driver = { "pcm", hdaa_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hda_pcm, hdaa, hdaa_pcm_driver, NULL, NULL); MODULE_DEPEND(snd_hda, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_hda, 1); diff --git a/sys/dev/sound/pci/hda/hdacc.c b/sys/dev/sound/pci/hda/hdacc.c index c79e9297025c..76aeaec757a5 100644 --- a/sys/dev/sound/pci/hda/hdacc.c +++ b/sys/dev/sound/pci/hda/hdacc.c @@ -1,801 +1,801 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 Stephane E. Potvin * Copyright (c) 2006 Ariff Abdullah * Copyright (c) 2008-2012 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Intel High Definition Audio (CODEC) driver for FreeBSD. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include #include struct hdacc_fg { device_t dev; nid_t nid; uint8_t type; uint32_t subsystem_id; }; struct hdacc_softc { device_t dev; struct mtx *lock; nid_t cad; device_t streams[2][16]; device_t tags[64]; int fgcnt; struct hdacc_fg *fgs; }; #define hdacc_lock(codec) snd_mtxlock((codec)->lock) #define hdacc_unlock(codec) snd_mtxunlock((codec)->lock) #define hdacc_lockassert(codec) snd_mtxassert((codec)->lock) MALLOC_DEFINE(M_HDACC, "hdacc", "HDA CODEC"); /* CODECs */ static const struct { uint32_t id; uint16_t revid; const char *name; } hdacc_codecs[] = { { HDA_CODEC_CS4206, 0, "Cirrus Logic CS4206" }, { HDA_CODEC_CS4207, 0, "Cirrus Logic CS4207" }, { HDA_CODEC_CS4210, 0, "Cirrus Logic CS4210" }, { HDA_CODEC_ALC215, 0, "Realtek ALC215" }, { HDA_CODEC_ALC221, 0, "Realtek ALC221" }, { HDA_CODEC_ALC222, 0, "Realtek ALC222" }, { HDA_CODEC_ALC225, 0, "Realtek ALC225" }, { HDA_CODEC_ALC230, 0, "Realtek ALC230" }, { HDA_CODEC_ALC231, 0, "Realtek ALC231" }, { HDA_CODEC_ALC233, 0, "Realtek ALC233" }, { HDA_CODEC_ALC234, 0, "Realtek ALC234" }, { HDA_CODEC_ALC235, 0, "Realtek ALC235" }, { HDA_CODEC_ALC236, 0, "Realtek ALC236" }, { HDA_CODEC_ALC245, 0, "Realtek ALC245" }, { HDA_CODEC_ALC255, 0, "Realtek ALC255" }, { HDA_CODEC_ALC256, 0, "Realtek ALC256" }, { HDA_CODEC_ALC257, 0, "Realtek ALC257" }, { HDA_CODEC_ALC260, 0, "Realtek ALC260" }, { HDA_CODEC_ALC262, 0, "Realtek ALC262" }, { HDA_CODEC_ALC267, 0, "Realtek ALC267" }, { HDA_CODEC_ALC268, 0, "Realtek ALC268" }, { HDA_CODEC_ALC269, 0, "Realtek ALC269" }, { HDA_CODEC_ALC270, 0, "Realtek ALC270" }, { HDA_CODEC_ALC272, 0, "Realtek ALC272" }, { HDA_CODEC_ALC273, 0, "Realtek ALC273" }, { HDA_CODEC_ALC274, 0, "Realtek ALC274" }, { HDA_CODEC_ALC275, 0, "Realtek ALC275" }, { HDA_CODEC_ALC276, 0, "Realtek ALC276" }, { HDA_CODEC_ALC292, 0, "Realtek ALC292" }, { HDA_CODEC_ALC295, 0, "Realtek ALC295" }, { HDA_CODEC_ALC280, 0, "Realtek ALC280" }, { HDA_CODEC_ALC282, 0, "Realtek ALC282" }, { HDA_CODEC_ALC283, 0, "Realtek ALC283" }, { HDA_CODEC_ALC284, 0, "Realtek ALC284" }, { HDA_CODEC_ALC285, 0, "Realtek ALC285" }, { HDA_CODEC_ALC286, 0, "Realtek ALC286" }, { HDA_CODEC_ALC288, 0, "Realtek ALC288" }, { HDA_CODEC_ALC289, 0, "Realtek ALC289" }, { HDA_CODEC_ALC290, 0, "Realtek ALC290" }, { HDA_CODEC_ALC292, 0, "Realtek ALC292" }, { HDA_CODEC_ALC293, 0, "Realtek ALC293" }, { HDA_CODEC_ALC294, 0, "Realtek ALC294" }, { HDA_CODEC_ALC295, 0, "Realtek ALC295" }, { HDA_CODEC_ALC298, 0, "Realtek ALC298" }, { HDA_CODEC_ALC299, 0, "Realtek ALC299" }, { HDA_CODEC_ALC300, 0, "Realtek ALC300" }, { HDA_CODEC_ALC623, 0, "Realtek ALC623" }, { HDA_CODEC_ALC660, 0, "Realtek ALC660-VD" }, { HDA_CODEC_ALC662, 0x0002, "Realtek ALC662 rev2" }, { HDA_CODEC_ALC662, 0x0101, "Realtek ALC662 rev1" }, { HDA_CODEC_ALC662, 0x0300, "Realtek ALC662 rev3" }, { HDA_CODEC_ALC662, 0, "Realtek ALC662" }, { HDA_CODEC_ALC663, 0, "Realtek ALC663" }, { HDA_CODEC_ALC665, 0, "Realtek ALC665" }, { HDA_CODEC_ALC670, 0, "Realtek ALC670" }, { HDA_CODEC_ALC671, 0, "Realtek ALC671" }, { HDA_CODEC_ALC680, 0, "Realtek ALC680" }, { HDA_CODEC_ALC700, 0, "Realtek ALC700" }, { HDA_CODEC_ALC701, 0, "Realtek ALC701" }, { HDA_CODEC_ALC703, 0, "Realtek ALC703" }, { HDA_CODEC_ALC861, 0x0340, "Realtek ALC660" }, { HDA_CODEC_ALC861, 0, "Realtek ALC861" }, { HDA_CODEC_ALC861VD, 0, "Realtek ALC861-VD" }, { HDA_CODEC_ALC880, 0, "Realtek ALC880" }, { HDA_CODEC_ALC882, 0, "Realtek ALC882" }, { HDA_CODEC_ALC883, 0, "Realtek ALC883" }, { HDA_CODEC_ALC885, 0x0101, "Realtek ALC889A" }, { HDA_CODEC_ALC885, 0x0103, "Realtek ALC889A" }, { HDA_CODEC_ALC885, 0, "Realtek ALC885" }, { HDA_CODEC_ALC887, 0, "Realtek ALC887" }, { HDA_CODEC_ALC888, 0x0101, "Realtek ALC1200" }, { HDA_CODEC_ALC888, 0, "Realtek ALC888" }, { HDA_CODEC_ALC889, 0, "Realtek ALC889" }, { HDA_CODEC_ALC892, 0, "Realtek ALC892" }, { HDA_CODEC_ALC897, 0, "Realtek ALC897" }, { HDA_CODEC_ALC899, 0, "Realtek ALC899" }, { HDA_CODEC_ALC1150, 0, "Realtek ALC1150" }, { HDA_CODEC_ALCS1200A, 0, "Realtek ALCS1200A" }, { HDA_CODEC_ALC1220_1, 0, "Realtek ALC1220" }, { HDA_CODEC_ALC1220, 0, "Realtek ALC1220" }, { HDA_CODEC_AD1882, 0, "Analog Devices AD1882" }, { HDA_CODEC_AD1882A, 0, "Analog Devices AD1882A" }, { HDA_CODEC_AD1883, 0, "Analog Devices AD1883" }, { HDA_CODEC_AD1884, 0, "Analog Devices AD1884" }, { HDA_CODEC_AD1884A, 0, "Analog Devices AD1884A" }, { HDA_CODEC_AD1981HD, 0, "Analog Devices AD1981HD" }, { HDA_CODEC_AD1983, 0, "Analog Devices AD1983" }, { HDA_CODEC_AD1984, 0, "Analog Devices AD1984" }, { HDA_CODEC_AD1984A, 0, "Analog Devices AD1984A" }, { HDA_CODEC_AD1984B, 0, "Analog Devices AD1984B" }, { HDA_CODEC_AD1986A, 0, "Analog Devices AD1986A" }, { HDA_CODEC_AD1987, 0, "Analog Devices AD1987" }, { HDA_CODEC_AD1988, 0, "Analog Devices AD1988A" }, { HDA_CODEC_AD1988B, 0, "Analog Devices AD1988B" }, { HDA_CODEC_AD1989A, 0, "Analog Devices AD1989A" }, { HDA_CODEC_AD1989B, 0, "Analog Devices AD1989B" }, { HDA_CODEC_CA0110, 0, "Creative CA0110-IBG" }, { HDA_CODEC_CA0110_2, 0, "Creative CA0110-IBG" }, { HDA_CODEC_CA0132, 0, "Creative CA0132" }, { HDA_CODEC_SB0880, 0, "Creative SB0880 X-Fi" }, { HDA_CODEC_CMI9880, 0, "CMedia CMI9880" }, { HDA_CODEC_CMI98802, 0, "CMedia CMI9880" }, { HDA_CODEC_CXD9872RDK, 0, "Sigmatel CXD9872RD/K" }, { HDA_CODEC_CXD9872AKD, 0, "Sigmatel CXD9872AKD" }, { HDA_CODEC_STAC9200D, 0, "Sigmatel STAC9200D" }, { HDA_CODEC_STAC9204X, 0, "Sigmatel STAC9204X" }, { HDA_CODEC_STAC9204D, 0, "Sigmatel STAC9204D" }, { HDA_CODEC_STAC9205X, 0, "Sigmatel STAC9205X" }, { HDA_CODEC_STAC9205D, 0, "Sigmatel STAC9205D" }, { HDA_CODEC_STAC9220, 0, "Sigmatel STAC9220" }, { HDA_CODEC_STAC9220_A1, 0, "Sigmatel STAC9220_A1" }, { HDA_CODEC_STAC9220_A2, 0, "Sigmatel STAC9220_A2" }, { HDA_CODEC_STAC9221, 0, "Sigmatel STAC9221" }, { HDA_CODEC_STAC9221_A2, 0, "Sigmatel STAC9221_A2" }, { HDA_CODEC_STAC9221D, 0, "Sigmatel STAC9221D" }, { HDA_CODEC_STAC922XD, 0, "Sigmatel STAC9220D/9223D" }, { HDA_CODEC_STAC9227X, 0, "Sigmatel STAC9227X" }, { HDA_CODEC_STAC9227D, 0, "Sigmatel STAC9227D" }, { HDA_CODEC_STAC9228X, 0, "Sigmatel STAC9228X" }, { HDA_CODEC_STAC9228D, 0, "Sigmatel STAC9228D" }, { HDA_CODEC_STAC9229X, 0, "Sigmatel STAC9229X" }, { HDA_CODEC_STAC9229D, 0, "Sigmatel STAC9229D" }, { HDA_CODEC_STAC9230X, 0, "Sigmatel STAC9230X" }, { HDA_CODEC_STAC9230D, 0, "Sigmatel STAC9230D" }, { HDA_CODEC_STAC9250, 0, "Sigmatel STAC9250" }, { HDA_CODEC_STAC9251, 0, "Sigmatel STAC9251" }, { HDA_CODEC_STAC9255, 0, "Sigmatel STAC9255" }, { HDA_CODEC_STAC9255D, 0, "Sigmatel STAC9255D" }, { HDA_CODEC_STAC9254, 0, "Sigmatel STAC9254" }, { HDA_CODEC_STAC9254D, 0, "Sigmatel STAC9254D" }, { HDA_CODEC_STAC9271X, 0, "Sigmatel STAC9271X" }, { HDA_CODEC_STAC9271D, 0, "Sigmatel STAC9271D" }, { HDA_CODEC_STAC9272X, 0, "Sigmatel STAC9272X" }, { HDA_CODEC_STAC9272D, 0, "Sigmatel STAC9272D" }, { HDA_CODEC_STAC9273X, 0, "Sigmatel STAC9273X" }, { HDA_CODEC_STAC9273D, 0, "Sigmatel STAC9273D" }, { HDA_CODEC_STAC9274, 0, "Sigmatel STAC9274" }, { HDA_CODEC_STAC9274D, 0, "Sigmatel STAC9274D" }, { HDA_CODEC_STAC9274X5NH, 0, "Sigmatel STAC9274X5NH" }, { HDA_CODEC_STAC9274D5NH, 0, "Sigmatel STAC9274D5NH" }, { HDA_CODEC_STAC9872AK, 0, "Sigmatel STAC9872AK" }, { HDA_CODEC_IDT92HD005, 0, "IDT 92HD005" }, { HDA_CODEC_IDT92HD005D, 0, "IDT 92HD005D" }, { HDA_CODEC_IDT92HD206X, 0, "IDT 92HD206X" }, { HDA_CODEC_IDT92HD206D, 0, "IDT 92HD206D" }, { HDA_CODEC_IDT92HD66B1X5, 0, "IDT 92HD66B1X5" }, { HDA_CODEC_IDT92HD66B2X5, 0, "IDT 92HD66B2X5" }, { HDA_CODEC_IDT92HD66B3X5, 0, "IDT 92HD66B3X5" }, { HDA_CODEC_IDT92HD66C1X5, 0, "IDT 92HD66C1X5" }, { HDA_CODEC_IDT92HD66C2X5, 0, "IDT 92HD66C2X5" }, { HDA_CODEC_IDT92HD66C3X5, 0, "IDT 92HD66C3X5" }, { HDA_CODEC_IDT92HD66B1X3, 0, "IDT 92HD66B1X3" }, { HDA_CODEC_IDT92HD66B2X3, 0, "IDT 92HD66B2X3" }, { HDA_CODEC_IDT92HD66B3X3, 0, "IDT 92HD66B3X3" }, { HDA_CODEC_IDT92HD66C1X3, 0, "IDT 92HD66C1X3" }, { HDA_CODEC_IDT92HD66C2X3, 0, "IDT 92HD66C2X3" }, { HDA_CODEC_IDT92HD66C3_65, 0, "IDT 92HD66C3_65" }, { HDA_CODEC_IDT92HD700X, 0, "IDT 92HD700X" }, { HDA_CODEC_IDT92HD700D, 0, "IDT 92HD700D" }, { HDA_CODEC_IDT92HD71B5, 0, "IDT 92HD71B5" }, { HDA_CODEC_IDT92HD71B5_2, 0, "IDT 92HD71B5" }, { HDA_CODEC_IDT92HD71B6, 0, "IDT 92HD71B6" }, { HDA_CODEC_IDT92HD71B6_2, 0, "IDT 92HD71B6" }, { HDA_CODEC_IDT92HD71B7, 0, "IDT 92HD71B7" }, { HDA_CODEC_IDT92HD71B7_2, 0, "IDT 92HD71B7" }, { HDA_CODEC_IDT92HD71B8, 0, "IDT 92HD71B8" }, { HDA_CODEC_IDT92HD71B8_2, 0, "IDT 92HD71B8" }, { HDA_CODEC_IDT92HD73C1, 0, "IDT 92HD73C1" }, { HDA_CODEC_IDT92HD73D1, 0, "IDT 92HD73D1" }, { HDA_CODEC_IDT92HD73E1, 0, "IDT 92HD73E1" }, { HDA_CODEC_IDT92HD75B3, 0, "IDT 92HD75B3" }, { HDA_CODEC_IDT92HD75BX, 0, "IDT 92HD75BX" }, { HDA_CODEC_IDT92HD81B1C, 0, "IDT 92HD81B1C" }, { HDA_CODEC_IDT92HD81B1X, 0, "IDT 92HD81B1X" }, { HDA_CODEC_IDT92HD83C1C, 0, "IDT 92HD83C1C" }, { HDA_CODEC_IDT92HD83C1X, 0, "IDT 92HD83C1X" }, { HDA_CODEC_IDT92HD87B1_3, 0, "IDT 92HD87B1/3" }, { HDA_CODEC_IDT92HD87B2_4, 0, "IDT 92HD87B2/4" }, { HDA_CODEC_IDT92HD89C3, 0, "IDT 92HD89C3" }, { HDA_CODEC_IDT92HD89C2, 0, "IDT 92HD89C2" }, { HDA_CODEC_IDT92HD89C1, 0, "IDT 92HD89C1" }, { HDA_CODEC_IDT92HD89B3, 0, "IDT 92HD89B3" }, { HDA_CODEC_IDT92HD89B2, 0, "IDT 92HD89B2" }, { HDA_CODEC_IDT92HD89B1, 0, "IDT 92HD89B1" }, { HDA_CODEC_IDT92HD89E3, 0, "IDT 92HD89E3" }, { HDA_CODEC_IDT92HD89E2, 0, "IDT 92HD89E2" }, { HDA_CODEC_IDT92HD89E1, 0, "IDT 92HD89E1" }, { HDA_CODEC_IDT92HD89D3, 0, "IDT 92HD89D3" }, { HDA_CODEC_IDT92HD89D2, 0, "IDT 92HD89D2" }, { HDA_CODEC_IDT92HD89D1, 0, "IDT 92HD89D1" }, { HDA_CODEC_IDT92HD89F3, 0, "IDT 92HD89F3" }, { HDA_CODEC_IDT92HD89F2, 0, "IDT 92HD89F2" }, { HDA_CODEC_IDT92HD89F1, 0, "IDT 92HD89F1" }, { HDA_CODEC_IDT92HD90BXX, 0, "IDT 92HD90BXX" }, { HDA_CODEC_IDT92HD91BXX, 0, "IDT 92HD91BXX" }, { HDA_CODEC_IDT92HD93BXX, 0, "IDT 92HD93BXX" }, { HDA_CODEC_IDT92HD95B, 0, "Tempo 92HD95B" }, { HDA_CODEC_IDT92HD98BXX, 0, "IDT 92HD98BXX" }, { HDA_CODEC_IDT92HD99BXX, 0, "IDT 92HD99BXX" }, { HDA_CODEC_CX20549, 0, "Conexant CX20549 (Venice)" }, { HDA_CODEC_CX20551, 0, "Conexant CX20551 (Waikiki)" }, { HDA_CODEC_CX20561, 0, "Conexant CX20561 (Hermosa)" }, { HDA_CODEC_CX20582, 0, "Conexant CX20582 (Pebble)" }, { HDA_CODEC_CX20583, 0, "Conexant CX20583 (Pebble HSF)" }, { HDA_CODEC_CX20584, 0, "Conexant CX20584" }, { HDA_CODEC_CX20585, 0, "Conexant CX20585" }, { HDA_CODEC_CX20588, 0, "Conexant CX20588" }, { HDA_CODEC_CX20590, 0, "Conexant CX20590" }, { HDA_CODEC_CX20631, 0, "Conexant CX20631" }, { HDA_CODEC_CX20632, 0, "Conexant CX20632" }, { HDA_CODEC_CX20641, 0, "Conexant CX20641" }, { HDA_CODEC_CX20642, 0, "Conexant CX20642" }, { HDA_CODEC_CX20651, 0, "Conexant CX20651" }, { HDA_CODEC_CX20652, 0, "Conexant CX20652" }, { HDA_CODEC_CX20664, 0, "Conexant CX20664" }, { HDA_CODEC_CX20665, 0, "Conexant CX20665" }, { HDA_CODEC_CX21722, 0, "Conexant CX21722" }, { HDA_CODEC_CX20722, 0, "Conexant CX20722" }, { HDA_CODEC_CX21724, 0, "Conexant CX21724" }, { HDA_CODEC_CX20724, 0, "Conexant CX20724" }, { HDA_CODEC_CX20751, 0, "Conexant CX20751/2" }, { HDA_CODEC_CX20751_2, 0, "Conexant CX20751/2" }, { HDA_CODEC_CX20753, 0, "Conexant CX20753/4" }, { HDA_CODEC_CX20755, 0, "Conexant CX20755" }, { HDA_CODEC_CX20756, 0, "Conexant CX20756" }, { HDA_CODEC_CX20757, 0, "Conexant CX20757" }, { HDA_CODEC_CX20952, 0, "Conexant CX20952" }, { HDA_CODEC_VT1708_8, 0, "VIA VT1708_8" }, { HDA_CODEC_VT1708_9, 0, "VIA VT1708_9" }, { HDA_CODEC_VT1708_A, 0, "VIA VT1708_A" }, { HDA_CODEC_VT1708_B, 0, "VIA VT1708_B" }, { HDA_CODEC_VT1709_0, 0, "VIA VT1709_0" }, { HDA_CODEC_VT1709_1, 0, "VIA VT1709_1" }, { HDA_CODEC_VT1709_2, 0, "VIA VT1709_2" }, { HDA_CODEC_VT1709_3, 0, "VIA VT1709_3" }, { HDA_CODEC_VT1709_4, 0, "VIA VT1709_4" }, { HDA_CODEC_VT1709_5, 0, "VIA VT1709_5" }, { HDA_CODEC_VT1709_6, 0, "VIA VT1709_6" }, { HDA_CODEC_VT1709_7, 0, "VIA VT1709_7" }, { HDA_CODEC_VT1708B_0, 0, "VIA VT1708B_0" }, { HDA_CODEC_VT1708B_1, 0, "VIA VT1708B_1" }, { HDA_CODEC_VT1708B_2, 0, "VIA VT1708B_2" }, { HDA_CODEC_VT1708B_3, 0, "VIA VT1708B_3" }, { HDA_CODEC_VT1708B_4, 0, "VIA VT1708B_4" }, { HDA_CODEC_VT1708B_5, 0, "VIA VT1708B_5" }, { HDA_CODEC_VT1708B_6, 0, "VIA VT1708B_6" }, { HDA_CODEC_VT1708B_7, 0, "VIA VT1708B_7" }, { HDA_CODEC_VT1708S_0, 0, "VIA VT1708S_0" }, { HDA_CODEC_VT1708S_1, 0, "VIA VT1708S_1" }, { HDA_CODEC_VT1708S_2, 0, "VIA VT1708S_2" }, { HDA_CODEC_VT1708S_3, 0, "VIA VT1708S_3" }, { HDA_CODEC_VT1708S_4, 0, "VIA VT1708S_4" }, { HDA_CODEC_VT1708S_5, 0, "VIA VT1708S_5" }, { HDA_CODEC_VT1708S_6, 0, "VIA VT1708S_6" }, { HDA_CODEC_VT1708S_7, 0, "VIA VT1708S_7" }, { HDA_CODEC_VT1702_0, 0, "VIA VT1702_0" }, { HDA_CODEC_VT1702_1, 0, "VIA VT1702_1" }, { HDA_CODEC_VT1702_2, 0, "VIA VT1702_2" }, { HDA_CODEC_VT1702_3, 0, "VIA VT1702_3" }, { HDA_CODEC_VT1702_4, 0, "VIA VT1702_4" }, { HDA_CODEC_VT1702_5, 0, "VIA VT1702_5" }, { HDA_CODEC_VT1702_6, 0, "VIA VT1702_6" }, { HDA_CODEC_VT1702_7, 0, "VIA VT1702_7" }, { HDA_CODEC_VT1716S_0, 0, "VIA VT1716S_0" }, { HDA_CODEC_VT1716S_1, 0, "VIA VT1716S_1" }, { HDA_CODEC_VT1718S_0, 0, "VIA VT1718S_0" }, { HDA_CODEC_VT1718S_1, 0, "VIA VT1718S_1" }, { HDA_CODEC_VT1802_0, 0, "VIA VT1802_0" }, { HDA_CODEC_VT1802_1, 0, "VIA VT1802_1" }, { HDA_CODEC_VT1812, 0, "VIA VT1812" }, { HDA_CODEC_VT1818S, 0, "VIA VT1818S" }, { HDA_CODEC_VT1828S, 0, "VIA VT1828S" }, { HDA_CODEC_VT2002P_0, 0, "VIA VT2002P_0" }, { HDA_CODEC_VT2002P_1, 0, "VIA VT2002P_1" }, { HDA_CODEC_VT2020, 0, "VIA VT2020" }, { HDA_CODEC_ATIRS600_1, 0, "ATI RS600" }, { HDA_CODEC_ATIRS600_2, 0, "ATI RS600" }, { HDA_CODEC_ATIRS690, 0, "ATI RS690/780" }, { HDA_CODEC_ATIR6XX, 0, "ATI R6xx" }, { HDA_CODEC_NVIDIAMCP67, 0, "NVIDIA MCP67" }, { HDA_CODEC_NVIDIAMCP73, 0, "NVIDIA MCP73" }, { HDA_CODEC_NVIDIAMCP78, 0, "NVIDIA MCP78" }, { HDA_CODEC_NVIDIAMCP78_2, 0, "NVIDIA MCP78" }, { HDA_CODEC_NVIDIAMCP78_3, 0, "NVIDIA MCP78" }, { HDA_CODEC_NVIDIAMCP78_4, 0, "NVIDIA MCP78" }, { HDA_CODEC_NVIDIAMCP7A, 0, "NVIDIA MCP7A" }, { HDA_CODEC_NVIDIAGM204, 0, "NVIDIA GM204" }, { HDA_CODEC_NVIDIAGT220, 0, "NVIDIA GT220" }, { HDA_CODEC_NVIDIAGT21X, 0, "NVIDIA GT21x" }, { HDA_CODEC_NVIDIAMCP89, 0, "NVIDIA MCP89" }, { HDA_CODEC_NVIDIAGT240, 0, "NVIDIA GT240" }, { HDA_CODEC_NVIDIAGTS450, 0, "NVIDIA GTS450" }, { HDA_CODEC_NVIDIAGT440, 0, "NVIDIA GT440" }, { HDA_CODEC_NVIDIAGTX550, 0, "NVIDIA GTX550" }, { HDA_CODEC_NVIDIAGTX570, 0, "NVIDIA GTX570" }, { HDA_CODEC_NVIDIATEGRA30, 0, "NVIDIA Tegra30" }, { HDA_CODEC_NVIDIATEGRA114, 0, "NVIDIA Tegra114" }, { HDA_CODEC_NVIDIATEGRA124, 0, "NVIDIA Tegra124" }, { HDA_CODEC_NVIDIATEGRA210, 0, "NVIDIA Tegra210" }, { HDA_CODEC_INTELIP, 0, "Intel Ibex Peak" }, { HDA_CODEC_INTELBL, 0, "Intel Bearlake" }, { HDA_CODEC_INTELCA, 0, "Intel Cantiga" }, { HDA_CODEC_INTELEL, 0, "Intel Eaglelake" }, { HDA_CODEC_INTELIP2, 0, "Intel Ibex Peak" }, { HDA_CODEC_INTELCPT, 0, "Intel Cougar Point" }, { HDA_CODEC_INTELPPT, 0, "Intel Panther Point" }, { HDA_CODEC_INTELHSW, 0, "Intel Haswell" }, { HDA_CODEC_INTELBDW, 0, "Intel Broadwell" }, { HDA_CODEC_INTELSKLK, 0, "Intel Skylake" }, { HDA_CODEC_INTELKBLK, 0, "Intel Kaby Lake" }, { HDA_CODEC_INTELJLK, 0, "Intel Jasper Lake" }, { HDA_CODEC_INTELELLK, 0, "Intel Elkhart Lake" }, { HDA_CODEC_INTELCT, 0, "Intel Cedar Trail" }, { HDA_CODEC_INTELVV2, 0, "Intel Valleyview2" }, { HDA_CODEC_INTELBR, 0, "Intel Braswell" }, { HDA_CODEC_INTELCL, 0, "Intel Crestline" }, { HDA_CODEC_INTELBXTN, 0, "Intel Broxton" }, { HDA_CODEC_INTELCNLK, 0, "Intel Cannon Lake" }, { HDA_CODEC_INTELGMLK, 0, "Intel Gemini Lake" }, { HDA_CODEC_INTELGMLK1, 0, "Intel Gemini Lake" }, { HDA_CODEC_INTELICLK, 0, "Intel Ice Lake" }, { HDA_CODEC_INTELTGLK, 0, "Intel Tiger Lake" }, { HDA_CODEC_INTELTGLKH, 0, "Intel Tiger Lake-H" }, { HDA_CODEC_INTELALLK, 0, "Intel Alder Lake" }, { HDA_CODEC_SII1390, 0, "Silicon Image SiI1390" }, { HDA_CODEC_SII1392, 0, "Silicon Image SiI1392" }, { HDA_CODEC_VMWARE, 0, "VMware" }, /* Unknown CODECs */ { HDA_CODEC_ADXXXX, 0, "Analog Devices" }, { HDA_CODEC_AGEREXXXX, 0, "Lucent/Agere Systems" }, { HDA_CODEC_ALCXXXX, 0, "Realtek" }, { HDA_CODEC_ATIXXXX, 0, "ATI" }, { HDA_CODEC_CAXXXX, 0, "Creative" }, { HDA_CODEC_CMIXXXX, 0, "CMedia" }, { HDA_CODEC_CMIXXXX2, 0, "CMedia" }, { HDA_CODEC_CSXXXX, 0, "Cirrus Logic" }, { HDA_CODEC_CXXXXX, 0, "Conexant" }, { HDA_CODEC_CHXXXX, 0, "Chrontel" }, { HDA_CODEC_IDTXXXX, 0, "IDT" }, { HDA_CODEC_INTELXXXX, 0, "Intel" }, { HDA_CODEC_MOTOXXXX, 0, "Motorola" }, { HDA_CODEC_NVIDIAXXXX, 0, "NVIDIA" }, { HDA_CODEC_SIIXXXX, 0, "Silicon Image" }, { HDA_CODEC_STACXXXX, 0, "Sigmatel" }, { HDA_CODEC_VMWAREXXXX, 0, "VMware" }, { HDA_CODEC_VTXXXX, 0, "VIA" }, }; static int hdacc_suspend(device_t dev) { HDA_BOOTHVERBOSE( device_printf(dev, "Suspend...\n"); ); bus_generic_suspend(dev); HDA_BOOTHVERBOSE( device_printf(dev, "Suspend done\n"); ); return (0); } static int hdacc_resume(device_t dev) { HDA_BOOTHVERBOSE( device_printf(dev, "Resume...\n"); ); bus_generic_resume(dev); HDA_BOOTHVERBOSE( device_printf(dev, "Resume done\n"); ); return (0); } static int hdacc_probe(device_t dev) { uint32_t id, revid; char buf[128]; int i; id = ((uint32_t)hda_get_vendor_id(dev) << 16) + hda_get_device_id(dev); revid = ((uint32_t)hda_get_revision_id(dev) << 8) + hda_get_stepping_id(dev); for (i = 0; i < nitems(hdacc_codecs); i++) { if (!HDA_DEV_MATCH(hdacc_codecs[i].id, id)) continue; if (hdacc_codecs[i].revid != 0 && hdacc_codecs[i].revid != revid) continue; break; } if (i < nitems(hdacc_codecs)) { if ((hdacc_codecs[i].id & 0xffff) != 0xffff) strlcpy(buf, hdacc_codecs[i].name, sizeof(buf)); else snprintf(buf, sizeof(buf), "%s (0x%04x)", hdacc_codecs[i].name, hda_get_device_id(dev)); } else snprintf(buf, sizeof(buf), "Generic (0x%04x)", id); device_set_descf(dev, "%s HDA CODEC", buf); return (BUS_PROBE_DEFAULT); } static int hdacc_attach(device_t dev) { struct hdacc_softc *codec = device_get_softc(dev); device_t child; int cad = (intptr_t)device_get_ivars(dev); uint32_t subnode; int startnode; int endnode; int i, n; codec->lock = HDAC_GET_MTX(device_get_parent(dev), dev); codec->dev = dev; codec->cad = cad; hdacc_lock(codec); subnode = hda_command(dev, HDA_CMD_GET_PARAMETER(0, 0x0, HDA_PARAM_SUB_NODE_COUNT)); hdacc_unlock(codec); if (subnode == HDA_INVALID) return (EIO); codec->fgcnt = HDA_PARAM_SUB_NODE_COUNT_TOTAL(subnode); startnode = HDA_PARAM_SUB_NODE_COUNT_START(subnode); endnode = startnode + codec->fgcnt; HDA_BOOTHVERBOSE( device_printf(dev, "Root Node at nid=0: %d subnodes %d-%d\n", HDA_PARAM_SUB_NODE_COUNT_TOTAL(subnode), startnode, endnode - 1); ); codec->fgs = malloc(sizeof(struct hdacc_fg) * codec->fgcnt, M_HDACC, M_ZERO | M_WAITOK); for (i = startnode, n = 0; i < endnode; i++, n++) { codec->fgs[n].nid = i; hdacc_lock(codec); codec->fgs[n].type = HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE(hda_command(dev, HDA_CMD_GET_PARAMETER(0, i, HDA_PARAM_FCT_GRP_TYPE))); codec->fgs[n].subsystem_id = hda_command(dev, HDA_CMD_GET_SUBSYSTEM_ID(0, i)); hdacc_unlock(codec); codec->fgs[n].dev = child = device_add_child(dev, NULL, DEVICE_UNIT_ANY); if (child == NULL) { device_printf(dev, "Failed to add function device\n"); continue; } device_set_ivars(child, &codec->fgs[n]); } bus_attach_children(dev); return (0); } static int hdacc_detach(device_t dev) { struct hdacc_softc *codec = device_get_softc(dev); int error; - if ((error = device_delete_children(dev)) != 0) + if ((error = bus_generic_detach(dev)) != 0) return (error); free(codec->fgs, M_HDACC); return (0); } static int hdacc_child_location(device_t dev, device_t child, struct sbuf *sb) { struct hdacc_fg *fg = device_get_ivars(child); sbuf_printf(sb, "nid=%d", fg->nid); return (0); } static int hdacc_child_pnpinfo_method(device_t dev, device_t child, struct sbuf *sb) { struct hdacc_fg *fg = device_get_ivars(child); sbuf_printf(sb, "type=0x%02x subsystem=0x%08x", fg->type, fg->subsystem_id); return (0); } static int hdacc_print_child(device_t dev, device_t child) { struct hdacc_fg *fg = device_get_ivars(child); int retval; retval = bus_print_child_header(dev, child); retval += printf(" at nid %d", fg->nid); retval += bus_print_child_footer(dev, child); return (retval); } static void hdacc_probe_nomatch(device_t dev, device_t child) { struct hdacc_softc *codec = device_get_softc(dev); struct hdacc_fg *fg = device_get_ivars(child); device_printf(child, "<%s %s Function Group> at nid %d on %s " "(no driver attached)\n", device_get_desc(dev), fg->type == HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO ? "Audio" : (fg->type == HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_MODEM ? "Modem" : "Unknown"), fg->nid, device_get_nameunit(dev)); HDA_BOOTVERBOSE( device_printf(dev, "Subsystem ID: 0x%08x\n", hda_get_subsystem_id(dev)); ); HDA_BOOTHVERBOSE( device_printf(dev, "Power down FG nid=%d to the D3 state...\n", fg->nid); ); hdacc_lock(codec); hda_command(dev, HDA_CMD_SET_POWER_STATE(0, fg->nid, HDA_CMD_POWER_STATE_D3)); hdacc_unlock(codec); } static int hdacc_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct hdacc_fg *fg = device_get_ivars(child); switch (which) { case HDA_IVAR_NODE_ID: *result = fg->nid; break; case HDA_IVAR_NODE_TYPE: *result = fg->type; break; case HDA_IVAR_SUBSYSTEM_ID: *result = fg->subsystem_id; break; default: return(BUS_READ_IVAR(device_get_parent(dev), dev, which, result)); } return (0); } static struct mtx * hdacc_get_mtx(device_t dev, device_t child) { struct hdacc_softc *codec = device_get_softc(dev); return (codec->lock); } static uint32_t hdacc_codec_command(device_t dev, device_t child, uint32_t verb) { return (HDAC_CODEC_COMMAND(device_get_parent(dev), dev, verb)); } static int hdacc_stream_alloc(device_t dev, device_t child, int dir, int format, int stripe, uint32_t **dmapos) { struct hdacc_softc *codec = device_get_softc(dev); int stream; stream = HDAC_STREAM_ALLOC(device_get_parent(dev), dev, dir, format, stripe, dmapos); if (stream > 0) codec->streams[dir][stream] = child; return (stream); } static void hdacc_stream_free(device_t dev, device_t child, int dir, int stream) { struct hdacc_softc *codec = device_get_softc(dev); codec->streams[dir][stream] = NULL; HDAC_STREAM_FREE(device_get_parent(dev), dev, dir, stream); } static int hdacc_stream_start(device_t dev, device_t child, int dir, int stream, bus_addr_t buf, int blksz, int blkcnt) { return (HDAC_STREAM_START(device_get_parent(dev), dev, dir, stream, buf, blksz, blkcnt)); } static void hdacc_stream_stop(device_t dev, device_t child, int dir, int stream) { HDAC_STREAM_STOP(device_get_parent(dev), dev, dir, stream); } static void hdacc_stream_reset(device_t dev, device_t child, int dir, int stream) { HDAC_STREAM_RESET(device_get_parent(dev), dev, dir, stream); } static uint32_t hdacc_stream_getptr(device_t dev, device_t child, int dir, int stream) { return (HDAC_STREAM_GETPTR(device_get_parent(dev), dev, dir, stream)); } static void hdacc_stream_intr(device_t dev, int dir, int stream) { struct hdacc_softc *codec = device_get_softc(dev); device_t child; if ((child = codec->streams[dir][stream]) != NULL) HDAC_STREAM_INTR(child, dir, stream); } static int hdacc_unsol_alloc(device_t dev, device_t child, int wanted) { struct hdacc_softc *codec = device_get_softc(dev); int tag; wanted &= 0x3f; tag = wanted; do { if (codec->tags[tag] == NULL) { codec->tags[tag] = child; HDAC_UNSOL_ALLOC(device_get_parent(dev), dev, tag); return (tag); } tag++; tag &= 0x3f; } while (tag != wanted); return (-1); } static void hdacc_unsol_free(device_t dev, device_t child, int tag) { struct hdacc_softc *codec = device_get_softc(dev); KASSERT(tag >= 0 && tag <= 0x3f, ("Wrong tag value %d\n", tag)); codec->tags[tag] = NULL; HDAC_UNSOL_FREE(device_get_parent(dev), dev, tag); } static void hdacc_unsol_intr(device_t dev, uint32_t resp) { struct hdacc_softc *codec = device_get_softc(dev); device_t child; int tag; tag = resp >> 26; if ((child = codec->tags[tag]) != NULL) HDAC_UNSOL_INTR(child, resp); else device_printf(codec->dev, "Unexpected unsolicited " "response with tag %d: %08x\n", tag, resp); } static void hdacc_pindump(device_t dev) { device_t *devlist; int devcount, i; if (device_get_children(dev, &devlist, &devcount) != 0) return; for (i = 0; i < devcount; i++) HDAC_PINDUMP(devlist[i]); free(devlist, M_TEMP); } static device_method_t hdacc_methods[] = { /* device interface */ DEVMETHOD(device_probe, hdacc_probe), DEVMETHOD(device_attach, hdacc_attach), DEVMETHOD(device_detach, hdacc_detach), DEVMETHOD(device_suspend, hdacc_suspend), DEVMETHOD(device_resume, hdacc_resume), /* Bus interface */ DEVMETHOD(bus_child_location, hdacc_child_location), DEVMETHOD(bus_child_pnpinfo, hdacc_child_pnpinfo_method), DEVMETHOD(bus_print_child, hdacc_print_child), DEVMETHOD(bus_probe_nomatch, hdacc_probe_nomatch), DEVMETHOD(bus_read_ivar, hdacc_read_ivar), DEVMETHOD(hdac_get_mtx, hdacc_get_mtx), DEVMETHOD(hdac_codec_command, hdacc_codec_command), DEVMETHOD(hdac_stream_alloc, hdacc_stream_alloc), DEVMETHOD(hdac_stream_free, hdacc_stream_free), DEVMETHOD(hdac_stream_start, hdacc_stream_start), DEVMETHOD(hdac_stream_stop, hdacc_stream_stop), DEVMETHOD(hdac_stream_reset, hdacc_stream_reset), DEVMETHOD(hdac_stream_getptr, hdacc_stream_getptr), DEVMETHOD(hdac_stream_intr, hdacc_stream_intr), DEVMETHOD(hdac_unsol_alloc, hdacc_unsol_alloc), DEVMETHOD(hdac_unsol_free, hdacc_unsol_free), DEVMETHOD(hdac_unsol_intr, hdacc_unsol_intr), DEVMETHOD(hdac_pindump, hdacc_pindump), DEVMETHOD_END }; static driver_t hdacc_driver = { "hdacc", hdacc_methods, sizeof(struct hdacc_softc), }; DRIVER_MODULE(snd_hda, hdac, hdacc_driver, NULL, NULL); diff --git a/sys/dev/sound/pci/hdsp.c b/sys/dev/sound/pci/hdsp.c index ac343928b26b..4712d78ea88b 100644 --- a/sys/dev/sound/pci/hdsp.c +++ b/sys/dev/sound/pci/hdsp.c @@ -1,1022 +1,1022 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2016 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * RME HDSP driver for FreeBSD. * Supported cards: HDSP 9632, HDSP 9652. */ #include #include #include #include #include #include #include static bool hdsp_unified_pcm = false; static SYSCTL_NODE(_hw, OID_AUTO, hdsp, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "PCI HDSP"); SYSCTL_BOOL(_hw_hdsp, OID_AUTO, unified_pcm, CTLFLAG_RWTUN, &hdsp_unified_pcm, 0, "Combine physical ports in one unified pcm device"); static struct hdsp_clock_source hdsp_clock_source_table_9632[] = { { "internal", HDSP_CLOCK_INTERNAL }, { "adat", HDSP_CLOCK_ADAT1 }, { "spdif", HDSP_CLOCK_SPDIF }, { "word", HDSP_CLOCK_WORD }, { NULL, HDSP_CLOCK_INTERNAL } }; static struct hdsp_clock_source hdsp_clock_source_table_9652[] = { { "internal", HDSP_CLOCK_INTERNAL }, { "adat1", HDSP_CLOCK_ADAT1 }, { "adat2", HDSP_CLOCK_ADAT2 }, { "adat3", HDSP_CLOCK_ADAT3 }, { "spdif", HDSP_CLOCK_SPDIF }, { "word", HDSP_CLOCK_WORD }, { "adat_sync", HDSP_CLOCK_ADAT_SYNC }, { NULL, HDSP_CLOCK_INTERNAL } }; static struct hdsp_channel chan_map_9632[] = { { HDSP_CHAN_9632_ADAT, "adat" }, { HDSP_CHAN_9632_SPDIF, "s/pdif" }, { HDSP_CHAN_9632_LINE, "line" }, { HDSP_CHAN_9632_EXT, "ext" }, { 0, NULL }, }; static struct hdsp_channel chan_map_9632_uni[] = { { HDSP_CHAN_9632_ALL, "all" }, { 0, NULL }, }; static struct hdsp_channel chan_map_9652[] = { { HDSP_CHAN_9652_ADAT1, "adat1" }, { HDSP_CHAN_9652_ADAT2, "adat2" }, { HDSP_CHAN_9652_ADAT3, "adat3" }, { HDSP_CHAN_9652_SPDIF, "s/pdif" }, { 0, NULL }, }; static struct hdsp_channel chan_map_9652_uni[] = { { HDSP_CHAN_9652_ALL, "all" }, { 0, NULL }, }; static void hdsp_intr(void *p) { struct sc_pcminfo *scp; struct sc_info *sc; device_t *devlist; int devcount; int status; int err; int i; sc = (struct sc_info *)p; snd_mtxlock(sc->lock); status = hdsp_read_1(sc, HDSP_STATUS_REG); if (status & HDSP_AUDIO_IRQ_PENDING) { if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) return; for (i = 0; i < devcount; i++) { scp = device_get_ivars(devlist[i]); if (scp->ih != NULL) scp->ih(scp); } hdsp_write_1(sc, HDSP_INTERRUPT_ACK, 0); free(devlist, M_TEMP); } snd_mtxunlock(sc->lock); } static void hdsp_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { #if 0 device_printf(sc->dev, "hdsp_dmapsetmap()\n"); #endif } static int hdsp_alloc_resources(struct sc_info *sc) { /* Allocate resource. */ sc->csid = PCIR_BAR(0); sc->cs = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY, &sc->csid, RF_ACTIVE); if (!sc->cs) { device_printf(sc->dev, "Unable to map SYS_RES_MEMORY.\n"); return (ENXIO); } sc->cst = rman_get_bustag(sc->cs); sc->csh = rman_get_bushandle(sc->cs); /* Allocate interrupt resource. */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || bus_setup_intr(sc->dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV, NULL, hdsp_intr, sc, &sc->ih)) { device_printf(sc->dev, "Unable to alloc interrupt resource.\n"); return (ENXIO); } /* Allocate DMA resources. */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev), /*alignment*/4, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/2 * HDSP_DMASEGSIZE, /*nsegments*/2, /*maxsegsz*/HDSP_DMASEGSIZE, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, /*dmatag*/&sc->dmat) != 0) { device_printf(sc->dev, "Unable to create dma tag.\n"); return (ENXIO); } sc->bufsize = HDSP_DMASEGSIZE; /* pbuf (play buffer). */ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_WAITOK, &sc->pmap)) { device_printf(sc->dev, "Can't alloc pbuf.\n"); return (ENXIO); } if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->bufsize, hdsp_dmapsetmap, sc, BUS_DMA_NOWAIT)) { device_printf(sc->dev, "Can't load pbuf.\n"); return (ENXIO); } /* rbuf (rec buffer). */ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_WAITOK, &sc->rmap)) { device_printf(sc->dev, "Can't alloc rbuf.\n"); return (ENXIO); } if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->bufsize, hdsp_dmapsetmap, sc, BUS_DMA_NOWAIT)) { device_printf(sc->dev, "Can't load rbuf.\n"); return (ENXIO); } bzero(sc->pbuf, sc->bufsize); bzero(sc->rbuf, sc->bufsize); return (0); } static void hdsp_map_dmabuf(struct sc_info *sc) { uint32_t paddr, raddr; paddr = vtophys(sc->pbuf); raddr = vtophys(sc->rbuf); hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_OUT, paddr); hdsp_write_4(sc, HDSP_PAGE_ADDR_BUF_IN, raddr); } static const char * hdsp_control_input_level(uint32_t control) { switch (control & HDSP_INPUT_LEVEL_MASK) { case HDSP_INPUT_LEVEL_LOWGAIN: return ("LowGain"); case HDSP_INPUT_LEVEL_PLUS4DBU: return ("+4dBu"); case HDSP_INPUT_LEVEL_MINUS10DBV: return ("-10dBV"); default: return (NULL); } } static int hdsp_sysctl_input_level(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; const char *label; char buf[16] = "invalid"; int error; uint32_t control; sc = oidp->oid_arg1; /* Only available on HDSP 9632. */ if (sc->type != HDSP_9632) return (ENXIO); /* Extract current input level from control register. */ control = sc->ctrl_register & HDSP_INPUT_LEVEL_MASK; label = hdsp_control_input_level(control); if (label != NULL) strlcpy(buf, label, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find input level matching the sysctl string. */ label = hdsp_control_input_level(HDSP_INPUT_LEVEL_LOWGAIN); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_INPUT_LEVEL_LOWGAIN; label = hdsp_control_input_level(HDSP_INPUT_LEVEL_PLUS4DBU); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_INPUT_LEVEL_PLUS4DBU; label = hdsp_control_input_level(HDSP_INPUT_LEVEL_MINUS10DBV); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_INPUT_LEVEL_MINUS10DBV; /* Set input level in control register. */ control &= HDSP_INPUT_LEVEL_MASK; if (control != (sc->ctrl_register & HDSP_INPUT_LEVEL_MASK)) { snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSP_INPUT_LEVEL_MASK; sc->ctrl_register |= control; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); snd_mtxunlock(sc->lock); } return (0); } static const char * hdsp_control_output_level(uint32_t control) { switch (control & HDSP_OUTPUT_LEVEL_MASK) { case HDSP_OUTPUT_LEVEL_MINUS10DBV: return ("-10dBV"); case HDSP_OUTPUT_LEVEL_PLUS4DBU: return ("+4dBu"); case HDSP_OUTPUT_LEVEL_HIGHGAIN: return ("HighGain"); default: return (NULL); } } static int hdsp_sysctl_output_level(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; const char *label; char buf[16] = "invalid"; int error; uint32_t control; sc = oidp->oid_arg1; /* Only available on HDSP 9632. */ if (sc->type != HDSP_9632) return (ENXIO); /* Extract current output level from control register. */ control = sc->ctrl_register & HDSP_OUTPUT_LEVEL_MASK; label = hdsp_control_output_level(control); if (label != NULL) strlcpy(buf, label, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find output level matching the sysctl string. */ label = hdsp_control_output_level(HDSP_OUTPUT_LEVEL_MINUS10DBV); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_OUTPUT_LEVEL_MINUS10DBV; label = hdsp_control_output_level(HDSP_OUTPUT_LEVEL_PLUS4DBU); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_OUTPUT_LEVEL_PLUS4DBU; label = hdsp_control_output_level(HDSP_OUTPUT_LEVEL_HIGHGAIN); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_OUTPUT_LEVEL_HIGHGAIN; /* Set output level in control register. */ control &= HDSP_OUTPUT_LEVEL_MASK; if (control != (sc->ctrl_register & HDSP_OUTPUT_LEVEL_MASK)) { snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSP_OUTPUT_LEVEL_MASK; sc->ctrl_register |= control; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); snd_mtxunlock(sc->lock); } return (0); } static const char * hdsp_control_phones_level(uint32_t control) { switch (control & HDSP_PHONES_LEVEL_MASK) { case HDSP_PHONES_LEVEL_MINUS12DB: return ("-12dB"); case HDSP_PHONES_LEVEL_MINUS6DB: return ("-6dB"); case HDSP_PHONES_LEVEL_0DB: return ("0dB"); default: return (NULL); } } static int hdsp_sysctl_phones_level(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; const char *label; char buf[16] = "invalid"; int error; uint32_t control; sc = oidp->oid_arg1; /* Only available on HDSP 9632. */ if (sc->type != HDSP_9632) return (ENXIO); /* Extract current phones level from control register. */ control = sc->ctrl_register & HDSP_PHONES_LEVEL_MASK; label = hdsp_control_phones_level(control); if (label != NULL) strlcpy(buf, label, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find phones level matching the sysctl string. */ label = hdsp_control_phones_level(HDSP_PHONES_LEVEL_MINUS12DB); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_PHONES_LEVEL_MINUS12DB; label = hdsp_control_phones_level(HDSP_PHONES_LEVEL_MINUS6DB); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_PHONES_LEVEL_MINUS6DB; label = hdsp_control_phones_level(HDSP_PHONES_LEVEL_0DB); if (strncasecmp(buf, label, sizeof(buf)) == 0) control = HDSP_PHONES_LEVEL_0DB; /* Set phones level in control register. */ control &= HDSP_PHONES_LEVEL_MASK; if (control != (sc->ctrl_register & HDSP_PHONES_LEVEL_MASK)) { snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSP_PHONES_LEVEL_MASK; sc->ctrl_register |= control; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); snd_mtxunlock(sc->lock); } return (0); } static int hdsp_sysctl_sample_rate(SYSCTL_HANDLER_ARGS) { struct sc_info *sc = oidp->oid_arg1; int error; unsigned int speed, multiplier; speed = sc->force_speed; /* Process sysctl (unsigned) integer request. */ error = sysctl_handle_int(oidp, &speed, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* Speed from 32000 to 192000, 0 falls back to pcm speed setting. */ sc->force_speed = 0; if (speed > 0) { multiplier = 1; if ((speed > (96000 + 128000) / 2) && sc->type == HDSP_9632) multiplier = 4; else if (speed > (48000 + 64000) / 2) multiplier = 2; if (speed < ((32000 + 44100) / 2) * multiplier) sc->force_speed = 32000 * multiplier; else if (speed < ((44100 + 48000) / 2) * multiplier) sc->force_speed = 44100 * multiplier; else sc->force_speed = 48000 * multiplier; } return (0); } static int hdsp_sysctl_period(SYSCTL_HANDLER_ARGS) { struct sc_info *sc = oidp->oid_arg1; int error; unsigned int period; period = sc->force_period; /* Process sysctl (unsigned) integer request. */ error = sysctl_handle_int(oidp, &period, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* Period is from 2^5 to 2^14, 0 falls back to pcm latency settings. */ sc->force_period = 0; if (period > 0) { sc->force_period = 32; while (sc->force_period < period && sc->force_period < 4096) sc->force_period <<= 1; } return (0); } static uint32_t hdsp_control_clock_preference(enum hdsp_clock_type type) { switch (type) { case HDSP_CLOCK_INTERNAL: return (HDSP_CONTROL_MASTER); case HDSP_CLOCK_ADAT1: return (HDSP_CONTROL_CLOCK(0)); case HDSP_CLOCK_ADAT2: return (HDSP_CONTROL_CLOCK(1)); case HDSP_CLOCK_ADAT3: return (HDSP_CONTROL_CLOCK(2)); case HDSP_CLOCK_SPDIF: return (HDSP_CONTROL_CLOCK(3)); case HDSP_CLOCK_WORD: return (HDSP_CONTROL_CLOCK(4)); case HDSP_CLOCK_ADAT_SYNC: return (HDSP_CONTROL_CLOCK(5)); default: return (HDSP_CONTROL_MASTER); } } static int hdsp_sysctl_clock_preference(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdsp_clock_source *clock_table, *clock; char buf[16] = "invalid"; int error; uint32_t control; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSP_9632) clock_table = hdsp_clock_source_table_9632; else if (sc->type == HDSP_9652) clock_table = hdsp_clock_source_table_9652; else return (ENXIO); /* Extract preferred clock source from control register. */ control = sc->ctrl_register & HDSP_CONTROL_CLOCK_MASK; for (clock = clock_table; clock->name != NULL; ++clock) { if (hdsp_control_clock_preference(clock->type) == control) break; } if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find clock source matching the sysctl string. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (strncasecmp(buf, clock->name, sizeof(buf)) == 0) break; } /* Set preferred clock source in control register. */ if (clock->name != NULL) { control = hdsp_control_clock_preference(clock->type); control &= HDSP_CONTROL_CLOCK_MASK; snd_mtxlock(sc->lock); sc->ctrl_register &= ~HDSP_CONTROL_CLOCK_MASK; sc->ctrl_register |= control; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); snd_mtxunlock(sc->lock); } return (0); } static uint32_t hdsp_status2_clock_source(enum hdsp_clock_type type) { switch (type) { case HDSP_CLOCK_INTERNAL: return (0); case HDSP_CLOCK_ADAT1: return (HDSP_STATUS2_CLOCK(0)); case HDSP_CLOCK_ADAT2: return (HDSP_STATUS2_CLOCK(1)); case HDSP_CLOCK_ADAT3: return (HDSP_STATUS2_CLOCK(2)); case HDSP_CLOCK_SPDIF: return (HDSP_STATUS2_CLOCK(3)); case HDSP_CLOCK_WORD: return (HDSP_STATUS2_CLOCK(4)); case HDSP_CLOCK_ADAT_SYNC: return (HDSP_STATUS2_CLOCK(5)); default: return (0); } } static int hdsp_sysctl_clock_source(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdsp_clock_source *clock_table, *clock; char buf[16] = "invalid"; uint32_t status2; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSP_9632) clock_table = hdsp_clock_source_table_9632; else if (sc->type == HDSP_9652) clock_table = hdsp_clock_source_table_9652; else return (ENXIO); /* Read current (autosync) clock source from status2 register. */ snd_mtxlock(sc->lock); status2 = hdsp_read_4(sc, HDSP_STATUS2_REG); status2 &= HDSP_STATUS2_CLOCK_MASK; snd_mtxunlock(sc->lock); /* Translate status2 register value to clock source. */ for (clock = clock_table; clock->name != NULL; ++clock) { /* In clock master mode, override with internal clock source. */ if (sc->ctrl_register & HDSP_CONTROL_MASTER) { if (clock->type == HDSP_CLOCK_INTERNAL) break; } else if (hdsp_status2_clock_source(clock->type) == status2) break; } /* Process sysctl string request. */ if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdsp_sysctl_clock_list(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdsp_clock_source *clock_table, *clock; char buf[256]; int n; sc = oidp->oid_arg1; n = 0; /* Select clock source table for device type. */ if (sc->type == HDSP_9632) clock_table = hdsp_clock_source_table_9632; else if (sc->type == HDSP_9652) clock_table = hdsp_clock_source_table_9652; else return (ENXIO); /* List available clock sources. */ buf[0] = 0; for (clock = clock_table; clock->name != NULL; ++clock) { if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); n += strlcpy(buf + n, clock->name, sizeof(buf) - n); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static bool hdsp_clock_source_locked(enum hdsp_clock_type type, uint32_t status, uint32_t status2) { switch (type) { case HDSP_CLOCK_INTERNAL: return (true); case HDSP_CLOCK_ADAT1: return ((status >> 3) & 0x01); case HDSP_CLOCK_ADAT2: return ((status >> 2) & 0x01); case HDSP_CLOCK_ADAT3: return ((status >> 1) & 0x01); case HDSP_CLOCK_SPDIF: return (!((status >> 25) & 0x01)); case HDSP_CLOCK_WORD: return ((status2 >> 3) & 0x01); case HDSP_CLOCK_ADAT_SYNC: return ((status >> 5) & 0x01); default: return (false); } } static bool hdsp_clock_source_synced(enum hdsp_clock_type type, uint32_t status, uint32_t status2) { switch (type) { case HDSP_CLOCK_INTERNAL: return (true); case HDSP_CLOCK_ADAT1: return ((status >> 18) & 0x01); case HDSP_CLOCK_ADAT2: return ((status >> 17) & 0x01); case HDSP_CLOCK_ADAT3: return ((status >> 16) & 0x01); case HDSP_CLOCK_SPDIF: return (((status >> 4) & 0x01) && !((status >> 25) & 0x01)); case HDSP_CLOCK_WORD: return ((status2 >> 4) & 0x01); case HDSP_CLOCK_ADAT_SYNC: return ((status >> 27) & 0x01); default: return (false); } } static int hdsp_sysctl_sync_status(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdsp_clock_source *clock_table, *clock; char buf[256]; char *state; int n; uint32_t status, status2; sc = oidp->oid_arg1; n = 0; /* Select sync ports table for device type. */ if (sc->type == HDSP_9632) clock_table = hdsp_clock_source_table_9632; else if (sc->type == HDSP_9652) clock_table = hdsp_clock_source_table_9652; else return (ENXIO); /* Read current lock and sync bits from status registers. */ snd_mtxlock(sc->lock); status = hdsp_read_4(sc, HDSP_STATUS_REG); status2 = hdsp_read_4(sc, HDSP_STATUS2_REG); snd_mtxunlock(sc->lock); /* List clock sources with lock and sync state. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (clock->type == HDSP_CLOCK_INTERNAL) continue; if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); state = "none"; if (hdsp_clock_source_locked(clock->type, status, status2)) { if (hdsp_clock_source_synced(clock->type, status, status2)) state = "sync"; else state = "lock"; } n += snprintf(buf + n, sizeof(buf) - n, "%s(%s)", clock->name, state); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdsp_probe(device_t dev) { uint32_t rev; if (pci_get_vendor(dev) == PCI_VENDOR_XILINX && pci_get_device(dev) == PCI_DEVICE_XILINX_HDSP) { rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_9632: device_set_desc(dev, "RME HDSP 9632"); return (0); case PCI_REVISION_9652: device_set_desc(dev, "RME HDSP 9652"); return (0); } } return (ENXIO); } static int hdsp_init(struct sc_info *sc) { unsigned mixer_controls; /* Set latency. */ sc->period = 256; /* * The pcm channel latency settings propagate unreliable blocksizes, * different for recording and playback, and skewed due to rounding * and total buffer size limits. * Force period to a consistent default until these issues are fixed. */ sc->force_period = 256; sc->ctrl_register = hdsp_encode_latency(2); /* Set rate. */ sc->speed = HDSP_SPEED_DEFAULT; sc->force_speed = 0; sc->ctrl_register &= ~HDSP_FREQ_MASK; sc->ctrl_register |= HDSP_FREQ_MASK_DEFAULT; /* Set internal clock source (master). */ sc->ctrl_register &= ~HDSP_CONTROL_CLOCK_MASK; sc->ctrl_register |= HDSP_CONTROL_MASTER; /* SPDIF from coax in, line out. */ sc->ctrl_register &= ~HDSP_CONTROL_SPDIF_COAX; sc->ctrl_register |= HDSP_CONTROL_SPDIF_COAX; sc->ctrl_register &= ~HDSP_CONTROL_LINE_OUT; sc->ctrl_register |= HDSP_CONTROL_LINE_OUT; /* Default gain levels. */ sc->ctrl_register &= ~HDSP_INPUT_LEVEL_MASK; sc->ctrl_register |= HDSP_INPUT_LEVEL_LOWGAIN; sc->ctrl_register &= ~HDSP_OUTPUT_LEVEL_MASK; sc->ctrl_register |= HDSP_OUTPUT_LEVEL_MINUS10DBV; sc->ctrl_register &= ~HDSP_PHONES_LEVEL_MASK; sc->ctrl_register |= HDSP_PHONES_LEVEL_MINUS12DB; hdsp_write_4(sc, HDSP_CONTROL_REG, sc->ctrl_register); if (sc->type == HDSP_9652) hdsp_write_4(sc, HDSP_CONTROL2_REG, HDSP_CONTROL2_9652_MIXER); else hdsp_write_4(sc, HDSP_CONTROL2_REG, 0); switch (sc->type) { case HDSP_9632: /* Mixer matrix is 2 source rows (input, playback) per output. */ mixer_controls = 2 * HDSP_MIX_SLOTS_9632 * HDSP_MIX_SLOTS_9632; break; case HDSP_9652: /* Mixer matrix is 2 source rows (input, playback) per output. */ mixer_controls = 2 * HDSP_MIX_SLOTS_9652 * HDSP_MIX_SLOTS_9652; break; default: return (ENXIO); } /* Initialize mixer matrix by silencing all controls. */ for (unsigned offset = 0; offset < mixer_controls * 2; offset += 4) { /* Only accepts 4 byte values, pairs of 16 bit volume controls. */ hdsp_write_4(sc, HDSP_MIXER_BASE + offset, (HDSP_MIN_GAIN << 16) | HDSP_MIN_GAIN); } /* Reset pointer, rewrite frequency (same register) for 9632. */ hdsp_write_4(sc, HDSP_RESET_POINTER, 0); if (sc->type == HDSP_9632) { /* Set DDS value. */ hdsp_write_4(sc, HDSP_FREQ_REG, hdsp_freq_reg_value(sc->speed)); } return (0); } static int hdsp_attach(device_t dev) { struct hdsp_channel *chan_map; struct sc_pcminfo *scp; struct sc_info *sc; uint32_t rev; int i, err; #if 0 device_printf(dev, "hdsp_attach()\n"); #endif sc = device_get_softc(dev); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_hdsp softc"); sc->dev = dev; pci_enable_busmaster(dev); rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_9632: sc->type = HDSP_9632; chan_map = hdsp_unified_pcm ? chan_map_9632_uni : chan_map_9632; break; case PCI_REVISION_9652: sc->type = HDSP_9652; chan_map = hdsp_unified_pcm ? chan_map_9652_uni : chan_map_9652; break; default: return (ENXIO); } /* Allocate resources. */ err = hdsp_alloc_resources(sc); if (err) { device_printf(dev, "Unable to allocate system resources.\n"); return (ENXIO); } if (hdsp_init(sc) != 0) return (ENXIO); for (i = 0; i < HDSP_MAX_CHANS && chan_map[i].descr != NULL; i++) { scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_WAITOK | M_ZERO); scp->hc = &chan_map[i]; scp->sc = sc; scp->dev = device_add_child(dev, "pcm", -1); device_set_ivars(scp->dev, scp); } hdsp_map_dmabuf(sc); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sync_status", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_sync_status, "A", "List clock source signal lock and sync status"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_source", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_clock_source, "A", "Currently effective clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_preference", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_clock_preference, "A", "Set 'internal' (master) or preferred autosync clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_list", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_clock_list, "A", "List of supported clock sources"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "period", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_period, "A", "Force period of samples per interrupt (32, 64, ... 4096)"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sample_rate", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_sample_rate, "A", "Force sample rate (32000, 44100, 48000, ... 192000)"); if (sc->type == HDSP_9632) { SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "phones_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_phones_level, "A", "Phones output level ('0dB', '-6dB', '-12dB')"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "output_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_output_level, "A", "Analog output level ('HighGain', '+4dBU', '-10dBV')"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "input_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdsp_sysctl_input_level, "A", "Analog input level ('LowGain', '+4dBU', '-10dBV')"); } bus_attach_children(dev); return (0); } static void hdsp_child_deleted(device_t dev, device_t child) { free(device_get_ivars(child), M_DEVBUF); } static void hdsp_dmafree(struct sc_info *sc) { bus_dmamap_unload(sc->dmat, sc->rmap); bus_dmamap_unload(sc->dmat, sc->pmap); bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); sc->rbuf = sc->pbuf = NULL; } static int hdsp_detach(device_t dev) { struct sc_info *sc; int err; sc = device_get_softc(dev); if (sc == NULL) { device_printf(dev,"Can't detach: softc is null.\n"); return (0); } - err = device_delete_children(dev); + err = bus_generic_detach(dev); if (err) return (err); hdsp_dmafree(sc); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); if (sc->cs) bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(0), sc->cs); if (sc->lock) snd_mtxfree(sc->lock); return (0); } static device_method_t hdsp_methods[] = { DEVMETHOD(device_probe, hdsp_probe), DEVMETHOD(device_attach, hdsp_attach), DEVMETHOD(device_detach, hdsp_detach), DEVMETHOD(bus_child_deleted, hdsp_child_deleted), { 0, 0 } }; static driver_t hdsp_driver = { "hdsp", hdsp_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hdsp, pci, hdsp_driver, 0, 0); diff --git a/sys/dev/sound/pci/hdspe.c b/sys/dev/sound/pci/hdspe.c index 4b5c31801d55..c292b2ddef56 100644 --- a/sys/dev/sound/pci/hdspe.c +++ b/sys/dev/sound/pci/hdspe.c @@ -1,914 +1,914 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012-2016 Ruslan Bukin * Copyright (c) 2023-2024 Florian Walpen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * RME HDSPe driver for FreeBSD. * Supported cards: AIO, RayDAT. */ #include #include #include #include #include #include #include static bool hdspe_unified_pcm = false; static SYSCTL_NODE(_hw, OID_AUTO, hdspe, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "PCI HDSPe"); SYSCTL_BOOL(_hw_hdspe, OID_AUTO, unified_pcm, CTLFLAG_RWTUN, &hdspe_unified_pcm, 0, "Combine physical ports in one unified pcm device"); static struct hdspe_clock_source hdspe_clock_source_table_rd[] = { { "internal", 0 << 1 | 1, HDSPE_STATUS1_CLOCK(15), 0, 0 }, { "word", 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 1 << 24, 1 << 25 }, { "aes", 1 << 1 | 0, HDSPE_STATUS1_CLOCK( 1), 1 << 0, 1 << 8 }, { "spdif", 2 << 1 | 0, HDSPE_STATUS1_CLOCK( 2), 1 << 1, 1 << 9 }, { "adat1", 3 << 1 | 0, HDSPE_STATUS1_CLOCK( 3), 1 << 2, 1 << 10 }, { "adat2", 4 << 1 | 0, HDSPE_STATUS1_CLOCK( 4), 1 << 3, 1 << 11 }, { "adat3", 5 << 1 | 0, HDSPE_STATUS1_CLOCK( 5), 1 << 4, 1 << 12 }, { "adat4", 6 << 1 | 0, HDSPE_STATUS1_CLOCK( 6), 1 << 5, 1 << 13 }, { "tco", 9 << 1 | 0, HDSPE_STATUS1_CLOCK( 9), 1 << 26, 1 << 27 }, { "sync_in", 10 << 1 | 0, HDSPE_STATUS1_CLOCK(10), 0, 0 }, { NULL, 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 0, 0 }, }; static struct hdspe_clock_source hdspe_clock_source_table_aio[] = { { "internal", 0 << 1 | 1, HDSPE_STATUS1_CLOCK(15), 0, 0 }, { "word", 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 1 << 24, 1 << 25 }, { "aes", 1 << 1 | 0, HDSPE_STATUS1_CLOCK( 1), 1 << 0, 1 << 8 }, { "spdif", 2 << 1 | 0, HDSPE_STATUS1_CLOCK( 2), 1 << 1, 1 << 9 }, { "adat", 3 << 1 | 0, HDSPE_STATUS1_CLOCK( 3), 1 << 2, 1 << 10 }, { "tco", 9 << 1 | 0, HDSPE_STATUS1_CLOCK( 9), 1 << 26, 1 << 27 }, { "sync_in", 10 << 1 | 0, HDSPE_STATUS1_CLOCK(10), 0, 0 }, { NULL, 0 << 1 | 0, HDSPE_STATUS1_CLOCK( 0), 0, 0 }, }; static struct hdspe_channel chan_map_aio[] = { { HDSPE_CHAN_AIO_LINE, "line" }, { HDSPE_CHAN_AIO_EXT, "ext" }, { HDSPE_CHAN_AIO_PHONE, "phone" }, { HDSPE_CHAN_AIO_AES, "aes" }, { HDSPE_CHAN_AIO_SPDIF, "s/pdif" }, { HDSPE_CHAN_AIO_ADAT, "adat" }, { 0, NULL }, }; static struct hdspe_channel chan_map_aio_uni[] = { { HDSPE_CHAN_AIO_ALL, "all" }, { 0, NULL }, }; static struct hdspe_channel chan_map_rd[] = { { HDSPE_CHAN_RAY_AES, "aes" }, { HDSPE_CHAN_RAY_SPDIF, "s/pdif" }, { HDSPE_CHAN_RAY_ADAT1, "adat1" }, { HDSPE_CHAN_RAY_ADAT2, "adat2" }, { HDSPE_CHAN_RAY_ADAT3, "adat3" }, { HDSPE_CHAN_RAY_ADAT4, "adat4" }, { 0, NULL }, }; static struct hdspe_channel chan_map_rd_uni[] = { { HDSPE_CHAN_RAY_ALL, "all" }, { 0, NULL }, }; static void hdspe_intr(void *p) { struct sc_pcminfo *scp; struct sc_info *sc; device_t *devlist; int devcount; int status; int err; int i; sc = (struct sc_info *)p; snd_mtxlock(sc->lock); status = hdspe_read_1(sc, HDSPE_STATUS_REG); if (status & HDSPE_AUDIO_IRQ_PENDING) { if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) return; for (i = 0; i < devcount; i++) { scp = device_get_ivars(devlist[i]); if (scp->ih != NULL) scp->ih(scp); } hdspe_write_1(sc, HDSPE_INTERRUPT_ACK, 0); free(devlist, M_TEMP); } snd_mtxunlock(sc->lock); } static void hdspe_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { #if 0 device_printf(sc->dev, "hdspe_dmapsetmap()\n"); #endif } static int hdspe_alloc_resources(struct sc_info *sc) { /* Allocate resource. */ sc->csid = PCIR_BAR(0); sc->cs = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY, &sc->csid, RF_ACTIVE); if (!sc->cs) { device_printf(sc->dev, "Unable to map SYS_RES_MEMORY.\n"); return (ENXIO); } sc->cst = rman_get_bustag(sc->cs); sc->csh = rman_get_bushandle(sc->cs); /* Allocate interrupt resource. */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || bus_setup_intr(sc->dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV, NULL, hdspe_intr, sc, &sc->ih)) { device_printf(sc->dev, "Unable to alloc interrupt resource.\n"); return (ENXIO); } /* Allocate DMA resources. */ if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev), /*alignment*/4, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/2 * HDSPE_DMASEGSIZE, /*nsegments*/2, /*maxsegsz*/HDSPE_DMASEGSIZE, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, /*dmatag*/&sc->dmat) != 0) { device_printf(sc->dev, "Unable to create dma tag.\n"); return (ENXIO); } sc->bufsize = HDSPE_DMASEGSIZE; /* pbuf (play buffer). */ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_WAITOK, &sc->pmap)) { device_printf(sc->dev, "Can't alloc pbuf.\n"); return (ENXIO); } if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->bufsize, hdspe_dmapsetmap, sc, BUS_DMA_NOWAIT)) { device_printf(sc->dev, "Can't load pbuf.\n"); return (ENXIO); } /* rbuf (rec buffer). */ if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_WAITOK, &sc->rmap)) { device_printf(sc->dev, "Can't alloc rbuf.\n"); return (ENXIO); } if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->bufsize, hdspe_dmapsetmap, sc, BUS_DMA_NOWAIT)) { device_printf(sc->dev, "Can't load rbuf.\n"); return (ENXIO); } bzero(sc->pbuf, sc->bufsize); bzero(sc->rbuf, sc->bufsize); return (0); } static void hdspe_map_dmabuf(struct sc_info *sc) { uint32_t paddr, raddr; int i; paddr = vtophys(sc->pbuf); raddr = vtophys(sc->rbuf); for (i = 0; i < HDSPE_MAX_SLOTS * 16; i++) { hdspe_write_4(sc, HDSPE_PAGE_ADDR_BUF_OUT + 4 * i, paddr + i * 4096); hdspe_write_4(sc, HDSPE_PAGE_ADDR_BUF_IN + 4 * i, raddr + i * 4096); } } static const char * hdspe_settings_input_level(uint32_t settings) { switch (settings & HDSPE_INPUT_LEVEL_MASK) { case HDSPE_INPUT_LEVEL_LOWGAIN: return ("LowGain"); case HDSPE_INPUT_LEVEL_PLUS4DBU: return ("+4dBu"); case HDSPE_INPUT_LEVEL_MINUS10DBV: return ("-10dBV"); default: return (NULL); } } static int hdspe_sysctl_input_level(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; const char *label; char buf[16] = "invalid"; int error; uint32_t settings; sc = oidp->oid_arg1; /* Only available on HDSPE AIO. */ if (sc->type != HDSPE_AIO) return (ENXIO); /* Extract current input level from settings register. */ settings = sc->settings_register & HDSPE_INPUT_LEVEL_MASK; label = hdspe_settings_input_level(settings); if (label != NULL) strlcpy(buf, label, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find input level matching the sysctl string. */ label = hdspe_settings_input_level(HDSPE_INPUT_LEVEL_LOWGAIN); if (strncasecmp(buf, label, sizeof(buf)) == 0) settings = HDSPE_INPUT_LEVEL_LOWGAIN; label = hdspe_settings_input_level(HDSPE_INPUT_LEVEL_PLUS4DBU); if (strncasecmp(buf, label, sizeof(buf)) == 0) settings = HDSPE_INPUT_LEVEL_PLUS4DBU; label = hdspe_settings_input_level(HDSPE_INPUT_LEVEL_MINUS10DBV); if (strncasecmp(buf, label, sizeof(buf)) == 0) settings = HDSPE_INPUT_LEVEL_MINUS10DBV; /* Set input level in settings register. */ settings &= HDSPE_INPUT_LEVEL_MASK; if (settings != (sc->settings_register & HDSPE_INPUT_LEVEL_MASK)) { snd_mtxlock(sc->lock); sc->settings_register &= ~HDSPE_INPUT_LEVEL_MASK; sc->settings_register |= settings; hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register); snd_mtxunlock(sc->lock); } return (0); } static const char * hdspe_settings_output_level(uint32_t settings) { switch (settings & HDSPE_OUTPUT_LEVEL_MASK) { case HDSPE_OUTPUT_LEVEL_HIGHGAIN: return ("HighGain"); case HDSPE_OUTPUT_LEVEL_PLUS4DBU: return ("+4dBu"); case HDSPE_OUTPUT_LEVEL_MINUS10DBV: return ("-10dBV"); default: return (NULL); } } static int hdspe_sysctl_output_level(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; const char *label; char buf[16] = "invalid"; int error; uint32_t settings; sc = oidp->oid_arg1; /* Only available on HDSPE AIO. */ if (sc->type != HDSPE_AIO) return (ENXIO); /* Extract current output level from settings register. */ settings = sc->settings_register & HDSPE_OUTPUT_LEVEL_MASK; label = hdspe_settings_output_level(settings); if (label != NULL) strlcpy(buf, label, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find output level matching the sysctl string. */ label = hdspe_settings_output_level(HDSPE_OUTPUT_LEVEL_HIGHGAIN); if (strncasecmp(buf, label, sizeof(buf)) == 0) settings = HDSPE_OUTPUT_LEVEL_HIGHGAIN; label = hdspe_settings_output_level(HDSPE_OUTPUT_LEVEL_PLUS4DBU); if (strncasecmp(buf, label, sizeof(buf)) == 0) settings = HDSPE_OUTPUT_LEVEL_PLUS4DBU; label = hdspe_settings_output_level(HDSPE_OUTPUT_LEVEL_MINUS10DBV); if (strncasecmp(buf, label, sizeof(buf)) == 0) settings = HDSPE_OUTPUT_LEVEL_MINUS10DBV; /* Set output level in settings register. */ settings &= HDSPE_OUTPUT_LEVEL_MASK; if (settings != (sc->settings_register & HDSPE_OUTPUT_LEVEL_MASK)) { snd_mtxlock(sc->lock); sc->settings_register &= ~HDSPE_OUTPUT_LEVEL_MASK; sc->settings_register |= settings; hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register); snd_mtxunlock(sc->lock); } return (0); } static const char * hdspe_settings_phones_level(uint32_t settings) { switch (settings & HDSPE_PHONES_LEVEL_MASK) { case HDSPE_PHONES_LEVEL_HIGHGAIN: return ("HighGain"); case HDSPE_PHONES_LEVEL_PLUS4DBU: return ("+4dBu"); case HDSPE_PHONES_LEVEL_MINUS10DBV: return ("-10dBV"); default: return (NULL); } } static int hdspe_sysctl_phones_level(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; const char *label; char buf[16] = "invalid"; int error; uint32_t settings; sc = oidp->oid_arg1; /* Only available on HDSPE AIO. */ if (sc->type != HDSPE_AIO) return (ENXIO); /* Extract current phones level from settings register. */ settings = sc->settings_register & HDSPE_PHONES_LEVEL_MASK; label = hdspe_settings_phones_level(settings); if (label != NULL) strlcpy(buf, label, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find phones level matching the sysctl string. */ label = hdspe_settings_phones_level(HDSPE_PHONES_LEVEL_HIGHGAIN); if (strncasecmp(buf, label, sizeof(buf)) == 0) settings = HDSPE_PHONES_LEVEL_HIGHGAIN; label = hdspe_settings_phones_level(HDSPE_PHONES_LEVEL_PLUS4DBU); if (strncasecmp(buf, label, sizeof(buf)) == 0) settings = HDSPE_PHONES_LEVEL_PLUS4DBU; label = hdspe_settings_phones_level(HDSPE_PHONES_LEVEL_MINUS10DBV); if (strncasecmp(buf, label, sizeof(buf)) == 0) settings = HDSPE_PHONES_LEVEL_MINUS10DBV; /* Set phones level in settings register. */ settings &= HDSPE_PHONES_LEVEL_MASK; if (settings != (sc->settings_register & HDSPE_PHONES_LEVEL_MASK)) { snd_mtxlock(sc->lock); sc->settings_register &= ~HDSPE_PHONES_LEVEL_MASK; sc->settings_register |= settings; hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register); snd_mtxunlock(sc->lock); } return (0); } static int hdspe_sysctl_sample_rate(SYSCTL_HANDLER_ARGS) { struct sc_info *sc = oidp->oid_arg1; int error; unsigned int speed, multiplier; speed = sc->force_speed; /* Process sysctl (unsigned) integer request. */ error = sysctl_handle_int(oidp, &speed, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* Speed from 32000 to 192000, 0 falls back to pcm speed setting. */ sc->force_speed = 0; if (speed > 0) { multiplier = 1; if (speed > (96000 + 128000) / 2) multiplier = 4; else if (speed > (48000 + 64000) / 2) multiplier = 2; if (speed < ((32000 + 44100) / 2) * multiplier) sc->force_speed = 32000 * multiplier; else if (speed < ((44100 + 48000) / 2) * multiplier) sc->force_speed = 44100 * multiplier; else sc->force_speed = 48000 * multiplier; } return (0); } static int hdspe_sysctl_period(SYSCTL_HANDLER_ARGS) { struct sc_info *sc = oidp->oid_arg1; int error; unsigned int period; period = sc->force_period; /* Process sysctl (unsigned) integer request. */ error = sysctl_handle_int(oidp, &period, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* Period is from 2^5 to 2^14, 0 falls back to pcm latency settings. */ sc->force_period = 0; if (period > 0) { sc->force_period = 32; while (sc->force_period < period && sc->force_period < 4096) sc->force_period <<= 1; } return (0); } static int hdspe_sysctl_clock_preference(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[16] = "invalid"; int error; uint32_t setting; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* Extract preferred clock source from settings register. */ setting = sc->settings_register & HDSPE_SETTING_CLOCK_MASK; for (clock = clock_table; clock->name != NULL; ++clock) { if (clock->setting == setting) break; } if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); /* Process sysctl string request. */ error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Find clock source matching the sysctl string. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (strncasecmp(buf, clock->name, sizeof(buf)) == 0) break; } /* Set preferred clock source in settings register. */ if (clock->name != NULL) { setting = clock->setting & HDSPE_SETTING_CLOCK_MASK; snd_mtxlock(sc->lock); sc->settings_register &= ~HDSPE_SETTING_CLOCK_MASK; sc->settings_register |= setting; hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register); snd_mtxunlock(sc->lock); } return (0); } static int hdspe_sysctl_clock_source(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[16] = "invalid"; uint32_t status; sc = oidp->oid_arg1; /* Select sync ports table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* Read current (autosync) clock source from status register. */ snd_mtxlock(sc->lock); status = hdspe_read_4(sc, HDSPE_STATUS1_REG); status &= HDSPE_STATUS1_CLOCK_MASK; snd_mtxunlock(sc->lock); /* Translate status register value to clock source. */ for (clock = clock_table; clock->name != NULL; ++clock) { /* In clock master mode, override with internal clock source. */ if (sc->settings_register & HDSPE_SETTING_MASTER) { if (clock->setting & HDSPE_SETTING_MASTER) break; } else if (clock->status == status) break; } /* Process sysctl string request. */ if (clock->name != NULL) strlcpy(buf, clock->name, sizeof(buf)); return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdspe_sysctl_clock_list(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[256]; int n; sc = oidp->oid_arg1; n = 0; /* Select clock source table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* List available clock sources. */ buf[0] = 0; for (clock = clock_table; clock->name != NULL; ++clock) { if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); n += strlcpy(buf + n, clock->name, sizeof(buf) - n); } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdspe_sysctl_sync_status(SYSCTL_HANDLER_ARGS) { struct sc_info *sc; struct hdspe_clock_source *clock_table, *clock; char buf[256]; char *state; int n; uint32_t status; sc = oidp->oid_arg1; n = 0; /* Select sync ports table for device type. */ if (sc->type == HDSPE_AIO) clock_table = hdspe_clock_source_table_aio; else if (sc->type == HDSPE_RAYDAT) clock_table = hdspe_clock_source_table_rd; else return (ENXIO); /* Read current lock and sync bits from status register. */ snd_mtxlock(sc->lock); status = hdspe_read_4(sc, HDSPE_STATUS1_REG); snd_mtxunlock(sc->lock); /* List clock sources with lock and sync state. */ for (clock = clock_table; clock->name != NULL; ++clock) { if (clock->sync_bit != 0) { if (n > 0) n += strlcpy(buf + n, ",", sizeof(buf) - n); state = "none"; if ((clock->sync_bit & status) != 0) state = "sync"; else if ((clock->lock_bit & status) != 0) state = "lock"; n += snprintf(buf + n, sizeof(buf) - n, "%s(%s)", clock->name, state); } } return (sysctl_handle_string(oidp, buf, sizeof(buf), req)); } static int hdspe_probe(device_t dev) { uint32_t rev; if ((pci_get_vendor(dev) == PCI_VENDOR_XILINX || pci_get_vendor(dev) == PCI_VENDOR_RME) && pci_get_device(dev) == PCI_DEVICE_XILINX_HDSPE) { rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_AIO: device_set_desc(dev, "RME HDSPe AIO"); return (0); case PCI_REVISION_RAYDAT: device_set_desc(dev, "RME HDSPe RayDAT"); return (0); } } return (ENXIO); } static int hdspe_init(struct sc_info *sc) { long long period; /* Set latency. */ sc->period = 32; /* * The pcm channel latency settings propagate unreliable blocksizes, * different for recording and playback, and skewed due to rounding * and total buffer size limits. * Force period to a consistent default until these issues are fixed. */ sc->force_period = 256; sc->ctrl_register = hdspe_encode_latency(7); /* Set rate. */ sc->speed = HDSPE_SPEED_DEFAULT; sc->force_speed = 0; sc->ctrl_register &= ~HDSPE_FREQ_MASK; sc->ctrl_register |= HDSPE_FREQ_MASK_DEFAULT; hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register); switch (sc->type) { case HDSPE_RAYDAT: case HDSPE_AIO: period = HDSPE_FREQ_AIO; break; default: return (ENXIO); } /* Set DDS value. */ period /= sc->speed; hdspe_write_4(sc, HDSPE_FREQ_REG, period); /* Other settings. */ sc->settings_register = 0; /* Default gain levels. */ sc->settings_register &= ~HDSPE_INPUT_LEVEL_MASK; sc->settings_register |= HDSPE_INPUT_LEVEL_LOWGAIN; sc->settings_register &= ~HDSPE_OUTPUT_LEVEL_MASK; sc->settings_register |= HDSPE_OUTPUT_LEVEL_MINUS10DBV; sc->settings_register &= ~HDSPE_PHONES_LEVEL_MASK; sc->settings_register |= HDSPE_PHONES_LEVEL_MINUS10DBV; hdspe_write_4(sc, HDSPE_SETTINGS_REG, sc->settings_register); return (0); } static int hdspe_attach(device_t dev) { struct hdspe_channel *chan_map; struct sc_pcminfo *scp; struct sc_info *sc; uint32_t rev; int i, err; #if 0 device_printf(dev, "hdspe_attach()\n"); #endif sc = device_get_softc(dev); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_hdspe softc"); sc->dev = dev; pci_enable_busmaster(dev); rev = pci_get_revid(dev); switch (rev) { case PCI_REVISION_AIO: sc->type = HDSPE_AIO; chan_map = hdspe_unified_pcm ? chan_map_aio_uni : chan_map_aio; break; case PCI_REVISION_RAYDAT: sc->type = HDSPE_RAYDAT; chan_map = hdspe_unified_pcm ? chan_map_rd_uni : chan_map_rd; break; default: return (ENXIO); } /* Allocate resources. */ err = hdspe_alloc_resources(sc); if (err) { device_printf(dev, "Unable to allocate system resources.\n"); return (ENXIO); } if (hdspe_init(sc) != 0) return (ENXIO); for (i = 0; i < HDSPE_MAX_CHANS && chan_map[i].descr != NULL; i++) { scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_WAITOK | M_ZERO); scp->hc = &chan_map[i]; scp->sc = sc; scp->dev = device_add_child(dev, "pcm", DEVICE_UNIT_ANY); device_set_ivars(scp->dev, scp); } hdspe_map_dmabuf(sc); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sync_status", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_sync_status, "A", "List clock source signal lock and sync status"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_source", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_clock_source, "A", "Currently effective clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_preference", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_clock_preference, "A", "Set 'internal' (master) or preferred autosync clock source"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "clock_list", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_clock_list, "A", "List of supported clock sources"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "period", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_period, "A", "Force period of samples per interrupt (32, 64, ... 4096)"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sample_rate", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_sample_rate, "A", "Force sample rate (32000, 44100, 48000, ... 192000)"); if (sc->type == HDSPE_AIO) { SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "phones_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_phones_level, "A", "Phones output level ('HighGain', '+4dBU', '-10dBV')"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "output_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_output_level, "A", "Analog output level ('HighGain', '+4dBU', '-10dBV')"); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "input_level", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, sc, 0, hdspe_sysctl_input_level, "A", "Analog input level ('LowGain', '+4dBU', '-10dBV')"); } bus_attach_children(dev); return (0); } static void hdspe_child_deleted(device_t dev, device_t child) { free(device_get_ivars(child), M_DEVBUF); } static void hdspe_dmafree(struct sc_info *sc) { bus_dmamap_unload(sc->dmat, sc->rmap); bus_dmamap_unload(sc->dmat, sc->pmap); bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); sc->rbuf = sc->pbuf = NULL; } static int hdspe_detach(device_t dev) { struct sc_info *sc; int err; sc = device_get_softc(dev); if (sc == NULL) { device_printf(dev,"Can't detach: softc is null.\n"); return (0); } - err = device_delete_children(dev); + err = bus_generic_detach(dev); if (err) return (err); hdspe_dmafree(sc); if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); if (sc->cs) bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(0), sc->cs); if (sc->lock) snd_mtxfree(sc->lock); return (0); } static device_method_t hdspe_methods[] = { DEVMETHOD(device_probe, hdspe_probe), DEVMETHOD(device_attach, hdspe_attach), DEVMETHOD(device_detach, hdspe_detach), DEVMETHOD(bus_child_deleted, hdspe_child_deleted), { 0, 0 } }; static driver_t hdspe_driver = { "hdspe", hdspe_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_hdspe, pci, hdspe_driver, 0, 0); diff --git a/sys/dev/spibus/spibus.c b/sys/dev/spibus/spibus.c index 9251bd0c2962..f082b9ec171a 100644 --- a/sys/dev/spibus/spibus.c +++ b/sys/dev/spibus/spibus.c @@ -1,299 +1,299 @@ /*- * Copyright (c) 2006 M. Warner Losh * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spibus_if.h" static int spibus_probe(device_t dev) { device_set_desc(dev, "SPI bus"); return (BUS_PROBE_DEFAULT); } int spibus_attach(device_t dev) { struct spibus_softc *sc = SPIBUS_SOFTC(dev); sc->dev = dev; bus_enumerate_hinted_children(dev); bus_attach_children(dev); return (0); } /* * Since this is not a self-enumerating bus, and since we always add * children in attach, we have to always delete children here. */ int spibus_detach(device_t dev) { - return (device_delete_children(dev)); + return (bus_generic_detach(dev)); } static int spibus_suspend(device_t dev) { return (bus_generic_suspend(dev)); } static int spibus_resume(device_t dev) { return (bus_generic_resume(dev)); } static int spibus_print_child(device_t dev, device_t child) { struct spibus_ivar *devi = SPIBUS_IVAR(child); int retval = 0; retval += bus_print_child_header(dev, child); retval += printf(" at cs %d", devi->cs); retval += printf(" mode %d", devi->mode); retval += resource_list_print_type(&devi->rl, "irq", SYS_RES_IRQ, "%jd"); retval += bus_print_child_footer(dev, child); return (retval); } void spibus_probe_nomatch(device_t bus, device_t child) { struct spibus_ivar *devi = SPIBUS_IVAR(child); device_printf(bus, " at cs %d mode %d\n", devi->cs, devi->mode); return; } int spibus_child_location(device_t bus, device_t child, struct sbuf *sb) { struct spibus_ivar *devi = SPIBUS_IVAR(child); int cs; cs = devi->cs & ~SPIBUS_CS_HIGH; /* trim 'cs high' bit */ sbuf_printf(sb, "bus=%d cs=%d", device_get_unit(bus), cs); return (0); } int spibus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct spibus_ivar *devi = SPIBUS_IVAR(child); switch (which) { default: return (EINVAL); case SPIBUS_IVAR_CS: *(uint32_t *)result = devi->cs; break; case SPIBUS_IVAR_MODE: *(uint32_t *)result = devi->mode; break; case SPIBUS_IVAR_CLOCK: *(uint32_t *)result = devi->clock; break; case SPIBUS_IVAR_CS_DELAY: *(uint32_t *)result = devi->cs_delay; break; } return (0); } int spibus_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct spibus_ivar *devi = SPIBUS_IVAR(child); if (devi == NULL || device_get_parent(child) != bus) return (EDOOFUS); switch (which) { case SPIBUS_IVAR_CLOCK: /* Any non-zero value is allowed for max clock frequency. */ if (value == 0) return (EINVAL); devi->clock = (uint32_t)value; break; case SPIBUS_IVAR_CS: /* Chip select cannot be changed. */ return (EINVAL); case SPIBUS_IVAR_MODE: /* Valid SPI modes are 0-3. */ if (value > 3) return (EINVAL); devi->mode = (uint32_t)value; break; case SPIBUS_IVAR_CS_DELAY: devi->cs_delay = (uint32_t)value; break; default: return (EINVAL); } return (0); } device_t spibus_add_child_common(device_t dev, u_int order, const char *name, int unit, size_t ivars_size) { device_t child; struct spibus_ivar *devi; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (child); devi = malloc(ivars_size, M_DEVBUF, M_NOWAIT | M_ZERO); if (devi == NULL) { device_delete_child(dev, child); return (0); } resource_list_init(&devi->rl); device_set_ivars(child, devi); return (child); } void spibus_child_deleted(device_t dev, device_t child) { struct spibus_ivar *devi; devi = device_get_ivars(child); if (devi == NULL) return; resource_list_free(&devi->rl); free(devi, M_DEVBUF); } static device_t spibus_add_child(device_t dev, u_int order, const char *name, int unit) { return (spibus_add_child_common( dev, order, name, unit, sizeof(struct spibus_ivar))); } static void spibus_hinted_child(device_t bus, const char *dname, int dunit) { device_t child; int irq; struct spibus_ivar *devi; child = BUS_ADD_CHILD(bus, 0, dname, dunit); devi = SPIBUS_IVAR(child); devi->mode = SPIBUS_MODE_NONE; resource_int_value(dname, dunit, "clock", &devi->clock); resource_int_value(dname, dunit, "cs", &devi->cs); resource_int_value(dname, dunit, "mode", &devi->mode); if (resource_int_value(dname, dunit, "irq", &irq) == 0) { if (bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1) != 0) device_printf(bus, "Warning: bus_set_resource() failed\n"); } } static struct resource_list * spibus_get_resource_list(device_t bus __unused, device_t child) { struct spibus_ivar *devi; devi = SPIBUS_IVAR(child); return (&devi->rl); } static int spibus_transfer_impl(device_t dev, device_t child, struct spi_command *cmd) { return (SPIBUS_TRANSFER(device_get_parent(dev), child, cmd)); } static device_method_t spibus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, spibus_probe), DEVMETHOD(device_attach, spibus_attach), DEVMETHOD(device_detach, spibus_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, spibus_suspend), DEVMETHOD(device_resume, spibus_resume), /* Bus interface */ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource_list, spibus_get_resource_list), DEVMETHOD(bus_add_child, spibus_add_child), DEVMETHOD(bus_child_deleted, spibus_child_deleted), DEVMETHOD(bus_print_child, spibus_print_child), DEVMETHOD(bus_probe_nomatch, spibus_probe_nomatch), DEVMETHOD(bus_read_ivar, spibus_read_ivar), DEVMETHOD(bus_write_ivar, spibus_write_ivar), DEVMETHOD(bus_child_location, spibus_child_location), DEVMETHOD(bus_hinted_child, spibus_hinted_child), /* spibus interface */ DEVMETHOD(spibus_transfer, spibus_transfer_impl), DEVMETHOD_END }; driver_t spibus_driver = { "spibus", spibus_methods, sizeof(struct spibus_softc) }; DRIVER_MODULE(spibus, spi, spibus_driver, 0, 0); MODULE_VERSION(spibus, 1); diff --git a/sys/dev/usb/controller/dwc_otg_acpi.c b/sys/dev/usb/controller/dwc_otg_acpi.c index 9b982dfd6e41..5d509911a54a 100644 --- a/sys/dev/usb/controller/dwc_otg_acpi.c +++ b/sys/dev/usb/controller/dwc_otg_acpi.c @@ -1,180 +1,183 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 Hans Petter Selasky. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static device_probe_t dwc_otg_probe; static device_attach_t dwc_otg_attach; static device_attach_t dwc_otg_detach; static char *dwc_otg_ids[] = { "BCM2848", NULL }; static int dwc_otg_probe(device_t dev) { int rv; if (acpi_disabled("dwc_otg")) return (ENXIO); rv = ACPI_ID_PROBE(device_get_parent(dev), dev, dwc_otg_ids, NULL); if (rv > 0) return (rv); device_set_desc(dev, "DWC OTG 2.0 integrated USB controller"); return (BUS_PROBE_DEFAULT); } static int dwc_otg_attach(device_t dev) { struct dwc_otg_softc *sc = device_get_softc(dev); int err; int rid; sc->sc_bus.parent = dev; /* assume device mode (this is only used for the Raspberry Pi 4's * USB-C port, which only works in device mode) */ sc->sc_mode = DWC_MODE_DEVICE; rid = 0; sc->sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_io_res == NULL) goto error; rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->sc_irq_res == NULL) goto error; err = dwc_otg_init(sc); if (err == 0) { err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) goto error; return (0); error: dwc_otg_detach(dev); return (ENXIO); } static int dwc_otg_detach(device_t dev) { struct dwc_otg_softc *sc = device_get_softc(dev); + int error; /* during module unload there are lots of children leftover */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); if (sc->sc_irq_res && sc->sc_intr_hdl) { /* * only call dwc_otg_uninit() after dwc_otg_init() */ dwc_otg_uninit(sc); bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_hdl); sc->sc_intr_hdl = NULL; } /* free IRQ channel, if any */ if (sc->sc_irq_res) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } /* free memory resource, if any */ if (sc->sc_io_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_io_res); sc->sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_bus, NULL); return (0); } static device_method_t dwc_otg_methods[] = { /* Device interface */ DEVMETHOD(device_probe, dwc_otg_probe), DEVMETHOD(device_attach, dwc_otg_attach), DEVMETHOD(device_detach, dwc_otg_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t dwc_otg_driver = { .name = "dwcotg", .methods = dwc_otg_methods, .size = sizeof(struct dwc_otg_softc), }; DRIVER_MODULE(dwcotg, acpi, dwc_otg_driver, 0, 0); MODULE_DEPEND(dwcotg, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/dwc_otg_fdt.c b/sys/dev/usb/controller/dwc_otg_fdt.c index d692638857c5..3d5dcb9e9a7b 100644 --- a/sys/dev/usb/controller/dwc_otg_fdt.c +++ b/sys/dev/usb/controller/dwc_otg_fdt.c @@ -1,215 +1,218 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 Hans Petter Selasky. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static device_probe_t dwc_otg_probe; static struct ofw_compat_data compat_data[] = { { "synopsys,designware-hs-otg2", 1 }, { "snps,dwc2", 1 }, { NULL, 0 } }; static int dwc_otg_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "DWC OTG 2.0 integrated USB controller"); return (BUS_PROBE_DEFAULT); } static int dwc_otg_irq_index(device_t dev, int *rid) { int idx, rv; phandle_t node; node = ofw_bus_get_node(dev); rv = ofw_bus_find_string_index(node, "interrupt-names", "usb", &idx); if (rv != 0) return (rv); *rid = idx; return (0); } int dwc_otg_attach(device_t dev) { struct dwc_otg_fdt_softc *sc = device_get_softc(dev); char usb_mode[24]; int err; int rid; sc->sc_otg.sc_bus.parent = dev; /* get USB mode, if any */ if (OF_getprop(ofw_bus_get_node(dev), "dr_mode", &usb_mode, sizeof(usb_mode)) > 0) { /* ensure proper zero termination */ usb_mode[sizeof(usb_mode) - 1] = 0; if (strcasecmp(usb_mode, "host") == 0) sc->sc_otg.sc_mode = DWC_MODE_HOST; else if (strcasecmp(usb_mode, "peripheral") == 0) sc->sc_otg.sc_mode = DWC_MODE_DEVICE; else if (strcasecmp(usb_mode, "otg") != 0) { device_printf(dev, "Invalid FDT dr_mode: %s\n", usb_mode); } } rid = 0; sc->sc_otg.sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!(sc->sc_otg.sc_io_res)) goto error; /* * brcm,bcm2708-usb FDT provides two interrupts, we need only the USB * interrupt (VC_USB). The latest FDT for it provides an * interrupt-names property and swapped them around, while older ones * did not have interrupt-names and put the usb interrupt in the second * position. We'll attempt to use interrupt-names first with a fallback * to the old method of assuming the index based on the compatible * string. */ if (dwc_otg_irq_index(dev, &rid) != 0) rid = ofw_bus_is_compatible(dev, "brcm,bcm2708-usb") ? 1 : 0; sc->sc_otg.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->sc_otg.sc_irq_res == NULL) goto error; sc->sc_otg.sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (sc->sc_otg.sc_bus.bdev == NULL) goto error; err = dwc_otg_init(&sc->sc_otg); if (err == 0) { err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev); } if (err) goto error; return (0); error: dwc_otg_detach(dev); return (ENXIO); } int dwc_otg_detach(device_t dev) { struct dwc_otg_fdt_softc *sc = device_get_softc(dev); + int error; /* during module unload there are lots of children leftover */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) { /* * only call dwc_otg_uninit() after dwc_otg_init() */ dwc_otg_uninit(&sc->sc_otg); bus_teardown_intr(dev, sc->sc_otg.sc_irq_res, sc->sc_otg.sc_intr_hdl); sc->sc_otg.sc_intr_hdl = NULL; } /* free IRQ channel, if any */ if (sc->sc_otg.sc_irq_res) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_otg.sc_irq_res); sc->sc_otg.sc_irq_res = NULL; } /* free memory resource, if any */ if (sc->sc_otg.sc_io_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_otg.sc_io_res); sc->sc_otg.sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL); return (0); } static device_method_t dwc_otg_methods[] = { /* Device interface */ DEVMETHOD(device_probe, dwc_otg_probe), DEVMETHOD(device_attach, dwc_otg_attach), DEVMETHOD(device_detach, dwc_otg_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; driver_t dwc_otg_driver = { .name = "dwcotg", .methods = dwc_otg_methods, .size = sizeof(struct dwc_otg_fdt_softc), }; DRIVER_MODULE(dwcotg, simplebus, dwc_otg_driver, 0, 0); MODULE_DEPEND(dwcotg, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/ehci_fsl.c b/sys/dev/usb/controller/ehci_fsl.c index 49d23996b696..ed3d4c64f4f8 100644 --- a/sys/dev/usb/controller/ehci_fsl.c +++ b/sys/dev/usb/controller/ehci_fsl.c @@ -1,423 +1,419 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2010-2012 Semihalf * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_platform.h" /* * Register the driver */ /* Forward declarations */ static int fsl_ehci_attach(device_t self); static int fsl_ehci_detach(device_t self); static int fsl_ehci_probe(device_t self); static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fsl_ehci_probe), DEVMETHOD(device_attach, fsl_ehci_attach), DEVMETHOD(device_detach, fsl_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), { 0, 0 } }; /* kobj_class definition */ static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(struct ehci_softc) }; DRIVER_MODULE(ehci, simplebus, ehci_driver, 0, 0); MODULE_DEPEND(ehci, usb, 1, 1, 1); /* * Private defines */ #define FSL_EHCI_REG_OFF 0x100 #define FSL_EHCI_REG_SIZE 0x300 /* * Internal interface registers' offsets. * Offsets from 0x000 ehci dev space, big-endian access. */ enum internal_reg { SNOOP1 = 0x400, SNOOP2 = 0x404, AGE_CNT_THRESH = 0x408, SI_CTRL = 0x410, CONTROL = 0x500 }; /* CONTROL register bit flags */ enum control_flags { USB_EN = 0x00000004, UTMI_PHY_EN = 0x00000200, ULPI_INT_EN = 0x00000001 }; /* SI_CTRL register bit flags */ enum si_ctrl_flags { FETCH_32 = 1, FETCH_64 = 0 }; #define SNOOP_RANGE_2GB 0x1E /* * Operational registers' offsets. * Offsets from USBCMD register, little-endian access. */ enum special_op_reg { USBMODE = 0x0A8, PORTSC = 0x084, ULPI_VIEWPORT = 0x70 }; /* USBMODE register bit flags */ enum usbmode_flags { HOST_MODE = 0x3, DEVICE_MODE = 0x2 }; #define PORT_POWER_MASK 0x00001000 /* * Private methods */ static void set_to_host_mode(ehci_softc_t *sc) { int tmp; tmp = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, USBMODE); bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, USBMODE, tmp | HOST_MODE); } static void enable_usb(device_t dev, bus_space_tag_t iot, bus_space_handle_t ioh) { int tmp; phandle_t node; char *phy_type; phy_type = NULL; tmp = bus_space_read_4(iot, ioh, CONTROL) | USB_EN; node = ofw_bus_get_node(dev); if ((node != 0) && (OF_getprop_alloc(node, "phy_type", (void **)&phy_type) > 0)) { if (strncasecmp(phy_type, "utmi", strlen("utmi")) == 0) tmp |= UTMI_PHY_EN; OF_prop_free(phy_type); } bus_space_write_4(iot, ioh, CONTROL, tmp); } static void set_32b_prefetch(bus_space_tag_t iot, bus_space_handle_t ioh) { bus_space_write_4(iot, ioh, SI_CTRL, FETCH_32); } static void set_snooping(bus_space_tag_t iot, bus_space_handle_t ioh) { bus_space_write_4(iot, ioh, SNOOP1, SNOOP_RANGE_2GB); bus_space_write_4(iot, ioh, SNOOP2, 0x80000000 | SNOOP_RANGE_2GB); } static void clear_port_power(ehci_softc_t *sc) { int tmp; tmp = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, PORTSC); bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, PORTSC, tmp & ~PORT_POWER_MASK); } /* * Public methods */ static int fsl_ehci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (((ofw_bus_is_compatible(dev, "fsl-usb2-dr")) == 0) && ((ofw_bus_is_compatible(dev, "fsl-usb2-mph")) == 0)) return (ENXIO); device_set_desc(dev, "Freescale integrated EHCI controller"); return (BUS_PROBE_DEFAULT); } static int fsl_ehci_attach(device_t self) { ehci_softc_t *sc; int rid; int err; bus_space_handle_t ioh; bus_space_tag_t iot; sc = device_get_softc(self); rid = 0; sc->sc_bus.parent = self; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) return (ENOMEM); /* Allocate io resource for EHCI */ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_io_res == NULL) { err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } iot = rman_get_bustag(sc->sc_io_res); /* * Set handle to USB related registers subregion used by generic * EHCI driver */ ioh = rman_get_bushandle(sc->sc_io_res); err = bus_space_subregion(iot, ioh, FSL_EHCI_REG_OFF, FSL_EHCI_REG_SIZE, &sc->sc_io_hdl); if (err != 0) { err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } /* Set little-endian tag for use by the generic EHCI driver */ sc->sc_io_tag = &bs_le_tag; /* Allocate irq */ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->sc_irq_res == NULL) { err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } /* Setup interrupt handler */ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } /* Add USB device */ sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENOMEM); } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); sc->sc_id_vendor = 0x1234; strlcpy(sc->sc_vendor, "Freescale", sizeof(sc->sc_vendor)); /* Enable USB */ err = ehci_reset(sc); if (err) { device_printf(self, "Could not reset the controller\n"); err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (ENXIO); } enable_usb(self, iot, ioh); set_snooping(iot, ioh); set_to_host_mode(sc); set_32b_prefetch(iot, ioh); /* * If usb subsystem is enabled in U-Boot, port power has to be turned * off to allow proper discovery of devices during boot up. */ clear_port_power(sc); /* Set flags */ sc->sc_flags |= EHCI_SCFLG_DONTRESET | EHCI_SCFLG_NORESTERM; err = ehci_init(sc); if (!err) { sc->sc_flags |= EHCI_SCFLG_DONEINIT; err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(self, "USB init failed err=%d\n", err); err = fsl_ehci_detach(self); if (err) { device_printf(self, "Detach of the driver failed with error %d\n", err); } return (EIO); } return (0); } static int fsl_ehci_detach(device_t self) { - int err; ehci_softc_t *sc; + /* During module unload there are lots of children leftover */ + err = bus_generic_detach(self); + if (err != 0) + return (err); + sc = device_get_softc(self); /* * only call ehci_detach() after ehci_init() */ if (sc->sc_flags & EHCI_SCFLG_DONEINIT) { ehci_detach(sc); sc->sc_flags &= ~EHCI_SCFLG_DONEINIT; } /* Disable interrupts that might have been switched on in ehci_init */ if (sc->sc_io_tag && sc->sc_io_hdl) bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, EHCI_USBINTR, 0); if (sc->sc_irq_res && sc->sc_intr_hdl) { err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) { device_printf(self, "Could not tear down irq, %d\n", err); return (err); } sc->sc_intr_hdl = NULL; } - if (sc->sc_bus.bdev) { - device_delete_child(self, sc->sc_bus.bdev); - sc->sc_bus.bdev = NULL; - } - - /* During module unload there are lots of children leftover */ - device_delete_children(self); - if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, 0, sc->sc_io_res); sc->sc_io_res = NULL; sc->sc_io_tag = 0; sc->sc_io_hdl = 0; } return (0); } diff --git a/sys/dev/usb/controller/ehci_imx.c b/sys/dev/usb/controller/ehci_imx.c index 0ab9bf0ed6f2..caba6b7da85a 100644 --- a/sys/dev/usb/controller/ehci_imx.c +++ b/sys/dev/usb/controller/ehci_imx.c @@ -1,507 +1,507 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2010-2012 Semihalf * Copyright (c) 2012 The FreeBSD Foundation * Copyright (c) 2013 Ian Lepore * All rights reserved. * * Portions of this software were developed by Oleksandr Rybalko * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include /* * EHCI driver for Freescale i.MX SoCs which incorporate the USBOH3 controller. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include "opt_platform.h" /* * Notes on the hardware and related FDT data seen in the wild. * * There are two sets of registers in the USBOH3 implementation; documentation * refers to them as "core" and "non-core" registers. A set of core register * exists for each OTG or EHCI device. There is a single set of non-core * registers per USBOH3, and they control aspects of operation not directly * related to the USB specs, such as whether interrupts from each of the core * devices are able to generate a SoC wakeup event. * * In the FreeBSD universe we might be inclined to describe the core and * non-core registers by using a pair of resource address/size values (two * entries in the reg property for each core). However, we have to work with * existing FDT data (which mostly comes from the linux universe), and the way * they've chosen to represent this is with an entry for a "usbmisc" device * whose reg property describes the non-core registers. The way we handle FDT * data, this means that the resources (memory-mapped register range) for the * non-core registers belongs to a device other than the echi devices. * * Because the main ehci device cannot access registers in a range that's * defined in the fdt data as belonging to another device, we implement a teeny * little "usbmisc" driver which exists only to provide access to the usbmisc * control register for each of the 4 usb controller instances. That little * driver is implemented here in this file, before the main driver. * * In addition to the single usbmisc device, the existing FDT data defines a * separate device for each of the OTG or EHCI cores within the USBOH3. Each of * those devices has a set of core registers described by the reg property. * * The core registers for each of the four cores in the USBOH3 are divided into * two parts: a set of imx-specific registers at an offset of 0 from the * beginning of the register range, and the standard USB (EHCI or OTG) registers * at an offset of 0x100 from the beginning of the register range. The FreeBSD * way of dealing with this might be to map out two ranges in the reg property, * but that's not what the alternate universe has done. To work with existing * FDT data, we acquire the resource that maps all the core registers, then use * bus_space_subregion() to create another resource that maps just the standard * USB registers, which we provide to the standard USB code in the ehci_softc. * * The following compat strings have been seen for the OTG and EHCI cores. The * FDT compat table in this driver contains all these strings, but as of this * writing, not all of these SoCs have been tested with the driver. The fact * that imx27 is common to all of them gives some hope that the driver will work * on all these SoCs. * - "fsl,imx23-usb", "fsl,imx27-usb"; * - "fsl,imx25-usb", "fsl,imx27-usb"; * - "fsl,imx28-usb", "fsl,imx27-usb"; * - "fsl,imx51-usb", "fsl,imx27-usb"; * - "fsl,imx53-usb", "fsl,imx27-usb"; * - "fsl,imx6q-usb", "fsl,imx27-usb"; * * The FDT data for some SoCs contains the following properties, which we don't * currently do anything with: * - fsl,usbmisc = <&usbmisc 0>; * - fsl,usbphy = <&usbphy0>; * * Some imx SoCs have FDT data related to USB PHY, some don't. We have separate * usbphy drivers where needed; this data is mentioned here just to keep all the * imx-FDT-usb-related info in one place. Here are the usbphy compat strings * known to exist: * - "nop-usbphy" * - "usb-nop-xceiv"; * - "fsl,imx23-usbphy" * - "fsl,imx28-usbphy", "fsl,imx23-usbphy"; * - "fsl,imx6q-usbphy", "fsl,imx23-usbphy"; * */ /*----------------------------------------------------------------------------- * imx_usbmisc driver *---------------------------------------------------------------------------*/ #define USBNC_OVER_CUR_POL (1u << 8) #define USBNC_OVER_CUR_DIS (1u << 7) struct imx_usbmisc_softc { device_t dev; struct resource *mmio; }; static struct ofw_compat_data usbmisc_compat_data[] = { {"fsl,imx6q-usbmisc", true}, {"fsl,imx51-usbmisc", true}, {"fsl,imx25-usbmisc", true}, {NULL, false}, }; static void imx_usbmisc_set_ctrl(device_t dev, u_int index, uint32_t bits) { struct imx_usbmisc_softc *sc; uint32_t reg; sc = device_get_softc(dev); reg = bus_read_4(sc->mmio, index * sizeof(uint32_t)); bus_write_4(sc->mmio, index * sizeof(uint32_t), reg | bits); } #ifdef notyet static void imx_usbmisc_clr_ctrl(device_t dev, u_int index, uint32_t bits) { struct imx_usbmisc_softc *sc; uint32_t reg; sc = device_get_softc(dev); reg = bus_read_4(sc->mmio, index * sizeof(uint32_t)); bus_write_4(sc->mmio, index * sizeof(uint32_t), reg & ~bits); } #endif static int imx_usbmisc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, usbmisc_compat_data)->ocd_data) { device_set_desc(dev, "i.MX USB Misc Control"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int imx_usbmisc_detach(device_t dev) { struct imx_usbmisc_softc *sc; sc = device_get_softc(dev); if (sc->mmio != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mmio); return (0); } static int imx_usbmisc_attach(device_t dev) { struct imx_usbmisc_softc *sc; int rid; sc = device_get_softc(dev); /* Allocate bus_space resources. */ rid = 0; sc->mmio = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mmio == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); return (ENXIO); } OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev); return (0); } static device_method_t imx_usbmisc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, imx_usbmisc_probe), DEVMETHOD(device_attach, imx_usbmisc_attach), DEVMETHOD(device_detach, imx_usbmisc_detach), DEVMETHOD_END }; static driver_t imx_usbmisc_driver = { "imx_usbmisc", imx_usbmisc_methods, sizeof(struct imx_usbmisc_softc) }; /* * This driver needs to start before the ehci driver, but later than the usual * "special" drivers like clocks and cpu. Ehci starts at DEFAULT so * DEFAULT-1000 seems good. */ EARLY_DRIVER_MODULE(imx_usbmisc, simplebus, imx_usbmisc_driver, 0, 0, BUS_PASS_DEFAULT - 1000); /*----------------------------------------------------------------------------- * imx_ehci driver... *---------------------------------------------------------------------------*/ /* * Each EHCI device in the SoC has some SoC-specific per-device registers at an * offset of 0, then the standard EHCI registers begin at an offset of 0x100. */ #define IMX_EHCI_REG_OFF 0x100 #define IMX_EHCI_REG_SIZE 0x100 struct imx_ehci_softc { ehci_softc_t ehci_softc; device_t dev; struct resource *ehci_mem_res; /* EHCI core regs. */ struct resource *ehci_irq_res; /* EHCI core IRQ. */ }; static struct ofw_compat_data compat_data[] = { {"fsl,imx6q-usb", 1}, {"fsl,imx53-usb", 1}, {"fsl,imx51-usb", 1}, {"fsl,imx28-usb", 1}, {"fsl,imx27-usb", 1}, {"fsl,imx25-usb", 1}, {"fsl,imx23-usb", 1}, {NULL, 0}, }; static void imx_ehci_post_reset(struct ehci_softc *ehci_softc) { uint32_t usbmode; /* Force HOST mode */ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM); usbmode &= ~EHCI_UM_CM; usbmode |= EHCI_UM_CM_HOST; EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode); } static int imx_ehci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) { device_set_desc(dev, "Freescale i.MX integrated USB controller"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int imx_ehci_detach(device_t dev) { struct imx_ehci_softc *sc; ehci_softc_t *esc; int err; sc = device_get_softc(dev); esc = &sc->ehci_softc; /* First detach all children; we can't detach if that fails. */ - if ((err = device_delete_children(dev)) != 0) + if ((err = bus_generic_detach(dev)) != 0) return (err); if (esc->sc_flags & EHCI_SCFLG_DONEINIT) ehci_detach(esc); if (esc->sc_intr_hdl != NULL) bus_teardown_intr(dev, esc->sc_irq_res, esc->sc_intr_hdl); if (sc->ehci_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->ehci_irq_res); if (sc->ehci_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->ehci_mem_res); usb_bus_mem_free_all(&esc->sc_bus, &ehci_iterate_hw_softc); return (0); } static void imx_ehci_disable_oc(struct imx_ehci_softc *sc) { device_t usbmdev; pcell_t usbmprops[2]; phandle_t node; ssize_t size; int index; /* Get the reference to the usbmisc driver from the fdt data */ node = ofw_bus_get_node(sc->dev); size = OF_getencprop(node, "fsl,usbmisc", usbmprops, sizeof(usbmprops)); if (size < sizeof(usbmprops)) { device_printf(sc->dev, "failed to retrieve fsl,usbmisc " "property, cannot disable overcurrent protection"); return; } /* Retrieve the device_t via the xref handle. */ usbmdev = OF_device_from_xref(usbmprops[0]); if (usbmdev == NULL) { device_printf(sc->dev, "usbmisc device not found, " "cannot disable overcurrent protection"); return; } /* Call the device routine to set the overcurrent disable bit. */ index = usbmprops[1]; imx_usbmisc_set_ctrl(usbmdev, index, USBNC_OVER_CUR_DIS); } static int imx_ehci_attach(device_t dev) { struct imx_ehci_softc *sc; ehci_softc_t *esc; int err, rid; sc = device_get_softc(dev); sc->dev = dev; esc = &sc->ehci_softc; err = 0; /* Allocate bus_space resources. */ rid = 0; sc->ehci_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->ehci_mem_res == NULL) { device_printf(dev, "Cannot allocate memory resources\n"); err = ENXIO; goto out; } rid = 0; sc->ehci_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->ehci_irq_res == NULL) { device_printf(dev, "Cannot allocate IRQ resources\n"); err = ENXIO; goto out; } esc->sc_io_tag = rman_get_bustag(sc->ehci_mem_res); esc->sc_bus.parent = dev; esc->sc_bus.devices = esc->sc_devices; esc->sc_bus.devices_max = EHCI_MAX_DEVICES; esc->sc_bus.dma_bits = 32; /* allocate all DMA memory */ if (usb_bus_mem_alloc_all(&esc->sc_bus, USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc) != 0) { device_printf(dev, "usb_bus_mem_alloc_all() failed\n"); err = ENOMEM; goto out; } /* * Set handle to USB related registers subregion used by * generic EHCI driver. */ err = bus_space_subregion(esc->sc_io_tag, rman_get_bushandle(sc->ehci_mem_res), IMX_EHCI_REG_OFF, IMX_EHCI_REG_SIZE, &esc->sc_io_hdl); if (err != 0) { device_printf(dev, "bus_space_subregion() failed\n"); err = ENXIO; goto out; } /* Setup interrupt handler. */ err = bus_setup_intr(dev, sc->ehci_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, esc, &esc->sc_intr_hdl); if (err != 0) { device_printf(dev, "Could not setup IRQ\n"); goto out; } /* Turn on clocks. */ imx_ccm_usb_enable(dev); /* Disable overcurrent detection, if configured to do so. */ if (OF_hasprop(ofw_bus_get_node(sc->dev), "disable-over-current")) imx_ehci_disable_oc(sc); /* Add USB bus device. */ esc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (esc->sc_bus.bdev == NULL) { device_printf(dev, "Could not add USB device\n"); goto out; } device_set_ivars(esc->sc_bus.bdev, &esc->sc_bus); esc->sc_id_vendor = USB_VENDOR_FREESCALE; strlcpy(esc->sc_vendor, "Freescale", sizeof(esc->sc_vendor)); /* * Set flags that affect ehci_init() behavior, and hook our post-reset * code into the standard controller code. */ esc->sc_flags |= EHCI_SCFLG_NORESTERM | EHCI_SCFLG_TT; esc->sc_vendor_post_reset = imx_ehci_post_reset; esc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc; err = ehci_init(esc); if (err != 0) { device_printf(dev, "USB init failed, usb_err_t=%d\n", err); goto out; } esc->sc_flags |= EHCI_SCFLG_DONEINIT; /* Probe the bus. */ err = device_probe_and_attach(esc->sc_bus.bdev); if (err != 0) { device_printf(dev, "device_probe_and_attach() failed\n"); goto out; } err = 0; out: if (err != 0) imx_ehci_detach(dev); return (err); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, imx_ehci_probe), DEVMETHOD(device_attach, imx_ehci_attach), DEVMETHOD(device_detach, imx_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD_END }; static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(struct imx_ehci_softc) }; DRIVER_MODULE(imx_ehci, simplebus, ehci_driver, 0, 0); MODULE_DEPEND(imx_ehci, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/ehci_msm.c b/sys/dev/usb/controller/ehci_msm.c index c628d5bf8fde..7f06ad6a1600 100644 --- a/sys/dev/usb/controller/ehci_msm.c +++ b/sys/dev/usb/controller/ehci_msm.c @@ -1,226 +1,222 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018 Ruslan Bukin * All rights reserved. * * This software was developed by BAE Systems, the University of Cambridge * Computer Laboratory, and Memorial University under DARPA/AFRL contract * FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent Computing * (TC) research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct ehci_msm_softc { ehci_softc_t base; struct resource *res[3]; }; static struct resource_spec ehci_msm_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_MEMORY, 1, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; #define EHCI_HC_DEVSTR "Qualcomm USB 2.0 controller" static device_attach_t ehci_msm_attach; static device_detach_t ehci_msm_detach; static int ehci_msm_probe(device_t dev) { if (!ofw_bus_is_compatible(dev, "qcom,ci-hdrc")) return (ENXIO); device_set_desc(dev, EHCI_HC_DEVSTR); return (BUS_PROBE_DEFAULT); } static int ehci_msm_attach(device_t dev) { struct ehci_msm_softc *esc; bus_space_handle_t bsh; ehci_softc_t *sc; int err; esc = device_get_softc(dev); sc = &esc->base; sc->sc_bus.parent = dev; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; if (bus_alloc_resources(dev, ehci_msm_spec, esc->res)) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } sc->sc_io_tag = rman_get_bustag(esc->res[0]); /* Get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc)) { return (ENOMEM); } /* EHCI registers */ sc->sc_io_tag = rman_get_bustag(esc->res[0]); bsh = rman_get_bushandle(esc->res[0]); sc->sc_io_size = rman_get_size(esc->res[0]); if (bus_space_subregion(sc->sc_io_tag, bsh, 0x100, sc->sc_io_size, &sc->sc_io_hdl) != 0) panic("%s: unable to subregion USB host registers", device_get_name(dev)); sc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(dev, "Could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR); sprintf(sc->sc_vendor, "Qualcomm"); err = bus_setup_intr(dev, esc->res[2], INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(dev, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; goto error; } sc->sc_flags |= EHCI_SCFLG_DONTRESET | EHCI_SCFLG_NORESTERM; err = ehci_init(sc); if (!err) { sc->sc_flags |= EHCI_SCFLG_DONEINIT; err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(dev, "USB init failed err=%d\n", err); goto error; } return (0); error: ehci_msm_detach(dev); return (ENXIO); } static int ehci_msm_detach(device_t dev) { ehci_softc_t *sc; device_t bdev; int err; sc = device_get_softc(dev); - if (sc->sc_bus.bdev) { - bdev = sc->sc_bus.bdev; - device_detach(bdev); - device_delete_child(dev, bdev); - } - - device_delete_children(dev); + err = bus_generic_detach(dev); + if (err != 0) + return (err); if (sc->sc_irq_res && sc->sc_intr_hdl) { /* only call ehci_detach() after ehci_init() */ ehci_detach(sc); err = bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_hdl); if (err) device_printf(dev, "Could not tear down irq, %d\n", err); sc->sc_intr_hdl = NULL; } if (sc->sc_irq_res) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_io_res); sc->sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); return (0); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ehci_msm_probe), DEVMETHOD(device_attach, ehci_msm_attach), DEVMETHOD(device_detach, ehci_msm_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t ehci_driver = { .name = "ehci", .methods = ehci_methods, .size = sizeof(ehci_softc_t), }; DRIVER_MODULE(ehci_msm, simplebus, ehci_driver, 0, 0); MODULE_DEPEND(ehci_msm, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/ehci_mv.c b/sys/dev/usb/controller/ehci_mv.c index 5535cb061949..817cd68257c8 100644 --- a/sys/dev/usb/controller/ehci_mv.c +++ b/sys/dev/usb/controller/ehci_mv.c @@ -1,383 +1,385 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. * All rights reserved. * * Developed by Semihalf. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of MARVELL nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * FDT attachment driver for the USB Enhanced Host Controller. */ #include #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(__aarch64__) #include #endif #include #define EHCI_VENDORID_MRVL 0x1286 #define EHCI_HC_DEVSTR "Marvell Integrated USB 2.0 controller" static device_attach_t mv_ehci_attach; static device_detach_t mv_ehci_detach; static int err_intr(void *arg); static struct resource *irq_err; static void *ih_err; /* EHCI HC regs start at this offset within USB range */ #define MV_USB_HOST_OFST 0x0100 #define USB_BRIDGE_INTR_CAUSE 0x210 #define USB_BRIDGE_INTR_MASK 0x214 #define USB_BRIDGE_ERR_ADDR 0x21C #define MV_USB_ADDR_DECODE_ERR (1 << 0) #define MV_USB_HOST_UNDERFLOW (1 << 1) #define MV_USB_HOST_OVERFLOW (1 << 2) #define MV_USB_DEVICE_UNDERFLOW (1 << 3) enum mv_ehci_hwtype { HWTYPE_NONE = 0, HWTYPE_MV_EHCI_V1, HWTYPE_MV_EHCI_V2, }; static struct ofw_compat_data compat_data[] = { {"mrvl,usb-ehci", HWTYPE_MV_EHCI_V1}, {"marvell,orion-ehci", HWTYPE_MV_EHCI_V2}, {"marvell,armada-3700-ehci", HWTYPE_MV_EHCI_V2}, {NULL, HWTYPE_NONE} }; static void mv_ehci_post_reset(struct ehci_softc *ehci_softc) { uint32_t usbmode; /* Force HOST mode */ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM); usbmode &= ~EHCI_UM_CM; usbmode |= EHCI_UM_CM_HOST; EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode); } static int mv_ehci_probe(device_t self) { if (!ofw_bus_status_okay(self)) return (ENXIO); if (!ofw_bus_search_compatible(self, compat_data)->ocd_data) return (ENXIO); device_set_desc(self, EHCI_HC_DEVSTR); return (BUS_PROBE_DEFAULT); } static int mv_ehci_attach(device_t self) { ehci_softc_t *sc = device_get_softc(self); enum mv_ehci_hwtype hwtype; bus_space_handle_t bsh; int err; int rid; /* initialise some bus fields */ sc->sc_bus.parent = self; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; hwtype = ofw_bus_search_compatible(self, compat_data)->ocd_data; if (hwtype == HWTYPE_NONE) { device_printf(self, "Wrong HW type flag detected\n"); return (ENXIO); } /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { return (ENOMEM); } rid = 0; sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_io_res) { device_printf(self, "Could not map memory\n"); goto error; } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); bsh = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res) - MV_USB_HOST_OFST; /* * Marvell EHCI host controller registers start at certain offset * within the whole USB registers range, so create a subregion for the * host mode configuration purposes. */ if (bus_space_subregion(sc->sc_io_tag, bsh, MV_USB_HOST_OFST, sc->sc_io_size, &sc->sc_io_hdl) != 0) panic("%s: unable to subregion USB host registers", device_get_name(self)); rid = 0; if (hwtype == HWTYPE_MV_EHCI_V1) { irq_err = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (irq_err == NULL) { device_printf(self, "Could not allocate error irq\n"); mv_ehci_detach(self); return (ENXIO); } rid = 1; } /* * Notice: Marvell EHCI controller has TWO interrupt lines, so make * sure to use the correct rid for the main one (controller interrupt) * -- refer to DTS for the right resource number to use here. */ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(self, "Could not allocate irq\n"); goto error; } sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR); sprintf(sc->sc_vendor, "Marvell"); if (hwtype == HWTYPE_MV_EHCI_V1) { err = bus_setup_intr(self, irq_err, INTR_TYPE_BIO, err_intr, NULL, sc, &ih_err); if (err) { device_printf(self, "Could not setup error irq, %d\n", err); ih_err = NULL; goto error; } } EWRITE4(sc, USB_BRIDGE_INTR_MASK, MV_USB_ADDR_DECODE_ERR | MV_USB_HOST_UNDERFLOW | MV_USB_HOST_OVERFLOW | MV_USB_DEVICE_UNDERFLOW); err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; goto error; } /* * Workaround for Marvell integrated EHCI controller: reset of * the EHCI core clears the USBMODE register, which sets the core in * an undefined state (neither host nor agent), so it needs to be set * again for proper operation. * * Refer to errata document MV-S500832-00D.pdf (p. 5.24 GL USB-2) for * details. */ sc->sc_vendor_post_reset = mv_ehci_post_reset; if (bootverbose) device_printf(self, "5.24 GL USB-2 workaround enabled\n"); /* XXX all MV chips need it? */ sc->sc_flags |= EHCI_SCFLG_TT | EHCI_SCFLG_NORESTERM; sc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc; err = ehci_init(sc); if (!err) { err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(self, "USB init failed err=%d\n", err); goto error; } return (0); error: mv_ehci_detach(self); return (ENXIO); } static int mv_ehci_detach(device_t self) { ehci_softc_t *sc = device_get_softc(self); int err; /* during module unload there are lots of children leftover */ - device_delete_children(self); + err = bus_generic_detach(self); + if (err != 0) + return (err); /* * disable interrupts that might have been switched on in mv_ehci_attach */ if (sc->sc_io_res) { EWRITE4(sc, USB_BRIDGE_INTR_MASK, 0); } if (sc->sc_irq_res && sc->sc_intr_hdl) { /* * only call ehci_detach() after ehci_init() */ ehci_detach(sc); err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) /* XXX or should we panic? */ device_printf(self, "Could not tear down irq, %d\n", err); sc->sc_intr_hdl = NULL; } if (irq_err && ih_err) { err = bus_teardown_intr(self, irq_err, ih_err); if (err) device_printf(self, "Could not tear down irq, %d\n", err); ih_err = NULL; } if (irq_err) { bus_release_resource(self, SYS_RES_IRQ, 0, irq_err); irq_err = NULL; } if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, 1, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, 0, sc->sc_io_res); sc->sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); return (0); } static int err_intr(void *arg) { ehci_softc_t *sc = arg; unsigned cause; cause = EREAD4(sc, USB_BRIDGE_INTR_CAUSE); if (cause) { printf("USB error: "); if (cause & MV_USB_ADDR_DECODE_ERR) { uint32_t addr; addr = EREAD4(sc, USB_BRIDGE_ERR_ADDR); printf("address decoding error (addr=%#x)\n", addr); } if (cause & MV_USB_HOST_UNDERFLOW) printf("host underflow\n"); if (cause & MV_USB_HOST_OVERFLOW) printf("host overflow\n"); if (cause & MV_USB_DEVICE_UNDERFLOW) printf("device underflow\n"); if (cause & ~(MV_USB_ADDR_DECODE_ERR | MV_USB_HOST_UNDERFLOW | MV_USB_HOST_OVERFLOW | MV_USB_DEVICE_UNDERFLOW)) printf("unknown cause (cause=%#x)\n", cause); EWRITE4(sc, USB_BRIDGE_INTR_CAUSE, 0); } return (FILTER_HANDLED); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, mv_ehci_probe), DEVMETHOD(device_attach, mv_ehci_attach), DEVMETHOD(device_detach, mv_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t ehci_driver = { "ehci", ehci_methods, sizeof(ehci_softc_t), }; DRIVER_MODULE(ehci_mv, simplebus, ehci_driver, 0, 0); MODULE_DEPEND(ehci_mv, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/ehci_pci.c b/sys/dev/usb/controller/ehci_pci.c index 5141548bf793..bc75669a8d20 100644 --- a/sys/dev/usb/controller/ehci_pci.c +++ b/sys/dev/usb/controller/ehci_pci.c @@ -1,608 +1,611 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (augustss@carlstedt.se) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include /* * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. * * The EHCI 1.0 spec can be found at * http://developer.intel.com/technology/usb/download/ehci-r10.pdf * and the USB 2.0 spec at * http://www.usb.org/developers/docs/usb_20.zip */ /* The low level controller code for EHCI has been split into * PCI probes and EHCI specific code. This was done to facilitate the * sharing of code between *BSD's */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #define PCI_EHCI_VENDORID_ACERLABS 0x10b9 #define PCI_EHCI_VENDORID_AMD 0x1022 #define PCI_EHCI_VENDORID_APPLE 0x106b #define PCI_EHCI_VENDORID_ATI 0x1002 #define PCI_EHCI_VENDORID_CMDTECH 0x1095 #define PCI_EHCI_VENDORID_HYGON 0x1d94 #define PCI_EHCI_VENDORID_INTEL 0x8086 #define PCI_EHCI_VENDORID_NEC 0x1033 #define PCI_EHCI_VENDORID_OPTI 0x1045 #define PCI_EHCI_VENDORID_PHILIPS 0x1131 #define PCI_EHCI_VENDORID_SIS 0x1039 #define PCI_EHCI_VENDORID_NVIDIA 0x12D2 #define PCI_EHCI_VENDORID_NVIDIA2 0x10DE #define PCI_EHCI_VENDORID_VIA 0x1106 #define PCI_EHCI_VENDORID_VMWARE 0x15ad #define PCI_EHCI_VENDORID_ZHAOXIN 0x1d17 static device_probe_t ehci_pci_probe; static device_attach_t ehci_pci_attach; static device_detach_t ehci_pci_detach; static usb_take_controller_t ehci_pci_take_controller; static const char * ehci_pci_match(device_t self) { uint32_t device_id = pci_get_devid(self); switch (device_id) { case 0x523910b9: return "ALi M5239 USB 2.0 controller"; case 0x10227463: return "AMD 8111 USB 2.0 controller"; case 0x20951022: return ("AMD CS5536 (Geode) USB 2.0 controller"); case 0x78081022: return ("AMD FCH USB 2.0 controller"); case 0x79081022: return ("AMD FCH USB 2.0 controller"); case 0x43451002: return "ATI SB200 USB 2.0 controller"; case 0x43731002: return "ATI SB400 USB 2.0 controller"; case 0x43961002: return ("AMD SB7x0/SB8x0/SB9x0 USB 2.0 controller"); case 0x0f348086: return ("Intel BayTrail USB 2.0 controller"); case 0x1c268086: return ("Intel Cougar Point USB 2.0 controller"); case 0x1c2d8086: return ("Intel Cougar Point USB 2.0 controller"); case 0x1d268086: return ("Intel Patsburg USB 2.0 controller"); case 0x1d2d8086: return ("Intel Patsburg USB 2.0 controller"); case 0x1e268086: return ("Intel Panther Point USB 2.0 controller"); case 0x1e2d8086: return ("Intel Panther Point USB 2.0 controller"); case 0x1f2c8086: return ("Intel Avoton USB 2.0 controller"); case 0x25ad8086: return "Intel 6300ESB USB 2.0 controller"; case 0x24cd8086: return "Intel 82801DB/L/M (ICH4) USB 2.0 controller"; case 0x24dd8086: return "Intel 82801EB/R (ICH5) USB 2.0 controller"; case 0x265c8086: return "Intel 82801FB (ICH6) USB 2.0 controller"; case 0x268c8086: return ("Intel 63XXESB USB 2.0 controller"); case 0x27cc8086: return "Intel 82801GB/R (ICH7) USB 2.0 controller"; case 0x28368086: return "Intel 82801H (ICH8) USB 2.0 controller USB2-A"; case 0x283a8086: return "Intel 82801H (ICH8) USB 2.0 controller USB2-B"; case 0x293a8086: return "Intel 82801I (ICH9) USB 2.0 controller"; case 0x293c8086: return "Intel 82801I (ICH9) USB 2.0 controller"; case 0x3a3a8086: return "Intel 82801JI (ICH10) USB 2.0 controller USB-A"; case 0x3a3c8086: return "Intel 82801JI (ICH10) USB 2.0 controller USB-B"; case 0x3a6c8086: return "Intel 82801JD (ICH10) USB 2.0 controller USB-A"; case 0x3a6a8086: return "Intel 82801JD (ICH10) USB 2.0 controller USB-B"; case 0x3b348086: return ("Intel PCH USB 2.0 controller USB-A"); case 0x3b3c8086: return ("Intel PCH USB 2.0 controller USB-B"); case 0x8c268086: return ("Intel Lynx Point USB 2.0 controller USB-A"); case 0x8c2d8086: return ("Intel Lynx Point USB 2.0 controller USB-B"); case 0x8ca68086: return ("Intel Wildcat Point USB 2.0 controller USB-A"); case 0x8cad8086: return ("Intel Wildcat Point USB 2.0 controller USB-B"); case 0x8d268086: return ("Intel Wellsburg USB 2.0 controller"); case 0x8d2d8086: return ("Intel Wellsburg USB 2.0 controller"); case 0x9c268086: return ("Intel Lynx Point-LP USB 2.0 controller"); case 0x9ca68086: return ("Intel Wildcat Point-LP USB 2.0 controller"); case 0x00e01033: return ("NEC uPD 72010x USB 2.0 controller"); case 0x006810de: return "NVIDIA nForce2 USB 2.0 controller"; case 0x008810de: return "NVIDIA nForce2 Ultra 400 USB 2.0 controller"; case 0x00d810de: return "NVIDIA nForce3 USB 2.0 controller"; case 0x00e810de: return "NVIDIA nForce3 250 USB 2.0 controller"; case 0x005b10de: return "NVIDIA nForce CK804 USB 2.0 controller"; case 0x036d10de: return "NVIDIA nForce MCP55 USB 2.0 controller"; case 0x03f210de: return "NVIDIA nForce MCP61 USB 2.0 controller"; case 0x0aa610de: return "NVIDIA nForce MCP79 USB 2.0 controller"; case 0x0aa910de: return "NVIDIA nForce MCP79 USB 2.0 controller"; case 0x0aaa10de: return "NVIDIA nForce MCP79 USB 2.0 controller"; case 0x15621131: return "Philips ISP156x USB 2.0 controller"; case 0x70021039: return "SiS 968 USB 2.0 controller"; case 0x31041106: return ("VIA VT6202 USB 2.0 controller"); case 0x077015ad: return ("VMware USB 2.0 controller"); case 0x31041d17: return ("Zhaoxin ZX-100/ZX-200/ZX-E USB 2.0 controller"); default: break; } if ((pci_get_class(self) == PCIC_SERIALBUS) && (pci_get_subclass(self) == PCIS_SERIALBUS_USB) && (pci_get_progif(self) == PCI_INTERFACE_EHCI)) { return ("EHCI (generic) USB 2.0 controller"); } return (NULL); /* dunno */ } static int ehci_pci_probe(device_t self) { const char *desc = ehci_pci_match(self); if (desc) { device_set_desc(self, desc); return (BUS_PROBE_DEFAULT); } else { return (ENXIO); } } static void ehci_pci_ati_quirk(device_t self, uint8_t is_sb700) { device_t smbdev; uint32_t val; if (is_sb700) { /* Lookup SMBUS PCI device */ smbdev = pci_find_device(PCI_EHCI_VENDORID_ATI, 0x4385); if (smbdev == NULL) return; val = pci_get_revid(smbdev); if (val != 0x3a && val != 0x3b) return; } /* * Note: this bit is described as reserved in SB700 * Register Reference Guide. */ val = pci_read_config(self, 0x53, 1); if (!(val & 0x8)) { val |= 0x8; pci_write_config(self, 0x53, val, 1); device_printf(self, "AMD SB600/700 quirk applied\n"); } } static void ehci_pci_via_quirk(device_t self) { uint32_t val; if ((pci_get_device(self) == 0x3104) && ((pci_get_revid(self) & 0xf0) == 0x60)) { /* Correct schedule sleep time to 10us */ val = pci_read_config(self, 0x4b, 1); if (val & 0x20) return; val |= 0x20; pci_write_config(self, 0x4b, val, 1); device_printf(self, "VIA-quirk applied\n"); } } static int ehci_pci_attach(device_t self) { ehci_softc_t *sc = device_get_softc(self); int err; int rid; /* initialise some bus fields */ sc->sc_bus.parent = self; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { return (ENOMEM); } pci_enable_busmaster(self); switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) { case PCI_USB_REV_PRE_1_0: case PCI_USB_REV_1_0: case PCI_USB_REV_1_1: /* * NOTE: some EHCI USB controllers have the wrong USB * revision number. It appears those controllers are * fully compliant so we just ignore this value in * some common cases. */ device_printf(self, "pre-2.0 USB revision (ignored)\n"); /* fallthrough */ case PCI_USB_REV_2_0: break; default: /* Quirk for Parallels Desktop 4.0 */ device_printf(self, "USB revision is unknown. Assuming v2.0.\n"); break; } rid = PCI_CBMEM; sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_io_res) { device_printf(self, "Could not map memory\n"); goto error; } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(self, "Could not allocate irq\n"); goto error; } sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); /* * ehci_pci_match will never return NULL if ehci_pci_probe * succeeded */ device_set_desc(sc->sc_bus.bdev, ehci_pci_match(self)); switch (pci_get_vendor(self)) { case PCI_EHCI_VENDORID_ACERLABS: sprintf(sc->sc_vendor, "AcerLabs"); break; case PCI_EHCI_VENDORID_AMD: sprintf(sc->sc_vendor, "AMD"); break; case PCI_EHCI_VENDORID_APPLE: sprintf(sc->sc_vendor, "Apple"); break; case PCI_EHCI_VENDORID_ATI: sprintf(sc->sc_vendor, "ATI"); break; case PCI_EHCI_VENDORID_CMDTECH: sprintf(sc->sc_vendor, "CMDTECH"); break; case PCI_EHCI_VENDORID_HYGON: sprintf(sc->sc_vendor, "Hygon"); break; case PCI_EHCI_VENDORID_INTEL: sprintf(sc->sc_vendor, "Intel"); break; case PCI_EHCI_VENDORID_NEC: sprintf(sc->sc_vendor, "NEC"); break; case PCI_EHCI_VENDORID_OPTI: sprintf(sc->sc_vendor, "OPTi"); break; case PCI_EHCI_VENDORID_PHILIPS: sprintf(sc->sc_vendor, "Philips"); break; case PCI_EHCI_VENDORID_SIS: sprintf(sc->sc_vendor, "SiS"); break; case PCI_EHCI_VENDORID_NVIDIA: case PCI_EHCI_VENDORID_NVIDIA2: sprintf(sc->sc_vendor, "nVidia"); break; case PCI_EHCI_VENDORID_VIA: sprintf(sc->sc_vendor, "VIA"); break; case PCI_EHCI_VENDORID_VMWARE: sprintf(sc->sc_vendor, "VMware"); break; case PCI_EHCI_VENDORID_ZHAOXIN: sprintf(sc->sc_vendor, "Zhaoxin"); break; default: if (bootverbose) device_printf(self, "(New EHCI DeviceId=0x%08x)\n", pci_get_devid(self)); sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); } err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; goto error; } ehci_pci_take_controller(self); /* Undocumented quirks taken from Linux */ switch (pci_get_vendor(self)) { case PCI_EHCI_VENDORID_ATI: /* SB600 and SB700 EHCI quirk */ switch (pci_get_device(self)) { case 0x4386: ehci_pci_ati_quirk(self, 0); break; case 0x4396: ehci_pci_ati_quirk(self, 1); break; default: break; } break; case PCI_EHCI_VENDORID_VIA: ehci_pci_via_quirk(self); break; default: break; } /* Dropped interrupts workaround */ switch (pci_get_vendor(self)) { case PCI_EHCI_VENDORID_ATI: case PCI_EHCI_VENDORID_VIA: sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG; if (bootverbose) device_printf(self, "Dropped interrupts workaround enabled\n"); break; default: break; } /* Doorbell feature workaround */ switch (pci_get_vendor(self)) { case PCI_EHCI_VENDORID_NVIDIA: case PCI_EHCI_VENDORID_NVIDIA2: sc->sc_flags |= EHCI_SCFLG_IAADBUG; if (bootverbose) device_printf(self, "Doorbell workaround enabled\n"); break; default: break; } err = ehci_init(sc); if (!err) { err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(self, "USB init failed err=%d\n", err); goto error; } return (0); error: ehci_pci_detach(self); return (ENXIO); } static int ehci_pci_detach(device_t self) { ehci_softc_t *sc = device_get_softc(self); + int error; /* during module unload there are lots of children leftover */ - device_delete_children(self); + error = bus_generic_detach(self); + if (error != 0) + return (error); pci_disable_busmaster(self); if (sc->sc_irq_res && sc->sc_intr_hdl) { /* * only call ehci_detach() after ehci_init() */ ehci_detach(sc); int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) /* XXX or should we panic? */ device_printf(self, "Could not tear down irq, %d\n", err); sc->sc_intr_hdl = NULL; } if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM, sc->sc_io_res); sc->sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); return (0); } static int ehci_pci_take_controller(device_t self) { ehci_softc_t *sc = device_get_softc(self); uint32_t cparams; uint32_t eec; uint16_t to; uint8_t eecp; uint8_t bios_sem; cparams = EREAD4(sc, EHCI_HCCPARAMS); /* Synchronise with the BIOS if it owns the controller. */ for (eecp = EHCI_HCC_EECP(cparams); eecp != 0; eecp = EHCI_EECP_NEXT(eec)) { eec = pci_read_config(self, eecp, 4); if (EHCI_EECP_ID(eec) != EHCI_EC_LEGSUP) { continue; } bios_sem = pci_read_config(self, eecp + EHCI_LEGSUP_BIOS_SEM, 1); if (bios_sem == 0) { continue; } device_printf(sc->sc_bus.bdev, "waiting for BIOS " "to give up control\n"); pci_write_config(self, eecp + EHCI_LEGSUP_OS_SEM, 1, 1); to = 500; while (1) { bios_sem = pci_read_config(self, eecp + EHCI_LEGSUP_BIOS_SEM, 1); if (bios_sem == 0) break; if (--to == 0) { device_printf(sc->sc_bus.bdev, "timed out waiting for BIOS\n"); break; } usb_pause_mtx(NULL, hz / 100); /* wait 10ms */ } } return (0); } static device_method_t ehci_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ehci_pci_probe), DEVMETHOD(device_attach, ehci_pci_attach), DEVMETHOD(device_detach, ehci_pci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(usb_take_controller, ehci_pci_take_controller), DEVMETHOD_END }; static driver_t ehci_driver = { .name = "ehci", .methods = ehci_pci_methods, .size = sizeof(struct ehci_softc), }; DRIVER_MODULE(ehci, pci, ehci_driver, 0, 0); MODULE_DEPEND(ehci, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/generic_ehci.c b/sys/dev/usb/controller/generic_ehci.c index e660a338df64..35a9564631e0 100644 --- a/sys/dev/usb/controller/generic_ehci.c +++ b/sys/dev/usb/controller/generic_ehci.c @@ -1,188 +1,190 @@ /*- * Copyright (c) 2012 Ganbold Tsagaankhuu * Copyright (c) 2016 The FreeBSD Foundation * All rights reserved. * * This software was developed by Andrew Turner under * sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Generic EHCI driver based on the Allwinner A10 EHCI driver */ #include #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "generic_ehci.h" int generic_ehci_attach(device_t self) { ehci_softc_t *sc = device_get_softc(self); int err; int rid; /* initialise some bus fields */ sc->sc_bus.parent = self; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = EHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) { return (ENOMEM); } sc->sc_bus.usbrev = USB_REV_2_0; rid = 0; sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_io_res) { device_printf(self, "Could not map memory\n"); goto error; } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(self, "Could not allocate irq\n"); goto error; } sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); strlcpy(sc->sc_vendor, "Generic", sizeof(sc->sc_vendor)); err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; goto error; } sc->sc_flags |= EHCI_SCFLG_DONTRESET; err = ehci_init(sc); if (!err) err = device_probe_and_attach(sc->sc_bus.bdev); if (err) goto error; return (0); error: generic_ehci_detach(self); return (ENXIO); } int generic_ehci_detach(device_t self) { ehci_softc_t *sc = device_get_softc(self); int err; /* during module unload there are lots of children leftover */ - device_delete_children(self); + err = bus_generic_detach(self); + if (err != 0) + return (err); if (sc->sc_irq_res && sc->sc_intr_hdl) { /* * only call ehci_detach() after ehci_init() */ ehci_detach(sc); err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) /* XXX or should we panic? */ device_printf(self, "Could not tear down irq, %d\n", err); sc->sc_intr_hdl = NULL; } if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, 0, sc->sc_io_res); sc->sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc); return (0); } static device_method_t ehci_methods[] = { /* Device interface */ DEVMETHOD(device_attach, generic_ehci_attach), DEVMETHOD(device_detach, generic_ehci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; driver_t generic_ehci_driver = { .name = "ehci", .methods = ehci_methods, .size = sizeof(ehci_softc_t), }; diff --git a/sys/dev/usb/controller/generic_ohci.c b/sys/dev/usb/controller/generic_ohci.c index fe4493fda977..5c0de59074d2 100644 --- a/sys/dev/usb/controller/generic_ohci.c +++ b/sys/dev/usb/controller/generic_ohci.c @@ -1,328 +1,330 @@ /*- * Copyright (c) 2016 Emmanuel Vadot All rights reserved. * Copyright (c) 2006 M. Warner Losh * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Generic OHCI driver based on AT91 OHCI */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "generic_usb_if.h" struct clk_list { TAILQ_ENTRY(clk_list) next; clk_t clk; }; struct phy_list { TAILQ_ENTRY(phy_list) next; phy_t phy; }; struct hwrst_list { TAILQ_ENTRY(hwrst_list) next; hwreset_t rst; }; struct generic_ohci_softc { ohci_softc_t ohci_sc; TAILQ_HEAD(, clk_list) clk_list; TAILQ_HEAD(, phy_list) phy_list; TAILQ_HEAD(, hwrst_list) rst_list; }; static int generic_ohci_detach(device_t); static int generic_ohci_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "generic-ohci")) return (ENXIO); device_set_desc(dev, "Generic OHCI Controller"); return (BUS_PROBE_DEFAULT); } static int generic_ohci_attach(device_t dev) { struct generic_ohci_softc *sc = device_get_softc(dev); int err, rid; int off; struct clk_list *clkp; struct phy_list *phyp; struct hwrst_list *rstp; clk_t clk; phy_t phy; hwreset_t rst; sc->ohci_sc.sc_bus.parent = dev; sc->ohci_sc.sc_bus.devices = sc->ohci_sc.sc_devices; sc->ohci_sc.sc_bus.devices_max = OHCI_MAX_DEVICES; sc->ohci_sc.sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->ohci_sc.sc_bus, USB_GET_DMA_TAG(dev), &ohci_iterate_hw_softc)) { return (ENOMEM); } rid = 0; sc->ohci_sc.sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->ohci_sc.sc_io_res == 0) { err = ENOMEM; goto error; } sc->ohci_sc.sc_io_tag = rman_get_bustag(sc->ohci_sc.sc_io_res); sc->ohci_sc.sc_io_hdl = rman_get_bushandle(sc->ohci_sc.sc_io_res); sc->ohci_sc.sc_io_size = rman_get_size(sc->ohci_sc.sc_io_res); rid = 0; sc->ohci_sc.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->ohci_sc.sc_irq_res == 0) { err = ENXIO; goto error; } sc->ohci_sc.sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (sc->ohci_sc.sc_bus.bdev == 0) { err = ENXIO; goto error; } device_set_ivars(sc->ohci_sc.sc_bus.bdev, &sc->ohci_sc.sc_bus); strlcpy(sc->ohci_sc.sc_vendor, "Generic", sizeof(sc->ohci_sc.sc_vendor)); err = bus_setup_intr(dev, sc->ohci_sc.sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ohci_interrupt, sc, &sc->ohci_sc.sc_intr_hdl); if (err) { sc->ohci_sc.sc_intr_hdl = NULL; goto error; } TAILQ_INIT(&sc->clk_list); /* Enable clock */ for (off = 0; clk_get_by_ofw_index(dev, 0, off, &clk) == 0; off++) { err = clk_enable(clk); if (err != 0) { device_printf(dev, "Could not enable clock %s\n", clk_get_name(clk)); goto error; } clkp = malloc(sizeof(*clkp), M_DEVBUF, M_WAITOK | M_ZERO); clkp->clk = clk; TAILQ_INSERT_TAIL(&sc->clk_list, clkp, next); } /* De-assert reset */ TAILQ_INIT(&sc->rst_list); for (off = 0; hwreset_get_by_ofw_idx(dev, 0, off, &rst) == 0; off++) { err = hwreset_deassert(rst); if (err != 0) { device_printf(dev, "Could not de-assert reset\n"); goto error; } rstp = malloc(sizeof(*rstp), M_DEVBUF, M_WAITOK | M_ZERO); rstp->rst = rst; TAILQ_INSERT_TAIL(&sc->rst_list, rstp, next); } /* Enable phy */ TAILQ_INIT(&sc->phy_list); for (off = 0; phy_get_by_ofw_idx(dev, 0, off, &phy) == 0; off++) { err = phy_usb_set_mode(phy, PHY_USB_MODE_HOST); if (err != 0) { device_printf(dev, "Could not set phy to host mode\n"); goto error; } err = phy_enable(phy); if (err != 0) { device_printf(dev, "Could not enable phy\n"); goto error; } phyp = malloc(sizeof(*phyp), M_DEVBUF, M_WAITOK | M_ZERO); phyp->phy = phy; TAILQ_INSERT_TAIL(&sc->phy_list, phyp, next); } if (GENERIC_USB_INIT(dev) != 0) { err = ENXIO; goto error; } err = ohci_init(&sc->ohci_sc); if (err == 0) err = device_probe_and_attach(sc->ohci_sc.sc_bus.bdev); if (err) goto error; return (0); error: generic_ohci_detach(dev); return (err); } static int generic_ohci_detach(device_t dev) { struct generic_ohci_softc *sc = device_get_softc(dev); int err; struct clk_list *clk, *clk_tmp; struct phy_list *phy, *phy_tmp; struct hwrst_list *rst, *rst_tmp; /* during module unload there are lots of children leftover */ - device_delete_children(dev); + err = bus_generic_detach(dev); + if (err != 0) + return (err); /* * Put the controller into reset, then disable clocks and do * the MI tear down. We have to disable the clocks/hardware * after we do the rest of the teardown. We also disable the * clocks in the opposite order we acquire them, but that * doesn't seem to be absolutely necessary. We free up the * clocks after we disable them, so the system could, in * theory, reuse them. */ bus_space_write_4(sc->ohci_sc.sc_io_tag, sc->ohci_sc.sc_io_hdl, OHCI_CONTROL, 0); if (sc->ohci_sc.sc_irq_res && sc->ohci_sc.sc_intr_hdl) { /* * only call ohci_detach() after ohci_init() */ ohci_detach(&sc->ohci_sc); err = bus_teardown_intr(dev, sc->ohci_sc.sc_irq_res, sc->ohci_sc.sc_intr_hdl); sc->ohci_sc.sc_intr_hdl = NULL; } if (sc->ohci_sc.sc_irq_res) { bus_release_resource(dev, SYS_RES_IRQ, 0, sc->ohci_sc.sc_irq_res); sc->ohci_sc.sc_irq_res = NULL; } if (sc->ohci_sc.sc_io_res) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->ohci_sc.sc_io_res); sc->ohci_sc.sc_io_res = NULL; } usb_bus_mem_free_all(&sc->ohci_sc.sc_bus, &ohci_iterate_hw_softc); /* Disable phy */ TAILQ_FOREACH_SAFE(phy, &sc->phy_list, next, phy_tmp) { err = phy_disable(phy->phy); if (err != 0) device_printf(dev, "Could not disable phy\n"); phy_release(phy->phy); TAILQ_REMOVE(&sc->phy_list, phy, next); free(phy, M_DEVBUF); } /* Assert reset */ TAILQ_FOREACH_SAFE(rst, &sc->rst_list, next, rst_tmp) { hwreset_assert(rst->rst); hwreset_release(rst->rst); TAILQ_REMOVE(&sc->rst_list, rst, next); free(rst, M_DEVBUF); } /* Disable clock */ TAILQ_FOREACH_SAFE(clk, &sc->clk_list, next, clk_tmp) { err = clk_disable(clk->clk); if (err != 0) device_printf(dev, "Could not disable clock %s\n", clk_get_name(clk->clk)); err = clk_release(clk->clk); if (err != 0) device_printf(dev, "Could not release clock %s\n", clk_get_name(clk->clk)); TAILQ_REMOVE(&sc->clk_list, clk, next); free(clk, M_DEVBUF); } if (GENERIC_USB_DEINIT(dev) != 0) return (ENXIO); return (0); } static device_method_t generic_ohci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, generic_ohci_probe), DEVMETHOD(device_attach, generic_ohci_attach), DEVMETHOD(device_detach, generic_ohci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; driver_t generic_ohci_driver = { .name = "ohci", .methods = generic_ohci_methods, .size = sizeof(struct generic_ohci_softc), }; DRIVER_MODULE(ohci, simplebus, generic_ohci_driver, 0, 0); MODULE_DEPEND(ohci, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/generic_xhci.c b/sys/dev/usb/controller/generic_xhci.c index 95551aafa519..f64e1fd01ba0 100644 --- a/sys/dev/usb/controller/generic_xhci.c +++ b/sys/dev/usb/controller/generic_xhci.c @@ -1,197 +1,199 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2015 Semihalf. * Copyright (c) 2015 Stormshield. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "generic_xhci.h" #if __SIZEOF_LONG__ == 8 #define IS_DMA_32B 0 #elif __SIZEOF_LONG__ == 4 #define IS_DMA_32B 1 #else #error unsupported long size #endif int generic_xhci_attach(device_t dev) { struct xhci_softc *sc = device_get_softc(dev); int err = 0, rid = 0; sc->sc_bus.parent = dev; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = XHCI_MAX_DEVICES; sc->sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_io_res == NULL) { device_printf(dev, "Failed to map memory\n"); generic_xhci_detach(dev); return (ENXIO); } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res); sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(dev, "Failed to allocate IRQ\n"); generic_xhci_detach(dev); return (ENXIO); } sc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (sc->sc_bus.bdev == NULL) { device_printf(dev, "Failed to add USB device\n"); generic_xhci_detach(dev); return (ENXIO); } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); sprintf(sc->sc_vendor, XHCI_HC_VENDOR); device_set_desc(sc->sc_bus.bdev, XHCI_HC_DEVSTR); err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)xhci_interrupt, sc, &sc->sc_intr_hdl); if (err != 0) { device_printf(dev, "Failed to setup error IRQ, %d\n", err); sc->sc_intr_hdl = NULL; generic_xhci_detach(dev); return (err); } err = xhci_init(sc, dev, (sc->sc_quirks & XHCI_QUIRK_DMA_32B) == 0 ? IS_DMA_32B : 1); if (err != 0) { device_printf(dev, "Failed to init XHCI, with error %d\n", err); generic_xhci_detach(dev); return (ENXIO); } err = xhci_start_controller(sc); if (err != 0) { device_printf(dev, "Failed to start XHCI controller, with error %d\n", err); generic_xhci_detach(dev); return (ENXIO); } err = device_probe_and_attach(sc->sc_bus.bdev); if (err != 0) { device_printf(dev, "Failed to initialize USB, with error %d\n", err); generic_xhci_detach(dev); return (ENXIO); } return (0); } int generic_xhci_detach(device_t dev) { struct xhci_softc *sc = device_get_softc(dev); int err; /* during module unload there are lots of children leftover */ - device_delete_children(dev); + err = bus_generic_detach(dev); + if (err != 0) + return (err); if (sc->sc_irq_res != NULL && sc->sc_intr_hdl != NULL) { err = bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_hdl); if (err != 0) device_printf(dev, "Could not tear down irq, %d\n", err); sc->sc_intr_hdl = NULL; } if (sc->sc_irq_res != NULL) { bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->sc_irq_res), sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->sc_io_res), sc->sc_io_res); sc->sc_io_res = NULL; } xhci_uninit(sc); return (0); } static device_method_t xhci_methods[] = { /* Device interface */ DEVMETHOD(device_attach, generic_xhci_attach), DEVMETHOD(device_detach, generic_xhci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; driver_t generic_xhci_driver = { "xhci", xhci_methods, sizeof(struct xhci_softc), }; diff --git a/sys/dev/usb/controller/musb_otg_allwinner.c b/sys/dev/usb/controller/musb_otg_allwinner.c index c52b381fd16a..574e8e712713 100644 --- a/sys/dev/usb/controller/musb_otg_allwinner.c +++ b/sys/dev/usb/controller/musb_otg_allwinner.c @@ -1,621 +1,616 @@ /*- * Copyright (c) 2016 Jared McNeill * Copyright (c) 2018 Andrew Turner * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237 * ("CTSRD"), as part of the DARPA CRASH research programme. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Allwinner USB Dual-Role Device (DRD) controller */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __arm__ #include #include #endif #define DRD_EP_MAX 5 #define DRD_EP_MAX_H3 4 #define MUSB2_REG_AWIN_VEND0 0x0043 #define VEND0_PIO_MODE 0 #if defined(__arm__) #define bs_parent_space(bs) ((bs)->bs_parent) typedef bus_space_tag_t awusb_bs_tag; #elif defined(__aarch64__) #define bs_parent_space(bs) (bs) typedef void * awusb_bs_tag; #endif #define AWUSB_OKAY 0x01 #define AWUSB_NO_CONFDATA 0x02 static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-musb", AWUSB_OKAY }, { "allwinner,sun6i-a31-musb", AWUSB_OKAY }, { "allwinner,sun8i-a33-musb", AWUSB_OKAY | AWUSB_NO_CONFDATA }, { "allwinner,sun8i-h3-musb", AWUSB_OKAY | AWUSB_NO_CONFDATA }, { NULL, 0 } }; static const struct musb_otg_ep_cfg musbotg_ep_allwinner[] = { { .ep_end = DRD_EP_MAX, .ep_fifosz_shift = 9, .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_512, }, { .ep_end = -1, }, }; static const struct musb_otg_ep_cfg musbotg_ep_allwinner_h3[] = { { .ep_end = DRD_EP_MAX_H3, .ep_fifosz_shift = 9, .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_512, }, { .ep_end = -1, }, }; struct awusbdrd_softc { struct musbotg_softc sc; struct resource *res[2]; clk_t clk; hwreset_t reset; phy_t phy; struct bus_space bs; int flags; }; static struct resource_spec awusbdrd_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; #define REMAPFLAG 0x8000 #define REGDECL(a, b) [(a)] = ((b) | REMAPFLAG) /* Allwinner USB DRD register mappings */ static const uint16_t awusbdrd_regmap[] = { REGDECL(MUSB2_REG_EPFIFO(0), 0x0000), REGDECL(MUSB2_REG_EPFIFO(1), 0x0004), REGDECL(MUSB2_REG_EPFIFO(2), 0x0008), REGDECL(MUSB2_REG_EPFIFO(3), 0x000c), REGDECL(MUSB2_REG_EPFIFO(4), 0x0010), REGDECL(MUSB2_REG_EPFIFO(5), 0x0014), REGDECL(MUSB2_REG_POWER, 0x0040), REGDECL(MUSB2_REG_DEVCTL, 0x0041), REGDECL(MUSB2_REG_EPINDEX, 0x0042), REGDECL(MUSB2_REG_INTTX, 0x0044), REGDECL(MUSB2_REG_INTRX, 0x0046), REGDECL(MUSB2_REG_INTTXE, 0x0048), REGDECL(MUSB2_REG_INTRXE, 0x004a), REGDECL(MUSB2_REG_INTUSB, 0x004c), REGDECL(MUSB2_REG_INTUSBE, 0x0050), REGDECL(MUSB2_REG_FRAME, 0x0054), REGDECL(MUSB2_REG_TESTMODE, 0x007c), REGDECL(MUSB2_REG_TXMAXP, 0x0080), REGDECL(MUSB2_REG_TXCSRL, 0x0082), REGDECL(MUSB2_REG_TXCSRH, 0x0083), REGDECL(MUSB2_REG_RXMAXP, 0x0084), REGDECL(MUSB2_REG_RXCSRL, 0x0086), REGDECL(MUSB2_REG_RXCSRH, 0x0087), REGDECL(MUSB2_REG_RXCOUNT, 0x0088), REGDECL(MUSB2_REG_TXTI, 0x008c), REGDECL(MUSB2_REG_TXNAKLIMIT, 0x008d), REGDECL(MUSB2_REG_RXNAKLIMIT, 0x008f), REGDECL(MUSB2_REG_RXTI, 0x008e), REGDECL(MUSB2_REG_TXFIFOSZ, 0x0090), REGDECL(MUSB2_REG_TXFIFOADD, 0x0092), REGDECL(MUSB2_REG_RXFIFOSZ, 0x0094), REGDECL(MUSB2_REG_RXFIFOADD, 0x0096), REGDECL(MUSB2_REG_FADDR, 0x0098), REGDECL(MUSB2_REG_TXFADDR(0), 0x0098), REGDECL(MUSB2_REG_TXHADDR(0), 0x009a), REGDECL(MUSB2_REG_TXHUBPORT(0), 0x009b), REGDECL(MUSB2_REG_RXFADDR(0), 0x009c), REGDECL(MUSB2_REG_RXHADDR(0), 0x009e), REGDECL(MUSB2_REG_RXHUBPORT(0), 0x009f), REGDECL(MUSB2_REG_TXFADDR(1), 0x0098), REGDECL(MUSB2_REG_TXHADDR(1), 0x009a), REGDECL(MUSB2_REG_TXHUBPORT(1), 0x009b), REGDECL(MUSB2_REG_RXFADDR(1), 0x009c), REGDECL(MUSB2_REG_RXHADDR(1), 0x009e), REGDECL(MUSB2_REG_RXHUBPORT(1), 0x009f), REGDECL(MUSB2_REG_TXFADDR(2), 0x0098), REGDECL(MUSB2_REG_TXHADDR(2), 0x009a), REGDECL(MUSB2_REG_TXHUBPORT(2), 0x009b), REGDECL(MUSB2_REG_RXFADDR(2), 0x009c), REGDECL(MUSB2_REG_RXHADDR(2), 0x009e), REGDECL(MUSB2_REG_RXHUBPORT(2), 0x009f), REGDECL(MUSB2_REG_TXFADDR(3), 0x0098), REGDECL(MUSB2_REG_TXHADDR(3), 0x009a), REGDECL(MUSB2_REG_TXHUBPORT(3), 0x009b), REGDECL(MUSB2_REG_RXFADDR(3), 0x009c), REGDECL(MUSB2_REG_RXHADDR(3), 0x009e), REGDECL(MUSB2_REG_RXHUBPORT(3), 0x009f), REGDECL(MUSB2_REG_TXFADDR(4), 0x0098), REGDECL(MUSB2_REG_TXHADDR(4), 0x009a), REGDECL(MUSB2_REG_TXHUBPORT(4), 0x009b), REGDECL(MUSB2_REG_RXFADDR(4), 0x009c), REGDECL(MUSB2_REG_RXHADDR(4), 0x009e), REGDECL(MUSB2_REG_RXHUBPORT(4), 0x009f), REGDECL(MUSB2_REG_TXFADDR(5), 0x0098), REGDECL(MUSB2_REG_TXHADDR(5), 0x009a), REGDECL(MUSB2_REG_TXHUBPORT(5), 0x009b), REGDECL(MUSB2_REG_RXFADDR(5), 0x009c), REGDECL(MUSB2_REG_RXHADDR(5), 0x009e), REGDECL(MUSB2_REG_RXHUBPORT(5), 0x009f), REGDECL(MUSB2_REG_CONFDATA, 0x00c0), }; static bus_size_t awusbdrd_reg(bus_size_t o) { bus_size_t v; KASSERT(o < nitems(awusbdrd_regmap), ("%s: Invalid register %#lx", __func__, o)); if (o >= nitems(awusbdrd_regmap)) return (o); v = awusbdrd_regmap[o]; KASSERT((v & REMAPFLAG) != 0, ("%s: reg %#lx not in regmap", __func__, o)); return (v & ~REMAPFLAG); } static int awusbdrd_filt(bus_size_t o) { switch (o) { case MUSB2_REG_MISC: case MUSB2_REG_RXDBDIS: case MUSB2_REG_TXDBDIS: return (1); default: return (0); } } static uint8_t awusbdrd_bs_r_1(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o) { struct bus_space *bs = t; switch (o) { case MUSB2_REG_HWVERS: return (0); /* no known equivalent */ } return (bus_space_read_1(bs_parent_space(bs), h, awusbdrd_reg(o))); } static uint8_t awusbdrd_bs_r_1_noconf(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o) { /* * There is no confdata register on some SoCs, return the same * magic value as Linux. */ if (o == MUSB2_REG_CONFDATA) return (0xde); return (awusbdrd_bs_r_1(t, h, o)); } static uint16_t awusbdrd_bs_r_2(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o) { struct bus_space *bs = t; if (awusbdrd_filt(o) != 0) return (0); return bus_space_read_2(bs_parent_space(bs), h, awusbdrd_reg(o)); } static void awusbdrd_bs_w_1(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o, uint8_t v) { struct bus_space *bs = t; if (awusbdrd_filt(o) != 0) return; bus_space_write_1(bs_parent_space(bs), h, awusbdrd_reg(o), v); } static void awusbdrd_bs_w_2(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o, uint16_t v) { struct bus_space *bs = t; if (awusbdrd_filt(o) != 0) return; bus_space_write_2(bs_parent_space(bs), h, awusbdrd_reg(o), v); } static void awusbdrd_bs_rm_1(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o, uint8_t *d, bus_size_t c) { struct bus_space *bs = t; bus_space_read_multi_1(bs_parent_space(bs), h, awusbdrd_reg(o), d, c); } static void awusbdrd_bs_rm_4(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o, uint32_t *d, bus_size_t c) { struct bus_space *bs = t; bus_space_read_multi_4(bs_parent_space(bs), h, awusbdrd_reg(o), d, c); } static void awusbdrd_bs_wm_1(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o, const uint8_t *d, bus_size_t c) { struct bus_space *bs = t; if (awusbdrd_filt(o) != 0) return; bus_space_write_multi_1(bs_parent_space(bs), h, awusbdrd_reg(o), d, c); } static void awusbdrd_bs_wm_4(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o, const uint32_t *d, bus_size_t c) { struct bus_space *bs = t; if (awusbdrd_filt(o) != 0) return; bus_space_write_multi_4(bs_parent_space(bs), h, awusbdrd_reg(o), d, c); } static void awusbdrd_intr(void *arg) { struct awusbdrd_softc *sc = arg; uint8_t intusb; uint16_t inttx, intrx; intusb = MUSB2_READ_1(&sc->sc, MUSB2_REG_INTUSB); inttx = MUSB2_READ_2(&sc->sc, MUSB2_REG_INTTX); intrx = MUSB2_READ_2(&sc->sc, MUSB2_REG_INTRX); if (intusb == 0 && inttx == 0 && intrx == 0) return; if (intusb) MUSB2_WRITE_1(&sc->sc, MUSB2_REG_INTUSB, intusb); if (inttx) MUSB2_WRITE_2(&sc->sc, MUSB2_REG_INTTX, inttx); if (intrx) MUSB2_WRITE_2(&sc->sc, MUSB2_REG_INTRX, intrx); musbotg_interrupt(arg, intrx, inttx, intusb); } static int awusbdrd_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner USB DRD"); return (BUS_PROBE_DEFAULT); } static int awusbdrd_attach(device_t dev) { char usb_mode[24]; struct awusbdrd_softc *sc; uint8_t musb_mode; int phy_mode; int error; sc = device_get_softc(dev); sc->flags = ofw_bus_search_compatible(dev, compat_data)->ocd_data; error = bus_alloc_resources(dev, awusbdrd_spec, sc->res); if (error != 0) return (error); musb_mode = MUSB2_HOST_MODE; /* default */ phy_mode = PHY_USB_MODE_HOST; if (OF_getprop(ofw_bus_get_node(dev), "dr_mode", &usb_mode, sizeof(usb_mode)) > 0) { usb_mode[sizeof(usb_mode) - 1] = 0; if (strcasecmp(usb_mode, "host") == 0) { musb_mode = MUSB2_HOST_MODE; phy_mode = PHY_USB_MODE_HOST; } else if (strcasecmp(usb_mode, "peripheral") == 0) { musb_mode = MUSB2_DEVICE_MODE; phy_mode = PHY_USB_MODE_DEVICE; } else if (strcasecmp(usb_mode, "otg") == 0) { /* * XXX phy has PHY_USB_MODE_OTG, but MUSB does not have * it. It's not clear how to propagate mode changes * from phy layer (that detects them) to MUSB. */ musb_mode = MUSB2_DEVICE_MODE; phy_mode = PHY_USB_MODE_DEVICE; } else { device_printf(dev, "Invalid FDT dr_mode: %s\n", usb_mode); } } /* AHB gate clock is required */ error = clk_get_by_ofw_index(dev, 0, 0, &sc->clk); if (error != 0) goto fail; /* AHB reset is only present on some SoCs */ (void)hwreset_get_by_ofw_idx(dev, 0, 0, &sc->reset); /* Enable clocks */ error = clk_enable(sc->clk); if (error != 0) { device_printf(dev, "failed to enable clock: %d\n", error); goto fail; } if (sc->reset != NULL) { error = hwreset_deassert(sc->reset); if (error != 0) { device_printf(dev, "failed to de-assert reset: %d\n", error); goto fail; } } /* XXX not sure if this is universally needed. */ (void)phy_get_by_ofw_name(dev, 0, "usb", &sc->phy); if (sc->phy != NULL) { device_printf(dev, "setting phy mode %d\n", phy_mode); if (musb_mode == MUSB2_HOST_MODE) { error = phy_enable(sc->phy); if (error != 0) { device_printf(dev, "Could not enable phy\n"); goto fail; } } error = phy_usb_set_mode(sc->phy, phy_mode); if (error != 0) { device_printf(dev, "Could not set phy mode\n"); goto fail; } } sc->sc.sc_bus.parent = dev; sc->sc.sc_bus.devices = sc->sc.sc_devices; sc->sc.sc_bus.devices_max = MUSB2_MAX_DEVICES; sc->sc.sc_bus.dma_bits = 32; error = usb_bus_mem_alloc_all(&sc->sc.sc_bus, USB_GET_DMA_TAG(dev), NULL); if (error != 0) { error = ENOMEM; goto fail; } #if defined(__arm__) sc->bs.bs_parent = rman_get_bustag(sc->res[0]); #elif defined(__aarch64__) sc->bs.bs_cookie = rman_get_bustag(sc->res[0]); #endif if ((sc->flags & AWUSB_NO_CONFDATA) == AWUSB_NO_CONFDATA) sc->bs.bs_r_1 = awusbdrd_bs_r_1_noconf; else sc->bs.bs_r_1 = awusbdrd_bs_r_1; sc->bs.bs_r_2 = awusbdrd_bs_r_2; sc->bs.bs_w_1 = awusbdrd_bs_w_1; sc->bs.bs_w_2 = awusbdrd_bs_w_2; sc->bs.bs_rm_1 = awusbdrd_bs_rm_1; sc->bs.bs_rm_4 = awusbdrd_bs_rm_4; sc->bs.bs_wm_1 = awusbdrd_bs_wm_1; sc->bs.bs_wm_4 = awusbdrd_bs_wm_4; sc->sc.sc_io_tag = &sc->bs; sc->sc.sc_io_hdl = rman_get_bushandle(sc->res[0]); sc->sc.sc_io_size = rman_get_size(sc->res[0]); sc->sc.sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY); if (sc->sc.sc_bus.bdev == NULL) { error = ENXIO; goto fail; } device_set_ivars(sc->sc.sc_bus.bdev, &sc->sc.sc_bus); sc->sc.sc_id = 0; sc->sc.sc_platform_data = sc; sc->sc.sc_mode = musb_mode; if (ofw_bus_is_compatible(dev, "allwinner,sun8i-h3-musb")) { sc->sc.sc_ep_cfg = musbotg_ep_allwinner_h3; sc->sc.sc_ep_max = DRD_EP_MAX_H3; } else { sc->sc.sc_ep_cfg = musbotg_ep_allwinner; sc->sc.sc_ep_max = DRD_EP_MAX; } error = bus_setup_intr(dev, sc->res[1], INTR_MPSAFE | INTR_TYPE_BIO, NULL, awusbdrd_intr, sc, &sc->sc.sc_intr_hdl); if (error != 0) goto fail; /* Enable PIO mode */ bus_write_1(sc->res[0], MUSB2_REG_AWIN_VEND0, VEND0_PIO_MODE); #ifdef __arm__ /* Map SRAMD area to USB0 (sun4i/sun7i only) */ switch (allwinner_soc_family()) { case ALLWINNERSOC_SUN4I: case ALLWINNERSOC_SUN7I: a10_map_to_otg(); break; } #endif error = musbotg_init(&sc->sc); if (error != 0) goto fail; error = device_probe_and_attach(sc->sc.sc_bus.bdev); if (error != 0) goto fail; musbotg_vbus_interrupt(&sc->sc, 1); /* XXX VBUS */ return (0); fail: if (sc->phy != NULL) { if (musb_mode == MUSB2_HOST_MODE) (void)phy_disable(sc->phy); phy_release(sc->phy); } if (sc->reset != NULL) { hwreset_assert(sc->reset); hwreset_release(sc->reset); } if (sc->clk != NULL) clk_release(sc->clk); bus_release_resources(dev, awusbdrd_spec, sc->res); return (error); } static int awusbdrd_detach(device_t dev) { struct awusbdrd_softc *sc; - device_t bdev; int error; - sc = device_get_softc(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); - if (sc->sc.sc_bus.bdev != NULL) { - bdev = sc->sc.sc_bus.bdev; - device_detach(bdev); - device_delete_child(dev, bdev); - } + sc = device_get_softc(dev); musbotg_uninit(&sc->sc); error = bus_teardown_intr(dev, sc->res[1], sc->sc.sc_intr_hdl); if (error != 0) return (error); usb_bus_mem_free_all(&sc->sc.sc_bus, NULL); if (sc->phy != NULL) { if (sc->sc.sc_mode == MUSB2_HOST_MODE) phy_disable(sc->phy); phy_release(sc->phy); } if (sc->reset != NULL) { if (hwreset_assert(sc->reset) != 0) device_printf(dev, "failed to assert reset\n"); hwreset_release(sc->reset); } if (sc->clk != NULL) clk_release(sc->clk); bus_release_resources(dev, awusbdrd_spec, sc->res); - device_delete_children(dev); - return (0); } static device_method_t awusbdrd_methods[] = { /* Device interface */ DEVMETHOD(device_probe, awusbdrd_probe), DEVMETHOD(device_attach, awusbdrd_attach), DEVMETHOD(device_detach, awusbdrd_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; static driver_t awusbdrd_driver = { .name = "musbotg", .methods = awusbdrd_methods, .size = sizeof(struct awusbdrd_softc), }; DRIVER_MODULE(musbotg, simplebus, awusbdrd_driver, 0, 0); MODULE_DEPEND(musbotg, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/ohci_pci.c b/sys/dev/usb/controller/ohci_pci.c index 03291ff34a6f..027be4ce9588 100644 --- a/sys/dev/usb/controller/ohci_pci.c +++ b/sys/dev/usb/controller/ohci_pci.c @@ -1,378 +1,381 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (augustss@carlstedt.se) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include /* * USB Open Host Controller driver. * * OHCI spec: http://www.intel.com/design/usb/ohci11d.pdf */ /* The low level controller code for OHCI has been split into * PCI probes and OHCI specific code. This was done to facilitate the * sharing of code between *BSD's */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #define PCI_OHCI_VENDORID_ACERLABS 0x10b9 #define PCI_OHCI_VENDORID_AMD 0x1022 #define PCI_OHCI_VENDORID_APPLE 0x106b #define PCI_OHCI_VENDORID_ATI 0x1002 #define PCI_OHCI_VENDORID_CMDTECH 0x1095 #define PCI_OHCI_VENDORID_HYGON 0x1d94 #define PCI_OHCI_VENDORID_NEC 0x1033 #define PCI_OHCI_VENDORID_NVIDIA 0x12D2 #define PCI_OHCI_VENDORID_NVIDIA2 0x10DE #define PCI_OHCI_VENDORID_OPTI 0x1045 #define PCI_OHCI_VENDORID_SIS 0x1039 #define PCI_OHCI_BASE_REG 0x10 static device_probe_t ohci_pci_probe; static device_attach_t ohci_pci_attach; static device_detach_t ohci_pci_detach; static usb_take_controller_t ohci_pci_take_controller; static int ohci_pci_take_controller(device_t self) { uint32_t reg; uint32_t int_line; if (pci_get_powerstate(self) != PCI_POWERSTATE_D0) { device_printf(self, "chip is in D%d mode " "-- setting to D0\n", pci_get_powerstate(self)); reg = pci_read_config(self, PCI_CBMEM, 4); int_line = pci_read_config(self, PCIR_INTLINE, 4); pci_set_powerstate(self, PCI_POWERSTATE_D0); pci_write_config(self, PCI_CBMEM, reg, 4); pci_write_config(self, PCIR_INTLINE, int_line, 4); } return (0); } static const char * ohci_pci_match(device_t self) { uint32_t device_id = pci_get_devid(self); switch (device_id) { case 0x523710b9: return ("AcerLabs M5237 (Aladdin-V) USB controller"); case 0x740c1022: return ("AMD-756 USB Controller"); case 0x74141022: return ("AMD-766 USB Controller"); case 0x78071022: return ("AMD FCH USB Controller"); case 0x43741002: return "ATI SB400 USB Controller"; case 0x43751002: return "ATI SB400 USB Controller"; case 0x43971002: return ("AMD SB7x0/SB8x0/SB9x0 USB controller"); case 0x43981002: return ("AMD SB7x0/SB8x0/SB9x0 USB controller"); case 0x43991002: return ("AMD SB7x0/SB8x0/SB9x0 USB controller"); case 0x06701095: return ("CMD Tech 670 (USB0670) USB controller"); case 0x06731095: return ("CMD Tech 673 (USB0673) USB controller"); case 0xc8611045: return ("OPTi 82C861 (FireLink) USB controller"); case 0x00351033: return ("NEC uPD 9210 USB controller"); case 0x00d710de: return ("nVidia nForce3 USB Controller"); case 0x005a10de: return ("nVidia nForce CK804 USB Controller"); case 0x036c10de: return ("nVidia nForce MCP55 USB Controller"); case 0x03f110de: return ("nVidia nForce MCP61 USB Controller"); case 0x0aa510de: return ("nVidia nForce MCP79 USB Controller"); case 0x0aa710de: return ("nVidia nForce MCP79 USB Controller"); case 0x0aa810de: return ("nVidia nForce MCP79 USB Controller"); case 0x70011039: return ("SiS 5571 USB controller"); case 0x0019106b: return ("Apple KeyLargo USB controller"); case 0x003f106b: return ("Apple KeyLargo/Intrepid USB controller"); default: break; } if ((pci_get_class(self) == PCIC_SERIALBUS) && (pci_get_subclass(self) == PCIS_SERIALBUS_USB) && (pci_get_progif(self) == PCI_INTERFACE_OHCI)) { return ("OHCI (generic) USB controller"); } return (NULL); } static int ohci_pci_probe(device_t self) { const char *desc = ohci_pci_match(self); if (desc) { device_set_desc(self, desc); return (0); } else { return (ENXIO); } } static int ohci_pci_attach(device_t self) { ohci_softc_t *sc = device_get_softc(self); int rid; int err; /* initialise some bus fields */ sc->sc_bus.parent = self; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = OHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &ohci_iterate_hw_softc)) { return (ENOMEM); } sc->sc_dev = self; pci_enable_busmaster(self); rid = PCI_CBMEM; sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_io_res) { device_printf(self, "Could not map memory\n"); goto error; } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(self, "Could not allocate irq\n"); goto error; } sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); /* * ohci_pci_match will never return NULL if ohci_pci_probe * succeeded */ device_set_desc(sc->sc_bus.bdev, ohci_pci_match(self)); switch (pci_get_vendor(self)) { case PCI_OHCI_VENDORID_ACERLABS: sprintf(sc->sc_vendor, "AcerLabs"); break; case PCI_OHCI_VENDORID_AMD: sprintf(sc->sc_vendor, "AMD"); break; case PCI_OHCI_VENDORID_APPLE: sprintf(sc->sc_vendor, "Apple"); break; case PCI_OHCI_VENDORID_ATI: sprintf(sc->sc_vendor, "ATI"); break; case PCI_OHCI_VENDORID_CMDTECH: sprintf(sc->sc_vendor, "CMDTECH"); break; case PCI_OHCI_VENDORID_HYGON: sprintf(sc->sc_vendor, "Hygon"); break; case PCI_OHCI_VENDORID_NEC: sprintf(sc->sc_vendor, "NEC"); break; case PCI_OHCI_VENDORID_NVIDIA: case PCI_OHCI_VENDORID_NVIDIA2: sprintf(sc->sc_vendor, "nVidia"); break; case PCI_OHCI_VENDORID_OPTI: sprintf(sc->sc_vendor, "OPTi"); break; case PCI_OHCI_VENDORID_SIS: sprintf(sc->sc_vendor, "SiS"); break; default: if (bootverbose) { device_printf(self, "(New OHCI DeviceId=0x%08x)\n", pci_get_devid(self)); } sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); } /* sc->sc_bus.usbrev; set by ohci_init() */ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)ohci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; goto error; } err = ohci_init(sc); if (!err) { err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(self, "USB init failed\n"); goto error; } return (0); error: ohci_pci_detach(self); return (ENXIO); } static int ohci_pci_detach(device_t self) { ohci_softc_t *sc = device_get_softc(self); + int error; /* during module unload there are lots of children leftover */ - device_delete_children(self); + error = bus_generic_detach(self); + if (error != 0) + return (error); pci_disable_busmaster(self); if (sc->sc_irq_res && sc->sc_intr_hdl) { /* * only call ohci_detach() after ohci_init() */ ohci_detach(sc); int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) { /* XXX or should we panic? */ device_printf(self, "Could not tear down irq, %d\n", err); } sc->sc_intr_hdl = NULL; } if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM, sc->sc_io_res); sc->sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_bus, &ohci_iterate_hw_softc); return (0); } static device_method_t ohci_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ohci_pci_probe), DEVMETHOD(device_attach, ohci_pci_attach), DEVMETHOD(device_detach, ohci_pci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(usb_take_controller, ohci_pci_take_controller), DEVMETHOD_END }; static driver_t ohci_driver = { .name = "ohci", .methods = ohci_pci_methods, .size = sizeof(struct ohci_softc), }; DRIVER_MODULE(ohci, pci, ohci_driver, 0, 0); MODULE_DEPEND(ohci, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/uhci_pci.c b/sys/dev/usb/controller/uhci_pci.c index 90f3dca8f830..0dc2763aed8d 100644 --- a/sys/dev/usb/controller/uhci_pci.c +++ b/sys/dev/usb/controller/uhci_pci.c @@ -1,479 +1,482 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (augustss@carlstedt.se) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include /* Universal Host Controller Interface * * UHCI spec: http://www.intel.com/ */ /* The low level controller code for UHCI has been split into * PCI probes and UHCI specific code. This was done to facilitate the * sharing of code between *BSD's */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #define PCI_UHCI_VENDORID_INTEL 0x8086 #define PCI_UHCI_VENDORID_HP 0x103c #define PCI_UHCI_VENDORID_VIA 0x1106 #define PCI_UHCI_VENDORID_VMWARE 0x15ad #define PCI_UHCI_VENDORID_ZHAOXIN 0x1d17 /* PIIX4E has no separate stepping */ static device_probe_t uhci_pci_probe; static device_attach_t uhci_pci_attach; static device_detach_t uhci_pci_detach; static usb_take_controller_t uhci_pci_take_controller; static int uhci_pci_take_controller(device_t self) { pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); return (0); } static const char * uhci_pci_match(device_t self) { uint32_t device_id = pci_get_devid(self); switch (device_id) { case 0x26888086: return ("Intel 631XESB/632XESB/3100 USB controller USB-1"); case 0x26898086: return ("Intel 631XESB/632XESB/3100 USB controller USB-2"); case 0x268a8086: return ("Intel 631XESB/632XESB/3100 USB controller USB-3"); case 0x268b8086: return ("Intel 631XESB/632XESB/3100 USB controller USB-4"); case 0x70208086: return ("Intel 82371SB (PIIX3) USB controller"); case 0x71128086: return ("Intel 82371AB/EB (PIIX4) USB controller"); case 0x24128086: return ("Intel 82801AA (ICH) USB controller"); case 0x24228086: return ("Intel 82801AB (ICH0) USB controller"); case 0x24428086: return ("Intel 82801BA/BAM (ICH2) USB controller USB-A"); case 0x24448086: return ("Intel 82801BA/BAM (ICH2) USB controller USB-B"); case 0x24828086: return ("Intel 82801CA/CAM (ICH3) USB controller USB-A"); case 0x24848086: return ("Intel 82801CA/CAM (ICH3) USB controller USB-B"); case 0x24878086: return ("Intel 82801CA/CAM (ICH3) USB controller USB-C"); case 0x24c28086: return ("Intel 82801DB (ICH4) USB controller USB-A"); case 0x24c48086: return ("Intel 82801DB (ICH4) USB controller USB-B"); case 0x24c78086: return ("Intel 82801DB (ICH4) USB controller USB-C"); case 0x24d28086: return ("Intel 82801EB (ICH5) USB controller USB-A"); case 0x24d48086: return ("Intel 82801EB (ICH5) USB controller USB-B"); case 0x24d78086: return ("Intel 82801EB (ICH5) USB controller USB-C"); case 0x24de8086: return ("Intel 82801EB (ICH5) USB controller USB-D"); case 0x25a98086: return ("Intel 6300ESB USB controller USB-A"); case 0x25aa8086: return ("Intel 6300ESB USB controller USB-B"); case 0x26588086: return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-A"); case 0x26598086: return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-B"); case 0x265a8086: return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-C"); case 0x265b8086: return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-D"); case 0x27c88086: return ("Intel 82801G (ICH7) USB controller USB-A"); case 0x27c98086: return ("Intel 82801G (ICH7) USB controller USB-B"); case 0x27ca8086: return ("Intel 82801G (ICH7) USB controller USB-C"); case 0x27cb8086: return ("Intel 82801G (ICH7) USB controller USB-D"); case 0x28308086: return ("Intel 82801H (ICH8) USB controller USB-A"); case 0x28318086: return ("Intel 82801H (ICH8) USB controller USB-B"); case 0x28328086: return ("Intel 82801H (ICH8) USB controller USB-C"); case 0x28348086: return ("Intel 82801H (ICH8) USB controller USB-D"); case 0x28358086: return ("Intel 82801H (ICH8) USB controller USB-E"); case 0x29348086: return ("Intel 82801I (ICH9) USB controller"); case 0x29358086: return ("Intel 82801I (ICH9) USB controller"); case 0x29368086: return ("Intel 82801I (ICH9) USB controller"); case 0x29378086: return ("Intel 82801I (ICH9) USB controller"); case 0x29388086: return ("Intel 82801I (ICH9) USB controller"); case 0x29398086: return ("Intel 82801I (ICH9) USB controller"); case 0x3a348086: return ("Intel 82801JI (ICH10) USB controller USB-A"); case 0x3a358086: return ("Intel 82801JI (ICH10) USB controller USB-B"); case 0x3a368086: return ("Intel 82801JI (ICH10) USB controller USB-C"); case 0x3a378086: return ("Intel 82801JI (ICH10) USB controller USB-D"); case 0x3a388086: return ("Intel 82801JI (ICH10) USB controller USB-E"); case 0x3a398086: return ("Intel 82801JI (ICH10) USB controller USB-F"); case 0x3a678086: return ("Intel 82801JD (ICH10) USB controller USB-A"); case 0x3a688086: return ("Intel 82801JD (ICH10) USB controller USB-B"); case 0x3a698086: return ("Intel 82801JD (ICH10) USB controller USB-C"); case 0x3a648086: return ("Intel 82801JD (ICH10) USB controller USB-D"); case 0x3a658086: return ("Intel 82801JD (ICH10) USB controller USB-E"); case 0x3a668086: return ("Intel 82801JD (ICH10) USB controller USB-F"); case 0x719a8086: return ("Intel 82443MX USB controller"); case 0x76028086: return ("Intel 82372FB/82468GX USB controller"); case 0x3300103c: return ("HP iLO Standard Virtual USB controller"); case 0x30381106: return ("VIA 83C572 USB controller"); case 0x077415ad: return ("VMware USB controller"); case 0x30381d17: return ("Zhaoxin ZX-100/ZX-200/ZX-E USB controller"); default: break; } if ((pci_get_class(self) == PCIC_SERIALBUS) && (pci_get_subclass(self) == PCIS_SERIALBUS_USB) && (pci_get_progif(self) == PCI_INTERFACE_UHCI)) { return ("UHCI (generic) USB controller"); } return (NULL); } static int uhci_pci_probe(device_t self) { const char *desc = uhci_pci_match(self); if (desc) { device_set_desc(self, desc); return (BUS_PROBE_DEFAULT); } else { return (ENXIO); } } static int uhci_pci_attach(device_t self) { uhci_softc_t *sc = device_get_softc(self); int rid; int err; /* initialise some bus fields */ sc->sc_bus.parent = self; sc->sc_bus.devices = sc->sc_devices; sc->sc_bus.devices_max = UHCI_MAX_DEVICES; sc->sc_bus.dma_bits = 32; /* get all DMA memory */ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self), &uhci_iterate_hw_softc)) { return ENOMEM; } sc->sc_dev = self; pci_enable_busmaster(self); rid = PCI_UHCI_BASE_REG; sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid, RF_ACTIVE); if (!sc->sc_io_res) { device_printf(self, "Could not map ports\n"); goto error; } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res); /* disable interrupts */ bus_space_write_2(sc->sc_io_tag, sc->sc_io_hdl, UHCI_INTR, 0); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(self, "Could not allocate irq\n"); goto error; } sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); /* * uhci_pci_match must never return NULL if uhci_pci_probe * succeeded */ device_set_desc(sc->sc_bus.bdev, uhci_pci_match(self)); switch (pci_get_vendor(self)) { case PCI_UHCI_VENDORID_INTEL: sprintf(sc->sc_vendor, "Intel"); break; case PCI_UHCI_VENDORID_HP: sprintf(sc->sc_vendor, "HP"); break; case PCI_UHCI_VENDORID_VIA: sprintf(sc->sc_vendor, "VIA"); break; case PCI_UHCI_VENDORID_VMWARE: sprintf(sc->sc_vendor, "VMware"); break; case PCI_UHCI_VENDORID_ZHAOXIN: sprintf(sc->sc_vendor, "Zhaoxin"); break; default: if (bootverbose) { device_printf(self, "(New UHCI DeviceId=0x%08x)\n", pci_get_devid(self)); } sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); } switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) { case PCI_USB_REV_PRE_1_0: sc->sc_bus.usbrev = USB_REV_PRE_1_0; break; case PCI_USB_REV_1_0: sc->sc_bus.usbrev = USB_REV_1_0; break; default: /* Quirk for Parallels Desktop 4.0 */ device_printf(self, "USB revision is unknown. Assuming v1.1.\n"); sc->sc_bus.usbrev = USB_REV_1_1; break; } err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)uhci_interrupt, sc, &sc->sc_intr_hdl); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->sc_intr_hdl = NULL; goto error; } /* * Set the PIRQD enable bit and switch off all the others. We don't * want legacy support to interfere with us XXX Does this also mean * that the BIOS won't touch the keyboard anymore if it is connected * to the ports of the root hub? */ #ifdef USB_DEBUG if (pci_read_config(self, PCI_LEGSUP, 2) != PCI_LEGSUP_USBPIRQDEN) { device_printf(self, "LegSup = 0x%04x\n", pci_read_config(self, PCI_LEGSUP, 2)); } #endif pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); err = uhci_init(sc); if (!err) { err = device_probe_and_attach(sc->sc_bus.bdev); } if (err) { device_printf(self, "USB init failed\n"); goto error; } return (0); error: uhci_pci_detach(self); return (ENXIO); } int uhci_pci_detach(device_t self) { uhci_softc_t *sc = device_get_softc(self); + int error; /* during module unload there are lots of children leftover */ - device_delete_children(self); + error = bus_generic_detach(self); + if (error != 0) + return (error); /* * disable interrupts that might have been switched on in * uhci_init. */ if (sc->sc_io_res) { USB_BUS_LOCK(&sc->sc_bus); /* stop the controller */ uhci_reset(sc); USB_BUS_UNLOCK(&sc->sc_bus); } pci_disable_busmaster(self); if (sc->sc_irq_res && sc->sc_intr_hdl) { int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); if (err) { /* XXX or should we panic? */ device_printf(self, "Could not tear down irq, %d\n", err); } sc->sc_intr_hdl = NULL; } if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res); sc->sc_irq_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_IOPORT, PCI_UHCI_BASE_REG, sc->sc_io_res); sc->sc_io_res = NULL; } usb_bus_mem_free_all(&sc->sc_bus, &uhci_iterate_hw_softc); return (0); } static device_method_t uhci_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, uhci_pci_probe), DEVMETHOD(device_attach, uhci_pci_attach), DEVMETHOD(device_detach, uhci_pci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(usb_take_controller, uhci_pci_take_controller), DEVMETHOD_END }; static driver_t uhci_driver = { .name = "uhci", .methods = uhci_pci_methods, .size = sizeof(struct uhci_softc), }; DRIVER_MODULE(uhci, pci, uhci_driver, 0, 0); MODULE_DEPEND(uhci, usb, 1, 1, 1); diff --git a/sys/dev/usb/controller/xhci_pci.c b/sys/dev/usb/controller/xhci_pci.c index 6f128f1d2fa7..c436f60aefcf 100644 --- a/sys/dev/usb/controller/xhci_pci.c +++ b/sys/dev/usb/controller/xhci_pci.c @@ -1,549 +1,552 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2010-2022 Hans Petter Selasky * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb_if.h" #define PCI_XHCI_VENDORID_AMD 0x1022 #define PCI_XHCI_VENDORID_INTEL 0x8086 #define PCI_XHCI_VENDORID_VMWARE 0x15ad #define PCI_XHCI_VENDORID_ZHAOXIN 0x1d17 static device_probe_t xhci_pci_probe; static device_detach_t xhci_pci_detach; static usb_take_controller_t xhci_pci_take_controller; static device_method_t xhci_device_methods[] = { /* device interface */ DEVMETHOD(device_probe, xhci_pci_probe), DEVMETHOD(device_attach, xhci_pci_attach), DEVMETHOD(device_detach, xhci_pci_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(usb_take_controller, xhci_pci_take_controller), DEVMETHOD_END }; DEFINE_CLASS_0(xhci, xhci_pci_driver, xhci_device_methods, sizeof(struct xhci_softc)); DRIVER_MODULE(xhci, pci, xhci_pci_driver, NULL, NULL); MODULE_DEPEND(xhci, usb, 1, 1, 1); static const char * xhci_pci_match(device_t self) { uint32_t device_id = pci_get_devid(self); switch (device_id) { case 0x145c1022: return ("AMD KERNCZ USB 3.0 controller"); case 0x148c1022: return ("AMD Starship USB 3.0 controller"); case 0x149c1022: return ("AMD Matisse USB 3.0 controller"); case 0x15e01022: case 0x15e11022: return ("AMD Raven USB 3.1 controller"); case 0x43ba1022: return ("AMD X399 USB 3.0 controller"); case 0x43b91022: /* X370 */ case 0x43bb1022: /* B350 */ return ("AMD 300 Series USB 3.1 controller"); case 0x43d51022: return ("AMD 400 Series USB 3.1 controller"); case 0x78121022: case 0x78141022: case 0x79141022: return ("AMD FCH USB 3.0 controller"); case 0x077815ad: case 0x077915ad: return ("VMware USB 3.0 controller"); case 0x145f1d94: return ("Hygon USB 3.0 controller"); case 0x01941033: return ("NEC uPD720200 USB 3.0 controller"); case 0x00151912: return ("NEC uPD720202 USB 3.0 controller"); case 0x10001b73: return ("Fresco Logic FL1000G USB 3.0 controller"); case 0x10091b73: return ("Fresco Logic FL1009 USB 3.0 controller"); case 0x11001b73: return ("Fresco Logic FL1100 USB 3.0 controller"); case 0x10421b21: return ("ASMedia ASM1042 USB 3.0 controller"); case 0x11421b21: return ("ASMedia ASM1042A USB 3.0 controller"); case 0x13431b21: return ("ASMedia ASM1143 USB 3.1 controller"); case 0x32421b21: return ("ASMedia ASM3242 USB 3.2 controller"); case 0x0b278086: return ("Intel Goshen Ridge Thunderbolt 4 USB controller"); case 0x0f358086: return ("Intel BayTrail USB 3.0 controller"); case 0x11388086: return ("Intel Maple Ridge Thunderbolt 4 USB controller"); case 0x15c18086: case 0x15d48086: case 0x15db8086: return ("Intel Alpine Ridge Thunderbolt 3 USB controller"); case 0x15e98086: case 0x15ec8086: case 0x15f08086: return ("Intel Titan Ridge Thunderbolt 3 USB controller"); case 0x19d08086: return ("Intel Denverton USB 3.0 controller"); case 0x9c318086: case 0x1e318086: return ("Intel Panther Point USB 3.0 controller"); case 0x22b58086: return ("Intel Braswell USB 3.0 controller"); case 0x31a88086: return ("Intel Gemini Lake USB 3.0 controller"); case 0x34ed8086: return ("Intel Ice Lake-LP USB 3.1 controller"); case 0x43ed8086: return ("Intel Tiger Lake-H USB 3.2 controller"); case 0x461e8086: return ("Intel Alder Lake-P Thunderbolt 4 USB controller"); case 0x51ed8086: return ("Intel Alder Lake USB 3.2 controller"); case 0x5aa88086: return ("Intel Apollo Lake USB 3.0 controller"); case 0x7ae08086: return ("Intel Alder Lake USB 3.2 controller"); case 0x8a138086: return ("Intel Ice Lake Thunderbolt 3 USB controller"); case 0x8c318086: return ("Intel Lynx Point USB 3.0 controller"); case 0x8cb18086: return ("Intel Wildcat Point USB 3.0 controller"); case 0x8d318086: return ("Intel Wellsburg USB 3.0 controller"); case 0x9a138086: return ("Intel Tiger Lake-LP Thunderbolt 4 USB controller"); case 0x9a178086: return ("Intel Tiger Lake-H Thunderbolt 4 USB controller"); case 0x9cb18086: return ("Broadwell Integrated PCH-LP chipset USB 3.0 controller"); case 0x9d2f8086: return ("Intel Sunrise Point-LP USB 3.0 controller"); case 0xa0ed8086: return ("Intel Tiger Lake-LP USB 3.2 controller"); case 0xa12f8086: return ("Intel Sunrise Point USB 3.0 controller"); case 0xa1af8086: return ("Intel Lewisburg USB 3.0 controller"); case 0xa2af8086: return ("Intel Union Point USB 3.0 controller"); case 0xa36d8086: return ("Intel Cannon Lake USB 3.1 controller"); case 0xa01b177d: return ("Cavium ThunderX USB 3.0 controller"); case 0x1ada10de: return ("NVIDIA TU106 USB 3.1 controller"); case 0x92021d17: return ("Zhaoxin ZX-100 USB 3.0 controller"); case 0x92031d17: return ("Zhaoxin ZX-200 USB 3.0 controller"); case 0x92041d17: return ("Zhaoxin ZX-E USB 3.0 controller"); default: break; } if ((pci_get_class(self) == PCIC_SERIALBUS) && (pci_get_subclass(self) == PCIS_SERIALBUS_USB) && (pci_get_progif(self) == PCIP_SERIALBUS_USB_XHCI)) { return ("XHCI (generic) USB 3.0 controller"); } return (NULL); /* dunno */ } static int xhci_pci_probe(device_t self) { const char *desc = xhci_pci_match(self); if (desc) { device_set_desc(self, desc); return (BUS_PROBE_DEFAULT); } else { return (ENXIO); } } static int xhci_use_msi = 1; TUNABLE_INT("hw.usb.xhci.msi", &xhci_use_msi); static int xhci_use_msix = 1; TUNABLE_INT("hw.usb.xhci.msix", &xhci_use_msix); static void xhci_interrupt_poll(void *_sc) { struct xhci_softc *sc = _sc; USB_BUS_UNLOCK(&sc->sc_bus); xhci_interrupt(sc); USB_BUS_LOCK(&sc->sc_bus); usb_callout_reset(&sc->sc_callout, 1, (void *)&xhci_interrupt_poll, sc); } static int xhci_pci_port_route(device_t self, uint32_t set, uint32_t clear) { uint32_t temp; uint32_t usb3_mask; uint32_t usb2_mask; temp = pci_read_config(self, PCI_XHCI_INTEL_USB3_PSSEN, 4) | pci_read_config(self, PCI_XHCI_INTEL_XUSB2PR, 4); temp |= set; temp &= ~clear; /* Don't set bits which the hardware doesn't support */ usb3_mask = pci_read_config(self, PCI_XHCI_INTEL_USB3PRM, 4); usb2_mask = pci_read_config(self, PCI_XHCI_INTEL_USB2PRM, 4); pci_write_config(self, PCI_XHCI_INTEL_USB3_PSSEN, temp & usb3_mask, 4); pci_write_config(self, PCI_XHCI_INTEL_XUSB2PR, temp & usb2_mask, 4); device_printf(self, "Port routing mask set to 0x%08x\n", temp); return (0); } int xhci_pci_attach(device_t self) { struct xhci_softc *sc = device_get_softc(self); int count, err, msix_table, rid; uint8_t usemsi = 1; uint8_t usedma32 = 0; rid = PCI_XHCI_CBMEM; sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_io_res) { device_printf(self, "Could not map memory\n"); return (ENOMEM); } sc->sc_io_tag = rman_get_bustag(sc->sc_io_res); sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res); sc->sc_io_size = rman_get_size(sc->sc_io_res); switch (pci_get_devid(self)) { case 0x10091b73: /* Fresco Logic FL1009 USB3.0 xHCI Controller */ case 0x8241104c: /* TUSB73x0 USB3.0 xHCI Controller */ sc->sc_no_deconfigure = 1; break; case 0x01941033: /* NEC uPD720200 USB 3.0 controller */ case 0x00141912: /* NEC uPD720201 USB 3.0 controller */ /* Don't use 64-bit DMA on these controllers. */ usedma32 = 1; break; case 0x10001b73: /* FL1000G */ /* Fresco Logic host doesn't support MSI. */ usemsi = 0; break; case 0x0f358086: /* BayTrail */ case 0x9c318086: /* Panther Point */ case 0x1e318086: /* Panther Point */ case 0x8c318086: /* Lynx Point */ case 0x8cb18086: /* Wildcat Point */ case 0x9cb18086: /* Broadwell Mobile Integrated */ /* * On Intel chipsets, reroute ports from EHCI to XHCI * controller and use a different IMOD value. */ sc->sc_port_route = &xhci_pci_port_route; sc->sc_imod_default = XHCI_IMOD_DEFAULT_LP; sc->sc_ctlstep = 1; break; default: break; } if (xhci_init(sc, self, usedma32)) { device_printf(self, "Could not initialize softc\n"); bus_release_resource(self, SYS_RES_MEMORY, PCI_XHCI_CBMEM, sc->sc_io_res); return (ENXIO); } pci_enable_busmaster(self); usb_callout_init_mtx(&sc->sc_callout, &sc->sc_bus.bus_mtx, 0); rid = 0; if (xhci_use_msix && (msix_table = pci_msix_table_bar(self)) >= 0) { if (msix_table == PCI_XHCI_CBMEM) { sc->sc_msix_res = sc->sc_io_res; } else { sc->sc_msix_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &msix_table, RF_ACTIVE); if (sc->sc_msix_res == NULL) { /* May not be enabled */ device_printf(self, "Unable to map MSI-X table\n"); } } if (sc->sc_msix_res != NULL) { count = 1; if (pci_alloc_msix(self, &count) == 0) { if (bootverbose) device_printf(self, "MSI-X enabled\n"); rid = 1; } else { if (sc->sc_msix_res != sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, msix_table, sc->sc_msix_res); } sc->sc_msix_res = NULL; } } } if (rid == 0 && xhci_use_msi && usemsi) { count = 1; if (pci_alloc_msi(self, &count) == 0) { if (bootverbose) device_printf(self, "MSI enabled\n"); rid = 1; } } sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, RF_ACTIVE | (rid != 0 ? 0 : RF_SHAREABLE)); if (sc->sc_irq_res == NULL) { pci_release_msi(self); device_printf(self, "Could not allocate IRQ\n"); /* goto error; FALLTHROUGH - use polling */ } sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY); if (sc->sc_bus.bdev == NULL) { device_printf(self, "Could not add USB device\n"); goto error; } device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); switch (pci_get_vendor(self)) { case PCI_XHCI_VENDORID_AMD: strlcpy(sc->sc_vendor, "AMD", sizeof(sc->sc_vendor)); break; case PCI_XHCI_VENDORID_INTEL: strlcpy(sc->sc_vendor, "Intel", sizeof(sc->sc_vendor)); break; case PCI_XHCI_VENDORID_VMWARE: strlcpy(sc->sc_vendor, "VMware", sizeof(sc->sc_vendor)); break; case PCI_XHCI_VENDORID_ZHAOXIN: strlcpy(sc->sc_vendor, "Zhaoxin", sizeof(sc->sc_vendor)); break; default: if (bootverbose) device_printf(self, "(New XHCI DeviceId=0x%08x)\n", pci_get_devid(self)); snprintf(sc->sc_vendor, sizeof(sc->sc_vendor), "(0x%04x)", pci_get_vendor(self)); break; } if (sc->sc_irq_res != NULL && xhci_use_polling() == 0) { err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)xhci_interrupt, sc, &sc->sc_intr_hdl); if (err != 0) { bus_release_resource(self, SYS_RES_IRQ, rman_get_rid(sc->sc_irq_res), sc->sc_irq_res); sc->sc_irq_res = NULL; pci_release_msi(self); device_printf(self, "Could not setup IRQ, err=%d\n", err); sc->sc_intr_hdl = NULL; } } if (sc->sc_irq_res == NULL || sc->sc_intr_hdl == NULL) { if (xhci_use_polling() != 0) { device_printf(self, "Interrupt polling at %dHz\n", hz); USB_BUS_LOCK(&sc->sc_bus); xhci_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } else goto error; } xhci_pci_take_controller(self); err = xhci_halt_controller(sc); if (err == 0) err = xhci_start_controller(sc); if (err == 0) err = device_probe_and_attach(sc->sc_bus.bdev); if (err) { device_printf(self, "XHCI halt/start/probe failed err=%d\n", err); goto error; } return (0); error: xhci_pci_detach(self); return (ENXIO); } static int xhci_pci_detach(device_t self) { struct xhci_softc *sc = device_get_softc(self); + int error; /* during module unload there are lots of children leftover */ - device_delete_children(self); + error = bus_generic_detach(self); + if (error != 0) + return (error); usb_callout_drain(&sc->sc_callout); xhci_halt_controller(sc); xhci_reset_controller(sc); pci_disable_busmaster(self); if (sc->sc_irq_res && sc->sc_intr_hdl) { bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl); sc->sc_intr_hdl = NULL; } if (sc->sc_irq_res) { bus_release_resource(self, SYS_RES_IRQ, rman_get_rid(sc->sc_irq_res), sc->sc_irq_res); sc->sc_irq_res = NULL; pci_release_msi(self); } if (sc->sc_msix_res != NULL && sc->sc_msix_res != sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, rman_get_rid(sc->sc_msix_res), sc->sc_msix_res); sc->sc_msix_res = NULL; } if (sc->sc_io_res) { bus_release_resource(self, SYS_RES_MEMORY, PCI_XHCI_CBMEM, sc->sc_io_res); sc->sc_io_res = NULL; } xhci_uninit(sc); return (0); } static int xhci_pci_take_controller(device_t self) { struct xhci_softc *sc = device_get_softc(self); uint32_t cparams; uint32_t eecp; uint32_t eec; uint16_t to; uint8_t bios_sem; cparams = XREAD4(sc, capa, XHCI_HCSPARAMS0); eec = -1; /* Synchronise with the BIOS if it owns the controller. */ for (eecp = XHCI_HCS0_XECP(cparams) << 2; eecp != 0 && XHCI_XECP_NEXT(eec); eecp += XHCI_XECP_NEXT(eec) << 2) { eec = XREAD4(sc, capa, eecp); if (XHCI_XECP_ID(eec) != XHCI_ID_USB_LEGACY) continue; bios_sem = XREAD1(sc, capa, eecp + XHCI_XECP_BIOS_SEM); if (bios_sem == 0) continue; device_printf(sc->sc_bus.bdev, "waiting for BIOS " "to give up control\n"); XWRITE1(sc, capa, eecp + XHCI_XECP_OS_SEM, 1); to = 500; while (1) { bios_sem = XREAD1(sc, capa, eecp + XHCI_XECP_BIOS_SEM); if (bios_sem == 0) break; if (--to == 0) { device_printf(sc->sc_bus.bdev, "timed out waiting for BIOS\n"); break; } usb_pause_mtx(NULL, hz / 100); /* wait 10ms */ } } return (0); } diff --git a/sys/dev/usb/input/usbhid.c b/sys/dev/usb/input/usbhid.c index d357a699b527..e88588182ae5 100644 --- a/sys/dev/usb/input/usbhid.c +++ b/sys/dev/usb/input/usbhid.c @@ -1,896 +1,900 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 The NetBSD Foundation, Inc. * Copyright (c) 2019 Vladimir Kondratyev * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include /* * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usbhid_debug #include #include #include "hid_if.h" static SYSCTL_NODE(_hw_usb, OID_AUTO, usbhid, CTLFLAG_RW, 0, "USB usbhid"); static int usbhid_enable = 0; SYSCTL_INT(_hw_usb_usbhid, OID_AUTO, enable, CTLFLAG_RWTUN, &usbhid_enable, 0, "Enable usbhid and prefer it to other USB HID drivers"); #ifdef USB_DEBUG static int usbhid_debug = 0; SYSCTL_INT(_hw_usb_usbhid, OID_AUTO, debug, CTLFLAG_RWTUN, &usbhid_debug, 0, "Debug level"); #endif /* Second set of USB transfers for polling mode */ #define POLL_XFER(xfer) ((xfer) + USBHID_N_TRANSFER) enum { USBHID_INTR_OUT_DT, USBHID_INTR_IN_DT, USBHID_CTRL_DT, USBHID_N_TRANSFER, }; struct usbhid_xfer_ctx; typedef int usbhid_callback_t(struct usbhid_xfer_ctx *xfer_ctx); union usbhid_device_request { struct { /* INTR xfers */ uint16_t maxlen; uint16_t actlen; } intr; struct usb_device_request ctrl; /* CTRL xfers */ }; /* Syncronous USB transfer context */ struct usbhid_xfer_ctx { union usbhid_device_request req; uint8_t *buf; int error; usbhid_callback_t *cb; void *cb_ctx; int waiters; bool influx; }; struct usbhid_softc { hid_intr_t *sc_intr_handler; void *sc_intr_ctx; void *sc_intr_buf; struct hid_device_info sc_hw; struct mtx sc_mtx; struct usb_config sc_config[USBHID_N_TRANSFER]; struct usb_xfer *sc_xfer[POLL_XFER(USBHID_N_TRANSFER)]; struct usbhid_xfer_ctx sc_xfer_ctx[POLL_XFER(USBHID_N_TRANSFER)]; bool sc_can_poll; struct usb_device *sc_udev; uint8_t sc_iface_no; uint8_t sc_iface_index; }; /* prototypes */ static device_probe_t usbhid_probe; static device_attach_t usbhid_attach; static device_detach_t usbhid_detach; static usb_callback_t usbhid_intr_out_callback; static usb_callback_t usbhid_intr_in_callback; static usb_callback_t usbhid_ctrl_callback; static usbhid_callback_t usbhid_intr_handler_cb; static usbhid_callback_t usbhid_sync_wakeup_cb; static void usbhid_intr_out_callback(struct usb_xfer *xfer, usb_error_t error) { struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int len; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: len = xfer_ctx->req.intr.maxlen; if (len == 0) { if (USB_IN_POLLING_MODE_FUNC()) xfer_ctx->error = 0; return; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, xfer_ctx->buf, len); usbd_xfer_set_frame_len(xfer, 0, len); usbd_transfer_submit(xfer); xfer_ctx->req.intr.maxlen = 0; if (USB_IN_POLLING_MODE_FUNC()) return; xfer_ctx->error = 0; goto tr_exit; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } xfer_ctx->error = EIO; tr_exit: (void)xfer_ctx->cb(xfer_ctx); return; } } static void usbhid_intr_in_callback(struct usb_xfer *xfer, usb_error_t error) { struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("transferred!\n"); usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, xfer_ctx->buf, actlen); xfer_ctx->req.intr.actlen = actlen; if (xfer_ctx->cb(xfer_ctx) != 0) return; case USB_ST_SETUP: re_submit: usbd_xfer_set_frame_len(xfer, 0, xfer_ctx->req.intr.maxlen); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto re_submit; } return; } } static void usbhid_ctrl_callback(struct usb_xfer *xfer, usb_error_t error) { struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer); struct usb_device_request *req = &xfer_ctx->req.ctrl; struct usb_page_cache *pc; int len = UGETW(req->wLength); bool is_rd = (req->bmRequestType & UT_READ) != 0; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: if (!is_rd && len != 0) { pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, xfer_ctx->buf, len); } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, req, sizeof(*req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(*req)); if (len != 0) usbd_xfer_set_frame_len(xfer, 1, len); usbd_xfer_set_frames(xfer, len != 0 ? 2 : 1); usbd_transfer_submit(xfer); return; case USB_ST_TRANSFERRED: if (is_rd && len != 0) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, sizeof(*req), xfer_ctx->buf, len); } xfer_ctx->error = 0; goto tr_exit; default: /* Error */ /* bomb out */ DPRINTFN(1, "error=%s\n", usbd_errstr(error)); xfer_ctx->error = EIO; tr_exit: (void)xfer_ctx->cb(xfer_ctx); return; } } static int usbhid_intr_handler_cb(struct usbhid_xfer_ctx *xfer_ctx) { struct usbhid_softc *sc = xfer_ctx->cb_ctx; sc->sc_intr_handler(sc->sc_intr_ctx, xfer_ctx->buf, xfer_ctx->req.intr.actlen); return (0); } static int usbhid_sync_wakeup_cb(struct usbhid_xfer_ctx *xfer_ctx) { if (!USB_IN_POLLING_MODE_FUNC()) wakeup(xfer_ctx->cb_ctx); return (ECANCELED); } static const struct usb_config usbhid_config[USBHID_N_TRANSFER] = { [USBHID_INTR_OUT_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .flags = {.pipe_bof = 1,.proxy_buffer = 1}, .callback = &usbhid_intr_out_callback, }, [USBHID_INTR_IN_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1}, .callback = &usbhid_intr_in_callback, }, [USBHID_CTRL_DT] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .flags = {.proxy_buffer = 1}, .callback = &usbhid_ctrl_callback, .timeout = 1000, /* 1 second */ }, }; static inline usb_frlength_t usbhid_xfer_max_len(struct usb_xfer *xfer) { return (xfer == NULL ? 0 : usbd_xfer_max_len(xfer)); } static inline int usbhid_xfer_check_len(struct usbhid_softc* sc, int xfer_idx, hid_size_t len) { if (USB_IN_POLLING_MODE_FUNC()) xfer_idx = POLL_XFER(xfer_idx); if (sc->sc_xfer[xfer_idx] == NULL) return (ENODEV); if (len > usbd_xfer_max_len(sc->sc_xfer[xfer_idx])) return (ENOBUFS); return (0); } static void usbhid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr, void *context, struct hid_rdesc_info *rdesc) { struct usbhid_softc* sc = device_get_softc(dev); uint16_t n; bool nowrite; int error; nowrite = hid_test_quirk(&sc->sc_hw, HQ_NOWRITE); /* * Setup the USB transfers one by one, so they are memory independent * which allows for handling panics triggered by the HID drivers * itself, typically by hkbd via CTRL+ALT+ESC sequences. Or if the HID * keyboard driver was processing a key at the moment of panic. */ if (intr == NULL) { if (sc->sc_can_poll) return; for (n = 0; n != USBHID_N_TRANSFER; n++) { if (nowrite && n == USBHID_INTR_OUT_DT) continue; error = usbd_transfer_setup(sc->sc_udev, &sc->sc_iface_index, sc->sc_xfer + POLL_XFER(n), sc->sc_config + n, 1, (void *)(sc->sc_xfer_ctx + POLL_XFER(n)), &sc->sc_mtx); if (error) DPRINTF("xfer %d setup error=%s\n", n, usbd_errstr(error)); } mtx_lock(&sc->sc_mtx); if (sc->sc_xfer[USBHID_INTR_IN_DT] != NULL && sc->sc_xfer[USBHID_INTR_IN_DT]->flags_int.started) usbd_transfer_start( sc->sc_xfer[POLL_XFER(USBHID_INTR_IN_DT)]); mtx_unlock(&sc->sc_mtx); sc->sc_can_poll = true; return; } sc->sc_intr_handler = intr; sc->sc_intr_ctx = context; bcopy(usbhid_config, sc->sc_config, sizeof(usbhid_config)); bzero(sc->sc_xfer, sizeof(sc->sc_xfer)); /* Set buffer sizes to match HID report sizes */ sc->sc_config[USBHID_INTR_OUT_DT].bufsize = rdesc->osize; sc->sc_config[USBHID_INTR_IN_DT].bufsize = rdesc->isize; sc->sc_config[USBHID_CTRL_DT].bufsize = MAX(rdesc->isize, MAX(rdesc->osize, rdesc->fsize)); for (n = 0; n != USBHID_N_TRANSFER; n++) { if (nowrite && n == USBHID_INTR_OUT_DT) continue; error = usbd_transfer_setup(sc->sc_udev, &sc->sc_iface_index, sc->sc_xfer + n, sc->sc_config + n, 1, (void *)(sc->sc_xfer_ctx + n), &sc->sc_mtx); if (error) DPRINTF("xfer %d setup error=%s\n", n, usbd_errstr(error)); } rdesc->rdsize = usbhid_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]); rdesc->grsize = usbhid_xfer_max_len(sc->sc_xfer[USBHID_CTRL_DT]); rdesc->srsize = rdesc->grsize; rdesc->wrsize = nowrite ? rdesc->srsize : usbhid_xfer_max_len(sc->sc_xfer[USBHID_INTR_OUT_DT]); sc->sc_intr_buf = malloc(rdesc->rdsize, M_USBDEV, M_ZERO | M_WAITOK); } static void usbhid_intr_unsetup(device_t dev, device_t child __unused) { struct usbhid_softc* sc = device_get_softc(dev); usbd_transfer_unsetup(sc->sc_xfer, USBHID_N_TRANSFER); if (sc->sc_can_poll) usbd_transfer_unsetup( sc->sc_xfer, POLL_XFER(USBHID_N_TRANSFER)); sc->sc_can_poll = false; free(sc->sc_intr_buf, M_USBDEV); } static int usbhid_intr_start(device_t dev, device_t child __unused) { struct usbhid_softc* sc = device_get_softc(dev); if (sc->sc_xfer[USBHID_INTR_IN_DT] == NULL) return (ENODEV); mtx_lock(&sc->sc_mtx); sc->sc_xfer_ctx[USBHID_INTR_IN_DT] = (struct usbhid_xfer_ctx) { .req.intr.maxlen = usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]), .cb = usbhid_intr_handler_cb, .cb_ctx = sc, .buf = sc->sc_intr_buf, }; sc->sc_xfer_ctx[POLL_XFER(USBHID_INTR_IN_DT)] = (struct usbhid_xfer_ctx) { .req.intr.maxlen = usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]), .cb = usbhid_intr_handler_cb, .cb_ctx = sc, .buf = sc->sc_intr_buf, }; usbd_transfer_start(sc->sc_xfer[USBHID_INTR_IN_DT]); if (sc->sc_can_poll) usbd_transfer_start(sc->sc_xfer[POLL_XFER(USBHID_INTR_IN_DT)]); mtx_unlock(&sc->sc_mtx); return (0); } static int usbhid_intr_stop(device_t dev, device_t child __unused) { struct usbhid_softc* sc = device_get_softc(dev); usbd_transfer_drain(sc->sc_xfer[USBHID_INTR_IN_DT]); usbd_transfer_drain(sc->sc_xfer[USBHID_INTR_OUT_DT]); if (sc->sc_can_poll) usbd_transfer_drain(sc->sc_xfer[POLL_XFER(USBHID_INTR_IN_DT)]); return (0); } static void usbhid_intr_poll(device_t dev, device_t child __unused) { struct usbhid_softc* sc = device_get_softc(dev); MPASS(sc->sc_can_poll); usbd_transfer_poll(sc->sc_xfer + USBHID_INTR_IN_DT, 1); usbd_transfer_poll(sc->sc_xfer + POLL_XFER(USBHID_INTR_IN_DT), 1); } /* * HID interface */ static int usbhid_sync_xfer(struct usbhid_softc* sc, int xfer_idx, union usbhid_device_request *req, void *buf) { int error, timeout; struct usbhid_xfer_ctx *xfer_ctx; xfer_ctx = sc->sc_xfer_ctx + xfer_idx; if (USB_IN_POLLING_MODE_FUNC()) { xfer_ctx = POLL_XFER(xfer_ctx); xfer_idx = POLL_XFER(xfer_idx); } else { mtx_lock(&sc->sc_mtx); ++xfer_ctx->waiters; while (xfer_ctx->influx) mtx_sleep(&xfer_ctx->waiters, &sc->sc_mtx, 0, "usbhid wt", 0); --xfer_ctx->waiters; xfer_ctx->influx = true; } xfer_ctx->buf = buf; xfer_ctx->req = *req; xfer_ctx->error = ETIMEDOUT; xfer_ctx->cb = &usbhid_sync_wakeup_cb; xfer_ctx->cb_ctx = xfer_ctx; timeout = USB_DEFAULT_TIMEOUT; usbd_transfer_start(sc->sc_xfer[xfer_idx]); if (USB_IN_POLLING_MODE_FUNC()) while (timeout > 0 && xfer_ctx->error == ETIMEDOUT) { usbd_transfer_poll(sc->sc_xfer + xfer_idx, 1); DELAY(1000); timeout--; } else msleep_sbt(xfer_ctx, &sc->sc_mtx, 0, "usbhid io", SBT_1MS * timeout, 0, C_HARDCLOCK); /* Perform usbhid_write() asyncronously to improve pipelining */ if (USB_IN_POLLING_MODE_FUNC() || xfer_ctx->error != 0 || sc->sc_config[xfer_idx].type != UE_INTERRUPT || sc->sc_config[xfer_idx].direction != UE_DIR_OUT) usbd_transfer_stop(sc->sc_xfer[xfer_idx]); error = xfer_ctx->error; if (error == 0) *req = xfer_ctx->req; if (!USB_IN_POLLING_MODE_FUNC()) { xfer_ctx->influx = false; if (xfer_ctx->waiters != 0) wakeup_one(&xfer_ctx->waiters); mtx_unlock(&sc->sc_mtx); } if (error) DPRINTF("USB IO error:%d\n", error); return (error); } static int usbhid_get_rdesc(device_t dev, device_t child __unused, void *buf, hid_size_t len) { struct usbhid_softc* sc = device_get_softc(dev); int error; error = usbd_req_get_report_descriptor(sc->sc_udev, NULL, buf, len, sc->sc_iface_index); if (error) DPRINTF("no report descriptor: %s\n", usbd_errstr(error)); return (error == 0 ? 0 : ENXIO); } static int usbhid_get_report(device_t dev, device_t child __unused, void *buf, hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, maxlen); if (error) return (error); req.ctrl.bmRequestType = UT_READ_CLASS_INTERFACE; req.ctrl.bRequest = UR_GET_REPORT; USETW2(req.ctrl.wValue, type, id); req.ctrl.wIndex[0] = sc->sc_iface_no; req.ctrl.wIndex[1] = 0; USETW(req.ctrl.wLength, maxlen); error = usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, buf); if (!error && actlen != NULL) *actlen = maxlen; return (error); } static int usbhid_set_report(device_t dev, device_t child __unused, const void *buf, hid_size_t len, uint8_t type, uint8_t id) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, len); if (error) return (error); req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.ctrl.bRequest = UR_SET_REPORT; USETW2(req.ctrl.wValue, type, id); req.ctrl.wIndex[0] = sc->sc_iface_no; req.ctrl.wIndex[1] = 0; USETW(req.ctrl.wLength, len); return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, __DECONST(void *, buf))); } static int usbhid_read(device_t dev, device_t child __unused, void *buf, hid_size_t maxlen, hid_size_t *actlen) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_INTR_IN_DT, maxlen); if (error) return (error); req.intr.maxlen = maxlen; error = usbhid_sync_xfer(sc, USBHID_INTR_IN_DT, &req, buf); if (error == 0 && actlen != NULL) *actlen = req.intr.actlen; return (error); } static int usbhid_write(device_t dev, device_t child __unused, const void *buf, hid_size_t len) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_INTR_OUT_DT, len); if (error) return (error); req.intr.maxlen = len; return (usbhid_sync_xfer(sc, USBHID_INTR_OUT_DT, &req, __DECONST(void *, buf))); } static int usbhid_set_idle(device_t dev, device_t child __unused, uint16_t duration, uint8_t id) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, 0); if (error) return (error); /* Duration is measured in 4 milliseconds per unit. */ req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.ctrl.bRequest = UR_SET_IDLE; USETW2(req.ctrl.wValue, (duration + 3) / 4, id); req.ctrl.wIndex[0] = sc->sc_iface_no; req.ctrl.wIndex[1] = 0; USETW(req.ctrl.wLength, 0); return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL)); } static int usbhid_set_protocol(device_t dev, device_t child __unused, uint16_t protocol) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, 0); if (error) return (error); req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.ctrl.bRequest = UR_SET_PROTOCOL; USETW(req.ctrl.wValue, protocol); req.ctrl.wIndex[0] = sc->sc_iface_no; req.ctrl.wIndex[1] = 0; USETW(req.ctrl.wLength, 0); return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL)); } static int usbhid_ioctl(device_t dev, device_t child __unused, unsigned long cmd, uintptr_t data) { struct usbhid_softc* sc = device_get_softc(dev); struct usb_ctl_request *ucr; union usbhid_device_request req; int error; switch (cmd) { case USB_REQUEST: ucr = (struct usb_ctl_request *)data; req.ctrl = ucr->ucr_request; error = usbhid_xfer_check_len( sc, USBHID_CTRL_DT, UGETW(req.ctrl.wLength)); if (error) break; error = usb_check_request(sc->sc_udev, &req.ctrl); if (error) break; error = usbhid_sync_xfer( sc, USBHID_CTRL_DT, &req, ucr->ucr_data); if (error == 0) ucr->ucr_actlen = UGETW(req.ctrl.wLength); break; default: error = EINVAL; } return (error); } static void usbhid_init_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw) { hw->idBus = BUS_USB; hw->idVendor = uaa->info.idVendor; hw->idProduct = uaa->info.idProduct; hw->idVersion = uaa->info.bcdDevice; /* Set various quirks based on usb_attach_arg */ hid_add_dynamic_quirk(hw, USB_GET_DRIVER_INFO(uaa)); } static void usbhid_fill_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw) { struct usb_device *udev = uaa->device; struct usb_interface *iface = uaa->iface; struct usb_hid_descriptor *hid; struct usb_endpoint *ep; snprintf(hw->name, sizeof(hw->name), "%s %s", usb_get_manufacturer(udev), usb_get_product(udev)); strlcpy(hw->serial, usb_get_serial(udev), sizeof(hw->serial)); if (uaa->info.bInterfaceClass == UICLASS_HID && iface != NULL && iface->idesc != NULL) { hid = hid_get_descriptor_from_usb( usbd_get_config_descriptor(udev), iface->idesc); if (hid != NULL) hw->rdescsize = UGETW(hid->descrs[0].wDescriptorLength); } /* See if there is a interrupt out endpoint. */ ep = usbd_get_endpoint(udev, uaa->info.bIfaceIndex, usbhid_config + USBHID_INTR_OUT_DT); if (ep == NULL || ep->methods == NULL) hid_add_dynamic_quirk(hw, HQ_NOWRITE); } static const STRUCT_USB_HOST_ID usbhid_devs[] = { /* the Xbox 360 gamepad doesn't use the HID class */ {USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER), USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD), USB_DRIVER_INFO(HQ_IS_XBOX360GP)}, /* HID keyboard with boot protocol support */ {USB_IFACE_CLASS(UICLASS_HID), USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), USB_IFACE_PROTOCOL(UIPROTO_BOOT_KEYBOARD), USB_DRIVER_INFO(HQ_HAS_KBD_BOOTPROTO)}, /* HID mouse with boot protocol support */ {USB_IFACE_CLASS(UICLASS_HID), USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), USB_IFACE_PROTOCOL(UIPROTO_MOUSE), USB_DRIVER_INFO(HQ_HAS_MS_BOOTPROTO)}, /* generic HID class */ {USB_IFACE_CLASS(UICLASS_HID), USB_DRIVER_INFO(HQ_NONE)}, }; static int usbhid_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct usbhid_softc *sc = device_get_softc(dev); int error; DPRINTFN(11, "\n"); if (usbhid_enable == 0) return (ENXIO); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); error = usbd_lookup_id_by_uaa(usbhid_devs, sizeof(usbhid_devs), uaa); if (error) return (error); if (usb_test_quirk(uaa, UQ_HID_IGNORE)) return (ENXIO); /* * Setup temporary hid_device_info so that we can figure out some * basic quirks for this device. */ usbhid_init_device_info(uaa, &sc->sc_hw); if (hid_test_quirk(&sc->sc_hw, HQ_HID_IGNORE)) return (ENXIO); return (BUS_PROBE_DEFAULT + 1); } static int usbhid_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct usbhid_softc *sc = device_get_softc(dev); device_t child; int error = 0; DPRINTFN(10, "sc=%p\n", sc); device_set_usb_desc(dev); sc->sc_udev = uaa->device; sc->sc_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index = uaa->info.bIfaceIndex; usbhid_fill_device_info(uaa, &sc->sc_hw); error = usbd_req_set_idle(uaa->device, NULL, uaa->info.bIfaceIndex, 0, 0); if (error) DPRINTF("set idle failed, error=%s (ignored)\n", usbd_errstr(error)); mtx_init(&sc->sc_mtx, "usbhid lock", NULL, MTX_DEF); child = device_add_child(dev, "hidbus", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(dev, "Could not add hidbus device\n"); usbhid_detach(dev); return (ENOMEM); } device_set_ivars(child, &sc->sc_hw); bus_attach_children(dev); return (0); /* success */ } static int usbhid_detach(device_t dev) { struct usbhid_softc *sc = device_get_softc(dev); + int error; + + error = bus_generic_detach(dev); + if (error != 0) + return (error); - device_delete_children(dev); mtx_destroy(&sc->sc_mtx); return (0); } static device_method_t usbhid_methods[] = { DEVMETHOD(device_probe, usbhid_probe), DEVMETHOD(device_attach, usbhid_attach), DEVMETHOD(device_detach, usbhid_detach), DEVMETHOD(hid_intr_setup, usbhid_intr_setup), DEVMETHOD(hid_intr_unsetup, usbhid_intr_unsetup), DEVMETHOD(hid_intr_start, usbhid_intr_start), DEVMETHOD(hid_intr_stop, usbhid_intr_stop), DEVMETHOD(hid_intr_poll, usbhid_intr_poll), /* HID interface */ DEVMETHOD(hid_get_rdesc, usbhid_get_rdesc), DEVMETHOD(hid_read, usbhid_read), DEVMETHOD(hid_write, usbhid_write), DEVMETHOD(hid_get_report, usbhid_get_report), DEVMETHOD(hid_set_report, usbhid_set_report), DEVMETHOD(hid_set_idle, usbhid_set_idle), DEVMETHOD(hid_set_protocol, usbhid_set_protocol), DEVMETHOD(hid_ioctl, usbhid_ioctl), DEVMETHOD_END }; static driver_t usbhid_driver = { .name = "usbhid", .methods = usbhid_methods, .size = sizeof(struct usbhid_softc), }; DRIVER_MODULE(usbhid, uhub, usbhid_driver, NULL, NULL); MODULE_DEPEND(usbhid, usb, 1, 1, 1); MODULE_DEPEND(usbhid, hid, 1, 1, 1); MODULE_DEPEND(usbhid, hidbus, 1, 1, 1); MODULE_VERSION(usbhid, 1); USB_PNP_HOST_INFO(usbhid_devs); diff --git a/sys/dev/usb/video/udl.c b/sys/dev/usb/video/udl.c index b5af3be8bc50..213f1f5bb957 100644 --- a/sys/dev/usb/video/udl.c +++ b/sys/dev/usb/video/udl.c @@ -1,1150 +1,1153 @@ /* $OpenBSD: udl.c,v 1.81 2014/12/09 07:05:06 doug Exp $ */ /*- * 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 static SYSCTL_NODE(_hw_usb, OID_AUTO, udl, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB UDL"); #ifdef USB_DEBUG static int udl_debug = 0; SYSCTL_INT(_hw_usb_udl, OID_AUTO, debug, CTLFLAG_RWTUN, &udl_debug, 0, "Debug level"); #endif #define UDL_FPS_MAX 60 #define UDL_FPS_MIN 1 static int udl_fps = 25; SYSCTL_INT(_hw_usb_udl, OID_AUTO, fps, CTLFLAG_RWTUN, &udl_fps, 0, "Frames Per Second, 1-60"); static struct mtx udl_buffer_mtx; static struct udl_buffer_head udl_buffer_head; MALLOC_DEFINE(M_USB_DL, "USB", "USB DisplayLink"); /* * 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; static fb_setblankmode_t udl_fb_setblankmode; 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); static int udl_power_save(struct udl_softc *, int, int); 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 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), DEVMETHOD_END }; static driver_t udl_driver = { .name = "udl", .methods = udl_methods, .size = sizeof(struct udl_softc), }; DRIVER_MODULE(udl, uhub, udl_driver, 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_PLUGABLE, 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)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_ITEC, DL165)}, {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_DVI_19, DL165)}, }; static void udl_buffer_init(void *arg) { mtx_init(&udl_buffer_mtx, "USB", "UDL", MTX_DEF); TAILQ_INIT(&udl_buffer_head); } SYSINIT(udl_buffer_init, SI_SUB_LOCK, SI_ORDER_FIRST, udl_buffer_init, NULL); CTASSERT(sizeof(struct udl_buffer) < PAGE_SIZE); static void * udl_buffer_alloc(uint32_t size) { struct udl_buffer *buf; mtx_lock(&udl_buffer_mtx); TAILQ_FOREACH(buf, &udl_buffer_head, entry) { if (buf->size == size) { TAILQ_REMOVE(&udl_buffer_head, buf, entry); break; } } mtx_unlock(&udl_buffer_mtx); if (buf != NULL) { uint8_t *ptr = ((uint8_t *)buf) - size; /* wipe and recycle buffer */ memset(ptr, 0, size); /* return buffer pointer */ return (ptr); } /* allocate new buffer */ return (malloc(size + sizeof(*buf), M_USB_DL, M_WAITOK | M_ZERO)); } static void udl_buffer_free(void *_buf, uint32_t size) { struct udl_buffer *buf; /* check for NULL pointer */ if (_buf == NULL) return; /* compute pointer to recycle list */ buf = (struct udl_buffer *)(((uint8_t *)_buf) + size); /* * Memory mapped buffers should never be freed. * Put display buffer into a recycle list. */ mtx_lock(&udl_buffer_mtx); buf->size = size; TAILQ_INSERT_TAIL(&udl_buffer_head, buf, entry); mtx_unlock(&udl_buffer_mtx); } 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].hdisplay); } static uint32_t udl_get_fb_height(struct udl_softc *sc) { unsigned i = sc->sc_cur_mode; return (udl_modes[i].vdisplay); } 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); int fps; if (sc->sc_power_save == 0) { fps = udl_fps; /* figure out number of frames per second */ if (fps < UDL_FPS_MIN) fps = UDL_FPS_MIN; else if (fps > UDL_FPS_MAX) fps = UDL_FPS_MAX; 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]); } else { fps = 1; } callout_reset(&sc->sc_callout, hz / fps, &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 = 0; sc->sc_fb_info.fb_vbase = (uintptr_t)sc->sc_fb_addr; sc->sc_fb_info.fb_priv = sc; sc->sc_fb_info.setblankmode = &udl_fb_setblankmode; sc->sc_fbdev = device_add_child(dev, "fbd", DEVICE_UNIT_ANY); 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); + int error; /* delete all child devices */ - device_delete_children(dev); + error = bus_generic_detach(dev); + if (error != 0) + return (error); 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); /* put main framebuffer into a recycle list, if any */ udl_buffer_free(sc->sc_fb_addr, sc->sc_fb_size); /* free shadow framebuffer memory, if any */ free(sc->sc_fb_copy, M_USB_DL); 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); } static int udl_fb_setblankmode(void *arg, int mode) { struct udl_softc *sc = arg; 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); } static struct udl_cmd_buf * udl_cmd_buf_alloc_locked(struct udl_softc *sc, int flags) { struct udl_cmd_buf *cb; 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; } return (cb); } static struct udl_cmd_buf * udl_cmd_buf_alloc(struct udl_softc *sc, int flags) { struct udl_cmd_buf *cb; UDL_LOCK(sc); cb = udl_cmd_buf_alloc_locked(sc, flags); 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_locked(struct udl_softc *sc) { const uint32_t max = udl_get_fb_size(sc); /* check if framebuffer is not ready */ if (sc->sc_fb_addr == NULL || sc->sc_fb_copy == NULL) return (NULL); 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_locked(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_locked(sc); if (cb == NULL) break; } else { 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); } 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); } 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 determine 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); /* check for zero size */ if (size == 0) size = PAGE_SIZE; /* * It is assumed that allocations above PAGE_SIZE bytes will * be PAGE_SIZE aligned for use with mmap() */ sc->sc_fb_addr = udl_buffer_alloc(size); sc->sc_fb_copy = malloc(size, M_USB_DL, 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; break; case DL160: sc->sc_chip = DL165; break; } break; } DPRINTF("chip 0x%04x\n", sc->sc_chip); error = udl_read_1(sc, 0xc484, &ui8); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("read 0x%02x from 0xc484\n", ui8); error = udl_write_1(sc, 0xc41f, 0x01); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("write 0x01 to 0xc41f\n"); error = udl_read_edid(sc, sc->sc_edid); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("read EDID\n"); error = udl_set_enc_key(sc, __DECONST(void *, udl_null_key_1), sizeof(udl_null_key_1)); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("set encryption key\n"); error = udl_write_1(sc, 0xc40b, 0x00); if (error != USB_ERR_NORMAL_COMPLETION) return (error); DPRINTF("write 0x00 to 0xc40b\n"); return (USB_ERR_NORMAL_COMPLETION); } static void udl_init_fb_offsets(struct udl_cmd_buf *cb, uint32_t start16, uint32_t stride16, uint32_t start8, uint32_t stride8) { udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0x00); udl_cmd_write_reg_3(cb, UDL_REG_ADDR_START16, start16); udl_cmd_write_reg_3(cb, UDL_REG_ADDR_STRIDE16, stride16); udl_cmd_write_reg_3(cb, UDL_REG_ADDR_START8, start8); udl_cmd_write_reg_3(cb, UDL_REG_ADDR_STRIDE8, stride8); udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff); } static int udl_init_resolution(struct udl_softc *sc) { const uint32_t max = udl_get_fb_size(sc); const uint8_t *buf = udl_modes[sc->sc_cur_mode].mode; struct udl_cmd_buf *cb; uint32_t delta; uint32_t i; int error; /* get new buffer */ cb = udl_cmd_buf_alloc(sc, M_WAITOK); if (cb == NULL) return (EAGAIN); /* write resolution values and set video memory offsets */ udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0x00); for (i = 0; i < UDL_MODE_SIZE; i++) udl_cmd_write_reg_1(cb, i, buf[i]); udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff); udl_init_fb_offsets(cb, 0x000000, 0x000a00, 0x555555, 0x000500); udl_cmd_buf_send(sc, cb); /* fill screen with black color */ for (i = 0; i < max; i += delta) { static const uint8_t udl_black[UDL_CMD_MAX_PIXEL_COUNT * 2] __aligned(4); delta = max - i; if (delta > UDL_CMD_MAX_PIXEL_COUNT * 2) delta = UDL_CMD_MAX_PIXEL_COUNT * 2; if (i == 0) error = udl_cmd_write_buf_le16(sc, udl_black, i, delta / 2, M_WAITOK); else error = udl_cmd_buf_copy_le16(sc, 0, i, delta / 2, M_WAITOK); if (error) return (error); } /* get new buffer */ cb = udl_cmd_buf_alloc(sc, M_WAITOK); if (cb == NULL) return (EAGAIN); /* show framebuffer content */ udl_cmd_write_reg_1(cb, UDL_REG_SCREEN, UDL_REG_SCREEN_ON); udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff); udl_cmd_buf_send(sc, cb); return (0); } static void udl_select_mode(struct udl_softc *sc) { struct udl_mode mode; int index = UDL_MAX_MODES; int i; /* try to get the preferred mode from EDID */ edid_parse(sc->sc_edid, &sc->sc_edid_info); #ifdef USB_DEBUG edid_print(&sc->sc_edid_info); #endif if (sc->sc_edid_info.edid_preferred_mode != NULL) { mode.hz = (sc->sc_edid_info.edid_preferred_mode->dot_clock * 1000) / (sc->sc_edid_info.edid_preferred_mode->htotal * sc->sc_edid_info.edid_preferred_mode->vtotal); mode.clock = sc->sc_edid_info.edid_preferred_mode->dot_clock / 10; mode.hdisplay = sc->sc_edid_info.edid_preferred_mode->hdisplay; mode.vdisplay = sc->sc_edid_info.edid_preferred_mode->vdisplay; index = udl_lookup_mode(mode.hdisplay, mode.vdisplay, mode.hz, sc->sc_chip, mode.clock); sc->sc_cur_mode = index; } else { DPRINTF("no preferred mode found!\n"); } if (index == UDL_MAX_MODES) { DPRINTF("no mode line found\n"); i = 0; while (i < sc->sc_edid_info.edid_nmodes) { mode.hz = (sc->sc_edid_info.edid_modes[i].dot_clock * 1000) / (sc->sc_edid_info.edid_modes[i].htotal * sc->sc_edid_info.edid_modes[i].vtotal); mode.clock = sc->sc_edid_info.edid_modes[i].dot_clock / 10; mode.hdisplay = sc->sc_edid_info.edid_modes[i].hdisplay; mode.vdisplay = sc->sc_edid_info.edid_modes[i].vdisplay; index = udl_lookup_mode(mode.hdisplay, mode.vdisplay, mode.hz, sc->sc_chip, mode.clock); if (index < UDL_MAX_MODES) if ((sc->sc_cur_mode == UDL_MAX_MODES) || (index > sc->sc_cur_mode)) sc->sc_cur_mode = index; i++; } } /* * If no mode found use default. */ if (sc->sc_cur_mode == UDL_MAX_MODES) sc->sc_cur_mode = udl_lookup_mode(800, 600, 60, sc->sc_chip, 0); } static int udl_cmd_write_buf_le16(struct udl_softc *sc, const uint8_t *buf, uint32_t off, uint8_t pixels, int flags) { struct udl_cmd_buf *cb; cb = udl_cmd_buf_alloc(sc, flags); if (cb == NULL) return (EAGAIN); 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, off); udl_cmd_insert_int_1(cb, pixels); udl_cmd_insert_buf_le16(cb, buf, 2 * pixels); udl_cmd_buf_send(sc, cb); return (0); } static int udl_cmd_buf_copy_le16(struct udl_softc *sc, uint32_t src, uint32_t dst, uint8_t pixels, int flags) { struct udl_cmd_buf *cb; cb = udl_cmd_buf_alloc(sc, flags); if (cb == NULL) return (EAGAIN); udl_cmd_insert_int_1(cb, UDL_BULK_SOC); udl_cmd_insert_int_1(cb, UDL_BULK_CMD_FB_COPY | UDL_BULK_CMD_FB_WORD); udl_cmd_insert_int_3(cb, dst); udl_cmd_insert_int_1(cb, pixels); udl_cmd_insert_int_3(cb, src); udl_cmd_buf_send(sc, cb); return (0); }