#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <machine/intr.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <armv7/omap/prcmvar.h>
#include <armv7/omap/omehcivar.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/fdt.h>
#include <dev/usb/ehcireg.h>
#include <dev/usb/ehcivar.h>
int omehci_match(struct device *, void *, void *);
void omehci_attach(struct device *, struct device *, void *);
int omehci_detach(struct device *, int);
int omehci_activate(struct device *, int);
struct omehci_softc {
struct ehci_softc sc;
void *sc_ih;
bus_space_handle_t uhh_ioh;
bus_space_handle_t tll_ioh;
uint32_t ehci_rev;
uint32_t tll_avail;
uint32_t port_mode[OMAP_HS_USB_PORTS];
};
int omehci_init(struct omehci_softc *);
void omehci_soft_phy_reset(struct omehci_softc *sc, unsigned int port);
const struct cfattach omehci_ca = {
sizeof (struct omehci_softc), omehci_match, omehci_attach,
omehci_detach, omehci_activate
};
struct cfdriver omehci_cd = {
NULL, "omehci", DV_DULL
};
int
omehci_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "ti,usbhs-host");
}
void
omehci_attach(struct device *parent, struct device *self, void *aux)
{
struct omehci_softc *sc = (struct omehci_softc *)self;
struct fdt_attach_args *faa = aux;
usbd_status r;
char *devname = sc->sc.sc_bus.bdev.dv_xname;
uint32_t i;
char port_mode[16];
char name[32];
int node;
uint32_t reg[2];
if (faa->fa_nreg < 1)
return;
sc->sc.iot = faa->fa_iot;
sc->sc.sc_bus.dmatag = faa->fa_dmat;
for (i = 0; i < OMAP_HS_USB_PORTS; i++)
sc->port_mode[i] = EHCI_HCD_OMAP_MODE_UNKNOWN;
strlcpy(name, "portX-mode", sizeof(name));
for (i = 0; i < OMAP_HS_USB_PORTS; i++) {
name[4] = '1' + i;
memset(port_mode, 0, sizeof(port_mode));
if (OF_getprop(faa->fa_node, name, port_mode,
sizeof(port_mode)) == -1)
continue;
if (strcmp(port_mode, "ehci-phy") == 0)
sc->port_mode[i] = EHCI_HCD_OMAP_MODE_PHY;
if (strcmp(port_mode, "ehci-hsic") == 0)
sc->port_mode[i] = EHCI_HCD_OMAP_MODE_HSIC;
if (strcmp(port_mode, "ehci-tll") == 0)
sc->port_mode[i] = EHCI_HCD_OMAP_MODE_TLL ;
}
for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
if (OF_is_compatible(node, "ti,ehci-omap"))
break;
}
if (node == 0)
panic("could not find ehci child node");
if (OF_getpropintarray(node, "reg", reg, sizeof(reg)) != sizeof(reg))
return;
if (bus_space_map(sc->sc.iot, reg[0], reg[1], 0, &sc->sc.ioh)) {
printf(": cannot map mem space\n");
goto out;
}
sc->sc.sc_size = reg[1];
if (bus_space_map(sc->sc.iot, faa->fa_reg[0].addr, faa->fa_reg[0].size,
0, &sc->uhh_ioh)) {
printf(": cannot map mem space\n");
goto mem0;
}
#if 0
if (sc->tll_avail &&
bus_space_map(sc->sc.iot, aa->aa_dev->mem[2].addr,
aa->aa_dev->mem[2].size, 0, &sc->tll_ioh)) {
printf(": cannot map mem space\n");
goto mem1;
}
#endif
printf("\n");
phy_enable_idx(node, 0);
if (omehci_init(sc))
return;
sc->sc.sc_offs = EREAD1(&sc->sc, EHCI_CAPLENGTH);
EOWRITE2(&sc->sc, EHCI_USBINTR, 0);
sc->sc_ih = arm_intr_establish_fdt(node, IPL_USB,
ehci_intr, &sc->sc, devname);
if (sc->sc_ih == NULL) {
printf(": unable to establish interrupt\n");
printf("XXX - disable ehci and prcm");
goto mem2;
}
strlcpy(sc->sc.sc_vendor, "TI OMAP", sizeof(sc->sc.sc_vendor));
r = ehci_init(&sc->sc);
if (r != USBD_NORMAL_COMPLETION) {
printf("%s: init failed, error=%d\n", devname, r);
printf("XXX - disable ehci and prcm");
goto intr;
}
config_found(self, &sc->sc.sc_bus, usbctlprint);
goto out;
intr:
arm_intr_disestablish(sc->sc_ih);
sc->sc_ih = NULL;
mem2:
#if 0
bus_space_unmap(sc->sc.iot, sc->tll_ioh, aa->aa_dev->mem[2].size);
mem1:
#endif
bus_space_unmap(sc->sc.iot, sc->uhh_ioh, faa->fa_reg[0].size);
mem0:
bus_space_unmap(sc->sc.iot, sc->sc.ioh, sc->sc.sc_size);
sc->sc.sc_size = 0;
out:
return;
}
int
omehci_init(struct omehci_softc *sc)
{
uint32_t i = 0, reg;
uint32_t reset_performed = 0;
uint32_t timeout = 0;
uint32_t tll_ch_mask = 0;
prcm_enablemodule(PRCM_USB);
if (reset_performed)
delay(10);
sc->ehci_rev = bus_space_read_4(sc->sc.iot, sc->uhh_ioh,
OMAP_USBHOST_UHH_REVISION);
if (sc->ehci_rev == OMAP_EHCI_REV1) {
prcm_enablemodule(PRCM_USBTLL);
bus_space_write_4(sc->sc.iot, sc->tll_ioh,
OMAP_USBTLL_SYSCONFIG, TLL_SYSCONFIG_SOFTRESET);
timeout = (hz < 10) ? 1 : ((100 * hz) / 1000);
while ((bus_space_read_4(sc->sc.iot, sc->tll_ioh,
OMAP_USBTLL_SYSSTATUS) & TLL_SYSSTATUS_RESETDONE)
== 0x00) {
delay(10);
if (timeout-- == 0) {
return 1;
}
}
bus_space_write_4(sc->sc.iot, sc->tll_ioh,
OMAP_USBTLL_SYSCONFIG,
TLL_SYSCONFIG_ENAWAKEUP | TLL_SYSCONFIG_AUTOIDLE |
TLL_SYSCONFIG_SIDLE_SMART_IDLE | TLL_SYSCONFIG_CACTIVITY);
} else if (sc->ehci_rev == OMAP_EHCI_REV2) {
if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_PHY) {
prcm_enablemodule(PRCM_USBP1_PHY);
} else if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_TLL)
prcm_enablemodule(PRCM_USBP1_UTMI);
else if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_HSIC)
prcm_enablemodule(PRCM_USBP1_HSIC);
if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_PHY) {
prcm_enablemodule(PRCM_USBP2_PHY);
} else if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_TLL)
prcm_enablemodule(PRCM_USBP2_UTMI);
else if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_HSIC)
prcm_enablemodule(PRCM_USBP2_HSIC);
}
reg = bus_space_read_4(sc->sc.iot, sc->uhh_ioh,
OMAP_USBHOST_UHH_SYSCONFIG);
if (sc->ehci_rev == OMAP_EHCI_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 (sc->ehci_rev == OMAP_EHCI_REV2) {
reg &= ~UHH_SYSCONFIG_IDLEMODE_MASK;
reg |= UHH_SYSCONFIG_IDLEMODE_NOIDLE;
reg &= ~UHH_SYSCONFIG_STANDBYMODE_MASK;
reg |= UHH_SYSCONFIG_STANDBYMODE_NOSTDBY;
}
bus_space_write_4(sc->sc.iot, sc->uhh_ioh, OMAP_USBHOST_UHH_SYSCONFIG,
reg);
reg = bus_space_read_4(sc->sc.iot, sc->uhh_ioh,
OMAP_USBHOST_UHH_HOSTCONFIG);
reg |= (UHH_HOSTCONFIG_ENA_INCR4 |
UHH_HOSTCONFIG_ENA_INCR8 |
UHH_HOSTCONFIG_ENA_INCR16);
reg &= ~UHH_HOSTCONFIG_ENA_INCR_ALIGN;
if (sc->ehci_rev == OMAP_EHCI_REV1) {
if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_UNKNOWN)
reg &= ~UHH_HOSTCONFIG_P1_CONNECT_STATUS;
if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_UNKNOWN)
reg &= ~UHH_HOSTCONFIG_P2_CONNECT_STATUS;
if (sc->port_mode[2] == EHCI_HCD_OMAP_MODE_UNKNOWN)
reg &= ~UHH_HOSTCONFIG_P3_CONNECT_STATUS;
if ((sc->port_mode[0] == EHCI_HCD_OMAP_MODE_PHY) ||
(sc->port_mode[1] == EHCI_HCD_OMAP_MODE_PHY) ||
(sc->port_mode[2] == EHCI_HCD_OMAP_MODE_PHY))
reg &= ~UHH_HOSTCONFIG_P1_ULPI_BYPASS;
else
reg |= UHH_HOSTCONFIG_P1_ULPI_BYPASS;
} else if (sc->ehci_rev == OMAP_EHCI_REV2) {
reg |= UHH_HOSTCONFIG_APP_START_CLK;
reg &= ~UHH_HOSTCONFIG_P1_MODE_MASK;
reg &= ~UHH_HOSTCONFIG_P2_MODE_MASK;
if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_TLL)
reg |= UHH_HOSTCONFIG_P1_MODE_UTMI_PHY;
else if (sc->port_mode[0] == EHCI_HCD_OMAP_MODE_HSIC)
reg |= UHH_HOSTCONFIG_P1_MODE_HSIC;
if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_TLL)
reg |= UHH_HOSTCONFIG_P2_MODE_UTMI_PHY;
else if (sc->port_mode[1] == EHCI_HCD_OMAP_MODE_HSIC)
reg |= UHH_HOSTCONFIG_P2_MODE_HSIC;
}
bus_space_write_4(sc->sc.iot, sc->uhh_ioh, OMAP_USBHOST_UHH_HOSTCONFIG, reg);
for (i = 0; i < OMAP_HS_USB_PORTS; i++)
if (sc->port_mode[i] == EHCI_HCD_OMAP_MODE_PHY)
tll_ch_mask |= 1 << i;
#ifdef notyet
if (tll_ch_mask)
omap_ehci_utmi_init(sc, tll_ch_mask);
#endif
reg = bus_space_read_4(sc->sc.iot, sc->sc.ioh, OMAP_USBHOST_USBCMD);
reg &= 0xff00ffff;
reg |= (1 << 16);
bus_space_write_4(sc->sc.iot, sc->sc.ioh, OMAP_USBHOST_USBCMD, reg);
for (i = 0; i < OMAP_HS_USB_PORTS; i++)
if (sc->port_mode[i] == EHCI_HCD_OMAP_MODE_PHY)
omehci_soft_phy_reset(sc, i);
return(0);
}
void
omehci_soft_phy_reset(struct omehci_softc *sc, unsigned int port)
{
unsigned long timeout = (hz < 10) ? 1 : ((100 * hz) / 1000);
uint32_t reg;
reg = ULPI_FUNC_CTRL_RESET
| (ULPI_SET(ULPI_FUNC_CTRL) << OMAP_USBHOST_INSNREG05_ULPI_REGADD_SHIFT)
| (2 << OMAP_USBHOST_INSNREG05_ULPI_OPSEL_SHIFT)
| ((port + 1) << OMAP_USBHOST_INSNREG05_ULPI_PORTSEL_SHIFT)
| (1 << OMAP_USBHOST_INSNREG05_ULPI_CONTROL_SHIFT);
bus_space_write_4(sc->sc.iot, sc->sc.ioh, OMAP_USBHOST_INSNREG05_ULPI, reg);
timeout += 1000000;
while ((bus_space_read_4(sc->sc.iot, sc->sc.ioh, OMAP_USBHOST_INSNREG05_ULPI)
& (1 << OMAP_USBHOST_INSNREG05_ULPI_CONTROL_SHIFT))) {
delay(10);
if (timeout-- == 0) {
printf("PHY reset operation timed out\n");
break;
}
}
}
int
omehci_detach(struct device *self, int flags)
{
struct omehci_softc *sc = (struct omehci_softc *)self;
int rv;
rv = ehci_detach(self, flags);
if (rv)
return (rv);
if (sc->sc_ih != NULL) {
arm_intr_disestablish(sc->sc_ih);
sc->sc_ih = NULL;
}
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);
}
int
omehci_activate(struct device *self, int act)
{
struct omehci_softc *sc = (struct omehci_softc *)self;
int rv;
switch (act) {
case DVACT_SUSPEND:
rv = config_activate_children(self, act);
sc->sc.sc_bus.use_polling++;
sc->sc.sc_bus.use_polling--;
break;
case DVACT_RESUME:
sc->sc.sc_bus.use_polling++;
sc->sc.sc_bus.use_polling--;
rv = config_activate_children(self, act);
break;
case DVACT_POWERDOWN:
rv = config_activate_children(self, act);
ehci_reset(&sc->sc);
break;
}
return rv;
}