NetBSD/sys/arch/arm/omap/omap3_ehci.c

845 lines
24 KiB
C

/* $NetBSD: omap3_ehci.c,v 1.10 2014/03/29 23:32:41 matt Exp $ */
/*-
* Copyright (c) 2010-2012 Jared D. McNeill <jmcneill@invisible.ca>
* 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. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: omap3_ehci.c,v 1.10 2014/03/29 23:32:41 matt Exp $");
#include "locators.h"
#include "opt_omap.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <sys/gpio.h>
#include <machine/intr.h>
#include <dev/pci/pcidevs.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <dev/usb/usb_mem.h>
#include <dev/usb/ehcireg.h>
#include <dev/usb/ehcivar.h>
#include <arm/omap/omap_var.h>
#include <arm/omap/omap2_gpio.h>
#include <arm/omap/omap2_obioreg.h>
#include <arm/omap/omap2_obiovar.h>
#include <arm/omap/omap2_reg.h>
#include <arm/omap/omap3_usbtllreg.h>
#include <arm/omap/omap3_uhhreg.h>
/* CORE_CM */
#define CORE_CM_BASE (OMAP2_CM_BASE + 0x0a00)
#define CORE_CM_SIZE 0x2000
/* CORE_CM registers */
#define CM_FCLKEN1_CORE 0x00
#define CM_FCLKEN3_CORE 0x08
#define EN_USBTLL 4 /* USB TLL clock enable */
/* also valid for CM_ICLKEN3_CORE */
#define CM_ICLKEN1_CORE 0x10
#define CM_ICLKEN3_CORE 0x18
#define CM_IDLEST1_CORE 0x20
#define CM_IDLEST3_CORE 0x28
#define CM_AUTOIDLE1_CORE 0x30
#define CM_AUTOIDLE3_CORE 0x38
#define AUTO_USBTLL 4 /* USB TLL auto enable/disable */
#define CM_CLKSEL_CORE 0x40
#define CM_CLKSTCTRL_CORE 0x48
#define CM_CLKSTST_CORE 0x4c
/* Clock_Control_Reg_CM */
#define CCR_CM_BASE (OMAP2_CM_BASE + 0x0d00)
#define CCR_CM_SIZE 0x800
/* DPLL clock control registers */
#define CM_CLKEN2_PLL 0x04
#define CM_IDLEST2_CKGEN 0x24
#define ST_PERIPH2_CLK 1 /* DPLL5 is locked */
#define CM_CLKSEL4_PLL 0x4c
#define CM_CLKSEL5_PLL 0x50
#define CM_AUTOIDLE2_PLL 0x34
#define AUTO_PERIPH2_DPLL 1 /* DPLL5 automatic control */
/* USBHOST_CM */
#define USBHOST_CM_BASE (OMAP2_CM_BASE + 0x1400)
#define USBHOST_CM_SIZE 0x2000
/* USBHOST_CM registers */
#define CM_FCLKEN_USBHOST 0x00
#define EN_USBHOST1 1 /* USB HOST 48 MHz clock enable */
#define EN_USBHOST2 2 /* USB HOST 120 MHz clock enable */
#define CM_ICLKEN_USBHOST 0x10
#define EN_USBHOST 1 /* USB HOST clock enable */
#define CM_IDLEST_USBHOST 0x20
#define CM_AUTOIDLE_USBHOST 0x30
#define CM_SLEEPDEP_USBHOST 0x44
#define CM_CLKSTCTRL_USBHOST 0x48
#define CM_CLKSTST_USBHOST 0x4c
/* USBTLL module */
#ifdef OMAP_3XXX
#define USBTLL_BASE 0x48062000
#elif defined(OMAP4) || defined(OMAP5)
#define USBTLL_BASE 0x4a062000
#endif
#define USBTLL_SIZE 0x1000
/* HS USB HOST module */
#ifdef OMAP_3XXX
#define UHH_BASE 0x48064000
#elif defined(OMAP4) || defined(OMAP5)
#define UHH_BASE 0x4a064000
#endif
#define UHH_SIZE 0x1000
enum omap3_ehci_port_mode {
OMAP3_EHCI_PORT_MODE_NONE,
OMAP3_EHCI_PORT_MODE_PHY,
OMAP3_EHCI_PORT_MODE_TLL,
OMAP3_EHCI_PORT_MODE_HSIC,
};
static const uint32_t uhh_map[3][4] = {
#if defined(OMAP4) || defined(OMAP5)
{
[OMAP3_EHCI_PORT_MODE_NONE] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_UTMI, UHH_HOSTCONFIG_P1_MODE),
[OMAP3_EHCI_PORT_MODE_PHY] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_ULPI_PHY, UHH_HOSTCONFIG_P1_MODE),
[OMAP3_EHCI_PORT_MODE_TLL] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_UTMI, UHH_HOSTCONFIG_P1_MODE),
[OMAP3_EHCI_PORT_MODE_HSIC] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_HSIC, UHH_HOSTCONFIG_P1_MODE),
}, {
[OMAP3_EHCI_PORT_MODE_NONE] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_UTMI, UHH_HOSTCONFIG_P2_MODE),
[OMAP3_EHCI_PORT_MODE_PHY] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_ULPI_PHY, UHH_HOSTCONFIG_P2_MODE),
[OMAP3_EHCI_PORT_MODE_TLL] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_UTMI, UHH_HOSTCONFIG_P2_MODE),
[OMAP3_EHCI_PORT_MODE_HSIC] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_HSIC, UHH_HOSTCONFIG_P2_MODE),
}, {
[OMAP3_EHCI_PORT_MODE_NONE] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_UTMI, UHH_HOSTCONFIG_P3_MODE),
[OMAP3_EHCI_PORT_MODE_PHY] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_ULPI_PHY, UHH_HOSTCONFIG_P3_MODE),
[OMAP3_EHCI_PORT_MODE_TLL] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_UTMI, UHH_HOSTCONFIG_P3_MODE),
[OMAP3_EHCI_PORT_MODE_HSIC] =
__SHIFTIN(UHH_HOSTCONFIG_PMODE_HSIC, UHH_HOSTCONFIG_P3_MODE),
}
#else
{
[OMAP3_EHCI_PORT_MODE_NONE] = UHH_HOSTCONFIG_P1_ULPI_BYPASS,
[OMAP3_EHCI_PORT_MODE_PHY] = 0,
[OMAP3_EHCI_PORT_MODE_TLL] = UHH_HOSTCONFIG_P1_ULPI_BYPASS,
[OMAP3_EHCI_PORT_MODE_HSIC] = UHH_HOSTCONFIG_P1_ULPI_BYPASS,
}, {
[OMAP3_EHCI_PORT_MODE_NONE] = UHH_HOSTCONFIG_P2_ULPI_BYPASS,
[OMAP3_EHCI_PORT_MODE_PHY] = 0,
[OMAP3_EHCI_PORT_MODE_TLL] = UHH_HOSTCONFIG_P2_ULPI_BYPASS,
[OMAP3_EHCI_PORT_MODE_HSIC] = UHH_HOSTCONFIG_P2_ULPI_BYPASS,
}, {
[OMAP3_EHCI_PORT_MODE_NONE] = UHH_HOSTCONFIG_P3_ULPI_BYPASS,
[OMAP3_EHCI_PORT_MODE_PHY] = 0,
[OMAP3_EHCI_PORT_MODE_TLL] = UHH_HOSTCONFIG_P3_ULPI_BYPASS,
[OMAP3_EHCI_PORT_MODE_HSIC] = UHH_HOSTCONFIG_P3_ULPI_BYPASS,
},
#endif
};
struct omap3_ehci_softc {
ehci_softc_t sc;
void *sc_ih;
bus_space_handle_t sc_ioh_usbtll;
bus_size_t sc_usbtll_size;
bus_space_handle_t sc_ioh_uhh;
bus_size_t sc_uhh_size;
uint16_t sc_nports;
bool sc_phy_reset;
struct {
enum omap3_ehci_port_mode mode;
int gpio;
bool value;
} sc_portconfig[3];
struct {
uint16_t m, n, m2;
} sc_dpll5;
};
#ifdef OMAP_3XXX
static void omap3_dpll5_init(struct omap3_ehci_softc *);
static void omap3_usbhost_init(struct omap3_ehci_softc *, int);
#endif
#if defined(OMAP4) || defined(OMAP5)
static void omap4_usbhost_init(struct omap3_ehci_softc *, int);
#endif
static void usbtll_reset(struct omap3_ehci_softc *);
static void usbtll_power(struct omap3_ehci_softc *, bool);
static void usbtll_init(struct omap3_ehci_softc *, int);
static void uhh_power(struct omap3_ehci_softc *, bool);
static void uhh_portconfig(struct omap3_ehci_softc *);
#define USBTLL_READ4(sc, reg) bus_space_read_4((sc)->sc.iot, (sc)->sc_ioh_usbtll, (reg))
#define USBTLL_WRITE4(sc, reg, v) bus_space_write_4((sc)->sc.iot, (sc)->sc_ioh_usbtll, (reg), (v))
#define UHH_READ4(sc, reg) bus_space_read_4((sc)->sc.iot, (sc)->sc_ioh_uhh, (reg))
#define UHH_WRITE4(sc, reg, v) bus_space_write_4((sc)->sc.iot, (sc)->sc_ioh_uhh, (reg), (v))
/* Table 23-55 "EHCI Registers Mapping Summary" */
#define EHCI_INSNREG00 0x90
#define EHCI_INSNREG01 0x94
#define EHCI_INSNREG02 0x98
#define EHCI_INSNREG03 0x9c
#define EHCI_INSNREG04 0xa0
#define EHCI_INSNREG05_ULPI 0xa4
#define ULPI_CONTROL (1 << 31)
#define ULPI_PORTSEL_SHIFT 24
#define ULPI_PORTSEL_MASK 0xf
#define ULPI_OPSEL_SHIFT 22
#define ULPI_OPSEL_MASK 0x3
#define ULPI_OPSEL_WRITE 0x2
#define ULPI_OPSEL_READ 0x3
#define ULPI_REGADD_SHIFT 16
#define ULPI_REGADD_MASK 0x3f
#define ULPI_WRDATA_SHIFT 0
#define ULPI_WRDATA_MASK 0xff
#define EHCI_INSNREG05_ITMI 0xa4
static void
omap3_ehci_phy_reset(struct omap3_ehci_softc *sc, unsigned int port)
{
uint32_t reg, v;
int retry = 1000;
reg = ULPI_FUNCTION_CTRL_RESET |
(5 << ULPI_REGADD_SHIFT) |
(ULPI_OPSEL_WRITE << ULPI_OPSEL_SHIFT) |
(((port + 1) & ULPI_PORTSEL_MASK) << ULPI_PORTSEL_SHIFT) |
ULPI_CONTROL;
EOWRITE4(&sc->sc, EHCI_INSNREG05_ULPI, reg);
v = EOREAD4(&sc->sc, EHCI_INSNREG05_ULPI);
while (v & (1 << 31) && --retry > 0) {
delay(10000);
v = EOREAD4(&sc->sc, EHCI_INSNREG05_ULPI);
}
if (retry == 0)
aprint_error_dev(sc->sc.sc_dev, "phy reset timeout, port = %d\n", port);
}
static void
omap3_ehci_find_companions(struct omap3_ehci_softc *sc)
{
device_t dv;
deviter_t di;
sc->sc.sc_ncomp = 0;
for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST);
dv != NULL;
dv = deviter_next(&di)) {
if (device_is_a(dv, "ohci") &&
device_parent(dv) == device_parent(sc->sc.sc_dev)) {
#ifdef OMAP3_EHCI_DEBUG
aprint_normal(" adding companion '%s'\n", device_xname(dv));
#endif
sc->sc.sc_comps[sc->sc.sc_ncomp++] = dv;
}
}
deviter_release(&di);
}
static void
omap3_ehci_attach1(device_t self)
{
struct omap3_ehci_softc *sc = device_private(self);
usbd_status err;
for (u_int i = 0; sc->sc_phy_reset && i < sc->sc_nports; i++) {
if (sc->sc_portconfig[i].gpio != -1) {
if (!omap2_gpio_has_pin(sc->sc_portconfig[i].gpio)) {
aprint_error_dev(self, "WARNING: "
"gpio pin %d not available\n",
sc->sc_portconfig[i].gpio);
continue;
}
omap2_gpio_ctl(sc->sc_portconfig[i].gpio, GPIO_PIN_OUTPUT);
omap2_gpio_write(sc->sc_portconfig[i].gpio,
!sc->sc_portconfig[i].value); // off
delay(10);
}
}
usbtll_power(sc, true);
usbtll_init(sc, 3);
uhh_power(sc, true);
uhh_portconfig(sc);
for (u_int i = 0; i < sc->sc_nports; i++) {
if (sc->sc_portconfig[i].mode == OMAP3_EHCI_PORT_MODE_PHY) {
omap3_ehci_phy_reset(sc, i);
}
}
delay(10);
for (u_int i = 0; sc->sc_phy_reset && i < sc->sc_nports; i++) {
if (sc->sc_portconfig[i].gpio != -1) {
if (!omap2_gpio_has_pin(sc->sc_portconfig[i].gpio))
continue;
omap2_gpio_ctl(sc->sc_portconfig[i].gpio, GPIO_PIN_OUTPUT);
omap2_gpio_write(sc->sc_portconfig[i].gpio,
sc->sc_portconfig[i].value);
delay(10);
}
}
omap3_ehci_find_companions(sc);
err = ehci_init(&sc->sc);
if (err != USBD_NORMAL_COMPLETION) {
aprint_error_dev(self, "init failed, error = %d\n", err);
return;
}
sc->sc.sc_child = config_found(self, &sc->sc.sc_bus, usbctlprint);
}
static int
omap3_ehci_match(device_t parent, cfdata_t match, void *opaque)
{
#ifdef OMAP3
struct obio_attach_args *obio = opaque;
#endif
#ifdef OMAP_3XXX
if (obio->obio_addr == EHCI1_BASE_OMAP3)
return 1;
#endif
#if defined(OMAP4) || defined(OMAP5)
if (obio->obio_addr == EHCI1_BASE_OMAP4)
return 1;
#endif
return 0;
}
static enum omap3_ehci_port_mode
omap3_ehci_get_port_mode(prop_dictionary_t prop, const char *key)
{
const char *s = NULL;
enum omap3_ehci_port_mode mode = OMAP3_EHCI_PORT_MODE_NONE;
if (prop_dictionary_get_cstring_nocopy(prop, key, &s) && s != NULL) {
if (strcmp(s, "phy") == 0) {
mode = OMAP3_EHCI_PORT_MODE_PHY;
#ifdef OMAP_3XXX
} else if (strcmp(s, "tll") == 0) {
mode = OMAP3_EHCI_PORT_MODE_TLL;
#endif
#if defined(OMAP4) || defined(OMAP5)
} else if (strcmp(s, "hsic") == 0) {
mode = OMAP3_EHCI_PORT_MODE_HSIC;
#endif
} else if (strcmp(s, "none") == 0) {
mode = OMAP3_EHCI_PORT_MODE_NONE;
} else {
panic("%s: unknown port mode %s", __func__, s);
}
}
return mode;
}
static int
omap3_ehci_get_port_gpio(prop_dictionary_t prop, const char *key)
{
int16_t gpio;
if (prop_dictionary_get_int16(prop, key, &gpio) == false)
gpio = -1;
return gpio;
}
static void
omap3_ehci_parse_properties(struct omap3_ehci_softc *sc, prop_dictionary_t prop)
{
prop_dictionary_get_uint16(prop, "nports", &sc->sc_nports);
prop_dictionary_get_bool(prop, "phy-reset", &sc->sc_phy_reset);
sc->sc_portconfig[0].mode = omap3_ehci_get_port_mode(prop, "port0-mode");
sc->sc_portconfig[0].gpio = omap3_ehci_get_port_gpio(prop, "port0-gpio");
prop_dictionary_get_bool(prop, "port0-gpioval", &sc->sc_portconfig[0].value);
if (sc->sc_nports > 1) {
sc->sc_portconfig[1].mode = omap3_ehci_get_port_mode(prop, "port1-mode");
sc->sc_portconfig[1].gpio = omap3_ehci_get_port_gpio(prop, "port1-gpio");
prop_dictionary_get_bool(prop, "port1-gpioval", &sc->sc_portconfig[1].value);
}
if (sc->sc_nports > 2) {
sc->sc_portconfig[2].mode = omap3_ehci_get_port_mode(prop, "port2-mode");
sc->sc_portconfig[2].gpio = omap3_ehci_get_port_gpio(prop, "port2-gpio");
prop_dictionary_get_bool(prop, "port2-gpioval", &sc->sc_portconfig[2].value);
}
#ifdef OMAP_3XXX
prop_dictionary_get_uint16(prop, "dpll5-m", &sc->sc_dpll5.m);
prop_dictionary_get_uint16(prop, "dpll5-n", &sc->sc_dpll5.n);
prop_dictionary_get_uint16(prop, "dpll5-m2", &sc->sc_dpll5.m2);
#endif
#ifdef OMAP3_EHCI_DEBUG
printf(" GPIO PHY reset: %d\n", sc->sc_phy_reset);
printf(" Port #0: mode %d, gpio %d\n", sc->sc_portconfig[0].mode, sc->sc_portconfig[0].gpio);
printf(" Port #1: mode %d, gpio %d\n", sc->sc_portconfig[1].mode, sc->sc_portconfig[1].gpio);
printf(" Port #2: mode %d, gpio %d\n", sc->sc_portconfig[2].mode, sc->sc_portconfig[2].gpio);
#ifdef OMAP_3XXX
printf(" DPLL5: m=%d n=%d m2=%d\n", sc->sc_dpll5.m, sc->sc_dpll5.n, sc->sc_dpll5.m2);
#endif
#endif
}
static void
omap3_ehci_attach(device_t parent, device_t self, void *opaque)
{
struct omap3_ehci_softc *sc = device_private(self);
struct obio_attach_args *obio = opaque;
int rv;
sc->sc.sc_dev = self;
sc->sc.sc_bus.hci_private = sc;
aprint_naive("\n");
aprint_normal(": OMAP USB controller\n");
omap3_ehci_parse_properties(sc, device_properties(self));
sc->sc.iot = obio->obio_iot;
rv = bus_space_map(obio->obio_iot, obio->obio_addr, obio->obio_size, 0, &sc->sc.ioh);
if (rv) {
aprint_error_dev(self, "couldn't map memory space\n");
return;
}
sc->sc.sc_size = obio->obio_size;
rv = bus_space_map(obio->obio_iot, USBTLL_BASE, USBTLL_SIZE, 0, &sc->sc_ioh_usbtll);
if (rv) {
aprint_error_dev(self, "couldn't map usbtll memory space\n");
return;
}
sc->sc_usbtll_size = USBTLL_SIZE;
rv = bus_space_map(obio->obio_iot, UHH_BASE, UHH_SIZE, 0, &sc->sc_ioh_uhh);
if (rv) {
aprint_error_dev(self, "couldn't map uhh memory space\n");
return;
}
sc->sc_uhh_size = UHH_SIZE;
sc->sc.sc_bus.dmatag = obio->obio_dmat;
sc->sc.sc_bus.usbrev = USBREV_2_0;
sc->sc.sc_id_vendor = PCI_VENDOR_TI;
strlcpy(sc->sc.sc_vendor, "OMAP3", sizeof(sc->sc.sc_vendor));
#ifdef OMAP_3XXX
omap3_dpll5_init(sc);
omap3_usbhost_init(sc, 1);
#endif /* OMAP_3XXX */
#if defined(OMAP4) || defined(OMAP5)
omap4_usbhost_init(sc, 1);
#endif
sc->sc.sc_offs = EREAD1(&sc->sc, EHCI_CAPLENGTH);
//EOWRITE4(&sc->sc, EHCI_INSNREG04, 1 << 5);
sc->sc_ih = intr_establish(obio->obio_intr, IPL_USB, IST_LEVEL, ehci_intr, &sc->sc);
//config_interrupts(self, omap3_ehci_attach1);
omap3_ehci_attach1(self);
}
static int
omap3_ehci_detach(device_t self, int flags)
{
struct omap3_ehci_softc *sc = device_private(self);
int rv;
rv = ehci_detach(&sc->sc, flags);
if (rv)
return rv;
EOWRITE2(&sc->sc, EHCI_USBINTR, 0);
EOREAD2(&sc->sc, EHCI_USBINTR);
if (sc->sc_ih) {
intr_disestablish(sc->sc_ih);
sc->sc_ih = NULL;
}
usbtll_power(sc, false);
if (sc->sc_usbtll_size) {
bus_space_unmap(sc->sc.iot, sc->sc_ioh_usbtll, sc->sc_usbtll_size);
sc->sc_usbtll_size = 0;
}
if (sc->sc_uhh_size) {
bus_space_unmap(sc->sc.iot, sc->sc_ioh_uhh, sc->sc_uhh_size);
sc->sc_uhh_size = 0;
}
if (sc->sc.sc_size) {
bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
sc->sc.sc_size = 0;
}
return 0;
}
#ifdef OMAP_3XXX
static void
omap3_dpll5_init(struct omap3_ehci_softc *sc)
{
bus_space_tag_t iot = sc->sc.iot;
bus_space_handle_t ioh;
int err;
if (sc->sc_dpll5.m == 0 || sc->sc_dpll5.n == 0 || sc->sc_dpll5.m2 == 0)
return;
err = bus_space_map(iot, CCR_CM_BASE, CCR_CM_SIZE, 0, &ioh);
if (err)
panic("%s: cannot map CCR_CM_BASE at %#x, error %d\n",
__func__, CCR_CM_BASE, err);
/* set the multiplier and divider values for the desired CLKOUT freq */
uint32_t m = sc->sc_dpll5.m;
uint32_t n = sc->sc_dpll5.n;
/* set the corresponding output dividers */
uint32_t m2 = sc->sc_dpll5.m2;
KASSERTMSG(479900000 <= 2 * m * (omap_sys_clk / ((n + 1) * m2)),
"m=%u n=%u m2=%u freq=%u",
m, n, m2, 2 * m * (omap_sys_clk / ((n + 1) * m2)));
KASSERTMSG(2 * m * (omap_sys_clk / ((n + 1) * m2)) <= 480100000,
"m=%u n=%u m2=%u freq=%u",
m, n, m2, 2 * m * (omap_sys_clk / ((n + 1) * m2)));
/* 4.7.6.2
* In the DPLL programming sequence, the DPLL_FREQSEL must be
* programmed before the new Multiplier factor M and the Divider
* factor N are programmed so that the new value is taken into
* account during current DPLL relock.
*/
bus_space_write_4(iot, ioh, CM_CLKEN2_PLL, (0x4 << 4) | 0x7);
bus_space_write_4(iot, ioh, CM_CLKSEL4_PLL, (m << 8) | n);
bus_space_write_4(iot, ioh, CM_CLKSEL5_PLL, m2);
/*
* Put DPLL5 into low power stop mode when the 120MHz clock
* is not required (restarted automatically)
*/
bus_space_write_4(iot, ioh, CM_AUTOIDLE2_PLL, AUTO_PERIPH2_DPLL);
bus_space_unmap(iot, ioh, CCR_CM_SIZE);
}
static void
omap3_usbhost_init(struct omap3_ehci_softc *sc, int enable)
{
bus_space_tag_t iot = sc->sc.iot;
bus_space_handle_t ioh;
uint32_t r;
int err;
/*
* USBHOST
*/
err = bus_space_map(iot, USBHOST_CM_BASE, USBHOST_CM_SIZE, 0, &ioh);
if (err)
panic("%s: cannot map USBHOST_CM_BASE at %#x, error %d\n",
__func__, USBHOST_CM_BASE, err);
r = bus_space_read_4(iot, ioh, CM_FCLKEN_USBHOST);
if (enable)
r |= (EN_USBHOST1 | EN_USBHOST2);
else
r &= ~(EN_USBHOST1 | EN_USBHOST2);
bus_space_write_4(iot, ioh, CM_FCLKEN_USBHOST, r);
r = bus_space_read_4(iot, ioh, CM_ICLKEN_USBHOST);
if (enable)
r |= EN_USBHOST;
else
r &= ~EN_USBHOST;
bus_space_write_4(iot, ioh, CM_ICLKEN_USBHOST, r);
bus_space_unmap(iot, ioh, USBHOST_CM_SIZE);
delay(10000);
/*
* USBTLL
*/
err = bus_space_map(iot, CORE_CM_BASE, CORE_CM_SIZE, 0, &ioh);
if (err)
panic("%s: cannot map CORE_CM_BASE a5 %#x, error %d\n",
__func__, CORE_CM_BASE, err);
r = bus_space_read_4(iot, ioh, CM_FCLKEN3_CORE);
if (enable)
r |= EN_USBTLL;
else
r &= ~EN_USBTLL;
bus_space_write_4(iot, ioh, CM_FCLKEN3_CORE, r);
r = bus_space_read_4(iot, ioh, CM_ICLKEN3_CORE);
if (enable)
r |= EN_USBTLL;
else
r &= ~EN_USBTLL;
bus_space_write_4(iot, ioh, CM_ICLKEN3_CORE, r);
r = bus_space_read_4(iot, ioh, CM_AUTOIDLE3_CORE);
if (enable)
r &= ~AUTO_USBTLL;
else
r |= AUTO_USBTLL;
bus_space_write_4(iot, ioh, CM_AUTOIDLE3_CORE, r);
bus_space_unmap(iot, ioh, CORE_CM_SIZE);
delay(10000);
#undef USBHOST_CM_SIZE
#undef USBHOST_CM_BASE
#undef CORE_CM_SIZE
#undef CORE_CM_BASE
}
#endif /* OMAP_3XXX */
#if defined(OMAP4) || defined(OMAP5)
static void
omap4_usbhost_init(struct omap3_ehci_softc *sc, int enable)
{
bus_space_tag_t iot = sc->sc.iot;
bus_space_handle_t ioh;
uint32_t val;
int err __diagused;
#ifdef OMAP5
bus_size_t off = OMAP5_CM_L3INIT_CORE;
#elif defined(OMAP4)
bus_size_t off = OMAP4_CM_L3INIT_CORE;
#endif
err = bus_space_map(iot, OMAP2_CM_BASE + off, 0x100, 0, &ioh);
KASSERT(err == 0);
val = bus_space_read_4(iot, ioh, OMAP4_CM_L3INIT_USB_HOST_HS_CLKCTRL);
val |= OMAP4_CM_L3INIT_USB_HOST_HS_CLKCTRL_OPTFCLKEN_HSIC60M_P3_CLK
| OMAP4_CM_L3INIT_USB_HOST_HS_CLKCTRL_OPTFCLKEN_HSIC60M_P2_CLK
| OMAP4_CM_L3INIT_USB_HOST_HS_CLKCTRL_OPTFCLKEN_HSIC480M_P3_CLK
| OMAP4_CM_L3INIT_USB_HOST_HS_CLKCTRL_OPTFCLKEN_HSIC480M_P2_CLK
| OMAP4_CM_L3INIT_USB_HOST_HS_CLKCTRL_OPTFCLKEN_UTMI_P3_CLK
| OMAP4_CM_L3INIT_USB_HOST_HS_CLKCTRL_OPTFCLKEN_UTMI_P2_CLK;
bus_space_write_4(iot, ioh, OMAP4_CM_L3INIT_USB_HOST_HS_CLKCTRL, val);
val = bus_space_read_4(iot, ioh, OMAP4_CM_L3INIT_USB_TLL_HS_CLKCTRL);
val |= OMAP4_CM_L3INIT_USB_TLL_HS_CLKCTRL_USB_CH2_CLK
| OMAP4_CM_L3INIT_USB_TLL_HS_CLKCTRL_USB_CH1_CLK;
bus_space_write_4(iot, ioh, OMAP4_CM_L3INIT_USB_TLL_HS_CLKCTRL, val);
bus_space_unmap(iot, ioh, 0x100);
}
#endif /* OMAP4 || OMAP5 */
static void
usbtll_reset(struct omap3_ehci_softc *sc)
{
uint32_t v;
int retry = 5000;
USBTLL_WRITE4(sc, USBTLL_SYSCONFIG, USBTLL_SYSCONFIG_SOFTRESET);
do {
v = USBTLL_READ4(sc, USBTLL_SYSSTATUS);
if (v & USBTLL_SYSSTATUS_RESETDONE)
break;
delay(10);
} while (retry-- > 0);
if (retry == 0)
aprint_error_dev(sc->sc.sc_dev,
"reset timeout, status = 0x%08x\n", v);
}
static void
usbtll_power(struct omap3_ehci_softc *sc, bool on)
{
uint32_t v;
usbtll_reset(sc);
if (on) {
v = USBTLL_SYSCONFIG_ENAWAKEUP |
USBTLL_SYSCONFIG_AUTOIDLE |
USBTLL_SYSCONFIG_SIDLEMODE |
USBTLL_SYSCONFIG_CLOCKACTIVITY;
USBTLL_WRITE4(sc, USBTLL_SYSCONFIG, v);
}
}
static void
usbtll_init(struct omap3_ehci_softc *sc, int chmask)
{
uint32_t v;
v = USBTLL_READ4(sc, USBTLL_SHARED_CONF);
v |= (USBTLL_SHARED_CONF_FCLK_IS_ON | (1 << 2)/*divration*/);
v &= ~USBTLL_SHARED_CONF_USB_90D_DDR_EN;
v &= ~USBTLL_SHARED_CONF_USB_180D_SDR_EN;
USBTLL_WRITE4(sc, USBTLL_SHARED_CONF, v);
for (u_int i = 0; i < sc->sc_nports; i++) {
if (sc->sc_portconfig[i].mode == OMAP3_EHCI_PORT_MODE_NONE)
continue;
v = USBTLL_READ4(sc, USBTLL_CHANNEL_CONF(i));
v &= ~(USBTLL_CHANNEL_CONF_ULPINOBITSTUFF|
USBTLL_CHANNEL_CONF_ULPIAUTOIDLE|
USBTLL_CHANNEL_CONF_ULPIDDRMODE);
//v |= USBTLL_CHANNEL_CONF_FSLSMODE;
v |= USBTLL_CHANNEL_CONF_CHANEN;
USBTLL_WRITE4(sc, USBTLL_CHANNEL_CONF(i), v);
}
}
static void
uhh_power(struct omap3_ehci_softc *sc, bool on)
{
int retry = 5000;
uint32_t v;
v = UHH_READ4(sc, UHH_REVISION);
const int vers = UHH_REVISION_MAJOR(v);
if (on) {
v = UHH_READ4(sc, UHH_SYSCONFIG);
if (vers >= UHH_REVISION_VERS2) {
v &= ~UHH4_SYSCONFIG_STANDBYMODE;
v |= UHH4_SYSCONFIG_STANDBYMODE_SMARTSTANDBY;
v &= ~UHH4_SYSCONFIG_SIDLEMODE;
v |= UHH4_SYSCONFIG_SIDLEMODE_SMARTIDLE;
} else {
v &= ~UHH3_SYSCONFIG_MIDLEMODE_MASK;
v |= UHH3_SYSCONFIG_MIDLEMODE_SMARTSTANDBY;
v &= ~UHH3_SYSCONFIG_SIDLEMODE_MASK;
v |= UHH3_SYSCONFIG_SIDLEMODE_SMARTIDLE;
v |= UHH3_SYSCONFIG_CLOCKACTIVITY;
v |= UHH3_SYSCONFIG_ENAWAKEUP;
v &= ~UHH3_SYSCONFIG_AUTOIDLE;
}
UHH_WRITE4(sc, UHH_SYSCONFIG, v);
v = UHH_READ4(sc, UHH_SYSCONFIG);
} else {
v = UHH_READ4(sc, UHH_SYSCONFIG);
if (vers >= UHH_REVISION_VERS2) {
v |= UHH4_SYSCONFIG_SOFTRESET;
} else {
v |= UHH3_SYSCONFIG_SOFTRESET;
}
UHH_WRITE4(sc, UHH_SYSCONFIG, v);
do {
v = UHH_READ4(sc, UHH_SYSSTATUS);
if (vers >= UHH_REVISION_VERS2) {
if ((v & UHH4_SYSSTATUS_RESETDONE_ALL) == 0)
break;
} else {
if (v & UHH3_SYSSTATUS_RESETDONE_ALL)
break;
}
delay(10);
} while (retry-- > 0);
if (retry == 0)
aprint_error_dev(sc->sc.sc_dev,
"reset timeout, status = 0x%08x\n", v);
}
}
static void
uhh_portconfig(struct omap3_ehci_softc *sc)
{
uint32_t v;
v = UHH_READ4(sc, UHH_HOSTCONFIG);
v |= UHH_HOSTCONFIG_ENA_INCR16;
v |= UHH_HOSTCONFIG_ENA_INCR8;
v |= UHH_HOSTCONFIG_ENA_INCR4;
v |= UHH_HOSTCONFIG_APP_START_CLK;
v &= ~UHH_HOSTCONFIG_ENA_INCR_ALIGN;
if (sc->sc_portconfig[0].mode == OMAP3_EHCI_PORT_MODE_NONE)
v &= ~UHH_HOSTCONFIG_P1_CONNECT_STATUS;
if (sc->sc_nports > 1
&& sc->sc_portconfig[1].mode == OMAP3_EHCI_PORT_MODE_NONE)
v &= ~UHH_HOSTCONFIG_P2_CONNECT_STATUS;
if (sc->sc_nports > 2
&& sc->sc_portconfig[2].mode == OMAP3_EHCI_PORT_MODE_NONE)
v &= ~UHH_HOSTCONFIG_P3_CONNECT_STATUS;
v &= ~(UHH_HOSTCONFIG_P1_ULPI_BYPASS|UHH_HOSTCONFIG_P2_ULPI_BYPASS
|UHH_HOSTCONFIG_P3_ULPI_BYPASS);
v &= ~(UHH_HOSTCONFIG_P1_MODE|UHH_HOSTCONFIG_P2_MODE
|UHH_HOSTCONFIG_P3_MODE);
v |= uhh_map[0][sc->sc_portconfig[0].mode];
if (sc->sc_nports > 1) {
v |= uhh_map[1][sc->sc_portconfig[1].mode];
if (sc->sc_nports > 2) {
v |= uhh_map[2][sc->sc_portconfig[2].mode];
}
}
UHH_WRITE4(sc, UHH_HOSTCONFIG, v);
}
CFATTACH_DECL2_NEW(omap3_ehci, sizeof(struct omap3_ehci_softc),
omap3_ehci_match,
omap3_ehci_attach,
omap3_ehci_detach,
ehci_activate,
NULL,
ehci_childdet
);