#include <sys/types.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/ofw_power.h>
#include <dev/ofw/fdt.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <dev/usb/xhcireg.h>
#include <dev/usb/xhcivar.h>
#define MTXHCI_MAX_PORTS 4
#define MTXHCI_RESET 0x00
#define RESET_ASSERT (1 << 0)
#define MTXHCI_CFG_HOST 0x04
#define MTXHCI_CFG_DEV 0x08
#define MTXHCI_CFG_PCIE 0x0c
#define CFG_PWRDN (1 << 0)
#define MTXHCI_STA 0x10
#define STA_USB3 (1 << 16)
#define STA_XHCI (1 << 11)
#define STA_SYS (1 << 10)
#define STA_REF (1 << 8)
#define STA_PLL (1 << 0)
#define MTXHCI_CAPS 0x24
#define CAP_USB2_PORTS(x) (((x) >> 8) & 0x7)
#define CAP_USB3_PORTS(x) (((x) >> 0) & 0x7)
#define MTXHCI_USB3_PORT(x) 0x30 + (x) * 8
#define MTXHCI_USB2_PORT(x) 0x50 + (x) * 8
#define CFG_PORT_HOST (1 << 2)
#define CFG_PORT_PWRDN (1 << 1)
#define CFG_PORT_DISABLE (1 << 0)
#define HREAD4(sc, reg) \
bus_space_read_4((sc)->sc.iot, (sc)->sc_port_ioh, (reg))
#define HWRITE4(sc, reg, val) \
bus_space_write_4((sc)->sc.iot, (sc)->sc_port_ioh, (reg), (val))
struct mtxhci_softc {
struct xhci_softc sc;
int sc_node;
bus_space_handle_t sc_port_ioh;
bus_size_t sc_port_ios;
int sc_port_node;
int sc_ports_usb2;
int sc_ports_usb3;
void *sc_ih;
};
int mtxhci_match(struct device *, void *, void *);
void mtxhci_attach(struct device *, struct device *, void *);
const struct cfattach mtxhci_ca = {
sizeof(struct mtxhci_softc), mtxhci_match, mtxhci_attach
};
struct cfdriver mtxhci_cd = {
NULL, "mtxhci", DV_DULL
};
int mtxhci_host_init(struct mtxhci_softc *);
int
mtxhci_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
if (OF_is_compatible(faa->fa_node, "mediatek,mtk-xhci"))
return 1;
return 0;
}
void
mtxhci_attach(struct device *parent, struct device *self, void *aux)
{
struct mtxhci_softc *sc = (struct mtxhci_softc *)self;
struct fdt_attach_args *faa = aux;
int error = 0, idx;
idx = OF_getindex(faa->fa_node, "ippc", "reg-names");
if (idx < 0 || idx >= faa->fa_nreg) {
printf(": no ippc registers\n");
return;
}
if (bus_space_map(faa->fa_iot, faa->fa_reg[idx].addr,
faa->fa_reg[idx].size, 0, &sc->sc_port_ioh)) {
printf(": can't map registers\n");
return;
}
sc->sc_port_node = faa->fa_node;
sc->sc_port_ios = faa->fa_reg[idx].size;
idx = OF_getindex(faa->fa_node, "mac", "reg-names");
if (idx < 0 || idx >= faa->fa_nreg) {
printf(": no mac registers\n");
goto unmap_port;
}
if (bus_space_map(faa->fa_iot, faa->fa_reg[idx].addr,
faa->fa_reg[idx].size, 0, &sc->sc.ioh)) {
printf(": can't map registers\n");
goto unmap_port;
}
sc->sc_node = faa->fa_node;
sc->sc.iot = faa->fa_iot;
sc->sc.sc_size = faa->fa_reg[idx].size;
sc->sc.sc_bus.dmatag = faa->fa_dmat;
sc->sc_ih = fdt_intr_establish(sc->sc_node, IPL_USB,
xhci_intr, sc, sc->sc.sc_bus.bdev.dv_xname);
if (sc->sc_ih == NULL) {
printf(": can't establish interrupt\n");
goto unmap;
}
power_domain_enable(sc->sc_node);
reset_deassert_all(sc->sc_node);
clock_set_assigned(sc->sc_node);
clock_enable_all(sc->sc_node);
if ((error = mtxhci_host_init(sc)) != 0) {
printf(": host init failed, error=%d\n", error);
goto disestablish_ret;
}
strlcpy(sc->sc.sc_vendor, "MediaTek", sizeof(sc->sc.sc_vendor));
if ((error = xhci_init(&sc->sc)) != 0) {
printf("%s: init failed, error=%d\n",
sc->sc.sc_bus.bdev.dv_xname, error);
goto disestablish_ret;
}
config_found(self, &sc->sc.sc_bus, usbctlprint);
xhci_config(&sc->sc);
return;
disestablish_ret:
fdt_intr_disestablish(sc->sc_ih);
unmap:
bus_space_unmap(faa->fa_iot, sc->sc.ioh, sc->sc.sc_size);
unmap_port:
bus_space_unmap(faa->fa_iot, sc->sc_port_ioh, sc->sc_port_ios);
}
int
mtxhci_host_init(struct mtxhci_softc *sc)
{
uint32_t mask, val;
int i, ntries;
val = HREAD4(sc, MTXHCI_CAPS);
sc->sc_ports_usb3 = MIN(MTXHCI_MAX_PORTS, CAP_USB3_PORTS(val));
sc->sc_ports_usb2 = MIN(MTXHCI_MAX_PORTS, CAP_USB2_PORTS(val));
if (sc->sc_ports_usb3 == 0 && sc->sc_ports_usb2 == 0)
return ENXIO;
phy_enable_idx(sc->sc_port_node, -1);
val = HREAD4(sc, MTXHCI_RESET);
val |= RESET_ASSERT;
HWRITE4(sc, MTXHCI_RESET, val);
delay(10);
val &= ~RESET_ASSERT;
HWRITE4(sc, MTXHCI_RESET, val);
delay(10);
val = HREAD4(sc, MTXHCI_CFG_DEV);
val |= CFG_PWRDN;
HWRITE4(sc, MTXHCI_CFG_DEV, val);
val = HREAD4(sc, MTXHCI_CFG_HOST);
val &= ~CFG_PWRDN;
HWRITE4(sc, MTXHCI_CFG_HOST, val);
mask = (STA_XHCI | STA_PLL | STA_SYS | STA_REF);
if (sc->sc_ports_usb3) {
mask |= STA_USB3;
val = HREAD4(sc, MTXHCI_CFG_PCIE);
val |= CFG_PWRDN;
HWRITE4(sc, MTXHCI_CFG_PCIE, val);
}
for (i = 0; i < sc->sc_ports_usb3; i++) {
val = HREAD4(sc, MTXHCI_USB3_PORT(i));
val &= ~(CFG_PORT_DISABLE | CFG_PORT_PWRDN);
val |= CFG_PORT_HOST;
HWRITE4(sc, MTXHCI_USB3_PORT(i), val);
}
for (i = 0; i < sc->sc_ports_usb2; i++) {
val = HREAD4(sc, MTXHCI_USB2_PORT(i));
val &= ~(CFG_PORT_DISABLE | CFG_PORT_PWRDN);
val |= CFG_PORT_HOST;
HWRITE4(sc, MTXHCI_USB2_PORT(i), val);
}
for (ntries = 0; ntries < 100; ntries++) {
val = HREAD4(sc, MTXHCI_STA);
if ((val & mask) == mask)
break;
delay(50);
}
if (ntries == 100)
return ETIMEDOUT;
return 0;
}