mirror of
https://github.com/Stichting-MINIX-Research-Foundation/netbsd.git
synced 2025-08-07 21:19:47 -04:00
397 lines
12 KiB
C
397 lines
12 KiB
C
/* $NetBSD: tegra_dc.c,v 1.3 2015/07/23 14:31:05 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 "locators.h"
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: tegra_dc.c,v 1.3 2015/07/23 14:31:05 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 <dev/videomode/videomode.h>
|
|
|
|
#include <arm/nvidia/tegra_reg.h>
|
|
#include <arm/nvidia/tegra_dcreg.h>
|
|
#include <arm/nvidia/tegra_var.h>
|
|
|
|
#define TEGRA_DC_NPORTS 2
|
|
#define TEGRA_DC_DEPTH 32
|
|
#define TEGRA_DC_FBALIGN PAGE_SIZE
|
|
|
|
static int tegra_dc_match(device_t, cfdata_t, void *);
|
|
static void tegra_dc_attach(device_t, device_t, void *);
|
|
|
|
struct tegra_dc_softc {
|
|
device_t sc_dev;
|
|
bus_space_tag_t sc_bst;
|
|
bus_space_handle_t sc_bsh;
|
|
bus_dma_tag_t sc_dmat;
|
|
int sc_port;
|
|
|
|
bus_dma_segment_t sc_dmasegs[1];
|
|
bus_size_t sc_dmasize;
|
|
bus_dmamap_t sc_dmamap;
|
|
void *sc_dmap;
|
|
|
|
device_t sc_fbdev;
|
|
};
|
|
|
|
static int tegra_dc_print(void *, const char *);
|
|
static int tegra_dc_allocmem(struct tegra_dc_softc *, bus_size_t);
|
|
static int tegra_dc_init(struct tegra_dc_softc *,
|
|
const struct videomode *);
|
|
static void tegra_dc_init_win(struct tegra_dc_softc *,
|
|
const struct videomode *);
|
|
static void tegra_dc_init_disp(struct tegra_dc_softc *,
|
|
const struct videomode *);
|
|
static void tegra_dc_update(struct tegra_dc_softc *);
|
|
|
|
CFATTACH_DECL_NEW(tegra_dc, sizeof(struct tegra_dc_softc),
|
|
tegra_dc_match, tegra_dc_attach, NULL, NULL);
|
|
|
|
#define DC_READ(sc, reg) \
|
|
bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
|
|
#define DC_WRITE(sc, reg, val) \
|
|
bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
|
|
#define DC_SET_CLEAR(sc, reg, set, clr) \
|
|
tegra_reg_set_clear((sc)->sc_bst, (sc)->sc_bsh, (reg), (set), (clr))
|
|
|
|
static int
|
|
tegra_dc_match(device_t parent, cfdata_t cf, void *aux)
|
|
{
|
|
struct tegraio_attach_args * const tio = aux;
|
|
const struct tegra_locators * const loc = &tio->tio_loc;
|
|
|
|
if (loc->loc_port == TEGRAIOCF_PORT_DEFAULT ||
|
|
loc->loc_port >= TEGRA_DC_NPORTS)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
tegra_dc_attach(device_t parent, device_t self, void *aux)
|
|
{
|
|
struct tegra_dc_softc * const sc = device_private(self);
|
|
struct tegraio_attach_args * const tio = aux;
|
|
const struct tegra_locators * const loc = &tio->tio_loc;
|
|
|
|
sc->sc_dev = self;
|
|
sc->sc_port = loc->loc_port;
|
|
sc->sc_bst = tio->tio_bst;
|
|
sc->sc_dmat = tio->tio_dmat;
|
|
if (bus_space_map(sc->sc_bst, TEGRA_GHOST_BASE + loc->loc_offset,
|
|
loc->loc_size, 0, &sc->sc_bsh) != 0) {
|
|
aprint_error(": couldn't map DC\n");
|
|
return;
|
|
}
|
|
|
|
aprint_naive("\n");
|
|
aprint_normal(": DISPLAY%c\n", loc->loc_port + 'A');
|
|
}
|
|
|
|
|
|
static int
|
|
tegra_dc_allocmem(struct tegra_dc_softc *sc, bus_size_t size)
|
|
{
|
|
int error, nsegs;
|
|
|
|
error = bus_dmamem_alloc(sc->sc_dmat, size, TEGRA_DC_FBALIGN, 0,
|
|
sc->sc_dmasegs, 1, &nsegs, BUS_DMA_WAITOK);
|
|
if (error)
|
|
return error;
|
|
error = bus_dmamem_map(sc->sc_dmat, sc->sc_dmasegs, nsegs, size,
|
|
&sc->sc_dmap, BUS_DMA_WAITOK | BUS_DMA_COHERENT);
|
|
if (error)
|
|
goto free;
|
|
error = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
|
|
BUS_DMA_WAITOK, &sc->sc_dmamap);
|
|
if (error)
|
|
goto unmap;
|
|
error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmamap, sc->sc_dmap,
|
|
size, NULL, BUS_DMA_WAITOK);
|
|
if (error)
|
|
goto destroy;
|
|
|
|
sc->sc_dmasize = size;
|
|
|
|
memset(sc->sc_dmap, 0, size);
|
|
|
|
return 0;
|
|
|
|
destroy:
|
|
bus_dmamap_destroy(sc->sc_dmat, sc->sc_dmamap);
|
|
unmap:
|
|
bus_dmamem_unmap(sc->sc_dmat, sc->sc_dmap, size);
|
|
free:
|
|
bus_dmamem_free(sc->sc_dmat, sc->sc_dmasegs, nsegs);
|
|
|
|
sc->sc_dmasize = 0;
|
|
sc->sc_dmap = NULL;
|
|
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
tegra_dc_print(void *aux, const char *pnp)
|
|
{
|
|
const struct tegrafb_attach_args * const tfb = aux;
|
|
|
|
aprint_normal(" output %s", device_xname(tfb->tfb_outputdev));
|
|
|
|
return UNCONF;
|
|
}
|
|
|
|
static int
|
|
tegra_dc_init(struct tegra_dc_softc *sc, const struct videomode *mode)
|
|
{
|
|
tegra_car_dc_enable(sc->sc_port);
|
|
|
|
tegra_dc_init_win(sc, mode);
|
|
tegra_dc_init_disp(sc, mode);
|
|
tegra_dc_update(sc);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
tegra_dc_init_win(struct tegra_dc_softc *sc, const struct videomode *mode)
|
|
{
|
|
/* Write access control */
|
|
DC_WRITE(sc, DC_CMD_STATE_ACCESS_REG,
|
|
DC_CMD_STATE_ACCESS_WRITE_MUX | DC_CMD_STATE_ACCESS_READ_MUX);
|
|
|
|
/* Enable window A programming */
|
|
DC_WRITE(sc, DC_CMD_DISPLAY_WINDOW_HEADER_REG,
|
|
DC_CMD_DISPLAY_WINDOW_HEADER_WINDOW_A_SELECT);
|
|
|
|
/* Set colour depth to ARGB8888 */
|
|
DC_WRITE(sc, DC_WINC_A_COLOR_DEPTH_REG,
|
|
__SHIFTIN(DC_WINC_A_COLOR_DEPTH_DEPTH_T_A8R8G8B8,
|
|
DC_WINC_A_COLOR_DEPTH_DEPTH));
|
|
|
|
/* Disable byte swapping */
|
|
DC_WRITE(sc, DC_WINC_A_BYTE_SWAP_REG,
|
|
__SHIFTIN(DC_WINC_A_BYTE_SWAP_SWAP_NOSWAP,
|
|
DC_WINC_A_BYTE_SWAP_SWAP));
|
|
|
|
/* Initial DDA */
|
|
DC_WRITE(sc, DC_WINC_A_H_INITIAL_DDA_REG, 0);
|
|
DC_WRITE(sc, DC_WINC_A_V_INITIAL_DDA_REG, 0);
|
|
DC_WRITE(sc, DC_WINC_A_DDA_INCREMENT_REG, 0x10001000);
|
|
|
|
/* Window position, size, stride */
|
|
DC_WRITE(sc, DC_WINC_A_POSITION_REG,
|
|
__SHIFTIN(0, DC_WINC_A_POSITION_V) |
|
|
__SHIFTIN(0, DC_WINC_A_POSITION_H));
|
|
DC_WRITE(sc, DC_WINC_A_SIZE_REG,
|
|
__SHIFTIN(mode->vdisplay, DC_WINC_A_SIZE_V) |
|
|
__SHIFTIN(mode->hdisplay, DC_WINC_A_SIZE_H));
|
|
DC_WRITE(sc, DC_WINC_A_PRESCALED_SIZE_REG,
|
|
__SHIFTIN(mode->vdisplay, DC_WINC_A_PRESCALED_SIZE_V) |
|
|
__SHIFTIN(mode->hdisplay * (TEGRA_DC_DEPTH / 8),
|
|
DC_WINC_A_PRESCALED_SIZE_H));
|
|
DC_WRITE(sc, DC_WINC_A_LINE_STRIDE_REG,
|
|
__SHIFTIN(mode->hdisplay * (TEGRA_DC_DEPTH / 8),
|
|
DC_WINC_A_LINE_STRIDE_LINE_STRIDE));
|
|
|
|
/* Framebuffer start address */
|
|
DC_WRITE(sc, DC_WINBUF_A_START_ADDR_REG,
|
|
(uint32_t)sc->sc_dmamap->dm_segs[0].ds_addr);
|
|
|
|
/* Offsets */
|
|
DC_WRITE(sc, DC_WINBUF_A_ADDR_H_OFFSET_REG, 0);
|
|
DC_WRITE(sc, DC_WINBUF_A_ADDR_V_OFFSET_REG, 0);
|
|
|
|
/* Surface kind */
|
|
DC_WRITE(sc, DC_WINBUF_A_SURFACE_KIND_REG,
|
|
__SHIFTIN(DC_WINBUF_A_SURFACE_KIND_SURFACE_KIND_PITCH,
|
|
DC_WINBUF_A_SURFACE_KIND_SURFACE_KIND));
|
|
|
|
/* Enable window A */
|
|
DC_WRITE(sc, DC_WINC_A_WIN_OPTIONS_REG,
|
|
DC_WINC_A_WIN_OPTIONS_WIN_ENABLE);
|
|
}
|
|
|
|
static void
|
|
tegra_dc_init_disp(struct tegra_dc_softc *sc, const struct videomode *mode)
|
|
{
|
|
const u_int hspw = mode->hsync_end - mode->hsync_start;
|
|
const u_int hbp = mode->htotal - mode->hsync_end;
|
|
const u_int hfp = mode->hsync_start - mode->hdisplay;
|
|
const u_int vspw = mode->vsync_end - mode->vsync_start;
|
|
const u_int vbp = mode->vtotal - mode->vsync_end;
|
|
const u_int vfp = mode->vsync_start - mode->vdisplay;
|
|
|
|
DC_WRITE(sc, DC_DISP_DISP_TIMING_OPTIONS_REG,
|
|
__SHIFTIN(1, DC_DISP_DISP_TIMING_OPTIONS_VSYNC_POS));
|
|
DC_WRITE(sc, DC_DISP_DISP_COLOR_CONTROL_REG,
|
|
__SHIFTIN(DC_DISP_DISP_COLOR_CONTROL_BASE_COLOR_SIZE_888,
|
|
DC_DISP_DISP_COLOR_CONTROL_BASE_COLOR_SIZE));
|
|
DC_WRITE(sc, DC_DISP_DISP_SIGNAL_OPTIONS0_REG,
|
|
DC_DISP_DISP_SIGNAL_OPTIONS0_H_PULSE2_ENABLE);
|
|
DC_WRITE(sc, DC_DISP_H_PULSE2_CONTROL_REG,
|
|
__SHIFTIN(DC_DISP_H_PULSE2_CONTROL_V_QUAL_VACTIVE,
|
|
DC_DISP_H_PULSE2_CONTROL_V_QUAL) |
|
|
__SHIFTIN(DC_DISP_H_PULSE2_CONTROL_LAST_END_A,
|
|
DC_DISP_H_PULSE2_CONTROL_LAST));
|
|
|
|
u_int pulse_start = 1 + hspw + hbp - 10;
|
|
DC_WRITE(sc, DC_DISP_H_PULSE2_POSITION_A_REG,
|
|
__SHIFTIN(pulse_start, DC_DISP_H_PULSE2_POSITION_A_START) |
|
|
__SHIFTIN(pulse_start + 8, DC_DISP_H_PULSE2_POSITION_A_END));
|
|
|
|
/* Pixel clock */
|
|
const u_int div = (tegra_car_plld2_rate() * 2) / (mode->dot_clock * 1000) - 2;
|
|
DC_WRITE(sc, DC_DISP_DISP_CLOCK_CONTROL_REG,
|
|
__SHIFTIN(0, DC_DISP_DISP_CLOCK_CONTROL_PIXEL_CLK_DIVIDER) |
|
|
__SHIFTIN(div, DC_DISP_DISP_CLOCK_CONTROL_SHIFT_CLK_DIVIDER));
|
|
|
|
/* Mode timings */
|
|
DC_WRITE(sc, DC_DISP_REF_TO_SYNC_REG,
|
|
__SHIFTIN(1, DC_DISP_REF_TO_SYNC_V) |
|
|
__SHIFTIN(1, DC_DISP_REF_TO_SYNC_H));
|
|
DC_WRITE(sc, DC_DISP_SYNC_WIDTH_REG,
|
|
__SHIFTIN(vspw, DC_DISP_SYNC_WIDTH_V) |
|
|
__SHIFTIN(hspw, DC_DISP_SYNC_WIDTH_H));
|
|
DC_WRITE(sc, DC_DISP_BACK_PORCH_REG,
|
|
__SHIFTIN(vbp, DC_DISP_BACK_PORCH_V) |
|
|
__SHIFTIN(hbp, DC_DISP_BACK_PORCH_H));
|
|
DC_WRITE(sc, DC_DISP_FRONT_PORCH_REG,
|
|
__SHIFTIN(vfp, DC_DISP_FRONT_PORCH_V) |
|
|
__SHIFTIN(hfp, DC_DISP_FRONT_PORCH_H));
|
|
DC_WRITE(sc, DC_DISP_DISP_ACTIVE_REG,
|
|
__SHIFTIN(mode->vdisplay, DC_DISP_DISP_ACTIVE_V) |
|
|
__SHIFTIN(mode->hdisplay, DC_DISP_DISP_ACTIVE_H));
|
|
|
|
/* Enable continus display mode */
|
|
DC_WRITE(sc, DC_CMD_DISPLAY_COMMAND_REG,
|
|
__SHIFTIN(DC_CMD_DISPLAY_COMMAND_DISPLAY_CTRL_MODE_C_DISPLAY,
|
|
DC_CMD_DISPLAY_COMMAND_DISPLAY_CTRL_MODE));
|
|
|
|
/* Enable power */
|
|
DC_SET_CLEAR(sc, DC_CMD_DISPLAY_POWER_CONTROL_REG,
|
|
DC_CMD_DISPLAY_POWER_CONTROL_PM1_ENABLE |
|
|
DC_CMD_DISPLAY_POWER_CONTROL_PM0_ENABLE |
|
|
DC_CMD_DISPLAY_POWER_CONTROL_PW4_ENABLE |
|
|
DC_CMD_DISPLAY_POWER_CONTROL_PW3_ENABLE |
|
|
DC_CMD_DISPLAY_POWER_CONTROL_PW2_ENABLE |
|
|
DC_CMD_DISPLAY_POWER_CONTROL_PW1_ENABLE |
|
|
DC_CMD_DISPLAY_POWER_CONTROL_PW0_ENABLE,
|
|
0);
|
|
}
|
|
|
|
static void
|
|
tegra_dc_update(struct tegra_dc_softc *sc)
|
|
{
|
|
/* Commit settings */
|
|
DC_WRITE(sc, DC_CMD_STATE_CONTROL_REG,
|
|
DC_CMD_STATE_CONTROL_GENERAL_UPDATE | DC_CMD_STATE_CONTROL_WIN_A_UPDATE);
|
|
DC_WRITE(sc, DC_CMD_STATE_CONTROL_REG,
|
|
DC_CMD_STATE_CONTROL_GENERAL_ACT_REQ | DC_CMD_STATE_CONTROL_WIN_A_ACT_REQ);
|
|
}
|
|
|
|
int
|
|
tegra_dc_port(device_t dev)
|
|
{
|
|
struct tegra_dc_softc * const sc = device_private(dev);
|
|
|
|
return sc->sc_port;
|
|
}
|
|
|
|
int
|
|
tegra_dc_enable(device_t dev, device_t outputdev,
|
|
const struct videomode *mode, const uint8_t *edid)
|
|
{
|
|
struct tegra_dc_softc * const sc = device_private(dev);
|
|
prop_dictionary_t prop = device_properties(dev);
|
|
bool is_console = false;
|
|
int error;
|
|
|
|
KASSERT(sc->sc_fbdev == NULL);
|
|
|
|
prop_dictionary_get_bool(prop, "is_console", &is_console);
|
|
|
|
const uint32_t depth = TEGRA_DC_DEPTH;
|
|
const uint32_t fbsize = mode->vdisplay * mode->hdisplay * (depth / 8);
|
|
const bus_size_t dmasize = (fbsize + PAGE_MASK) & ~PAGE_MASK;
|
|
error = tegra_dc_allocmem(sc, dmasize);
|
|
if (error) {
|
|
aprint_error_dev(dev,
|
|
"failed to allocate %u bytes of video memory: %d\n",
|
|
(u_int)dmasize, error);
|
|
return error;
|
|
}
|
|
|
|
error = tegra_dc_init(sc, mode);
|
|
if (error) {
|
|
aprint_error_dev(dev,
|
|
"failed to initialize display controller: %d\n", error);
|
|
return error;
|
|
}
|
|
|
|
struct tegrafb_attach_args tfb = {
|
|
.tfb_console = is_console,
|
|
.tfb_dmat = sc->sc_dmat,
|
|
.tfb_dmamap = sc->sc_dmamap,
|
|
.tfb_dmap = sc->sc_dmap,
|
|
.tfb_width = mode->hdisplay,
|
|
.tfb_height = mode->vdisplay,
|
|
.tfb_depth = depth,
|
|
.tfb_stride = mode->hdisplay * (depth / 8),
|
|
.tfb_outputdev = outputdev,
|
|
};
|
|
|
|
sc->sc_fbdev = config_found(sc->sc_dev, &tfb, tegra_dc_print);
|
|
|
|
if (sc->sc_fbdev != NULL && edid != NULL) {
|
|
prop_data_t data = prop_data_create_data(edid, 128);
|
|
prop_dictionary_set(device_properties(sc->sc_fbdev),
|
|
"EDID", data);
|
|
prop_object_release(data);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
tegra_dc_hdmi_start(device_t dev)
|
|
{
|
|
struct tegra_dc_softc * const sc = device_private(dev);
|
|
|
|
DC_SET_CLEAR(sc, DC_DISP_DISP_WIN_OPTIONS_REG,
|
|
DC_DISP_DISP_WIN_OPTIONS_HDMI_ENABLE, 0);
|
|
|
|
tegra_dc_update(sc);
|
|
}
|