NetBSD/sys/arch/arm/nvidia/tegra_car.c
2015-12-13 17:39:19 +00:00

931 lines
25 KiB
C

/* $NetBSD: tegra_car.c,v 1.31 2015/12/13 17:39:19 jmcneill Exp $ */
/*-
* Copyright (c) 2015 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. 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: tegra_car.c,v 1.31 2015/12/13 17:39:19 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/rndpool.h>
#include <sys/rndsource.h>
#include <arm/nvidia/tegra_reg.h>
#include <arm/nvidia/tegra_carreg.h>
#include <arm/nvidia/tegra_pmcreg.h>
#include <arm/nvidia/tegra_var.h>
#include <dev/fdt/fdtvar.h>
static int tegra_car_match(device_t, cfdata_t, void *);
static void tegra_car_attach(device_t, device_t, void *);
struct tegra_car_softc {
device_t sc_dev;
bus_space_tag_t sc_bst;
bus_space_handle_t sc_bsh;
kmutex_t sc_intr_lock;
kmutex_t sc_rnd_lock;
u_int sc_bytes_wanted;
void *sc_sih;
krndsource_t sc_rndsource;
};
static void tegra_car_init(struct tegra_car_softc *);
static void tegra_car_rnd_attach(device_t);
static void tegra_car_rnd_intr(void *);
static void tegra_car_rnd_callback(size_t, void *);
static struct tegra_car_softc *car_softc = NULL;
CFATTACH_DECL_NEW(tegra_car, sizeof(struct tegra_car_softc),
tegra_car_match, tegra_car_attach, NULL, NULL);
static int
tegra_car_match(device_t parent, cfdata_t cf, void *aux)
{
const char * const compatible[] = { "nvidia,tegra124-car", NULL };
struct fdt_attach_args * const faa = aux;
return of_match_compatible(faa->faa_phandle, compatible);
}
static void
tegra_car_attach(device_t parent, device_t self, void *aux)
{
struct tegra_car_softc * const sc = device_private(self);
struct fdt_attach_args * const faa = aux;
bus_addr_t addr;
bus_size_t size;
int error;
if (fdtbus_get_reg(faa->faa_phandle, 0, &addr, &size) != 0) {
aprint_error(": couldn't get registers\n");
return;
}
sc->sc_dev = self;
sc->sc_bst = faa->faa_bst;
error = bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh);
if (error) {
aprint_error(": couldn't map %#llx: %d", (uint64_t)addr, error);
return;
}
KASSERT(car_softc == NULL);
car_softc = sc;
aprint_naive("\n");
aprint_normal(": CAR\n");
tegra_car_init(sc);
aprint_debug_dev(self, "PLLX = %u Hz\n", tegra_car_pllx_rate());
aprint_debug_dev(self, "PLLC = %u Hz\n", tegra_car_pllc_rate());
aprint_debug_dev(self, "PLLE = %u Hz\n", tegra_car_plle_rate());
aprint_debug_dev(self, "PLLU = %u Hz\n", tegra_car_pllu_rate());
aprint_debug_dev(self, "PLLP0 = %u Hz\n", tegra_car_pllp0_rate());
aprint_debug_dev(self, "PLLD2 = %u Hz\n", tegra_car_plld2_rate());
config_interrupts(self, tegra_car_rnd_attach);
}
static void
tegra_car_init(struct tegra_car_softc *sc)
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
tegra_reg_set_clear(bst, bsh, CAR_PLLD2_BASE_REG,
__SHIFTIN(1, CAR_PLLD2_BASE_MDIV) |
__SHIFTIN(99, CAR_PLLD2_BASE_NDIV) |
__SHIFTIN(1, CAR_PLLD2_BASE_PLDIV),
CAR_PLLD2_BASE_REF_SRC_SEL |
CAR_PLLD2_BASE_PLDIV | CAR_PLLD2_BASE_NDIV | CAR_PLLD2_BASE_MDIV);
}
static void
tegra_car_rnd_attach(device_t self)
{
struct tegra_car_softc * const sc = device_private(self);
mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SERIAL);
mutex_init(&sc->sc_rnd_lock, MUTEX_DEFAULT, IPL_SERIAL);
sc->sc_bytes_wanted = 0;
sc->sc_sih = softint_establish(SOFTINT_SERIAL|SOFTINT_MPSAFE,
tegra_car_rnd_intr, sc);
if (sc->sc_sih == NULL) {
aprint_error_dev(sc->sc_dev, "couldn't establish softint\n");
return;
}
rndsource_setcb(&sc->sc_rndsource, tegra_car_rnd_callback, sc);
rnd_attach_source(&sc->sc_rndsource, device_xname(sc->sc_dev),
RND_TYPE_RNG, RND_FLAG_COLLECT_VALUE|RND_FLAG_HASCB);
}
static void
tegra_car_rnd_intr(void *priv)
{
struct tegra_car_softc * const sc = priv;
uint16_t buf[512];
uint32_t cnt;
mutex_enter(&sc->sc_intr_lock);
while (sc->sc_bytes_wanted) {
const u_int nbytes = MIN(sc->sc_bytes_wanted, 1024);
for (cnt = 0; cnt < sc->sc_bytes_wanted / 2; cnt++) {
buf[cnt] = bus_space_read_4(sc->sc_bst, sc->sc_bsh,
CAR_PLL_LFSR_REG) & 0xffff;
}
mutex_exit(&sc->sc_intr_lock);
mutex_enter(&sc->sc_rnd_lock);
rnd_add_data(&sc->sc_rndsource, buf, nbytes, nbytes * NBBY);
mutex_exit(&sc->sc_rnd_lock);
mutex_enter(&sc->sc_intr_lock);
sc->sc_bytes_wanted -= MIN(sc->sc_bytes_wanted, nbytes);
}
explicit_memset(buf, 0, sizeof(buf));
mutex_exit(&sc->sc_intr_lock);
}
static void
tegra_car_rnd_callback(size_t bytes_wanted, void *priv)
{
struct tegra_car_softc * const sc = priv;
mutex_enter(&sc->sc_intr_lock);
if (sc->sc_bytes_wanted == 0) {
softint_schedule(sc->sc_sih);
}
if (bytes_wanted > (UINT_MAX - sc->sc_bytes_wanted)) {
sc->sc_bytes_wanted = UINT_MAX;
} else {
sc->sc_bytes_wanted += bytes_wanted;
}
mutex_exit(&sc->sc_intr_lock);
}
static void
tegra_car_get_bs(bus_space_tag_t *pbst, bus_space_handle_t *pbsh)
{
if (car_softc) {
*pbst = car_softc->sc_bst;
*pbsh = car_softc->sc_bsh;
} else {
*pbst = &armv7_generic_bs_tag;
bus_space_subregion(*pbst, tegra_ppsb_bsh,
TEGRA_CAR_OFFSET, TEGRA_CAR_SIZE, pbsh);
}
}
u_int
tegra_car_osc_rate(void)
{
return TEGRA_REF_FREQ;
}
static u_int
tegra_car_pll_rate(u_int base_reg, u_int divm_mask, u_int divn_mask,
u_int divp_mask)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
uint64_t rate;
tegra_car_get_bs(&bst, &bsh);
const uint32_t base = bus_space_read_4(bst, bsh, base_reg);
const u_int divm = __SHIFTOUT(base, divm_mask);
const u_int divn = __SHIFTOUT(base, divn_mask);
const u_int divp = __SHIFTOUT(base, divp_mask);
rate = (uint64_t)tegra_car_osc_rate() * divn;
return rate / (divm << divp);
}
void
tegra_car_pllx_set_rate(u_int divm, u_int divn, u_int divp)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
uint32_t base, bp;
tegra_car_get_bs(&bst, &bsh);
bp = bus_space_read_4(bst, bsh, CAR_CCLKG_BURST_POLICY_REG);
bp &= ~CAR_CCLKG_BURST_POLICY_CPU_STATE;
bp |= __SHIFTIN(CAR_CCLKG_BURST_POLICY_CPU_STATE_IDLE,
CAR_CCLKG_BURST_POLICY_CPU_STATE);
bp &= ~CAR_CCLKG_BURST_POLICY_CWAKEUP_IDLE_SOURCE;
bp |= __SHIFTIN(CAR_CCLKG_BURST_POLICY_CWAKEUP_SOURCE_CLKM,
CAR_CCLKG_BURST_POLICY_CWAKEUP_IDLE_SOURCE);
bus_space_write_4(bst, bsh, CAR_CCLKG_BURST_POLICY_REG, bp);
base = bus_space_read_4(bst, bsh, CAR_PLLX_BASE_REG);
base &= ~CAR_PLLX_BASE_DIVM;
base &= ~CAR_PLLX_BASE_DIVN;
base &= ~CAR_PLLX_BASE_DIVP;
base |= __SHIFTIN(divm, CAR_PLLX_BASE_DIVM);
base |= __SHIFTIN(divn, CAR_PLLX_BASE_DIVN);
base |= __SHIFTIN(divp, CAR_PLLX_BASE_DIVP);
bus_space_write_4(bst, bsh, CAR_PLLX_BASE_REG, base);
tegra_reg_set_clear(bst, bsh, CAR_PLLX_MISC_REG,
CAR_PLLX_MISC_LOCK_ENABLE, 0);
do {
delay(2);
base = bus_space_read_4(bst, bsh, CAR_PLLX_BASE_REG);
} while ((base & CAR_PLLX_BASE_LOCK) == 0);
delay(100);
bp &= ~CAR_CCLKG_BURST_POLICY_CPU_STATE;
bp |= __SHIFTIN(CAR_CCLKG_BURST_POLICY_CPU_STATE_RUN,
CAR_CCLKG_BURST_POLICY_CPU_STATE);
bp &= ~CAR_CCLKG_BURST_POLICY_CWAKEUP_IDLE_SOURCE;
bp |= __SHIFTIN(CAR_CCLKG_BURST_POLICY_CWAKEUP_SOURCE_PLLX_OUT0_LJ,
CAR_CCLKG_BURST_POLICY_CWAKEUP_IDLE_SOURCE);
bus_space_write_4(bst, bsh, CAR_CCLKG_BURST_POLICY_REG, bp);
}
u_int
tegra_car_pllx_rate(void)
{
return tegra_car_pll_rate(CAR_PLLX_BASE_REG, CAR_PLLX_BASE_DIVM,
CAR_PLLX_BASE_DIVN, CAR_PLLX_BASE_DIVP);
}
u_int
tegra_car_pllc_rate(void)
{
return tegra_car_pll_rate(CAR_PLLC_BASE_REG, CAR_PLLC_BASE_DIVM,
CAR_PLLC_BASE_DIVN, CAR_PLLC_BASE_DIVP);
}
u_int
tegra_car_plle_rate(void)
{
return tegra_car_pll_rate(CAR_PLLE_BASE_REG, CAR_PLLE_BASE_DIVM,
CAR_PLLE_BASE_DIVN, CAR_PLLE_BASE_DIVP_CML);
}
u_int
tegra_car_pllu_rate(void)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
uint64_t rate;
tegra_car_get_bs(&bst, &bsh);
rate = tegra_car_osc_rate();
const uint32_t base = bus_space_read_4(bst, bsh, CAR_PLLU_BASE_REG);
const u_int divm = __SHIFTOUT(base, CAR_PLLU_BASE_DIVM);
const u_int divn = __SHIFTOUT(base, CAR_PLLU_BASE_DIVN);
const u_int divp = __SHIFTOUT(base, CAR_PLLU_BASE_VCO_FREQ) ? 0 : 1;
rate = (uint64_t)tegra_car_osc_rate() * divn;
return rate / (divm << divp);
}
u_int
tegra_car_pllp0_rate(void)
{
return tegra_car_pll_rate(CAR_PLLP_BASE_REG, CAR_PLLP_BASE_DIVM,
CAR_PLLP_BASE_DIVN, CAR_PLLP_BASE_DIVP);
}
u_int
tegra_car_plld2_rate(void)
{
return tegra_car_pll_rate(CAR_PLLD2_BASE_REG, CAR_PLLD2_BASE_MDIV,
CAR_PLLD2_BASE_NDIV, CAR_PLLD2_BASE_PLDIV);
}
u_int
tegra_car_uart_rate(u_int port)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
bus_size_t src_reg;
u_int src_rate;
tegra_car_get_bs(&bst, &bsh);
switch (port) {
case 0: src_reg = CAR_CLKSRC_UARTA_REG; break;
case 1: src_reg = CAR_CLKSRC_UARTB_REG; break;
case 2: src_reg = CAR_CLKSRC_UARTC_REG; break;
case 3: src_reg = CAR_CLKSRC_UARTD_REG; break;
default: return 0;
}
const uint32_t src = bus_space_read_4(bst, bsh, src_reg);
switch (__SHIFTOUT(src, CAR_CLKSRC_UART_SRC)) {
case 0:
src_rate = tegra_car_pllp0_rate();
break;
default:
panic("%s: unsupported src %#x", __func__, src);
}
if (__SHIFTOUT(src, CAR_CLKSRC_UART_DIV_ENB)) {
const u_int div = (__SHIFTOUT(src, CAR_CLKSRC_UART_DIV) / 2) + 1;
return src_rate / div;
} else {
return src_rate;
}
}
u_int
tegra_car_periph_sdmmc_rate(u_int port)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
bus_size_t src_reg;
tegra_car_get_bs(&bst, &bsh);
switch (port) {
case 0: src_reg = CAR_CLKSRC_SDMMC1_REG; break;
case 1: src_reg = CAR_CLKSRC_SDMMC2_REG; break;
case 2: src_reg = CAR_CLKSRC_SDMMC3_REG; break;
case 3: src_reg = CAR_CLKSRC_SDMMC4_REG; break;
default: return 0;
}
const uint32_t src = bus_space_read_4(bst, bsh, src_reg);
const u_int div = __SHIFTOUT(src, CAR_CLKSRC_SDMMC_DIV) + 2;
return (tegra_car_pllp0_rate() * 2) / div;
}
int
tegra_car_periph_sdmmc_set_rate(u_int port, u_int rate)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
bus_size_t src_reg, rst_reg, enb_reg;
u_int dev_bit;
uint32_t src;
KASSERT(rate > 0);
tegra_car_get_bs(&bst, &bsh);
switch (port) {
case 0:
src_reg = CAR_CLKSRC_SDMMC1_REG;
rst_reg = CAR_RST_DEV_L_SET_REG;
enb_reg = CAR_CLK_ENB_L_SET_REG;
dev_bit = CAR_DEV_L_SDMMC1;
break;
case 1:
src_reg = CAR_CLKSRC_SDMMC2_REG;
rst_reg = CAR_RST_DEV_L_SET_REG;
enb_reg = CAR_CLK_ENB_L_SET_REG;
dev_bit = CAR_DEV_L_SDMMC2;
break;
case 2:
src_reg = CAR_CLKSRC_SDMMC3_REG;
rst_reg = CAR_RST_DEV_U_SET_REG;
enb_reg = CAR_CLK_ENB_U_SET_REG;
dev_bit = CAR_DEV_U_SDMMC3;
break;
case 3:
src_reg = CAR_CLKSRC_SDMMC4_REG;
rst_reg = CAR_RST_DEV_L_SET_REG;
enb_reg = CAR_CLK_ENB_L_SET_REG;
dev_bit = CAR_DEV_L_SDMMC4;
break;
default: return EINVAL;
}
/* enter reset */
bus_space_write_4(bst, bsh, rst_reg, dev_bit);
/* enable clk */
bus_space_write_4(bst, bsh, enb_reg, dev_bit);
const u_int div = howmany(tegra_car_pllp0_rate() * 2, rate) - 2;
/* update clk div */
src = __SHIFTIN(CAR_CLKSRC_SDMMC_SRC_PLLP_OUT0,
CAR_CLKSRC_SDMMC_SRC);
src |= __SHIFTIN(div, CAR_CLKSRC_SDMMC_DIV);
bus_space_write_4(bst, bsh, src_reg, src);
/* leave reset */
bus_space_write_4(bst, bsh, rst_reg+4, dev_bit);
return 0;
}
int
tegra_car_periph_usb_enable(u_int port)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
bus_size_t rst_reg, enb_reg;
uint32_t dev_bit;
tegra_car_get_bs(&bst, &bsh);
switch (port) {
case 0:
rst_reg = CAR_RST_DEV_L_SET_REG;
enb_reg = CAR_CLK_ENB_L_SET_REG;
dev_bit = CAR_DEV_L_USBD;
break;
case 1:
rst_reg = CAR_RST_DEV_H_SET_REG;
enb_reg = CAR_CLK_ENB_H_SET_REG;
dev_bit = CAR_DEV_H_USB2;
break;
case 2:
rst_reg = CAR_RST_DEV_H_SET_REG;
enb_reg = CAR_CLK_ENB_H_SET_REG;
dev_bit = CAR_DEV_H_USB3;
break;
default:
return EINVAL;
}
/* enter reset */
bus_space_write_4(bst, bsh, rst_reg, dev_bit);
/* enable clk */
bus_space_write_4(bst, bsh, enb_reg, dev_bit);
/* leave reset */
bus_space_write_4(bst, bsh, rst_reg+4, dev_bit);
return 0;
}
void
tegra_car_utmip_init(void)
{
const u_int enable_dly_count = 0x02;
const u_int stable_count = 0x2f;
const u_int active_dly_count = 0x04;
const u_int xtal_freq_count = 0x76;
bus_space_tag_t bst;
bus_space_handle_t bsh;
tegra_car_get_bs(&bst, &bsh);
tegra_reg_set_clear(bst, bsh, CAR_UTMIP_PLL_CFG2_REG,
__SHIFTIN(stable_count, CAR_UTMIP_PLL_CFG2_STABLE_COUNT) |
__SHIFTIN(active_dly_count, CAR_UTMIP_PLL_CFG2_ACTIVE_DLY_COUNT),
CAR_UTMIP_PLL_CFG2_STABLE_COUNT |
CAR_UTMIP_PLL_CFG2_ACTIVE_DLY_COUNT);
tegra_reg_set_clear(bst, bsh, CAR_UTMIP_PLL_CFG1_REG,
__SHIFTIN(enable_dly_count, CAR_UTMIP_PLL_CFG1_ENABLE_DLY_COUNT) |
__SHIFTIN(xtal_freq_count, CAR_UTMIP_PLL_CFG1_XTAL_FREQ_COUNT),
CAR_UTMIP_PLL_CFG1_ENABLE_DLY_COUNT |
CAR_UTMIP_PLL_CFG1_XTAL_FREQ_COUNT);
tegra_reg_set_clear(bst, bsh, CAR_UTMIP_PLL_CFG1_REG,
0,
CAR_UTMIP_PLL_CFG1_PLLU_POWERDOWN |
CAR_UTMIP_PLL_CFG1_PLL_ENABLE_POWERDOWN);
}
void
tegra_car_utmip_enable(u_int port)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
uint32_t bit = 0;
tegra_car_get_bs(&bst, &bsh);
switch (port) {
case 0: bit = CAR_UTMIP_PLL_CFG2_PD_SAMP_A_POWERDOWN; break;
case 1: bit = CAR_UTMIP_PLL_CFG2_PD_SAMP_B_POWERDOWN; break;
case 2: bit = CAR_UTMIP_PLL_CFG2_PD_SAMP_C_POWERDOWN; break;
}
tegra_reg_set_clear(bst, bsh, CAR_UTMIP_PLL_CFG2_REG, 0, bit);
}
void
tegra_car_periph_hda_enable(void)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
tegra_car_get_bs(&bst, &bsh);
bus_space_write_4(bst, bsh, CAR_RST_DEV_V_SET_REG, CAR_DEV_V_HDA);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_V_SET_REG, CAR_DEV_V_HDA);
bus_space_write_4(bst, bsh, CAR_RST_DEV_V_SET_REG,
CAR_DEV_V_HDA2CODEC_2X);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_V_SET_REG,
CAR_DEV_V_HDA2CODEC_2X);
bus_space_write_4(bst, bsh, CAR_RST_DEV_W_SET_REG,
CAR_DEV_W_HDA2HDMICODEC);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_W_SET_REG,
CAR_DEV_W_HDA2HDMICODEC);
/* configure HDA2CODEC_2X for 48 MHz */
const u_int div = howmany(tegra_car_pllp0_rate() * 2, 48000000) - 2;
bus_space_write_4(bst, bsh, CAR_CLKSRC_HDA2CODEC_2X_REG,
__SHIFTIN(CAR_CLKSRC_HDA2CODEC_2X_SRC_PLLP_OUT0,
CAR_CLKSRC_HDA2CODEC_2X_SRC) |
__SHIFTIN(div, CAR_CLKSRC_HDA2CODEC_2X_DIV));
bus_space_write_4(bst, bsh, CAR_RST_DEV_V_CLR_REG, CAR_DEV_V_HDA);
bus_space_write_4(bst, bsh, CAR_RST_DEV_V_CLR_REG,
CAR_DEV_V_HDA2CODEC_2X);
bus_space_write_4(bst, bsh, CAR_RST_DEV_W_CLR_REG,
CAR_DEV_W_HDA2HDMICODEC);
}
void
tegra_car_periph_sata_enable(void)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
tegra_car_get_bs(&bst, &bsh);
/* Assert resets */
bus_space_write_4(bst, bsh, CAR_RST_DEV_V_SET_REG, CAR_DEV_V_SATA);
bus_space_write_4(bst, bsh, CAR_RST_DEV_W_SET_REG, CAR_DEV_W_SATACOLD);
/* Disable software control of SATA PLL */
tegra_reg_set_clear(bst, bsh, CAR_SATA_PLL_CFG0_REG,
0, CAR_SATA_PLL_CFG0_PADPLL_RESET_SWCTL);
/* Set SATA_OOB clock source to PLLP, 204MHz */
const u_int sataoob_div = 2;
bus_space_write_4(bst, bsh, CAR_CLKSRC_SATA_OOB_REG,
__SHIFTIN(CAR_CLKSRC_SATA_OOB_SRC_PLLP_OUT0,
CAR_CLKSRC_SATA_OOB_SRC) |
__SHIFTIN((sataoob_div - 1) * 2, CAR_CLKSRC_SATA_OOB_DIV));
/* Set SATA clock source to PLLP, 102MHz */
const u_int sata_div = 4;
bus_space_write_4(bst, bsh, CAR_CLKSRC_SATA_REG,
CAR_CLKSRC_SATA_AUX_CLK_ENB |
__SHIFTIN(CAR_CLKSRC_SATA_SRC_PLLP_OUT0,
CAR_CLKSRC_SATA_SRC) |
__SHIFTIN((sata_div - 1) * 2, CAR_CLKSRC_SATA_DIV));
/* Ungate SAX partition in the PMC */
tegra_pmc_power(PMC_PARTID_SAX, true);
delay(20);
/* Remove clamping from SAX partition in the PMC */
tegra_pmc_remove_clamping(PMC_PARTID_SAX);
delay(20);
/* De-assert reset to SATA PADPLL */
tegra_reg_set_clear(bst, bsh, CAR_SATA_PLL_CFG0_REG,
0, CAR_SATA_PLL_CFG0_PADPLL_RESET_OVERRIDE_VALUE);
delay(15);
/* Enable CML clock for SATA */
tegra_reg_set_clear(bst, bsh, CAR_PLLE_AUX_REG,
CAR_PLLE_AUX_CML1_OEN, 0);
/* Turn on the clocks to SATA and de-assert resets */
bus_space_write_4(bst, bsh, CAR_CLK_ENB_W_SET_REG, CAR_DEV_W_SATACOLD);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_V_SET_REG, CAR_DEV_V_SATA);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_V_SET_REG, CAR_DEV_V_SATA_OOB);
bus_space_write_4(bst, bsh, CAR_RST_DEV_W_CLR_REG, CAR_DEV_W_SATACOLD);
bus_space_write_4(bst, bsh, CAR_RST_DEV_V_CLR_REG, CAR_DEV_V_SATA);
}
int
tegra_car_periph_i2c_enable(u_int port, u_int rate)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
bus_size_t rst_reg, enb_reg, clksrc_reg;
uint32_t dev_bit;
tegra_car_get_bs(&bst, &bsh);
switch (port) {
case 0:
rst_reg = CAR_RST_DEV_L_SET_REG;
enb_reg = CAR_CLK_ENB_L_SET_REG;
dev_bit = CAR_DEV_L_I2C1;
clksrc_reg = CAR_CLKSRC_I2C1_REG;
break;
case 1:
rst_reg = CAR_RST_DEV_H_SET_REG;
enb_reg = CAR_CLK_ENB_H_SET_REG;
dev_bit = CAR_DEV_H_I2C2;
clksrc_reg = CAR_CLKSRC_I2C2_REG;
break;
case 2:
rst_reg = CAR_RST_DEV_U_SET_REG;
enb_reg = CAR_CLK_ENB_U_SET_REG;
dev_bit = CAR_DEV_U_I2C3;
clksrc_reg = CAR_CLKSRC_I2C3_REG;
break;
case 3:
rst_reg = CAR_RST_DEV_V_SET_REG;
enb_reg = CAR_CLK_ENB_V_SET_REG;
dev_bit = CAR_DEV_V_I2C4;
clksrc_reg = CAR_CLKSRC_I2C4_REG;
break;
case 4:
rst_reg = CAR_RST_DEV_H_SET_REG;
enb_reg = CAR_CLK_ENB_H_SET_REG;
dev_bit = CAR_DEV_H_I2C5;
clksrc_reg = CAR_CLKSRC_I2C5_REG;
break;
case 5:
rst_reg = CAR_RST_DEV_X_SET_REG;
enb_reg = CAR_CLK_ENB_X_SET_REG;
dev_bit = CAR_DEV_X_I2C6;
clksrc_reg = CAR_CLKSRC_I2C6_REG;
break;
default:
return EINVAL;
}
/* Enter reset, enable clock */
bus_space_write_4(bst, bsh, rst_reg, dev_bit);
bus_space_write_4(bst, bsh, enb_reg, dev_bit);
/* Set clock source to PLLP */
const u_int div = howmany(tegra_car_pllp0_rate() / 1000, rate / 1000);
bus_space_write_4(bst, bsh, clksrc_reg,
__SHIFTIN(CAR_CLKSRC_I2C_SRC_PLLP_OUT0, CAR_CLKSRC_I2C_SRC) |
__SHIFTIN(div - 1, CAR_CLKSRC_I2C_DIV));
/* Leave reset */
bus_space_write_4(bst, bsh, rst_reg+4, dev_bit);
return 0;
}
void
tegra_car_periph_cec_enable(void)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
tegra_car_get_bs(&bst, &bsh);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_W_SET_REG, CAR_DEV_W_CEC);
bus_space_write_4(bst, bsh, CAR_RST_DEV_W_CLR_REG, CAR_DEV_W_CEC);
}
void
tegra_car_hdmi_enable(u_int rate)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
uint32_t base;
int retry = 10000;
tegra_car_get_bs(&bst, &bsh);
/* Enter reset, enable clock */
bus_space_write_4(bst, bsh, CAR_RST_DEV_H_SET_REG, CAR_DEV_H_HDMI);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_H_SET_REG, CAR_DEV_H_HDMI);
/* Change IDDQ from 1 to 0 */
tegra_reg_set_clear(bst, bsh, CAR_PLLD2_BASE_REG,
0, CAR_PLLD2_BASE_IDDQ);
delay(2);
/* Enable lock */
tegra_reg_set_clear(bst, bsh, CAR_PLLD2_MISC_REG,
CAR_PLLD2_MISC_LOCK_ENABLE, 0);
/* Enable PLLD2 */
tegra_reg_set_clear(bst, bsh, CAR_PLLD2_BASE_REG,
CAR_PLLD2_BASE_ENABLE, 0);
/* Wait for lock */
do {
delay(2);
base = bus_space_read_4(bst, bsh, CAR_PLLD2_BASE_REG);
} while ((base & CAR_PLLD2_BASE_LOCK) == 0 && --retry > 0);
delay(100);
if (retry == 0) {
printf("WARNING: timeout waiting for PLLD2 lock\n");
}
/* Set clock source to PLLD2 */
const u_int div = (tegra_car_plld2_rate() * 2) / rate - 2;
bus_space_write_4(bst, bsh, CAR_CLKSRC_HDMI_REG,
__SHIFTIN(CAR_CLKSRC_HDMI_SRC_PLLD2_OUT0, CAR_CLKSRC_HDMI_SRC) |
__SHIFTIN(div, CAR_CLKSRC_HDMI_DIV));
/* Leave reset */
bus_space_write_4(bst, bsh, CAR_RST_DEV_H_CLR_REG, CAR_DEV_H_HDMI);
}
int
tegra_car_dc_enable(u_int port)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
bus_size_t src_reg;
uint32_t dev_bit;
u_int partid;
tegra_car_get_bs(&bst, &bsh);
switch (port) {
case 0:
dev_bit = CAR_DEV_L_DISP1;
src_reg = CAR_CLKSRC_DISP1_REG;
partid = PMC_PARTID_DIS;
break;
case 1:
dev_bit = CAR_DEV_L_DISP2;
src_reg = CAR_CLKSRC_DISP2_REG;
partid = PMC_PARTID_DISB;
break;
default:
return EINVAL;
}
/* Enter reset, enable clock */
bus_space_write_4(bst, bsh, CAR_RST_DEV_L_SET_REG, dev_bit);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_L_SET_REG, dev_bit);
/* Turn on power to display partition */
tegra_pmc_power(partid, true);
tegra_pmc_remove_clamping(partid);
/* Select PLLD2 for clock source */
bus_space_write_4(bst, bsh, src_reg,
__SHIFTIN(CAR_CLKSRC_DISP_SRC_PLLD2_OUT0,
CAR_CLKSRC_DISP_SRC));
/* Leave reset */
bus_space_write_4(bst, bsh, CAR_RST_DEV_L_CLR_REG, dev_bit);
return 0;
}
void
tegra_car_host1x_enable(void)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
tegra_car_get_bs(&bst, &bsh);
/* Enter reset, enable clock */
bus_space_write_4(bst, bsh, CAR_RST_DEV_L_SET_REG, CAR_DEV_L_HOST1X);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_L_SET_REG, CAR_DEV_L_HOST1X);
/* Select PLLP for clock source, 408 MHz */
bus_space_write_4(bst, bsh, CAR_CLKSRC_HOST1X_REG,
__SHIFTIN(CAR_CLKSRC_HOST1X_SRC_PLLP_OUT0,
CAR_CLKSRC_HOST1X_SRC) |
__SHIFTIN(0, CAR_CLKSRC_HOST1X_CLK_DIVISOR));
delay(2);
/* Leave reset */
bus_space_write_4(bst, bsh, CAR_RST_DEV_L_CLR_REG, CAR_DEV_L_HOST1X);
}
void
tegra_car_wdt_enable(u_int timer, bool enable)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
uint32_t enable_bits;
KASSERT(timer == 1 || timer == 2);
tegra_car_get_bs(&bst, &bsh);
enable_bits = enable ?
(CAR_RST_SOURCE_WDT_EN|CAR_RST_SOURCE_WDT_SYS_RST_EN) : 0;
tegra_reg_set_clear(bst, bsh, CAR_RST_SOURCE_REG,
__SHIFTIN(timer - 1, CAR_RST_SOURCE_WDT_SEL) |
enable_bits,
CAR_RST_SOURCE_WDT_SYS_RST_EN |
CAR_RST_SOURCE_WDT_SEL |
CAR_RST_SOURCE_WDT_EN);
}
void
tegra_car_gpu_enable(void)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
tegra_car_get_bs(&bst, &bsh);
/* Enter reset, enable clock */
bus_space_write_4(bst, bsh, CAR_RST_DEV_X_SET_REG, CAR_DEV_X_GPU);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_X_SET_REG, CAR_DEV_X_GPU);
/* Set PLLP_OUT5 to 204MHz */
const u_int rate = 204000000;
const u_int div = howmany(tegra_car_pllp0_rate() * 2, rate) - 2;
tegra_reg_set_clear(bst, bsh, CAR_PLLP_OUTC_REG,
__SHIFTIN(div, CAR_PLLP_OUTC_OUT5_RATIO) |
CAR_PLLP_OUTC_OUT5_CLKEN,
CAR_PLLP_OUTC_OUT5_RATIO);
delay(20);
/* Remove clamping from 3D partition in the PMC */
tegra_pmc_remove_clamping(PMC_PARTID_TD);
delay(20);
/* Leave reset */
bus_space_write_4(bst, bsh, CAR_RST_DEV_X_CLR_REG, CAR_DEV_X_GPU);
}
void
tegra_car_fuse_enable(void)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
tegra_car_get_bs(&bst, &bsh);
tegra_reg_set_clear(bst, bsh, CAR_CLK_ENB_H_SET_REG, CAR_DEV_H_FUSE, 0);
}
void
tegra_car_fuse_disable(void)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
tegra_car_get_bs(&bst, &bsh);
tegra_reg_set_clear(bst, bsh, CAR_CLK_ENB_H_SET_REG, 0, CAR_DEV_H_FUSE);
}
void
tegra_car_soctherm_enable(void)
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
tegra_car_get_bs(&bst, &bsh);
bus_space_write_4(bst, bsh, CAR_RST_DEV_U_SET_REG, CAR_DEV_U_SOC_THERM);
const u_int soctherm_rate = 51000000;
const u_int soctherm_div =
howmany(tegra_car_pllp0_rate() * 2, soctherm_rate) - 2;
bus_space_write_4(bst, bsh, CAR_CLKSRC_SOC_THERM_REG,
__SHIFTIN(soctherm_div, CAR_CLKSRC_SOC_THERM_DIV) |
__SHIFTIN(CAR_CLKSRC_SOC_THERM_SRC_PLLP_OUT0,
CAR_CLKSRC_SOC_THERM_SRC));
delay(20);
const u_int tsensor_rate = 400000;
const u_int tsensor_div =
howmany(TEGRA_REF_FREQ * 2, tsensor_rate) - 2;
bus_space_write_4(bst, bsh, CAR_CLKSRC_TSENSOR_REG,
__SHIFTIN(tsensor_div, CAR_CLKSRC_TSENSOR_DIV) |
__SHIFTIN(CAR_CLKSRC_TSENSOR_SRC_CLK_M, CAR_CLKSRC_TSENSOR_SRC));
delay(20);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_V_SET_REG, CAR_DEV_V_TSENSOR);
bus_space_write_4(bst, bsh, CAR_CLK_ENB_U_SET_REG, CAR_DEV_U_SOC_THERM);
bus_space_write_4(bst, bsh, CAR_RST_DEV_U_CLR_REG, CAR_DEV_U_SOC_THERM);
}