freebsd-dev/sys/arm/nvidia/tegra124/tegra124_xusbpadctl.c
Michal Meloun dac935533b EXTRES: Add OF node as argument to all <foo>_get_by_ofw_<bar>() functions.
In some cases, the driver must handle given properties located in
specific OF subnode. Instead of creating duplicate set of function, add
'node' as argument to existing functions, defaulting it to device OF node.

MFC after: 3 weeks
2016-07-10 18:28:15 +00:00

604 lines
15 KiB
C

/*-
* Copyright (c) 2016 Michal Meloun <mmel@FreeBSD.org>
* 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.
*
* $FreeBSD$
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/extres/hwreset/hwreset.h>
#include <dev/extres/phy/phy.h>
#include <dev/fdt/fdt_common.h>
#include <dev/fdt/fdt_pinctrl.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <gnu/dts/include/dt-bindings/pinctrl/pinctrl-tegra-xusb.h>
#include "phy_if.h"
#define XUSB_PADCTL_USB2_PAD_MUX 0x004
#define XUSB_PADCTL_ELPG_PROGRAM 0x01C
#define ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN (1 << 26)
#define ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 25)
#define ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN (1 << 24)
#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1 0x040
#define IOPHY_PLL_P0_CTL1_PLL0_LOCKDET (1 << 19)
#define IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK (0xf<< 12)
#define IOPHY_PLL_P0_CTL1_PLL_RST (1 << 1)
#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2 0x044
#define IOPHY_PLL_P0_CTL2_REFCLKBUF_EN (1 << 6)
#define IOPHY_PLL_P0_CTL2_TXCLKREF_EN (1 << 5)
#define IOPHY_PLL_P0_CTL2_TXCLKREF_SEL (1 << 4)
#define XUSB_PADCTL_USB3_PAD_MUX 0x134
#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1 0x138
#define IOPHY_PLL_S0_CTL1_PLL1_LOCKDET (1 << 27)
#define IOPHY_PLL_S0_CTL1_PLL1_MODE (1 << 24)
#define IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD (1 << 3)
#define IOPHY_PLL_S0_CTL1_PLL_RST_L (1 << 1)
#define IOPHY_PLL_S0_CTL1_PLL_IDDQ (1 << 0)
#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2 0x13C
#define XUSB_PADCTL_IOPHY_PLL_S0_CTL3 0x140
#define XUSB_PADCTL_IOPHY_PLL_S0_CTL4 0x144
#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1 0x148
#define IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD (1 << 1)
#define IOPHY_MISC_PAD_S0_CTL1_IDDQ (1 << 0)
#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2 0x14C
#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL3 0x150
#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL4 0x154
#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5 0x158
#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6 0x15C
struct lane_cfg {
char *function;
char **lanes;
int iddq;
};
struct xusbpadctl_softc {
device_t dev;
struct resource *mem_res;
hwreset_t rst;
int phy_ena_cnt;
};
static struct ofw_compat_data compat_data[] = {
{"nvidia,tegra124-xusb-padctl", 1},
{NULL, 0},
};
struct padctl_lane {
const char *name;
bus_size_t reg;
uint32_t shift;
uint32_t mask;
int iddq;
char **mux;
int nmux;
};
static char *otg_mux[] = {"snps", "xusb", "uart", "rsvd"};
static char *usb_mux[] = {"snps", "xusb"};
static char *pci_mux[] = {"pcie", "usb3", "sata", "rsvd"};
#define LANE(n, r, s, m, i, mx) \
{ \
.name = n, \
.reg = r, \
.shift = s, \
.mask = m, \
.iddq = i, \
.mux = mx, \
.nmux = nitems(mx), \
}
static const struct padctl_lane lanes_tbl[] = {
LANE("otg-0", XUSB_PADCTL_USB2_PAD_MUX, 0, 0x3, -1, otg_mux),
LANE("otg-1", XUSB_PADCTL_USB2_PAD_MUX, 2, 0x3, -1, otg_mux),
LANE("otg-2", XUSB_PADCTL_USB2_PAD_MUX, 4, 0x3, -1, otg_mux),
LANE("ulpi-0", XUSB_PADCTL_USB2_PAD_MUX, 12, 0x1, -1, usb_mux),
LANE("hsic-0", XUSB_PADCTL_USB2_PAD_MUX, 14, 0x1, -1, usb_mux),
LANE("hsic-1", XUSB_PADCTL_USB2_PAD_MUX, 15, 0x1, -1, usb_mux),
LANE("pcie-0", XUSB_PADCTL_USB3_PAD_MUX, 16, 0x3, 1, pci_mux),
LANE("pcie-1", XUSB_PADCTL_USB3_PAD_MUX, 18, 0x3, 2, pci_mux),
LANE("pcie-2", XUSB_PADCTL_USB3_PAD_MUX, 20, 0x3, 3, pci_mux),
LANE("pcie-3", XUSB_PADCTL_USB3_PAD_MUX, 22, 0x3, 4, pci_mux),
LANE("pcie-4", XUSB_PADCTL_USB3_PAD_MUX, 24, 0x3, 5, pci_mux),
LANE("sata-0", XUSB_PADCTL_USB3_PAD_MUX, 26, 0x3, 6, pci_mux),
};
static int
xusbpadctl_mux_function(const struct padctl_lane *lane, char *fnc_name)
{
int i;
for (i = 0; i < lane->nmux; i++) {
if (strcmp(fnc_name, lane->mux[i]) == 0)
return (i);
}
return (-1);
}
static int
xusbpadctl_config_lane(struct xusbpadctl_softc *sc, char *lane_name,
const struct padctl_lane *lane, struct lane_cfg *cfg)
{
int tmp;
uint32_t reg;
reg = bus_read_4(sc->mem_res, lane->reg);
if (cfg->function != NULL) {
tmp = xusbpadctl_mux_function(lane, cfg->function);
if (tmp == -1) {
device_printf(sc->dev,
"Unknown function %s for lane %s\n", cfg->function,
lane_name);
return (EINVAL);
}
reg &= ~(lane->mask << lane->shift);
reg |= (tmp & lane->mask) << lane->shift;
}
if (cfg->iddq != -1) {
if (lane->iddq == -1) {
device_printf(sc->dev, "Invalid IDDQ for lane %s\n",
lane_name);
return (EINVAL);
}
if (cfg->iddq != 0)
reg &= ~(1 << lane->iddq);
else
reg |= 1 << lane->iddq;
}
bus_write_4(sc->mem_res, lane->reg, reg);
return (0);
}
static const struct padctl_lane *
xusbpadctl_search_lane(char *lane_name)
{
int i;
for (i = 0; i < nitems(lanes_tbl); i++) {
if (strcmp(lane_name, lanes_tbl[i].name) == 0)
return (&lanes_tbl[i]);
}
return (NULL);
}
static int
xusbpadctl_config_node(struct xusbpadctl_softc *sc, char *lane_name,
struct lane_cfg *cfg)
{
const struct padctl_lane *lane;
int rv;
lane = xusbpadctl_search_lane(lane_name);
if (lane == NULL) {
device_printf(sc->dev, "Unknown lane: %s\n", lane_name);
return (ENXIO);
}
rv = xusbpadctl_config_lane(sc, lane_name, lane, cfg);
return (rv);
}
static int
xusbpadctl_read_node(struct xusbpadctl_softc *sc, phandle_t node,
struct lane_cfg *cfg, char **lanes, int *llanes)
{
int rv;
*llanes = OF_getprop_alloc(node, "nvidia,lanes", 1, (void **)lanes);
if (*llanes <= 0)
return (ENOENT);
/* Read function (mux) settings. */
rv = OF_getprop_alloc(node, "nvidia,function", 1,
(void **)&cfg->function);
if (rv <= 0)
cfg->function = NULL;
/* Read numeric properties. */
rv = OF_getencprop(node, "nvidia,iddq", &cfg->iddq,
sizeof(cfg->iddq));
if (rv <= 0)
cfg->iddq = -1;
return (0);
}
static int
xusbpadctl_process_node(struct xusbpadctl_softc *sc, phandle_t node)
{
struct lane_cfg cfg;
char *lanes, *lname;
int i, len, llanes, rv;
rv = xusbpadctl_read_node(sc, node, &cfg, &lanes, &llanes);
if (rv != 0)
return (rv);
len = 0;
lname = lanes;
do {
i = strlen(lname) + 1;
rv = xusbpadctl_config_node(sc, lname, &cfg);
if (rv != 0)
device_printf(sc->dev,
"Cannot configure lane: %s: %d\n", lname, rv);
len += i;
lname += i;
} while (len < llanes);
if (lanes != NULL)
OF_prop_free(lanes);
if (cfg.function != NULL)
OF_prop_free(cfg.function);
return (rv);
}
static int
xusbpadctl_pinctrl_cfg(device_t dev, phandle_t cfgxref)
{
struct xusbpadctl_softc *sc;
phandle_t node, cfgnode;
int rv;
sc = device_get_softc(dev);
cfgnode = OF_node_from_xref(cfgxref);
rv = 0;
for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) {
if (!fdt_is_enabled(node))
continue;
rv = xusbpadctl_process_node(sc, node);
if (rv != 0)
return (rv);
}
return (rv);
}
static int
xusbpadctl_phy_pcie_powerup(struct xusbpadctl_softc *sc)
{
uint32_t reg;
int i;
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
reg &= ~IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1, reg);
DELAY(100);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL2);
reg |= IOPHY_PLL_P0_CTL2_REFCLKBUF_EN;
reg |= IOPHY_PLL_P0_CTL2_TXCLKREF_EN;
reg |= IOPHY_PLL_P0_CTL2_TXCLKREF_SEL;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL2, reg);
DELAY(100);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
reg |= IOPHY_PLL_P0_CTL1_PLL_RST;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1, reg);
DELAY(100);
for (i = 0; i < 100; i++) {
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
if (reg & IOPHY_PLL_P0_CTL1_PLL0_LOCKDET)
return (0);
DELAY(10);
}
return (ETIMEDOUT);
}
static int
xusbpadctl_phy_pcie_powerdown(struct xusbpadctl_softc *sc)
{
uint32_t reg;
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1);
reg &= ~IOPHY_PLL_P0_CTL1_PLL_RST;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_P0_CTL1, reg);
DELAY(100);
return (0);
}
static int
xusbpadctl_phy_sata_powerup(struct xusbpadctl_softc *sc)
{
uint32_t reg;
int i;
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
reg &= ~IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD;
reg &= ~IOPHY_MISC_PAD_S0_CTL1_IDDQ;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1, reg);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
reg &= ~IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD;
reg &= ~IOPHY_PLL_S0_CTL1_PLL_IDDQ;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
reg |= IOPHY_PLL_S0_CTL1_PLL1_MODE;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
reg |= IOPHY_PLL_S0_CTL1_PLL_RST_L;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg);
for (i = 100; i >= 0; i--) {
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
if (reg & IOPHY_PLL_S0_CTL1_PLL1_LOCKDET)
break;
DELAY(100);
}
if (i <= 0) {
device_printf(sc->dev, "Failed to power up SATA phy\n");
return (ETIMEDOUT);
}
return (0);
}
static int
xusbpadctl_phy_sata_powerdown(struct xusbpadctl_softc *sc)
{
uint32_t reg;
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
reg &= ~IOPHY_PLL_S0_CTL1_PLL_RST_L;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg);
DELAY(100);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
reg &= ~IOPHY_PLL_S0_CTL1_PLL1_MODE;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg);
DELAY(100);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1);
reg |= IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD;
reg |= IOPHY_PLL_S0_CTL1_PLL_IDDQ;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_PLL_S0_CTL1, reg);
DELAY(100);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1);
reg |= IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD;
reg |= IOPHY_MISC_PAD_S0_CTL1_IDDQ;
bus_write_4(sc->mem_res, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1, reg);
DELAY(100);
return (0);
}
static int
xusbpadctl_phy_powerup(struct xusbpadctl_softc *sc)
{
uint32_t reg;
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM);
reg &= ~ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN;
bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg);
DELAY(100);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM);
reg &= ~ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY;
bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg);
DELAY(100);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM);
reg &= ~ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN;
bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg);
DELAY(100);
return (0);
}
static int
xusbpadctl_phy_powerdown(struct xusbpadctl_softc *sc)
{
uint32_t reg;
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM);
reg |= ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN;
bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg);
DELAY(100);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM);
reg |= ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY;
bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg);
DELAY(100);
reg = bus_read_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM);
reg |= ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN;
bus_write_4(sc->mem_res, XUSB_PADCTL_ELPG_PROGRAM, reg);
DELAY(100);
return (0);
}
static int
xusbpadctl_phy_enable(device_t dev, intptr_t id, bool enable)
{
struct xusbpadctl_softc *sc;
int rv;
sc = device_get_softc(dev);
if ((id != TEGRA_XUSB_PADCTL_PCIE) &&
(id != TEGRA_XUSB_PADCTL_SATA)) {
device_printf(dev, "Unknown phy: %d\n", id);
return (ENXIO);
}
rv = 0;
if (enable) {
if (sc->phy_ena_cnt == 0) {
rv = xusbpadctl_phy_powerup(sc);
if (rv != 0)
return (rv);
}
sc->phy_ena_cnt++;
}
if (id == TEGRA_XUSB_PADCTL_PCIE) {
if (enable)
rv = xusbpadctl_phy_pcie_powerup(sc);
else
rv = xusbpadctl_phy_pcie_powerdown(sc);
if (rv != 0)
return (rv);
} else if (id == TEGRA_XUSB_PADCTL_SATA) {
if (enable)
rv = xusbpadctl_phy_sata_powerup(sc);
else
rv = xusbpadctl_phy_sata_powerdown(sc);
if (rv != 0)
return (rv);
}
if (!enable) {
if (sc->phy_ena_cnt == 1) {
rv = xusbpadctl_phy_powerdown(sc);
if (rv != 0)
return (rv);
}
sc->phy_ena_cnt--;
}
return (0);
}
static int
xusbpadctl_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, "Tegra XUSB phy");
return (BUS_PROBE_DEFAULT);
}
static int
xusbpadctl_detach(device_t dev)
{
/* This device is always present. */
return (EBUSY);
}
static int
xusbpadctl_attach(device_t dev)
{
struct xusbpadctl_softc * sc;
int rid, rv;
phandle_t node;
sc = device_get_softc(dev);
sc->dev = dev;
rid = 0;
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
if (sc->mem_res == NULL) {
device_printf(dev, "Cannot allocate memory resources\n");
return (ENXIO);
}
node = ofw_bus_get_node(dev);
rv = hwreset_get_by_ofw_name(dev, 0, "padctl", &sc->rst);
if (rv != 0) {
device_printf(dev, "Cannot get 'padctl' reset: %d\n", rv);
return (rv);
}
rv = hwreset_deassert(sc->rst);
if (rv != 0) {
device_printf(dev, "Cannot unreset 'padctl' reset: %d\n", rv);
return (rv);
}
/* Register as a pinctrl device and use default configuration */
fdt_pinctrl_register(dev, NULL);
fdt_pinctrl_configure_by_name(dev, "default");
phy_register_provider(dev);
return (0);
}
static device_method_t tegra_xusbpadctl_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, xusbpadctl_probe),
DEVMETHOD(device_attach, xusbpadctl_attach),
DEVMETHOD(device_detach, xusbpadctl_detach),
/* fdt_pinctrl interface */
DEVMETHOD(fdt_pinctrl_configure, xusbpadctl_pinctrl_cfg),
/* phy interface */
DEVMETHOD(phy_enable, xusbpadctl_phy_enable),
DEVMETHOD_END
};
static driver_t tegra_xusbpadctl_driver = {
"tegra_xusbpadctl",
tegra_xusbpadctl_methods,
sizeof(struct xusbpadctl_softc),
};
static devclass_t tegra_xusbpadctl_devclass;
EARLY_DRIVER_MODULE(tegra_xusbpadctl, simplebus, tegra_xusbpadctl_driver,
tegra_xusbpadctl_devclass, 0, 0, 73);