Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_TEGRA
tristate "NVIDIA Tegra DRM"
depends on ARCH_TEGRA || COMPILE_TEST
depends on COMMON_CLK
depends on DRM
depends on OF
select DRM_CLIENT_SELECTION
select DRM_DISPLAY_DP_HELPER
select DRM_DISPLAY_HDMI_HELPER
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
select DRM_DISPLAY_DP_AUX_BUS
select DRM_KMS_HELPER
select DRM_MIPI_DSI
select DRM_PANEL
select FB_DMAMEM_HELPERS if DRM_FBDEV_EMULATION
select TEGRA_HOST1X
select INTERCONNECT
select IOMMU_IOVA
select CEC_CORE if CEC_NOTIFIER
select SND_SIMPLE_CARD if SND_SOC_TEGRA20_SPDIF
select SND_SOC_HDMI_CODEC if SND_SOC_TEGRA20_SPDIF
select SND_AUDIO_GRAPH_CARD if SND_SOC_TEGRA20_SPDIF
help
Choose this option if you have an NVIDIA Tegra SoC.
To compile this driver as a module, choose M here: the module
will be called tegra-drm.
if DRM_TEGRA
config DRM_TEGRA_DEBUG
bool "NVIDIA Tegra DRM debug support"
help
Say yes here to enable debugging support.
config DRM_TEGRA_STAGING
bool "Enable HOST1X interface"
depends on STAGING
help
Say yes if HOST1X should be available for userspace DRM users.
If unsure, choose N.
endif
@@ -0,0 +1,35 @@
# SPDX-License-Identifier: GPL-2.0
ccflags-$(CONFIG_DRM_TEGRA_DEBUG) += -DDEBUG
tegra-drm-y := \
drm.o \
uapi.o \
submit.o \
firewall.o \
gem.o \
fb.o \
dp.o \
hub.o \
plane.o \
dc.o \
output.o \
rgb.o \
hda.o \
hdmi.o \
mipi-phy.o \
dsi.o \
sor.o \
dpaux.o \
gr2d.o \
gr3d.o \
falcon.o \
vic.o \
nvdec.o \
nvjpg.o \
riscv.o
tegra-drm-y += trace.o
tegra-drm-$(CONFIG_DRM_FBDEV_EMULATION) += fbdev.o
obj-$(CONFIG_DRM_TEGRA) += tegra-drm.o
File diff suppressed because it is too large Load Diff
+854
View File
@@ -0,0 +1,854 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2012 Avionic Design GmbH
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
*/
#ifndef TEGRA_DC_H
#define TEGRA_DC_H 1
#include <linux/host1x.h>
#include <drm/drm_crtc.h>
#include "drm.h"
struct tegra_output;
#define TEGRA_DC_LEGACY_PLANES_NUM 7
struct tegra_dc_state {
struct drm_crtc_state base;
struct clk *clk;
unsigned long pclk;
unsigned int div;
u32 planes;
};
static inline struct tegra_dc_state *to_dc_state(struct drm_crtc_state *state)
{
if (state)
return container_of(state, struct tegra_dc_state, base);
return NULL;
}
struct tegra_dc_stats {
unsigned long frames;
unsigned long vblank;
unsigned long underflow;
unsigned long overflow;
unsigned long frames_total;
unsigned long vblank_total;
unsigned long underflow_total;
unsigned long overflow_total;
};
struct tegra_windowgroup_soc {
unsigned int index;
unsigned int dc;
const unsigned int *windows;
unsigned int num_windows;
};
struct tegra_dc_soc_info {
bool supports_background_color;
bool supports_interlacing;
bool supports_cursor;
bool supports_block_linear;
bool supports_sector_layout;
bool has_legacy_blending;
unsigned int pitch_align;
bool has_powergate;
bool coupled_pm;
bool has_nvdisplay;
const struct tegra_windowgroup_soc *wgrps;
unsigned int num_wgrps;
const u32 *primary_formats;
unsigned int num_primary_formats;
const u32 *overlay_formats;
unsigned int num_overlay_formats;
const u64 *modifiers;
bool has_win_a_without_filters;
bool has_win_b_vfilter_mem_client;
bool has_win_c_without_vert_filter;
bool plane_tiled_memory_bandwidth_x2;
bool has_pll_d2_out0;
};
struct tegra_dc {
struct host1x_client client;
struct host1x_syncpt *syncpt;
struct device *dev;
struct drm_crtc base;
unsigned int powergate;
int pipe;
struct clk *clk;
struct reset_control *rst;
void __iomem *regs;
int irq;
struct tegra_output *rgb;
struct tegra_pmc *pmc;
struct tegra_dc_stats stats;
struct list_head list;
struct drm_info_list *debugfs_files;
const struct tegra_dc_soc_info *soc;
bool has_opp_table;
u64 *cmu_output_lut;
dma_addr_t cmu_output_lut_phys;
};
static inline struct tegra_dc *
host1x_client_to_dc(struct host1x_client *client)
{
return container_of(client, struct tegra_dc, client);
}
static inline struct tegra_dc *to_tegra_dc(struct drm_crtc *crtc)
{
return crtc ? container_of(crtc, struct tegra_dc, base) : NULL;
}
static inline void tegra_dc_writel(struct tegra_dc *dc, u32 value,
unsigned int offset)
{
trace_dc_writel(dc->dev, offset, value);
writel(value, dc->regs + (offset << 2));
}
static inline u32 tegra_dc_readl(struct tegra_dc *dc, unsigned int offset)
{
u32 value = readl(dc->regs + (offset << 2));
trace_dc_readl(dc->dev, offset, value);
return value;
}
struct tegra_dc_window {
struct {
unsigned int x;
unsigned int y;
unsigned int w;
unsigned int h;
} src;
struct {
unsigned int x;
unsigned int y;
unsigned int w;
unsigned int h;
} dst;
unsigned int bits_per_pixel;
unsigned int stride[2];
unsigned long base[3];
unsigned int zpos;
bool reflect_x;
bool reflect_y;
struct tegra_bo_tiling tiling;
u32 format;
u32 swap;
};
/* from dc.c */
bool tegra_dc_has_output(struct tegra_dc *dc, struct device *dev);
void tegra_dc_commit(struct tegra_dc *dc);
int tegra_dc_state_setup_clock(struct tegra_dc *dc,
struct drm_crtc_state *crtc_state,
struct clk *clk, unsigned long pclk,
unsigned int div);
void tegra_crtc_atomic_post_commit(struct drm_crtc *crtc,
struct drm_atomic_commit *state);
/* from rgb.c */
int tegra_dc_rgb_probe(struct tegra_dc *dc);
void tegra_dc_rgb_remove(struct tegra_dc *dc);
int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc);
int tegra_dc_rgb_exit(struct tegra_dc *dc);
#define DC_CMD_GENERAL_INCR_SYNCPT 0x000
#define DC_CMD_GENERAL_INCR_SYNCPT_CNTRL 0x001
#define SYNCPT_CNTRL_NO_STALL (1 << 8)
#define SYNCPT_CNTRL_SOFT_RESET (1 << 0)
#define DC_CMD_GENERAL_INCR_SYNCPT_ERROR 0x002
#define DC_CMD_WIN_A_INCR_SYNCPT 0x008
#define DC_CMD_WIN_A_INCR_SYNCPT_CNTRL 0x009
#define DC_CMD_WIN_A_INCR_SYNCPT_ERROR 0x00a
#define DC_CMD_WIN_B_INCR_SYNCPT 0x010
#define DC_CMD_WIN_B_INCR_SYNCPT_CNTRL 0x011
#define DC_CMD_WIN_B_INCR_SYNCPT_ERROR 0x012
#define DC_CMD_WIN_C_INCR_SYNCPT 0x018
#define DC_CMD_WIN_C_INCR_SYNCPT_CNTRL 0x019
#define DC_CMD_WIN_C_INCR_SYNCPT_ERROR 0x01a
#define DC_CMD_CONT_SYNCPT_VSYNC 0x028
#define SYNCPT_VSYNC_ENABLE (1 << 8)
#define DC_CMD_DISPLAY_COMMAND_OPTION0 0x031
#define DC_CMD_DISPLAY_COMMAND 0x032
#define DISP_CTRL_MODE_STOP (0 << 5)
#define DISP_CTRL_MODE_C_DISPLAY (1 << 5)
#define DISP_CTRL_MODE_NC_DISPLAY (2 << 5)
#define DISP_CTRL_MODE_MASK (3 << 5)
#define DC_CMD_SIGNAL_RAISE 0x033
#define DC_CMD_DISPLAY_POWER_CONTROL 0x036
#define PW0_ENABLE (1 << 0)
#define PW1_ENABLE (1 << 2)
#define PW2_ENABLE (1 << 4)
#define PW3_ENABLE (1 << 6)
#define PW4_ENABLE (1 << 8)
#define PM0_ENABLE (1 << 16)
#define PM1_ENABLE (1 << 18)
#define DC_CMD_INT_STATUS 0x037
#define DC_CMD_INT_MASK 0x038
#define DC_CMD_INT_ENABLE 0x039
#define DC_CMD_INT_TYPE 0x03a
#define DC_CMD_INT_POLARITY 0x03b
#define CTXSW_INT (1 << 0)
#define FRAME_END_INT (1 << 1)
#define VBLANK_INT (1 << 2)
#define V_PULSE3_INT (1 << 4)
#define V_PULSE2_INT (1 << 5)
#define REGION_CRC_INT (1 << 6)
#define REG_TMOUT_INT (1 << 7)
#define WIN_A_UF_INT (1 << 8)
#define WIN_B_UF_INT (1 << 9)
#define WIN_C_UF_INT (1 << 10)
#define MSF_INT (1 << 12)
#define WIN_A_OF_INT (1 << 14)
#define WIN_B_OF_INT (1 << 15)
#define WIN_C_OF_INT (1 << 16)
#define HEAD_UF_INT (1 << 23)
#define SD3_BUCKET_WALK_DONE_INT (1 << 24)
#define DSC_OBUF_UF_INT (1 << 26)
#define DSC_RBUF_UF_INT (1 << 27)
#define DSC_BBUF_UF_INT (1 << 28)
#define DSC_TO_UF_INT (1 << 29)
#define DC_CMD_SIGNAL_RAISE1 0x03c
#define DC_CMD_SIGNAL_RAISE2 0x03d
#define DC_CMD_SIGNAL_RAISE3 0x03e
#define DC_CMD_STATE_ACCESS 0x040
#define READ_MUX (1 << 0)
#define WRITE_MUX (1 << 2)
#define DC_CMD_STATE_CONTROL 0x041
#define GENERAL_ACT_REQ (1 << 0)
#define WIN_A_ACT_REQ (1 << 1)
#define WIN_B_ACT_REQ (1 << 2)
#define WIN_C_ACT_REQ (1 << 3)
#define CURSOR_ACT_REQ (1 << 7)
#define GENERAL_UPDATE (1 << 8)
#define WIN_A_UPDATE (1 << 9)
#define WIN_B_UPDATE (1 << 10)
#define WIN_C_UPDATE (1 << 11)
#define CURSOR_UPDATE (1 << 15)
#define COMMON_ACTREQ (1 << 16)
#define COMMON_UPDATE (1 << 17)
#define NC_HOST_TRIG (1 << 24)
#define DC_CMD_DISPLAY_WINDOW_HEADER 0x042
#define WINDOW_A_SELECT (1 << 4)
#define WINDOW_B_SELECT (1 << 5)
#define WINDOW_C_SELECT (1 << 6)
#define DC_CMD_REG_ACT_CONTROL 0x043
#define DC_COM_CRC_CONTROL 0x300
#define DC_COM_CRC_CONTROL_ALWAYS (1 << 3)
#define DC_COM_CRC_CONTROL_FULL_FRAME (0 << 2)
#define DC_COM_CRC_CONTROL_ACTIVE_DATA (1 << 2)
#define DC_COM_CRC_CONTROL_WAIT (1 << 1)
#define DC_COM_CRC_CONTROL_ENABLE (1 << 0)
#define DC_COM_CRC_CHECKSUM 0x301
#define DC_COM_PIN_OUTPUT_ENABLE(x) (0x302 + (x))
#define DC_COM_PIN_OUTPUT_POLARITY(x) (0x306 + (x))
#define LVS_OUTPUT_POLARITY_LOW (1 << 28)
#define LHS_OUTPUT_POLARITY_LOW (1 << 30)
#define DC_COM_PIN_OUTPUT_DATA(x) (0x30a + (x))
#define DC_COM_PIN_INPUT_ENABLE(x) (0x30e + (x))
#define DC_COM_PIN_INPUT_DATA(x) (0x312 + (x))
#define DC_COM_PIN_OUTPUT_SELECT(x) (0x314 + (x))
#define DC_COM_PIN_MISC_CONTROL 0x31b
#define DC_COM_PIN_PM0_CONTROL 0x31c
#define DC_COM_PIN_PM0_DUTY_CYCLE 0x31d
#define DC_COM_PIN_PM1_CONTROL 0x31e
#define DC_COM_PIN_PM1_DUTY_CYCLE 0x31f
#define DC_COM_SPI_CONTROL 0x320
#define DC_COM_SPI_START_BYTE 0x321
#define DC_COM_HSPI_WRITE_DATA_AB 0x322
#define DC_COM_HSPI_WRITE_DATA_CD 0x323
#define DC_COM_HSPI_CS_DC 0x324
#define DC_COM_SCRATCH_REGISTER_A 0x325
#define DC_COM_SCRATCH_REGISTER_B 0x326
#define DC_COM_GPIO_CTRL 0x327
#define DC_COM_GPIO_DEBOUNCE_COUNTER 0x328
#define DC_COM_CRC_CHECKSUM_LATCHED 0x329
#define DC_COM_RG_UNDERFLOW 0x365
#define UNDERFLOW_MODE_RED (1 << 8)
#define UNDERFLOW_REPORT_ENABLE (1 << 0)
#define DC_DISP_DISP_SIGNAL_OPTIONS0 0x400
#define H_PULSE0_ENABLE (1 << 8)
#define H_PULSE1_ENABLE (1 << 10)
#define H_PULSE2_ENABLE (1 << 12)
#define DC_DISP_DISP_SIGNAL_OPTIONS1 0x401
#define DC_DISP_DISP_WIN_OPTIONS 0x402
#define HDMI_ENABLE (1 << 30)
#define DSI_ENABLE (1 << 29)
#define SOR1_TIMING_CYA (1 << 27)
#define CURSOR_ENABLE (1 << 16)
#define SOR_ENABLE(x) (1 << (25 + (((x) > 1) ? ((x) + 1) : (x))))
#define DC_DISP_DISP_MEM_HIGH_PRIORITY 0x403
#define CURSOR_THRESHOLD(x) (((x) & 0x03) << 24)
#define WINDOW_A_THRESHOLD(x) (((x) & 0x7f) << 16)
#define WINDOW_B_THRESHOLD(x) (((x) & 0x7f) << 8)
#define WINDOW_C_THRESHOLD(x) (((x) & 0xff) << 0)
#define DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER 0x404
#define CURSOR_DELAY(x) (((x) & 0x3f) << 24)
#define WINDOW_A_DELAY(x) (((x) & 0x3f) << 16)
#define WINDOW_B_DELAY(x) (((x) & 0x3f) << 8)
#define WINDOW_C_DELAY(x) (((x) & 0x3f) << 0)
#define DC_DISP_DISP_TIMING_OPTIONS 0x405
#define VSYNC_H_POSITION(x) ((x) & 0xfff)
#define DC_DISP_REF_TO_SYNC 0x406
#define DC_DISP_SYNC_WIDTH 0x407
#define DC_DISP_BACK_PORCH 0x408
#define DC_DISP_ACTIVE 0x409
#define DC_DISP_FRONT_PORCH 0x40a
#define DC_DISP_H_PULSE0_CONTROL 0x40b
#define DC_DISP_H_PULSE0_POSITION_A 0x40c
#define DC_DISP_H_PULSE0_POSITION_B 0x40d
#define DC_DISP_H_PULSE0_POSITION_C 0x40e
#define DC_DISP_H_PULSE0_POSITION_D 0x40f
#define DC_DISP_H_PULSE1_CONTROL 0x410
#define DC_DISP_H_PULSE1_POSITION_A 0x411
#define DC_DISP_H_PULSE1_POSITION_B 0x412
#define DC_DISP_H_PULSE1_POSITION_C 0x413
#define DC_DISP_H_PULSE1_POSITION_D 0x414
#define DC_DISP_H_PULSE2_CONTROL 0x415
#define DC_DISP_H_PULSE2_POSITION_A 0x416
#define DC_DISP_H_PULSE2_POSITION_B 0x417
#define DC_DISP_H_PULSE2_POSITION_C 0x418
#define DC_DISP_H_PULSE2_POSITION_D 0x419
#define DC_DISP_V_PULSE0_CONTROL 0x41a
#define DC_DISP_V_PULSE0_POSITION_A 0x41b
#define DC_DISP_V_PULSE0_POSITION_B 0x41c
#define DC_DISP_V_PULSE0_POSITION_C 0x41d
#define DC_DISP_V_PULSE1_CONTROL 0x41e
#define DC_DISP_V_PULSE1_POSITION_A 0x41f
#define DC_DISP_V_PULSE1_POSITION_B 0x420
#define DC_DISP_V_PULSE1_POSITION_C 0x421
#define DC_DISP_V_PULSE2_CONTROL 0x422
#define DC_DISP_V_PULSE2_POSITION_A 0x423
#define DC_DISP_V_PULSE3_CONTROL 0x424
#define DC_DISP_V_PULSE3_POSITION_A 0x425
#define DC_DISP_M0_CONTROL 0x426
#define DC_DISP_M1_CONTROL 0x427
#define DC_DISP_DI_CONTROL 0x428
#define DC_DISP_PP_CONTROL 0x429
#define DC_DISP_PP_SELECT_A 0x42a
#define DC_DISP_PP_SELECT_B 0x42b
#define DC_DISP_PP_SELECT_C 0x42c
#define DC_DISP_PP_SELECT_D 0x42d
#define PULSE_MODE_NORMAL (0 << 3)
#define PULSE_MODE_ONE_CLOCK (1 << 3)
#define PULSE_POLARITY_HIGH (0 << 4)
#define PULSE_POLARITY_LOW (1 << 4)
#define PULSE_QUAL_ALWAYS (0 << 6)
#define PULSE_QUAL_VACTIVE (2 << 6)
#define PULSE_QUAL_VACTIVE1 (3 << 6)
#define PULSE_LAST_START_A (0 << 8)
#define PULSE_LAST_END_A (1 << 8)
#define PULSE_LAST_START_B (2 << 8)
#define PULSE_LAST_END_B (3 << 8)
#define PULSE_LAST_START_C (4 << 8)
#define PULSE_LAST_END_C (5 << 8)
#define PULSE_LAST_START_D (6 << 8)
#define PULSE_LAST_END_D (7 << 8)
#define PULSE_START(x) (((x) & 0xfff) << 0)
#define PULSE_END(x) (((x) & 0xfff) << 16)
#define DC_DISP_DISP_CLOCK_CONTROL 0x42e
#define PIXEL_CLK_DIVIDER_PCD1 (0 << 8)
#define PIXEL_CLK_DIVIDER_PCD1H (1 << 8)
#define PIXEL_CLK_DIVIDER_PCD2 (2 << 8)
#define PIXEL_CLK_DIVIDER_PCD3 (3 << 8)
#define PIXEL_CLK_DIVIDER_PCD4 (4 << 8)
#define PIXEL_CLK_DIVIDER_PCD6 (5 << 8)
#define PIXEL_CLK_DIVIDER_PCD8 (6 << 8)
#define PIXEL_CLK_DIVIDER_PCD9 (7 << 8)
#define PIXEL_CLK_DIVIDER_PCD12 (8 << 8)
#define PIXEL_CLK_DIVIDER_PCD16 (9 << 8)
#define PIXEL_CLK_DIVIDER_PCD18 (10 << 8)
#define PIXEL_CLK_DIVIDER_PCD24 (11 << 8)
#define PIXEL_CLK_DIVIDER_PCD13 (12 << 8)
#define SHIFT_CLK_DIVIDER(x) ((x) & 0xff)
#define DC_DISP_DISP_INTERFACE_CONTROL 0x42f
#define DISP_DATA_FORMAT_DF1P1C (0 << 0)
#define DISP_DATA_FORMAT_DF1P2C24B (1 << 0)
#define DISP_DATA_FORMAT_DF1P2C18B (2 << 0)
#define DISP_DATA_FORMAT_DF1P2C16B (3 << 0)
#define DISP_DATA_FORMAT_DF2S (4 << 0)
#define DISP_DATA_FORMAT_DF3S (5 << 0)
#define DISP_DATA_FORMAT_DFSPI (6 << 0)
#define DISP_DATA_FORMAT_DF1P3C24B (7 << 0)
#define DISP_DATA_FORMAT_DF1P3C18B (8 << 0)
#define DISP_ALIGNMENT_MSB (0 << 8)
#define DISP_ALIGNMENT_LSB (1 << 8)
#define DISP_ORDER_RED_BLUE (0 << 9)
#define DISP_ORDER_BLUE_RED (1 << 9)
#define DC_DISP_DISP_COLOR_CONTROL 0x430
#define BASE_COLOR_SIZE666 ( 0 << 0)
#define BASE_COLOR_SIZE111 ( 1 << 0)
#define BASE_COLOR_SIZE222 ( 2 << 0)
#define BASE_COLOR_SIZE333 ( 3 << 0)
#define BASE_COLOR_SIZE444 ( 4 << 0)
#define BASE_COLOR_SIZE555 ( 5 << 0)
#define BASE_COLOR_SIZE565 ( 6 << 0)
#define BASE_COLOR_SIZE332 ( 7 << 0)
#define BASE_COLOR_SIZE888 ( 8 << 0)
#define BASE_COLOR_SIZE101010 (10 << 0)
#define BASE_COLOR_SIZE121212 (12 << 0)
#define DITHER_CONTROL_MASK (3 << 8)
#define DITHER_CONTROL_DISABLE (0 << 8)
#define DITHER_CONTROL_ORDERED (2 << 8)
#define DITHER_CONTROL_ERRDIFF (3 << 8)
#define BASE_COLOR_SIZE_MASK (0xf << 0)
#define BASE_COLOR_SIZE_666 ( 0 << 0)
#define BASE_COLOR_SIZE_111 ( 1 << 0)
#define BASE_COLOR_SIZE_222 ( 2 << 0)
#define BASE_COLOR_SIZE_333 ( 3 << 0)
#define BASE_COLOR_SIZE_444 ( 4 << 0)
#define BASE_COLOR_SIZE_555 ( 5 << 0)
#define BASE_COLOR_SIZE_565 ( 6 << 0)
#define BASE_COLOR_SIZE_332 ( 7 << 0)
#define BASE_COLOR_SIZE_888 ( 8 << 0)
#define BASE_COLOR_SIZE_101010 ( 10 << 0)
#define BASE_COLOR_SIZE_121212 ( 12 << 0)
#define CMU_ENABLE_ENABLE (1 << 20)
#define DC_DISP_SHIFT_CLOCK_OPTIONS 0x431
#define SC1_H_QUALIFIER_NONE (1 << 16)
#define SC0_H_QUALIFIER_NONE (1 << 0)
#define DC_DISP_DATA_ENABLE_OPTIONS 0x432
#define DE_SELECT_ACTIVE_BLANK (0 << 0)
#define DE_SELECT_ACTIVE (1 << 0)
#define DE_SELECT_ACTIVE_IS (2 << 0)
#define DE_CONTROL_ONECLK (0 << 2)
#define DE_CONTROL_NORMAL (1 << 2)
#define DE_CONTROL_EARLY_EXT (2 << 2)
#define DE_CONTROL_EARLY (3 << 2)
#define DE_CONTROL_ACTIVE_BLANK (4 << 2)
#define DC_DISP_SERIAL_INTERFACE_OPTIONS 0x433
#define DC_DISP_LCD_SPI_OPTIONS 0x434
#define DC_DISP_BORDER_COLOR 0x435
#define DC_DISP_COLOR_KEY0_LOWER 0x436
#define DC_DISP_COLOR_KEY0_UPPER 0x437
#define DC_DISP_COLOR_KEY1_LOWER 0x438
#define DC_DISP_COLOR_KEY1_UPPER 0x439
#define DC_DISP_CURSOR_FOREGROUND 0x43c
#define DC_DISP_CURSOR_BACKGROUND 0x43d
#define DC_DISP_CURSOR_START_ADDR 0x43e
#define CURSOR_CLIP_DISPLAY (0 << 28)
#define CURSOR_CLIP_WIN_A (1 << 28)
#define CURSOR_CLIP_WIN_B (2 << 28)
#define CURSOR_CLIP_WIN_C (3 << 28)
#define CURSOR_SIZE_32x32 (0 << 24)
#define CURSOR_SIZE_64x64 (1 << 24)
#define CURSOR_SIZE_128x128 (2 << 24)
#define CURSOR_SIZE_256x256 (3 << 24)
#define DC_DISP_CURSOR_START_ADDR_NS 0x43f
#define DC_DISP_CURSOR_POSITION 0x440
#define DC_DISP_CURSOR_POSITION_NS 0x441
#define DC_DISP_INIT_SEQ_CONTROL 0x442
#define DC_DISP_SPI_INIT_SEQ_DATA_A 0x443
#define DC_DISP_SPI_INIT_SEQ_DATA_B 0x444
#define DC_DISP_SPI_INIT_SEQ_DATA_C 0x445
#define DC_DISP_SPI_INIT_SEQ_DATA_D 0x446
#define DC_DISP_DC_MCCIF_FIFOCTRL 0x480
#define DC_DISP_MCCIF_DISPLAY0A_HYST 0x481
#define DC_DISP_MCCIF_DISPLAY0B_HYST 0x482
#define DC_DISP_MCCIF_DISPLAY1A_HYST 0x483
#define DC_DISP_MCCIF_DISPLAY1B_HYST 0x484
#define DC_DISP_DAC_CRT_CTRL 0x4c0
#define DC_DISP_DISP_MISC_CONTROL 0x4c1
#define DC_DISP_SD_CONTROL 0x4c2
#define DC_DISP_SD_CSC_COEFF 0x4c3
#define DC_DISP_SD_LUT(x) (0x4c4 + (x))
#define DC_DISP_SD_FLICKER_CONTROL 0x4cd
#define DC_DISP_DC_PIXEL_COUNT 0x4ce
#define DC_DISP_SD_HISTOGRAM(x) (0x4cf + (x))
#define DC_DISP_SD_BL_PARAMETERS 0x4d7
#define DC_DISP_SD_BL_TF(x) (0x4d8 + (x))
#define DC_DISP_SD_BL_CONTROL 0x4dc
#define DC_DISP_SD_HW_K_VALUES 0x4dd
#define DC_DISP_SD_MAN_K_VALUES 0x4de
#define DC_DISP_BLEND_BACKGROUND_COLOR 0x4e4
#define BACKGROUND_COLOR_ALPHA(x) (((x) & 0xff) << 24)
#define BACKGROUND_COLOR_BLUE(x) (((x) & 0xff) << 16)
#define BACKGROUND_COLOR_GREEN(x) (((x) & 0xff) << 8)
#define BACKGROUND_COLOR_RED(x) (((x) & 0xff) << 0)
#define DC_DISP_INTERLACE_CONTROL 0x4e5
#define INTERLACE_STATUS (1 << 2)
#define INTERLACE_START (1 << 1)
#define INTERLACE_ENABLE (1 << 0)
#define DC_DISP_CURSOR_START_ADDR_HI 0x4ec
#define DC_DISP_BLEND_CURSOR_CONTROL 0x4f1
#define CURSOR_COMPOSITION_MODE_BLEND (0 << 25)
#define CURSOR_COMPOSITION_MODE_XOR (1 << 25)
#define CURSOR_MODE_LEGACY (0 << 24)
#define CURSOR_MODE_NORMAL (1 << 24)
#define CURSOR_DST_BLEND_ZERO (0 << 16)
#define CURSOR_DST_BLEND_K1 (1 << 16)
#define CURSOR_DST_BLEND_NEG_K1_TIMES_SRC (2 << 16)
#define CURSOR_DST_BLEND_MASK (3 << 16)
#define CURSOR_SRC_BLEND_K1 (0 << 8)
#define CURSOR_SRC_BLEND_K1_TIMES_SRC (1 << 8)
#define CURSOR_SRC_BLEND_MASK (3 << 8)
#define CURSOR_ALPHA 0xff
#define DC_WIN_CORE_ACT_CONTROL 0x50e
#define VCOUNTER (0 << 0)
#define HCOUNTER (1 << 0)
#define DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA 0x543
#define LATENCY_CTL_MODE_ENABLE (1 << 2)
#define DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB 0x544
#define WATERMARK_MASK 0x1fffffff
#define DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER 0x560
#define PIPE_METER_INT(x) (((x) & 0xff) << 8)
#define PIPE_METER_FRAC(x) (((x) & 0xff) << 0)
#define DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG 0x561
#define MEMPOOL_ENTRIES(x) (((x) & 0xffff) << 0)
#define DC_WIN_CORE_IHUB_WGRP_FETCH_METER 0x562
#define SLOTS(x) (((x) & 0xff) << 0)
#define DC_WIN_CORE_IHUB_LINEBUF_CONFIG 0x563
#define MODE_TWO_LINES (0 << 14)
#define MODE_FOUR_LINES (1 << 14)
#define DC_WIN_CORE_IHUB_THREAD_GROUP 0x568
#define THREAD_NUM_MASK (0x1f << 1)
#define THREAD_NUM(x) (((x) & 0x1f) << 1)
#define THREAD_GROUP_ENABLE (1 << 0)
#define DC_WIN_H_FILTER_P(p) (0x601 + (p))
#define DC_WIN_V_FILTER_P(p) (0x619 + (p))
#define DC_WIN_CSC_YOF 0x611
#define DC_WIN_CSC_KYRGB 0x612
#define DC_WIN_CSC_KUR 0x613
#define DC_WIN_CSC_KVR 0x614
#define DC_WIN_CSC_KUG 0x615
#define DC_WIN_CSC_KVG 0x616
#define DC_WIN_CSC_KUB 0x617
#define DC_WIN_CSC_KVB 0x618
#define DC_WIN_WIN_OPTIONS 0x700
#define H_DIRECTION (1 << 0)
#define V_DIRECTION (1 << 2)
#define COLOR_EXPAND (1 << 6)
#define H_FILTER (1 << 8)
#define V_FILTER (1 << 10)
#define CSC_ENABLE (1 << 18)
#define WIN_ENABLE (1 << 30)
#define DC_WIN_BYTE_SWAP 0x701
#define BYTE_SWAP_NOSWAP (0 << 0)
#define BYTE_SWAP_SWAP2 (1 << 0)
#define BYTE_SWAP_SWAP4 (2 << 0)
#define BYTE_SWAP_SWAP4HW (3 << 0)
#define DC_WIN_BUFFER_CONTROL 0x702
#define BUFFER_CONTROL_HOST (0 << 0)
#define BUFFER_CONTROL_VI (1 << 0)
#define BUFFER_CONTROL_EPP (2 << 0)
#define BUFFER_CONTROL_MPEGE (3 << 0)
#define BUFFER_CONTROL_SB2D (4 << 0)
#define DC_WIN_COLOR_DEPTH 0x703
#define WIN_COLOR_DEPTH_P1 0
#define WIN_COLOR_DEPTH_P2 1
#define WIN_COLOR_DEPTH_P4 2
#define WIN_COLOR_DEPTH_P8 3
#define WIN_COLOR_DEPTH_B4G4R4A4 4
#define WIN_COLOR_DEPTH_B5G5R5A1 5
#define WIN_COLOR_DEPTH_B5G6R5 6
#define WIN_COLOR_DEPTH_A1B5G5R5 7
#define WIN_COLOR_DEPTH_B8G8R8A8 12
#define WIN_COLOR_DEPTH_R8G8B8A8 13
#define WIN_COLOR_DEPTH_B6x2G6x2R6x2A8 14
#define WIN_COLOR_DEPTH_R6x2G6x2B6x2A8 15
#define WIN_COLOR_DEPTH_YCbCr422 16
#define WIN_COLOR_DEPTH_YUV422 17
#define WIN_COLOR_DEPTH_YCbCr420P 18
#define WIN_COLOR_DEPTH_YUV420P 19
#define WIN_COLOR_DEPTH_YCbCr422P 20
#define WIN_COLOR_DEPTH_YUV422P 21
#define WIN_COLOR_DEPTH_YCbCr422R 22
#define WIN_COLOR_DEPTH_YUV422R 23
#define WIN_COLOR_DEPTH_YCbCr422RA 24
#define WIN_COLOR_DEPTH_YUV422RA 25
#define WIN_COLOR_DEPTH_R4G4B4A4 27
#define WIN_COLOR_DEPTH_R5G5B5A 28
#define WIN_COLOR_DEPTH_AR5G5B5 29
#define WIN_COLOR_DEPTH_B5G5R5X1 30
#define WIN_COLOR_DEPTH_X1B5G5R5 31
#define WIN_COLOR_DEPTH_R5G5B5X1 32
#define WIN_COLOR_DEPTH_X1R5G5B5 33
#define WIN_COLOR_DEPTH_R5G6B5 34
#define WIN_COLOR_DEPTH_A8R8G8B8 35
#define WIN_COLOR_DEPTH_A8B8G8R8 36
#define WIN_COLOR_DEPTH_B8G8R8X8 37
#define WIN_COLOR_DEPTH_R8G8B8X8 38
#define WIN_COLOR_DEPTH_YCbCr444P 41
#define WIN_COLOR_DEPTH_YCrCb420SP 42
#define WIN_COLOR_DEPTH_YCbCr420SP 43
#define WIN_COLOR_DEPTH_YCrCb422SP 44
#define WIN_COLOR_DEPTH_YCbCr422SP 45
#define WIN_COLOR_DEPTH_YCrCb444SP 48
#define WIN_COLOR_DEPTH_YCbCr444SP 49
#define WIN_COLOR_DEPTH_X8B8G8R8 65
#define WIN_COLOR_DEPTH_X8R8G8B8 66
#define DC_WIN_POSITION 0x704
#define H_POSITION(x) (((x) & 0x1fff) << 0) /* XXX 0x7fff on Tegra186 */
#define V_POSITION(x) (((x) & 0x1fff) << 16) /* XXX 0x7fff on Tegra186 */
#define DC_WIN_SIZE 0x705
#define H_SIZE(x) (((x) & 0x1fff) << 0) /* XXX 0x7fff on Tegra186 */
#define V_SIZE(x) (((x) & 0x1fff) << 16) /* XXX 0x7fff on Tegra186 */
#define DC_WIN_PRESCALED_SIZE 0x706
#define H_PRESCALED_SIZE(x) (((x) & 0x7fff) << 0)
#define V_PRESCALED_SIZE(x) (((x) & 0x1fff) << 16) /* XXX 0x7fff on Tegra186 */
#define DC_WIN_H_INITIAL_DDA 0x707
#define DC_WIN_V_INITIAL_DDA 0x708
#define DC_WIN_DDA_INC 0x709
#define H_DDA_INC(x) (((x) & 0xffff) << 0)
#define V_DDA_INC(x) (((x) & 0xffff) << 16)
#define DC_WIN_LINE_STRIDE 0x70a
#define DC_WIN_BUF_STRIDE 0x70b
#define DC_WIN_UV_BUF_STRIDE 0x70c
#define DC_WIN_BUFFER_ADDR_MODE 0x70d
#define DC_WIN_BUFFER_ADDR_MODE_LINEAR (0 << 0)
#define DC_WIN_BUFFER_ADDR_MODE_TILE (1 << 0)
#define DC_WIN_BUFFER_ADDR_MODE_LINEAR_UV (0 << 16)
#define DC_WIN_BUFFER_ADDR_MODE_TILE_UV (1 << 16)
#define DC_WIN_DV_CONTROL 0x70e
#define DC_WIN_BLEND_NOKEY 0x70f
#define BLEND_WEIGHT1(x) (((x) & 0xff) << 16)
#define BLEND_WEIGHT0(x) (((x) & 0xff) << 8)
#define DC_WIN_BLEND_1WIN 0x710
#define BLEND_CONTROL_FIX (0 << 2)
#define BLEND_CONTROL_ALPHA (1 << 2)
#define BLEND_COLOR_KEY_NONE (0 << 0)
#define BLEND_COLOR_KEY_0 (1 << 0)
#define BLEND_COLOR_KEY_1 (2 << 0)
#define BLEND_COLOR_KEY_BOTH (3 << 0)
#define DC_WIN_BLEND_2WIN_X 0x711
#define BLEND_CONTROL_DEPENDENT (2 << 2)
#define DC_WIN_BLEND_2WIN_Y 0x712
#define DC_WIN_BLEND_3WIN_XY 0x713
#define DC_WIN_HP_FETCH_CONTROL 0x714
#define DC_WINBUF_START_ADDR 0x800
#define DC_WINBUF_START_ADDR_NS 0x801
#define DC_WINBUF_START_ADDR_U 0x802
#define DC_WINBUF_START_ADDR_U_NS 0x803
#define DC_WINBUF_START_ADDR_V 0x804
#define DC_WINBUF_START_ADDR_V_NS 0x805
#define DC_WINBUF_ADDR_H_OFFSET 0x806
#define DC_WINBUF_ADDR_H_OFFSET_NS 0x807
#define DC_WINBUF_ADDR_V_OFFSET 0x808
#define DC_WINBUF_ADDR_V_OFFSET_NS 0x809
#define DC_WINBUF_UFLOW_STATUS 0x80a
#define DC_WINBUF_SURFACE_KIND 0x80b
#define DC_WINBUF_SURFACE_KIND_PITCH (0 << 0)
#define DC_WINBUF_SURFACE_KIND_TILED (1 << 0)
#define DC_WINBUF_SURFACE_KIND_BLOCK (2 << 0)
#define DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(x) (((x) & 0x7) << 4)
#define DC_WINBUF_START_ADDR_HI 0x80d
#define DC_WINBUF_START_ADDR_HI_U 0x80f
#define DC_WINBUF_START_ADDR_HI_V 0x811
#define DC_WINBUF_CDE_CONTROL 0x82f
#define ENABLE_SURFACE (1 << 0)
#define DC_WINBUF_AD_UFLOW_STATUS 0xbca
#define DC_WINBUF_BD_UFLOW_STATUS 0xdca
#define DC_WINBUF_CD_UFLOW_STATUS 0xfca
/* Tegra186 and later */
#define DC_DISP_CORE_SOR_SET_CONTROL(x) (0x403 + (x))
#define PROTOCOL_MASK (0xf << 8)
#define PROTOCOL_SINGLE_TMDS_A (0x1 << 8)
#define DC_DISP_CORE_HEAD_SET_CONTROL_OUTPUT_LUT 0x431
#define OUTPUT_LUT_MODE_MASK (3 << 5)
#define OUTPUT_LUT_MODE_INTERPOLATE (1 << 5)
#define OUTPUT_LUT_SIZE_MASK (3 << 1)
#define OUTPUT_LUT_SIZE_SIZE_1025 (2 << 1)
#define DC_DISP_COREPVT_HEAD_SET_OUTPUT_LUT_BASE 0x432
#define DC_DISP_COREPVT_HEAD_SET_OUTPUT_LUT_BASE_HI 0x433
#define DC_DISP_PCALC_HEAD_SET_CROPPED_POINT_IN_CURSOR 0x442
#define DC_DISP_PCALC_HEAD_SET_CROPPED_SIZE_IN_CURSOR 0x446
#define DC_WINC_PRECOMP_WGRP_PIPE_CAPA 0x500
#define DC_WINC_PRECOMP_WGRP_PIPE_CAPB 0x501
#define DC_WINC_PRECOMP_WGRP_PIPE_CAPC 0x502
#define MAX_PIXELS_5TAP444(x) ((x) & 0xffff)
#define DC_WINC_PRECOMP_WGRP_PIPE_CAPD 0x503
#define DC_WINC_PRECOMP_WGRP_PIPE_CAPE 0x504
#define MAX_PIXELS_2TAP444(x) ((x) & 0xffff)
#define DC_WINC_PRECOMP_WGRP_PIPE_CAPF 0x505
#define DC_WIN_CORE_WINDOWGROUP_SET_CONTROL 0x702
#define OWNER_MASK (0xf << 0)
#define OWNER(x) (((x) & 0xf) << 0)
#define DC_WIN_CROPPED_SIZE 0x706
#define DC_WIN_SET_INPUT_SCALER_H_START_PHASE 0x707
#define DC_WIN_SET_INPUT_SCALER_V_START_PHASE 0x708
#define DC_WIN_PLANAR_STORAGE 0x709
#define PITCH(x) (((x) >> 6) & 0x1fff)
#define DC_WIN_PLANAR_STORAGE_UV 0x70a
#define PITCH_U(x) ((((x) >> 6) & 0x1fff) << 0)
#define PITCH_V(x) ((((x) >> 6) & 0x1fff) << 16)
#define DC_WIN_SET_INPUT_SCALER_HPHASE_INCR 0x70b
#define DC_WIN_SET_INPUT_SCALER_VPHASE_INCR 0x70c
#define DC_WIN_SET_PARAMS 0x70d
#define CLAMP_BEFORE_BLEND (1 << 15)
#define DEGAMMA_NONE (0 << 13)
#define DEGAMMA_SRGB (1 << 13)
#define DEGAMMA_YUV8_10 (2 << 13)
#define DEGAMMA_YUV12 (3 << 13)
#define INPUT_RANGE_BYPASS (0 << 10)
#define INPUT_RANGE_LIMITED (1 << 10)
#define INPUT_RANGE_FULL (2 << 10)
#define COLOR_SPACE_RGB (0 << 8)
#define COLOR_SPACE_YUV_601 (1 << 8)
#define COLOR_SPACE_YUV_709 (2 << 8)
#define COLOR_SPACE_YUV_2020 (3 << 8)
#define DC_WIN_WINDOWGROUP_SET_CONTROL_INPUT_SCALER 0x70e
#define HORIZONTAL_TAPS_2 (1 << 3)
#define HORIZONTAL_TAPS_5 (4 << 3)
#define VERTICAL_TAPS_2 (1 << 0)
#define VERTICAL_TAPS_5 (4 << 0)
#define DC_WIN_WINDOWGROUP_SET_INPUT_SCALER_COEFF 0x70f
#define COEFF_INDEX(x) (((x) & 0xff) << 15)
#define COEFF_DATA(x) (((x) & 0x3ff) << 0)
#define DC_WIN_WINDOWGROUP_SET_INPUT_SCALER_USAGE 0x711
#define INPUT_SCALER_USE422 (1 << 2)
#define INPUT_SCALER_VBYPASS (1 << 1)
#define INPUT_SCALER_HBYPASS (1 << 0)
#define DC_WIN_BLEND_LAYER_CONTROL 0x716
#define COLOR_KEY_NONE (0 << 25)
#define COLOR_KEY_SRC (1 << 25)
#define COLOR_KEY_DST (2 << 25)
#define BLEND_BYPASS (1 << 24)
#define K2(x) (((x) & 0xff) << 16)
#define K1(x) (((x) & 0xff) << 8)
#define WINDOW_LAYER_DEPTH(x) (((x) & 0xff) << 0)
#define DC_WIN_BLEND_MATCH_SELECT 0x717
#define BLEND_FACTOR_DST_ALPHA_ZERO (0 << 12)
#define BLEND_FACTOR_DST_ALPHA_ONE (1 << 12)
#define BLEND_FACTOR_DST_ALPHA_NEG_K1_TIMES_SRC (2 << 12)
#define BLEND_FACTOR_DST_ALPHA_K2 (3 << 12)
#define BLEND_FACTOR_SRC_ALPHA_ZERO (0 << 8)
#define BLEND_FACTOR_SRC_ALPHA_K1 (1 << 8)
#define BLEND_FACTOR_SRC_ALPHA_K2 (2 << 8)
#define BLEND_FACTOR_SRC_ALPHA_NEG_K1_TIMES_DST (3 << 8)
#define BLEND_FACTOR_DST_COLOR_ZERO (0 << 4)
#define BLEND_FACTOR_DST_COLOR_ONE (1 << 4)
#define BLEND_FACTOR_DST_COLOR_K1 (2 << 4)
#define BLEND_FACTOR_DST_COLOR_K2 (3 << 4)
#define BLEND_FACTOR_DST_COLOR_K1_TIMES_DST (4 << 4)
#define BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_DST (5 << 4)
#define BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC (6 << 4)
#define BLEND_FACTOR_DST_COLOR_NEG_K1 (7 << 4)
#define BLEND_FACTOR_SRC_COLOR_ZERO (0 << 0)
#define BLEND_FACTOR_SRC_COLOR_ONE (1 << 0)
#define BLEND_FACTOR_SRC_COLOR_K1 (2 << 0)
#define BLEND_FACTOR_SRC_COLOR_K1_TIMES_DST (3 << 0)
#define BLEND_FACTOR_SRC_COLOR_NEG_K1_TIMES_DST (4 << 0)
#define BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC (5 << 0)
#define DC_WIN_BLEND_NOMATCH_SELECT 0x718
#define DC_WIN_PRECOMP_WGRP_PARAMS 0x724
#define SWAP_UV (1 << 0)
#define DC_WIN_WINDOW_SET_CONTROL 0x730
#define CONTROL_CSC_ENABLE (1 << 5)
#define DC_WINBUF_CROPPED_POINT 0x806
#define OFFSET_Y(x) (((x) & 0xffff) << 16)
#define OFFSET_X(x) (((x) & 0xffff) << 0)
#endif /* TEGRA_DC_H */
+820
View File
@@ -0,0 +1,820 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2013-2019 NVIDIA Corporation
* Copyright (C) 2015 Rob Clark
*/
#include <linux/string_choices.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_print.h>
#include "dp.h"
static const u8 drm_dp_edp_revisions[] = { 0x11, 0x12, 0x13, 0x14 };
static void drm_dp_link_caps_reset(struct drm_dp_link_caps *caps)
{
caps->enhanced_framing = false;
caps->tps3_supported = false;
caps->fast_training = false;
caps->channel_coding = false;
caps->alternate_scrambler_reset = false;
}
void drm_dp_link_caps_copy(struct drm_dp_link_caps *dest,
const struct drm_dp_link_caps *src)
{
dest->enhanced_framing = src->enhanced_framing;
dest->tps3_supported = src->tps3_supported;
dest->fast_training = src->fast_training;
dest->channel_coding = src->channel_coding;
dest->alternate_scrambler_reset = src->alternate_scrambler_reset;
}
static void drm_dp_link_reset(struct drm_dp_link *link)
{
unsigned int i;
if (!link)
return;
link->revision = 0;
link->max_rate = 0;
link->max_lanes = 0;
drm_dp_link_caps_reset(&link->caps);
link->aux_rd_interval.cr = 0;
link->aux_rd_interval.ce = 0;
link->edp = 0;
link->rate = 0;
link->lanes = 0;
for (i = 0; i < DP_MAX_SUPPORTED_RATES; i++)
link->rates[i] = 0;
link->num_rates = 0;
}
/**
* drm_dp_link_add_rate() - add a rate to the list of supported rates
* @link: the link to add the rate to
* @rate: the rate to add
*
* Add a link rate to the list of supported link rates.
*
* Returns:
* 0 on success or one of the following negative error codes on failure:
* - ENOSPC if the maximum number of supported rates has been reached
* - EEXISTS if the link already supports this rate
*
* See also:
* drm_dp_link_remove_rate()
*/
int drm_dp_link_add_rate(struct drm_dp_link *link, unsigned long rate)
{
unsigned int i, pivot;
if (link->num_rates == DP_MAX_SUPPORTED_RATES)
return -ENOSPC;
for (pivot = 0; pivot < link->num_rates; pivot++)
if (rate <= link->rates[pivot])
break;
if (pivot != link->num_rates && rate == link->rates[pivot])
return -EEXIST;
for (i = link->num_rates; i > pivot; i--)
link->rates[i] = link->rates[i - 1];
link->rates[pivot] = rate;
link->num_rates++;
return 0;
}
/**
* drm_dp_link_remove_rate() - remove a rate from the list of supported rates
* @link: the link from which to remove the rate
* @rate: the rate to remove
*
* Removes a link rate from the list of supported link rates.
*
* Returns:
* 0 on success or one of the following negative error codes on failure:
* - EINVAL if the specified rate is not among the supported rates
*
* See also:
* drm_dp_link_add_rate()
*/
int drm_dp_link_remove_rate(struct drm_dp_link *link, unsigned long rate)
{
unsigned int i;
for (i = 0; i < link->num_rates; i++)
if (rate == link->rates[i])
break;
if (i == link->num_rates)
return -EINVAL;
link->num_rates--;
while (i < link->num_rates) {
link->rates[i] = link->rates[i + 1];
i++;
}
return 0;
}
/**
* drm_dp_link_update_rates() - normalize the supported link rates array
* @link: the link for which to normalize the supported link rates
*
* Users should call this function after they've manually modified the array
* of supported link rates. This function removes any stale entries, compacts
* the array and updates the supported link rate count. Note that calling the
* drm_dp_link_remove_rate() function already does this janitorial work.
*
* See also:
* drm_dp_link_add_rate(), drm_dp_link_remove_rate()
*/
void drm_dp_link_update_rates(struct drm_dp_link *link)
{
unsigned int i, count = 0;
for (i = 0; i < link->num_rates; i++) {
if (link->rates[i] != 0)
link->rates[count++] = link->rates[i];
}
for (i = count; i < link->num_rates; i++)
link->rates[i] = 0;
link->num_rates = count;
}
/**
* drm_dp_link_probe() - probe a DisplayPort link for capabilities
* @aux: DisplayPort AUX channel
* @link: pointer to structure in which to return link capabilities
*
* The structure filled in by this function can usually be passed directly
* into drm_dp_link_power_up() and drm_dp_link_configure() to power up and
* configure the link based on the link's capabilities.
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_link_probe(struct drm_dp_aux *aux, struct drm_dp_link *link)
{
u8 dpcd[DP_RECEIVER_CAP_SIZE], value;
unsigned int rd_interval;
int err;
drm_dp_link_reset(link);
err = drm_dp_dpcd_read(aux, DP_DPCD_REV, dpcd, sizeof(dpcd));
if (err < 0)
return err;
link->revision = dpcd[DP_DPCD_REV];
link->max_rate = drm_dp_max_link_rate(dpcd);
link->max_lanes = drm_dp_max_lane_count(dpcd);
link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(dpcd);
link->caps.tps3_supported = drm_dp_tps3_supported(dpcd);
link->caps.fast_training = drm_dp_fast_training_cap(dpcd);
link->caps.channel_coding = drm_dp_channel_coding_supported(dpcd);
if (drm_dp_alternate_scrambler_reset_cap(dpcd)) {
link->caps.alternate_scrambler_reset = true;
err = drm_dp_dpcd_readb(aux, DP_EDP_DPCD_REV, &value);
if (err < 0)
return err;
if (value >= ARRAY_SIZE(drm_dp_edp_revisions))
DRM_ERROR("unsupported eDP version: %02x\n", value);
else
link->edp = drm_dp_edp_revisions[value];
}
/*
* The DPCD stores the AUX read interval in units of 4 ms. There are
* two special cases:
*
* 1) if the TRAINING_AUX_RD_INTERVAL field is 0, the clock recovery
* and channel equalization should use 100 us or 400 us AUX read
* intervals, respectively
*
* 2) for DP v1.4 and above, clock recovery should always use 100 us
* AUX read intervals
*/
rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
DP_TRAINING_AUX_RD_MASK;
if (rd_interval > 4) {
DRM_DEBUG_KMS("AUX interval %u out of range (max. 4)\n",
rd_interval);
rd_interval = 4;
}
rd_interval *= 4 * USEC_PER_MSEC;
if (rd_interval == 0 || link->revision >= DP_DPCD_REV_14)
link->aux_rd_interval.cr = 100;
if (rd_interval == 0)
link->aux_rd_interval.ce = 400;
link->rate = link->max_rate;
link->lanes = link->max_lanes;
/* Parse SUPPORTED_LINK_RATES from eDP 1.4 */
if (link->edp >= 0x14) {
u8 supported_rates[DP_MAX_SUPPORTED_RATES * 2];
unsigned int i;
u16 rate;
err = drm_dp_dpcd_read(aux, DP_SUPPORTED_LINK_RATES,
supported_rates,
sizeof(supported_rates));
if (err < 0)
return err;
for (i = 0; i < DP_MAX_SUPPORTED_RATES; i++) {
rate = supported_rates[i * 2 + 1] << 8 |
supported_rates[i * 2 + 0];
drm_dp_link_add_rate(link, rate * 200);
}
}
return 0;
}
/**
* drm_dp_link_configure() - configure a DisplayPort link
* @aux: DisplayPort AUX channel
* @link: pointer to a structure containing the link configuration
*
* Returns 0 on success or a negative error code on failure.
*/
int drm_dp_link_configure(struct drm_dp_aux *aux, struct drm_dp_link *link)
{
u8 values[2], value;
int err;
if (link->ops && link->ops->configure) {
err = link->ops->configure(link);
if (err < 0) {
DRM_ERROR("failed to configure DP link: %d\n", err);
return err;
}
}
values[0] = drm_dp_link_rate_to_bw_code(link->rate);
values[1] = link->lanes;
if (link->caps.enhanced_framing)
values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values));
if (err < 0)
return err;
if (link->caps.channel_coding)
value = DP_SET_ANSI_8B10B;
else
value = 0;
err = drm_dp_dpcd_writeb(aux, DP_MAIN_LINK_CHANNEL_CODING_SET, value);
if (err < 0)
return err;
if (link->caps.alternate_scrambler_reset) {
err = drm_dp_dpcd_writeb(aux, DP_EDP_CONFIGURATION_SET,
DP_ALTERNATE_SCRAMBLER_RESET_ENABLE);
if (err < 0)
return err;
}
return 0;
}
/**
* drm_dp_link_choose() - choose the lowest possible configuration for a mode
* @link: DRM DP link object
* @mode: DRM display mode
* @info: DRM display information
*
* According to the eDP specification, a source should select a configuration
* with the lowest number of lanes and the lowest possible link rate that can
* match the bitrate requirements of a video mode. However it must ensure not
* to exceed the capabilities of the sink.
*
* Returns: 0 on success or a negative error code on failure.
*/
int drm_dp_link_choose(struct drm_dp_link *link,
const struct drm_display_mode *mode,
const struct drm_display_info *info)
{
/* available link symbol clock rates */
static const unsigned int rates[3] = { 162000, 270000, 540000 };
/* available number of lanes */
static const unsigned int lanes[3] = { 1, 2, 4 };
unsigned long requirement, capacity;
unsigned int rate = link->max_rate;
unsigned int i, j;
/* bandwidth requirement */
requirement = mode->clock * info->bpc * 3;
for (i = 0; i < ARRAY_SIZE(lanes) && lanes[i] <= link->max_lanes; i++) {
for (j = 0; j < ARRAY_SIZE(rates) && rates[j] <= rate; j++) {
/*
* Capacity for this combination of lanes and rate,
* factoring in the ANSI 8B/10B encoding.
*
* Link rates in the DRM DP helpers are really link
* symbol frequencies, so a tenth of the actual rate
* of the link.
*/
capacity = lanes[i] * (rates[j] * 10) * 8 / 10;
if (capacity >= requirement) {
DRM_DEBUG_KMS("using %u lanes at %u kHz (%lu/%lu kbps)\n",
lanes[i], rates[j], requirement,
capacity);
link->lanes = lanes[i];
link->rate = rates[j];
return 0;
}
}
}
return -ERANGE;
}
/**
* DOC: Link training
*
* These functions contain common logic and helpers to implement DisplayPort
* link training.
*/
/**
* drm_dp_link_train_init() - initialize DisplayPort link training state
* @train: DisplayPort link training state
*/
void drm_dp_link_train_init(struct drm_dp_link_train *train)
{
struct drm_dp_link_train_set *request = &train->request;
struct drm_dp_link_train_set *adjust = &train->adjust;
unsigned int i;
for (i = 0; i < 4; i++) {
request->voltage_swing[i] = 0;
adjust->voltage_swing[i] = 0;
request->pre_emphasis[i] = 0;
adjust->pre_emphasis[i] = 0;
request->post_cursor[i] = 0;
adjust->post_cursor[i] = 0;
}
train->pattern = DP_TRAINING_PATTERN_DISABLE;
train->clock_recovered = false;
train->channel_equalized = false;
}
static bool drm_dp_link_train_valid(const struct drm_dp_link_train *train)
{
return train->clock_recovered && train->channel_equalized;
}
static int drm_dp_link_apply_training(struct drm_dp_link *link)
{
struct drm_dp_link_train_set *request = &link->train.request;
unsigned int lanes = link->lanes, *vs, *pe, *pc, i;
struct drm_dp_aux *aux = link->aux;
u8 values[4], pattern = 0;
int err;
err = link->ops->apply_training(link);
if (err < 0) {
DRM_ERROR("failed to apply link training: %d\n", err);
return err;
}
vs = request->voltage_swing;
pe = request->pre_emphasis;
pc = request->post_cursor;
/* write currently selected voltage-swing and pre-emphasis levels */
for (i = 0; i < lanes; i++)
values[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL(vs[i]) |
DP_TRAIN_PRE_EMPHASIS_LEVEL(pe[i]);
err = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_SET, values, lanes);
if (err < 0) {
DRM_ERROR("failed to set training parameters: %d\n", err);
return err;
}
/* write currently selected post-cursor level (if supported) */
if (link->revision >= 0x12 && link->rate == 540000) {
values[0] = values[1] = 0;
for (i = 0; i < lanes; i++)
values[i / 2] |= DP_LANE_POST_CURSOR(i, pc[i]);
err = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_1_SET2, values,
DIV_ROUND_UP(lanes, 2));
if (err < 0) {
DRM_ERROR("failed to set post-cursor: %d\n", err);
return err;
}
}
/* write link pattern */
if (link->train.pattern != DP_TRAINING_PATTERN_DISABLE)
pattern |= DP_LINK_SCRAMBLING_DISABLE;
pattern |= link->train.pattern;
err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, pattern);
if (err < 0) {
DRM_ERROR("failed to set training pattern: %d\n", err);
return err;
}
return 0;
}
static void drm_dp_link_train_wait(struct drm_dp_link *link)
{
unsigned long min = 0;
switch (link->train.pattern) {
case DP_TRAINING_PATTERN_1:
min = link->aux_rd_interval.cr;
break;
case DP_TRAINING_PATTERN_2:
case DP_TRAINING_PATTERN_3:
min = link->aux_rd_interval.ce;
break;
default:
break;
}
if (min > 0)
usleep_range(min, 2 * min);
}
static void drm_dp_link_get_adjustments(struct drm_dp_link *link,
u8 status[DP_LINK_STATUS_SIZE])
{
struct drm_dp_link_train_set *adjust = &link->train.adjust;
unsigned int i;
u8 post_cursor;
int err;
err = drm_dp_dpcd_read(link->aux, DP_ADJUST_REQUEST_POST_CURSOR2,
&post_cursor, sizeof(post_cursor));
if (err < 0) {
DRM_ERROR("failed to read post_cursor2: %d\n", err);
post_cursor = 0;
}
for (i = 0; i < link->lanes; i++) {
adjust->voltage_swing[i] =
drm_dp_get_adjust_request_voltage(status, i) >>
DP_TRAIN_VOLTAGE_SWING_SHIFT;
adjust->pre_emphasis[i] =
drm_dp_get_adjust_request_pre_emphasis(status, i) >>
DP_TRAIN_PRE_EMPHASIS_SHIFT;
adjust->post_cursor[i] =
(post_cursor >> (i << 1)) & 0x3;
}
}
static void drm_dp_link_train_adjust(struct drm_dp_link_train *train)
{
struct drm_dp_link_train_set *request = &train->request;
struct drm_dp_link_train_set *adjust = &train->adjust;
unsigned int i;
for (i = 0; i < 4; i++)
if (request->voltage_swing[i] != adjust->voltage_swing[i])
request->voltage_swing[i] = adjust->voltage_swing[i];
for (i = 0; i < 4; i++)
if (request->pre_emphasis[i] != adjust->pre_emphasis[i])
request->pre_emphasis[i] = adjust->pre_emphasis[i];
for (i = 0; i < 4; i++)
if (request->post_cursor[i] != adjust->post_cursor[i])
request->post_cursor[i] = adjust->post_cursor[i];
}
static int drm_dp_link_recover_clock(struct drm_dp_link *link)
{
u8 status[DP_LINK_STATUS_SIZE];
int err;
err = drm_dp_link_apply_training(link);
if (err < 0)
return err;
drm_dp_link_train_wait(link);
err = drm_dp_dpcd_read_link_status(link->aux, status);
if (err < 0) {
DRM_ERROR("failed to read link status: %d\n", err);
return err;
}
if (!drm_dp_clock_recovery_ok(status, link->lanes))
drm_dp_link_get_adjustments(link, status);
else
link->train.clock_recovered = true;
return 0;
}
static int drm_dp_link_clock_recovery(struct drm_dp_link *link)
{
unsigned int repeat;
int err;
/* start clock recovery using training pattern 1 */
link->train.pattern = DP_TRAINING_PATTERN_1;
for (repeat = 1; repeat < 5; repeat++) {
err = drm_dp_link_recover_clock(link);
if (err < 0) {
DRM_ERROR("failed to recover clock: %d\n", err);
return err;
}
if (link->train.clock_recovered)
break;
drm_dp_link_train_adjust(&link->train);
}
return 0;
}
static int drm_dp_link_equalize_channel(struct drm_dp_link *link)
{
struct drm_dp_aux *aux = link->aux;
u8 status[DP_LINK_STATUS_SIZE];
int err;
err = drm_dp_link_apply_training(link);
if (err < 0)
return err;
drm_dp_link_train_wait(link);
err = drm_dp_dpcd_read_link_status(aux, status);
if (err < 0) {
DRM_ERROR("failed to read link status: %d\n", err);
return err;
}
if (!drm_dp_clock_recovery_ok(status, link->lanes)) {
DRM_ERROR("clock recovery lost while equalizing channel\n");
link->train.clock_recovered = false;
return 0;
}
if (!drm_dp_channel_eq_ok(status, link->lanes))
drm_dp_link_get_adjustments(link, status);
else
link->train.channel_equalized = true;
return 0;
}
static int drm_dp_link_channel_equalization(struct drm_dp_link *link)
{
unsigned int repeat;
int err;
/* start channel equalization using pattern 2 or 3 */
if (link->caps.tps3_supported)
link->train.pattern = DP_TRAINING_PATTERN_3;
else
link->train.pattern = DP_TRAINING_PATTERN_2;
for (repeat = 1; repeat < 5; repeat++) {
err = drm_dp_link_equalize_channel(link);
if (err < 0) {
DRM_ERROR("failed to equalize channel: %d\n", err);
return err;
}
if (link->train.channel_equalized)
break;
drm_dp_link_train_adjust(&link->train);
}
return 0;
}
static int drm_dp_link_downgrade(struct drm_dp_link *link)
{
switch (link->rate) {
case 162000:
return -EINVAL;
case 270000:
link->rate = 162000;
break;
case 540000:
link->rate = 270000;
return 0;
}
return 0;
}
static void drm_dp_link_train_disable(struct drm_dp_link *link)
{
int err;
link->train.pattern = DP_TRAINING_PATTERN_DISABLE;
err = drm_dp_link_apply_training(link);
if (err < 0)
DRM_ERROR("failed to disable link training: %d\n", err);
}
static int drm_dp_link_train_full(struct drm_dp_link *link)
{
int err;
retry:
DRM_DEBUG_KMS("full-training link: %u lane%s at %u MHz\n",
link->lanes, str_plural(link->lanes),
link->rate / 100);
err = drm_dp_link_configure(link->aux, link);
if (err < 0) {
DRM_ERROR("failed to configure DP link: %d\n", err);
return err;
}
err = drm_dp_link_clock_recovery(link);
if (err < 0) {
DRM_ERROR("clock recovery failed: %d\n", err);
goto out;
}
if (!link->train.clock_recovered) {
DRM_ERROR("clock recovery failed, downgrading link\n");
err = drm_dp_link_downgrade(link);
if (err < 0)
goto out;
goto retry;
}
DRM_DEBUG_KMS("clock recovery succeeded\n");
err = drm_dp_link_channel_equalization(link);
if (err < 0) {
DRM_ERROR("channel equalization failed: %d\n", err);
goto out;
}
if (!link->train.channel_equalized) {
DRM_ERROR("channel equalization failed, downgrading link\n");
err = drm_dp_link_downgrade(link);
if (err < 0)
goto out;
goto retry;
}
DRM_DEBUG_KMS("channel equalization succeeded\n");
out:
drm_dp_link_train_disable(link);
return err;
}
static int drm_dp_link_train_fast(struct drm_dp_link *link)
{
u8 status[DP_LINK_STATUS_SIZE];
int err;
DRM_DEBUG_KMS("fast-training link: %u lane%s at %u MHz\n",
link->lanes, str_plural(link->lanes),
link->rate / 100);
err = drm_dp_link_configure(link->aux, link);
if (err < 0) {
DRM_ERROR("failed to configure DP link: %d\n", err);
return err;
}
/* transmit training pattern 1 for 500 microseconds */
link->train.pattern = DP_TRAINING_PATTERN_1;
err = drm_dp_link_apply_training(link);
if (err < 0)
goto out;
usleep_range(500, 1000);
/* transmit training pattern 2 or 3 for 500 microseconds */
if (link->caps.tps3_supported)
link->train.pattern = DP_TRAINING_PATTERN_3;
else
link->train.pattern = DP_TRAINING_PATTERN_2;
err = drm_dp_link_apply_training(link);
if (err < 0)
goto out;
usleep_range(500, 1000);
err = drm_dp_dpcd_read_link_status(link->aux, status);
if (err < 0) {
DRM_ERROR("failed to read link status: %d\n", err);
goto out;
}
if (!drm_dp_clock_recovery_ok(status, link->lanes)) {
DRM_ERROR("clock recovery failed\n");
err = -EIO;
}
if (!drm_dp_channel_eq_ok(status, link->lanes)) {
DRM_ERROR("channel equalization failed\n");
err = -EIO;
}
out:
drm_dp_link_train_disable(link);
return err;
}
/**
* drm_dp_link_train() - perform DisplayPort link training
* @link: a DP link object
*
* Uses the context stored in the DP link object to perform link training. It
* is expected that drivers will call drm_dp_link_probe() to obtain the link
* capabilities before performing link training.
*
* If the sink supports fast link training (no AUX CH handshake) and valid
* training settings are available, this function will try to perform fast
* link training and fall back to full link training on failure.
*
* Returns: 0 on success or a negative error code on failure.
*/
int drm_dp_link_train(struct drm_dp_link *link)
{
int err;
drm_dp_link_train_init(&link->train);
if (link->caps.fast_training) {
if (drm_dp_link_train_valid(&link->train)) {
err = drm_dp_link_train_fast(link);
if (err < 0)
DRM_ERROR("fast link training failed: %d\n",
err);
else
return 0;
} else {
DRM_DEBUG_KMS("training parameters not available\n");
}
} else {
DRM_DEBUG_KMS("fast link training not supported\n");
}
err = drm_dp_link_train_full(link);
if (err < 0)
DRM_ERROR("full link training failed: %d\n", err);
return err;
}
+173
View File
@@ -0,0 +1,173 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright (C) 2013-2019 NVIDIA Corporation.
* Copyright (C) 2015 Rob Clark
*/
#ifndef DRM_TEGRA_DP_H
#define DRM_TEGRA_DP_H 1
#include <linux/types.h>
struct drm_display_info;
struct drm_display_mode;
struct drm_dp_aux;
struct drm_dp_link;
/**
* struct drm_dp_link_caps - DP link capabilities
*/
struct drm_dp_link_caps {
/**
* @enhanced_framing:
*
* enhanced framing capability (mandatory as of DP 1.2)
*/
bool enhanced_framing;
/**
* @tps3_supported:
*
* training pattern sequence 3 supported for equalization
*/
bool tps3_supported;
/**
* @fast_training:
*
* AUX CH handshake not required for link training
*/
bool fast_training;
/**
* @channel_coding:
*
* ANSI 8B/10B channel coding capability
*/
bool channel_coding;
/**
* @alternate_scrambler_reset:
*
* eDP alternate scrambler reset capability
*/
bool alternate_scrambler_reset;
};
void drm_dp_link_caps_copy(struct drm_dp_link_caps *dest,
const struct drm_dp_link_caps *src);
/**
* struct drm_dp_link_ops - DP link operations
*/
struct drm_dp_link_ops {
/**
* @apply_training: apply the link training
*/
int (*apply_training)(struct drm_dp_link *link);
/**
* @configure: configure the DP link
*/
int (*configure)(struct drm_dp_link *link);
};
#define DP_TRAIN_VOLTAGE_SWING_LEVEL(x) ((x) << 0)
#define DP_TRAIN_PRE_EMPHASIS_LEVEL(x) ((x) << 3)
#define DP_LANE_POST_CURSOR(i, x) (((x) & 0x3) << (((i) & 1) << 2))
/**
* struct drm_dp_link_train_set - link training settings
* @voltage_swing: per-lane voltage swing
* @pre_emphasis: per-lane pre-emphasis
* @post_cursor: per-lane post-cursor
*/
struct drm_dp_link_train_set {
unsigned int voltage_swing[4];
unsigned int pre_emphasis[4];
unsigned int post_cursor[4];
};
/**
* struct drm_dp_link_train - link training state information
* @request: currently requested settings
* @adjust: adjustments requested by sink
* @pattern: currently requested training pattern
* @clock_recovered: flag to track if clock recovery has completed
* @channel_equalized: flag to track if channel equalization has completed
*/
struct drm_dp_link_train {
struct drm_dp_link_train_set request;
struct drm_dp_link_train_set adjust;
unsigned int pattern;
bool clock_recovered;
bool channel_equalized;
};
/**
* struct drm_dp_link - DP link capabilities and configuration
* @revision: DP specification revision supported on the link
* @max_rate: maximum clock rate supported on the link
* @max_lanes: maximum number of lanes supported on the link
* @caps: capabilities supported on the link (see &drm_dp_link_caps)
* @aux_rd_interval: AUX read interval to use for training (in microseconds)
* @aux_rd_interval.cr: clock recovery read interval
* @aux_rd_interval.ce: channel equalization read interval
* @edp: eDP revision (0x11: eDP 1.1, 0x12: eDP 1.2, ...)
* @rate: currently configured link rate
* @lanes: currently configured number of lanes
* @rates: additional supported link rates in kHz (eDP 1.4)
* @num_rates: number of additional supported link rates (eDP 1.4)
*/
struct drm_dp_link {
unsigned char revision;
unsigned int max_rate;
unsigned int max_lanes;
struct drm_dp_link_caps caps;
struct {
unsigned int cr;
unsigned int ce;
} aux_rd_interval;
unsigned char edp;
unsigned int rate;
unsigned int lanes;
unsigned long rates[DP_MAX_SUPPORTED_RATES];
unsigned int num_rates;
/**
* @ops: DP link operations
*/
const struct drm_dp_link_ops *ops;
/**
* @aux: DP AUX channel
*/
struct drm_dp_aux *aux;
/**
* @train: DP link training state
*/
struct drm_dp_link_train train;
};
int drm_dp_link_add_rate(struct drm_dp_link *link, unsigned long rate);
int drm_dp_link_remove_rate(struct drm_dp_link *link, unsigned long rate);
void drm_dp_link_update_rates(struct drm_dp_link *link);
int drm_dp_link_probe(struct drm_dp_aux *aux, struct drm_dp_link *link);
int drm_dp_link_configure(struct drm_dp_aux *aux, struct drm_dp_link *link);
int drm_dp_link_choose(struct drm_dp_link *link,
const struct drm_display_mode *mode,
const struct drm_display_info *info);
void drm_dp_link_train_init(struct drm_dp_link_train *train);
int drm_dp_link_train(struct drm_dp_link *link);
#endif
+822
View File
@@ -0,0 +1,822 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 NVIDIA Corporation
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pinctrl/pinconf-generic.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/workqueue.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/display/drm_dp_aux_bus.h>
#include <drm/drm_panel.h>
#include "dp.h"
#include "dpaux.h"
#include "drm.h"
#include "trace.h"
static DEFINE_MUTEX(dpaux_lock);
static LIST_HEAD(dpaux_list);
struct tegra_dpaux_soc {
unsigned int cmh;
unsigned int drvz;
unsigned int drvi;
};
struct tegra_dpaux {
struct drm_dp_aux aux;
struct device *dev;
const struct tegra_dpaux_soc *soc;
void __iomem *regs;
int irq;
struct tegra_output *output;
struct reset_control *rst;
struct clk *clk_parent;
struct clk *clk;
struct regulator *vdd;
struct completion complete;
struct work_struct work;
struct list_head list;
#ifdef CONFIG_GENERIC_PINCONF
struct pinctrl_dev *pinctrl;
struct pinctrl_desc desc;
#endif
};
static inline struct tegra_dpaux *to_dpaux(struct drm_dp_aux *aux)
{
return container_of(aux, struct tegra_dpaux, aux);
}
static inline struct tegra_dpaux *work_to_dpaux(struct work_struct *work)
{
return container_of(work, struct tegra_dpaux, work);
}
static inline u32 tegra_dpaux_readl(struct tegra_dpaux *dpaux,
unsigned int offset)
{
u32 value = readl(dpaux->regs + (offset << 2));
trace_dpaux_readl(dpaux->dev, offset, value);
return value;
}
static inline void tegra_dpaux_writel(struct tegra_dpaux *dpaux,
u32 value, unsigned int offset)
{
trace_dpaux_writel(dpaux->dev, offset, value);
writel(value, dpaux->regs + (offset << 2));
}
static void tegra_dpaux_write_fifo(struct tegra_dpaux *dpaux, const u8 *buffer,
size_t size)
{
size_t i, j;
for (i = 0; i < DIV_ROUND_UP(size, 4); i++) {
size_t num = min_t(size_t, size - i * 4, 4);
u32 value = 0;
for (j = 0; j < num; j++)
value |= buffer[i * 4 + j] << (j * 8);
tegra_dpaux_writel(dpaux, value, DPAUX_DP_AUXDATA_WRITE(i));
}
}
static void tegra_dpaux_read_fifo(struct tegra_dpaux *dpaux, u8 *buffer,
size_t size)
{
size_t i, j;
for (i = 0; i < DIV_ROUND_UP(size, 4); i++) {
size_t num = min_t(size_t, size - i * 4, 4);
u32 value;
value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXDATA_READ(i));
for (j = 0; j < num; j++)
buffer[i * 4 + j] = value >> (j * 8);
}
}
static ssize_t tegra_dpaux_transfer(struct drm_dp_aux *aux,
struct drm_dp_aux_msg *msg)
{
unsigned long timeout = msecs_to_jiffies(250);
struct tegra_dpaux *dpaux = to_dpaux(aux);
unsigned long status;
ssize_t ret = 0;
u8 reply = 0;
u32 value;
/* Tegra has 4x4 byte DP AUX transmit and receive FIFOs. */
if (msg->size > 16)
return -EINVAL;
/*
* Allow zero-sized messages only for I2C, in which case they specify
* address-only transactions.
*/
if (msg->size < 1) {
switch (msg->request & ~DP_AUX_I2C_MOT) {
case DP_AUX_I2C_WRITE_STATUS_UPDATE:
case DP_AUX_I2C_WRITE:
case DP_AUX_I2C_READ:
value = DPAUX_DP_AUXCTL_CMD_ADDRESS_ONLY;
break;
default:
return -EINVAL;
}
} else {
/* For non-zero-sized messages, set the CMDLEN field. */
value = DPAUX_DP_AUXCTL_CMDLEN(msg->size - 1);
}
switch (msg->request & ~DP_AUX_I2C_MOT) {
case DP_AUX_I2C_WRITE:
if (msg->request & DP_AUX_I2C_MOT)
value |= DPAUX_DP_AUXCTL_CMD_MOT_WR;
else
value |= DPAUX_DP_AUXCTL_CMD_I2C_WR;
break;
case DP_AUX_I2C_READ:
if (msg->request & DP_AUX_I2C_MOT)
value |= DPAUX_DP_AUXCTL_CMD_MOT_RD;
else
value |= DPAUX_DP_AUXCTL_CMD_I2C_RD;
break;
case DP_AUX_I2C_WRITE_STATUS_UPDATE:
if (msg->request & DP_AUX_I2C_MOT)
value |= DPAUX_DP_AUXCTL_CMD_MOT_RQ;
else
value |= DPAUX_DP_AUXCTL_CMD_I2C_RQ;
break;
case DP_AUX_NATIVE_WRITE:
value |= DPAUX_DP_AUXCTL_CMD_AUX_WR;
break;
case DP_AUX_NATIVE_READ:
value |= DPAUX_DP_AUXCTL_CMD_AUX_RD;
break;
default:
return -EINVAL;
}
tegra_dpaux_writel(dpaux, msg->address, DPAUX_DP_AUXADDR);
tegra_dpaux_writel(dpaux, value, DPAUX_DP_AUXCTL);
if ((msg->request & DP_AUX_I2C_READ) == 0) {
tegra_dpaux_write_fifo(dpaux, msg->buffer, msg->size);
ret = msg->size;
}
/* start transaction */
value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXCTL);
value |= DPAUX_DP_AUXCTL_TRANSACTREQ;
tegra_dpaux_writel(dpaux, value, DPAUX_DP_AUXCTL);
status = wait_for_completion_timeout(&dpaux->complete, timeout);
if (!status)
return -ETIMEDOUT;
/* read status and clear errors */
value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXSTAT);
tegra_dpaux_writel(dpaux, 0xf00, DPAUX_DP_AUXSTAT);
if (value & DPAUX_DP_AUXSTAT_TIMEOUT_ERROR)
return -ETIMEDOUT;
if ((value & DPAUX_DP_AUXSTAT_RX_ERROR) ||
(value & DPAUX_DP_AUXSTAT_SINKSTAT_ERROR) ||
(value & DPAUX_DP_AUXSTAT_NO_STOP_ERROR))
return -EIO;
switch ((value & DPAUX_DP_AUXSTAT_REPLY_TYPE_MASK) >> 16) {
case 0x00:
reply = DP_AUX_NATIVE_REPLY_ACK;
break;
case 0x01:
reply = DP_AUX_NATIVE_REPLY_NACK;
break;
case 0x02:
reply = DP_AUX_NATIVE_REPLY_DEFER;
break;
case 0x04:
reply = DP_AUX_I2C_REPLY_NACK;
break;
case 0x08:
reply = DP_AUX_I2C_REPLY_DEFER;
break;
}
if ((msg->size > 0) && (msg->reply == DP_AUX_NATIVE_REPLY_ACK)) {
if (msg->request & DP_AUX_I2C_READ) {
size_t count = value & DPAUX_DP_AUXSTAT_REPLY_MASK;
/*
* There might be a smarter way to do this, but since
* the DP helpers will already retry transactions for
* an -EBUSY return value, simply reuse that instead.
*/
if (count != msg->size) {
ret = -EBUSY;
goto out;
}
tegra_dpaux_read_fifo(dpaux, msg->buffer, count);
ret = count;
}
}
msg->reply = reply;
out:
return ret;
}
static void tegra_dpaux_hotplug(struct work_struct *work)
{
struct tegra_dpaux *dpaux = work_to_dpaux(work);
if (dpaux->output)
drm_helper_hpd_irq_event(dpaux->output->connector.dev);
}
static irqreturn_t tegra_dpaux_irq(int irq, void *data)
{
struct tegra_dpaux *dpaux = data;
u32 value;
/* clear interrupts */
value = tegra_dpaux_readl(dpaux, DPAUX_INTR_AUX);
tegra_dpaux_writel(dpaux, value, DPAUX_INTR_AUX);
if (value & (DPAUX_INTR_PLUG_EVENT | DPAUX_INTR_UNPLUG_EVENT))
schedule_work(&dpaux->work);
if (value & DPAUX_INTR_IRQ_EVENT) {
/* TODO: handle this */
}
if (value & DPAUX_INTR_AUX_DONE)
complete(&dpaux->complete);
return IRQ_HANDLED;
}
enum tegra_dpaux_functions {
DPAUX_PADCTL_FUNC_AUX,
DPAUX_PADCTL_FUNC_I2C,
DPAUX_PADCTL_FUNC_OFF,
};
static void tegra_dpaux_pad_power_down(struct tegra_dpaux *dpaux)
{
u32 value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE);
value |= DPAUX_HYBRID_SPARE_PAD_POWER_DOWN;
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE);
}
static void tegra_dpaux_pad_power_up(struct tegra_dpaux *dpaux)
{
u32 value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE);
value &= ~DPAUX_HYBRID_SPARE_PAD_POWER_DOWN;
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE);
}
static int tegra_dpaux_pad_config(struct tegra_dpaux *dpaux, unsigned function)
{
u32 value;
switch (function) {
case DPAUX_PADCTL_FUNC_AUX:
value = DPAUX_HYBRID_PADCTL_AUX_CMH(dpaux->soc->cmh) |
DPAUX_HYBRID_PADCTL_AUX_DRVZ(dpaux->soc->drvz) |
DPAUX_HYBRID_PADCTL_AUX_DRVI(dpaux->soc->drvi) |
DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV |
DPAUX_HYBRID_PADCTL_MODE_AUX;
break;
case DPAUX_PADCTL_FUNC_I2C:
value = DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV |
DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV |
DPAUX_HYBRID_PADCTL_AUX_CMH(dpaux->soc->cmh) |
DPAUX_HYBRID_PADCTL_AUX_DRVZ(dpaux->soc->drvz) |
DPAUX_HYBRID_PADCTL_AUX_DRVI(dpaux->soc->drvi) |
DPAUX_HYBRID_PADCTL_MODE_I2C;
break;
case DPAUX_PADCTL_FUNC_OFF:
tegra_dpaux_pad_power_down(dpaux);
return 0;
default:
return -ENOTSUPP;
}
tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_PADCTL);
tegra_dpaux_pad_power_up(dpaux);
return 0;
}
#ifdef CONFIG_GENERIC_PINCONF
static const struct pinctrl_pin_desc tegra_dpaux_pins[] = {
PINCTRL_PIN(0, "DP_AUX_CHx_P"),
PINCTRL_PIN(1, "DP_AUX_CHx_N"),
};
static const unsigned tegra_dpaux_pin_numbers[] = { 0, 1 };
static const char * const tegra_dpaux_groups[] = {
"dpaux-io",
};
static const char * const tegra_dpaux_functions[] = {
"aux",
"i2c",
"off",
};
static int tegra_dpaux_get_groups_count(struct pinctrl_dev *pinctrl)
{
return ARRAY_SIZE(tegra_dpaux_groups);
}
static const char *tegra_dpaux_get_group_name(struct pinctrl_dev *pinctrl,
unsigned int group)
{
return tegra_dpaux_groups[group];
}
static int tegra_dpaux_get_group_pins(struct pinctrl_dev *pinctrl,
unsigned group, const unsigned **pins,
unsigned *num_pins)
{
*pins = tegra_dpaux_pin_numbers;
*num_pins = ARRAY_SIZE(tegra_dpaux_pin_numbers);
return 0;
}
static const struct pinctrl_ops tegra_dpaux_pinctrl_ops = {
.get_groups_count = tegra_dpaux_get_groups_count,
.get_group_name = tegra_dpaux_get_group_name,
.get_group_pins = tegra_dpaux_get_group_pins,
.dt_node_to_map = pinconf_generic_dt_node_to_map_group,
.dt_free_map = pinconf_generic_dt_free_map,
};
static int tegra_dpaux_get_functions_count(struct pinctrl_dev *pinctrl)
{
return ARRAY_SIZE(tegra_dpaux_functions);
}
static const char *tegra_dpaux_get_function_name(struct pinctrl_dev *pinctrl,
unsigned int function)
{
return tegra_dpaux_functions[function];
}
static int tegra_dpaux_get_function_groups(struct pinctrl_dev *pinctrl,
unsigned int function,
const char * const **groups,
unsigned * const num_groups)
{
*num_groups = ARRAY_SIZE(tegra_dpaux_groups);
*groups = tegra_dpaux_groups;
return 0;
}
static int tegra_dpaux_set_mux(struct pinctrl_dev *pinctrl,
unsigned int function, unsigned int group)
{
struct tegra_dpaux *dpaux = pinctrl_dev_get_drvdata(pinctrl);
return tegra_dpaux_pad_config(dpaux, function);
}
static const struct pinmux_ops tegra_dpaux_pinmux_ops = {
.get_functions_count = tegra_dpaux_get_functions_count,
.get_function_name = tegra_dpaux_get_function_name,
.get_function_groups = tegra_dpaux_get_function_groups,
.set_mux = tegra_dpaux_set_mux,
};
#endif
static int tegra_dpaux_probe(struct platform_device *pdev)
{
struct tegra_dpaux *dpaux;
u32 value;
int err;
dpaux = devm_kzalloc(&pdev->dev, sizeof(*dpaux), GFP_KERNEL);
if (!dpaux)
return -ENOMEM;
dpaux->soc = of_device_get_match_data(&pdev->dev);
INIT_WORK(&dpaux->work, tegra_dpaux_hotplug);
init_completion(&dpaux->complete);
INIT_LIST_HEAD(&dpaux->list);
dpaux->dev = &pdev->dev;
dpaux->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(dpaux->regs))
return PTR_ERR(dpaux->regs);
dpaux->irq = platform_get_irq(pdev, 0);
if (dpaux->irq < 0)
return dpaux->irq;
if (!pdev->dev.pm_domain) {
dpaux->rst = devm_reset_control_get(&pdev->dev, "dpaux");
if (IS_ERR(dpaux->rst)) {
dev_err(&pdev->dev,
"failed to get reset control: %ld\n",
PTR_ERR(dpaux->rst));
return PTR_ERR(dpaux->rst);
}
}
dpaux->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(dpaux->clk)) {
dev_err(&pdev->dev, "failed to get module clock: %ld\n",
PTR_ERR(dpaux->clk));
return PTR_ERR(dpaux->clk);
}
dpaux->clk_parent = devm_clk_get(&pdev->dev, "parent");
if (IS_ERR(dpaux->clk_parent)) {
dev_err(&pdev->dev, "failed to get parent clock: %ld\n",
PTR_ERR(dpaux->clk_parent));
return PTR_ERR(dpaux->clk_parent);
}
err = clk_set_rate(dpaux->clk_parent, 270000000);
if (err < 0) {
dev_err(&pdev->dev, "failed to set clock to 270 MHz: %d\n",
err);
return err;
}
dpaux->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
if (IS_ERR(dpaux->vdd)) {
if (PTR_ERR(dpaux->vdd) != -ENODEV)
return dev_err_probe(&pdev->dev, PTR_ERR(dpaux->vdd),
"failed to get VDD supply\n");
dpaux->vdd = NULL;
}
platform_set_drvdata(pdev, dpaux);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
err = devm_request_irq(dpaux->dev, dpaux->irq, tegra_dpaux_irq, 0,
dev_name(dpaux->dev), dpaux);
if (err < 0) {
dev_err(dpaux->dev, "failed to request IRQ#%u: %d\n",
dpaux->irq, err);
goto err_pm_disable;
}
disable_irq(dpaux->irq);
dpaux->aux.transfer = tegra_dpaux_transfer;
dpaux->aux.dev = &pdev->dev;
drm_dp_aux_init(&dpaux->aux);
/*
* Assume that by default the DPAUX/I2C pads will be used for HDMI,
* so power them up and configure them in I2C mode.
*
* The DPAUX code paths reconfigure the pads in AUX mode, but there
* is no possibility to perform the I2C mode configuration in the
* HDMI path.
*/
err = tegra_dpaux_pad_config(dpaux, DPAUX_PADCTL_FUNC_I2C);
if (err < 0)
goto err_pm_disable;
#ifdef CONFIG_GENERIC_PINCONF
dpaux->desc.name = dev_name(&pdev->dev);
dpaux->desc.pins = tegra_dpaux_pins;
dpaux->desc.npins = ARRAY_SIZE(tegra_dpaux_pins);
dpaux->desc.pctlops = &tegra_dpaux_pinctrl_ops;
dpaux->desc.pmxops = &tegra_dpaux_pinmux_ops;
dpaux->desc.owner = THIS_MODULE;
dpaux->pinctrl = devm_pinctrl_register(&pdev->dev, &dpaux->desc, dpaux);
if (IS_ERR(dpaux->pinctrl)) {
dev_err(&pdev->dev, "failed to register pincontrol\n");
err = PTR_ERR(dpaux->pinctrl);
goto err_pm_disable;
}
#endif
/* enable and clear all interrupts */
value = DPAUX_INTR_AUX_DONE | DPAUX_INTR_IRQ_EVENT |
DPAUX_INTR_UNPLUG_EVENT | DPAUX_INTR_PLUG_EVENT;
tegra_dpaux_writel(dpaux, value, DPAUX_INTR_EN_AUX);
tegra_dpaux_writel(dpaux, value, DPAUX_INTR_AUX);
mutex_lock(&dpaux_lock);
list_add_tail(&dpaux->list, &dpaux_list);
mutex_unlock(&dpaux_lock);
err = devm_of_dp_aux_populate_ep_devices(&dpaux->aux);
if (err < 0) {
dev_err(dpaux->dev, "failed to populate AUX bus: %d\n", err);
goto err_pm_disable;
}
return 0;
err_pm_disable:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return err;
}
static void tegra_dpaux_remove(struct platform_device *pdev)
{
struct tegra_dpaux *dpaux = platform_get_drvdata(pdev);
cancel_work_sync(&dpaux->work);
/* make sure pads are powered down when not in use */
tegra_dpaux_pad_power_down(dpaux);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
mutex_lock(&dpaux_lock);
list_del(&dpaux->list);
mutex_unlock(&dpaux_lock);
}
static int tegra_dpaux_suspend(struct device *dev)
{
struct tegra_dpaux *dpaux = dev_get_drvdata(dev);
int err = 0;
if (dpaux->rst) {
err = reset_control_assert(dpaux->rst);
if (err < 0) {
dev_err(dev, "failed to assert reset: %d\n", err);
return err;
}
}
usleep_range(1000, 2000);
clk_disable_unprepare(dpaux->clk_parent);
clk_disable_unprepare(dpaux->clk);
return err;
}
static int tegra_dpaux_resume(struct device *dev)
{
struct tegra_dpaux *dpaux = dev_get_drvdata(dev);
int err;
err = clk_prepare_enable(dpaux->clk);
if (err < 0) {
dev_err(dev, "failed to enable clock: %d\n", err);
return err;
}
err = clk_prepare_enable(dpaux->clk_parent);
if (err < 0) {
dev_err(dev, "failed to enable parent clock: %d\n", err);
goto disable_clk;
}
usleep_range(1000, 2000);
if (dpaux->rst) {
err = reset_control_deassert(dpaux->rst);
if (err < 0) {
dev_err(dev, "failed to deassert reset: %d\n", err);
goto disable_parent;
}
usleep_range(1000, 2000);
}
return 0;
disable_parent:
clk_disable_unprepare(dpaux->clk_parent);
disable_clk:
clk_disable_unprepare(dpaux->clk);
return err;
}
static const struct dev_pm_ops tegra_dpaux_pm_ops = {
RUNTIME_PM_OPS(tegra_dpaux_suspend, tegra_dpaux_resume, NULL)
};
static const struct tegra_dpaux_soc tegra124_dpaux_soc = {
.cmh = 0x02,
.drvz = 0x04,
.drvi = 0x18,
};
static const struct tegra_dpaux_soc tegra210_dpaux_soc = {
.cmh = 0x02,
.drvz = 0x04,
.drvi = 0x30,
};
static const struct tegra_dpaux_soc tegra194_dpaux_soc = {
.cmh = 0x02,
.drvz = 0x04,
.drvi = 0x2c,
};
static const struct of_device_id tegra_dpaux_of_match[] = {
{ .compatible = "nvidia,tegra194-dpaux", .data = &tegra194_dpaux_soc },
{ .compatible = "nvidia,tegra186-dpaux", .data = &tegra210_dpaux_soc },
{ .compatible = "nvidia,tegra210-dpaux", .data = &tegra210_dpaux_soc },
{ .compatible = "nvidia,tegra124-dpaux", .data = &tegra124_dpaux_soc },
{ },
};
MODULE_DEVICE_TABLE(of, tegra_dpaux_of_match);
struct platform_driver tegra_dpaux_driver = {
.driver = {
.name = "tegra-dpaux",
.of_match_table = tegra_dpaux_of_match,
.pm = pm_ptr(&tegra_dpaux_pm_ops),
},
.probe = tegra_dpaux_probe,
.remove = tegra_dpaux_remove,
};
struct drm_dp_aux *drm_dp_aux_find_by_of_node(struct device_node *np)
{
struct tegra_dpaux *dpaux;
mutex_lock(&dpaux_lock);
list_for_each_entry(dpaux, &dpaux_list, list)
if (np == dpaux->dev->of_node) {
mutex_unlock(&dpaux_lock);
return &dpaux->aux;
}
mutex_unlock(&dpaux_lock);
return NULL;
}
int drm_dp_aux_attach(struct drm_dp_aux *aux, struct tegra_output *output)
{
struct tegra_dpaux *dpaux = to_dpaux(aux);
unsigned long timeout;
int err;
aux->drm_dev = output->connector.dev;
err = drm_dp_aux_register(aux);
if (err < 0)
return err;
output->connector.polled = DRM_CONNECTOR_POLL_HPD;
dpaux->output = output;
if (output->panel) {
enum drm_connector_status status;
if (dpaux->vdd) {
err = regulator_enable(dpaux->vdd);
if (err < 0)
return err;
}
timeout = jiffies + msecs_to_jiffies(250);
while (time_before(jiffies, timeout)) {
status = drm_dp_aux_detect(aux);
if (status == connector_status_connected)
break;
usleep_range(1000, 2000);
}
if (status != connector_status_connected)
return -ETIMEDOUT;
}
enable_irq(dpaux->irq);
return 0;
}
int drm_dp_aux_detach(struct drm_dp_aux *aux)
{
struct tegra_dpaux *dpaux = to_dpaux(aux);
unsigned long timeout;
int err;
drm_dp_aux_unregister(aux);
disable_irq(dpaux->irq);
if (dpaux->output->panel) {
enum drm_connector_status status;
if (dpaux->vdd) {
err = regulator_disable(dpaux->vdd);
if (err < 0)
return err;
}
timeout = jiffies + msecs_to_jiffies(250);
while (time_before(jiffies, timeout)) {
status = drm_dp_aux_detect(aux);
if (status == connector_status_disconnected)
break;
usleep_range(1000, 2000);
}
if (status != connector_status_disconnected)
return -ETIMEDOUT;
dpaux->output = NULL;
}
return 0;
}
enum drm_connector_status drm_dp_aux_detect(struct drm_dp_aux *aux)
{
struct tegra_dpaux *dpaux = to_dpaux(aux);
u32 value;
value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXSTAT);
if (value & DPAUX_DP_AUXSTAT_HPD_STATUS)
return connector_status_connected;
return connector_status_disconnected;
}
int drm_dp_aux_enable(struct drm_dp_aux *aux)
{
struct tegra_dpaux *dpaux = to_dpaux(aux);
return tegra_dpaux_pad_config(dpaux, DPAUX_PADCTL_FUNC_AUX);
}
int drm_dp_aux_disable(struct drm_dp_aux *aux)
{
struct tegra_dpaux *dpaux = to_dpaux(aux);
tegra_dpaux_pad_power_down(dpaux);
return 0;
}
+73
View File
@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2013 NVIDIA Corporation
*/
#ifndef DRM_TEGRA_DPAUX_H
#define DRM_TEGRA_DPAUX_H
#define DPAUX_CTXSW 0x00
#define DPAUX_INTR_EN_AUX 0x01
#define DPAUX_INTR_AUX 0x05
#define DPAUX_INTR_AUX_DONE (1 << 3)
#define DPAUX_INTR_IRQ_EVENT (1 << 2)
#define DPAUX_INTR_UNPLUG_EVENT (1 << 1)
#define DPAUX_INTR_PLUG_EVENT (1 << 0)
#define DPAUX_DP_AUXDATA_WRITE(x) (0x09 + ((x) << 2))
#define DPAUX_DP_AUXDATA_READ(x) (0x19 + ((x) << 2))
#define DPAUX_DP_AUXADDR 0x29
#define DPAUX_DP_AUXCTL 0x2d
#define DPAUX_DP_AUXCTL_TRANSACTREQ (1 << 16)
#define DPAUX_DP_AUXCTL_CMD_AUX_RD (9 << 12)
#define DPAUX_DP_AUXCTL_CMD_AUX_WR (8 << 12)
#define DPAUX_DP_AUXCTL_CMD_MOT_RQ (6 << 12)
#define DPAUX_DP_AUXCTL_CMD_MOT_RD (5 << 12)
#define DPAUX_DP_AUXCTL_CMD_MOT_WR (4 << 12)
#define DPAUX_DP_AUXCTL_CMD_I2C_RQ (2 << 12)
#define DPAUX_DP_AUXCTL_CMD_I2C_RD (1 << 12)
#define DPAUX_DP_AUXCTL_CMD_I2C_WR (0 << 12)
#define DPAUX_DP_AUXCTL_CMD_ADDRESS_ONLY (1 << 8)
#define DPAUX_DP_AUXCTL_CMDLEN(x) ((x) & 0xff)
#define DPAUX_DP_AUXSTAT 0x31
#define DPAUX_DP_AUXSTAT_HPD_STATUS (1 << 28)
#define DPAUX_DP_AUXSTAT_REPLY_TYPE_MASK (0xf0000)
#define DPAUX_DP_AUXSTAT_NO_STOP_ERROR (1 << 11)
#define DPAUX_DP_AUXSTAT_SINKSTAT_ERROR (1 << 10)
#define DPAUX_DP_AUXSTAT_RX_ERROR (1 << 9)
#define DPAUX_DP_AUXSTAT_TIMEOUT_ERROR (1 << 8)
#define DPAUX_DP_AUXSTAT_REPLY_MASK (0xff)
#define DPAUX_DP_AUX_SINKSTAT_LO 0x35
#define DPAUX_DP_AUX_SINKSTAT_HI 0x39
#define DPAUX_HPD_CONFIG 0x3d
#define DPAUX_HPD_CONFIG_UNPLUG_MIN_TIME(x) (((x) & 0xffff) << 16)
#define DPAUX_HPD_CONFIG_PLUG_MIN_TIME(x) ((x) & 0xffff)
#define DPAUX_HPD_IRQ_CONFIG 0x41
#define DPAUX_HPD_IRQ_CONFIG_MIN_LOW_TIME(x) ((x) & 0xffff)
#define DPAUX_DP_AUX_CONFIG 0x45
#define DPAUX_HYBRID_PADCTL 0x49
#define DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV (1 << 15)
#define DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV (1 << 14)
#define DPAUX_HYBRID_PADCTL_AUX_CMH(x) (((x) & 0x3) << 12)
#define DPAUX_HYBRID_PADCTL_AUX_DRVZ(x) (((x) & 0x7) << 8)
#define DPAUX_HYBRID_PADCTL_AUX_DRVI(x) (((x) & 0x3f) << 2)
#define DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV (1 << 1)
#define DPAUX_HYBRID_PADCTL_MODE_I2C (1 << 0)
#define DPAUX_HYBRID_PADCTL_MODE_AUX (0 << 0)
#define DPAUX_HYBRID_SPARE 0x4d
#define DPAUX_HYBRID_SPARE_PAD_POWER_DOWN (1 << 0)
#define DPAUX_SCRATCH_REG0 0x51
#define DPAUX_SCRATCH_REG1 0x55
#define DPAUX_SCRATCH_REG2 0x59
#endif
File diff suppressed because it is too large Load Diff
+214
View File
@@ -0,0 +1,214 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2012 Avionic Design GmbH
* Copyright (C) 2012-2013 NVIDIA CORPORATION. All rights reserved.
*/
#ifndef HOST1X_DRM_H
#define HOST1X_DRM_H 1
#include <linux/host1x.h>
#include <linux/iova.h>
#include <linux/gpio/consumer.h>
#include <drm/drm_atomic.h>
#include <drm/drm_bridge.h>
#include <drm/drm_encoder.h>
#include <drm/drm_fixed.h>
#include <drm/drm_probe_helper.h>
#include <uapi/drm/tegra_drm.h>
#include "gem.h"
#include "hub.h"
#include "trace.h"
/* XXX move to include/uapi/drm/drm_fourcc.h? */
#define DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT BIT_ULL(22)
struct drm_fb_helper;
struct drm_fb_helper_surface_size;
struct edid;
struct reset_control;
struct tegra_drm {
struct drm_device *drm;
struct iommu_domain *domain;
bool use_explicit_iommu;
struct mutex mm_lock;
struct drm_mm mm;
struct {
struct iova_domain domain;
unsigned long shift;
unsigned long limit;
} carveout;
struct mutex clients_lock;
struct list_head clients;
unsigned int hmask, vmask;
unsigned int pitch_align;
unsigned int num_crtcs;
struct tegra_display_hub *hub;
};
static inline struct host1x *tegra_drm_to_host1x(struct tegra_drm *tegra)
{
return dev_get_drvdata(tegra->drm->dev->parent);
}
struct tegra_drm_client;
struct tegra_drm_context {
struct tegra_drm_client *client;
struct host1x_channel *channel;
/* Only used by legacy UAPI. */
unsigned int id;
/* Only used by new UAPI. */
struct xarray mappings;
struct host1x_memory_context *memory_context;
};
struct tegra_drm_client_ops {
int (*open_channel)(struct tegra_drm_client *client,
struct tegra_drm_context *context);
void (*close_channel)(struct tegra_drm_context *context);
int (*is_addr_reg)(struct device *dev, u32 class, u32 offset);
int (*is_valid_class)(u32 class);
int (*submit)(struct tegra_drm_context *context,
struct drm_tegra_submit *args, struct drm_device *drm,
struct drm_file *file);
int (*get_streamid_offset)(struct tegra_drm_client *client, u32 *offset);
int (*can_use_memory_ctx)(struct tegra_drm_client *client, bool *supported);
};
int tegra_drm_submit(struct tegra_drm_context *context,
struct drm_tegra_submit *args, struct drm_device *drm,
struct drm_file *file);
static inline int
tegra_drm_get_streamid_offset_thi(struct tegra_drm_client *client, u32 *offset)
{
*offset = 0x30;
return 0;
}
struct tegra_drm_client {
struct host1x_client base;
struct list_head list;
struct tegra_drm *drm;
struct host1x_channel *shared_channel;
/* Set by driver */
unsigned int version;
const struct tegra_drm_client_ops *ops;
};
static inline struct tegra_drm_client *
host1x_to_drm_client(struct host1x_client *client)
{
return container_of(client, struct tegra_drm_client, base);
}
int tegra_drm_register_client(struct tegra_drm *tegra,
struct tegra_drm_client *client);
int tegra_drm_unregister_client(struct tegra_drm *tegra,
struct tegra_drm_client *client);
int host1x_client_iommu_attach(struct host1x_client *client);
void host1x_client_iommu_detach(struct host1x_client *client);
void *tegra_drm_alloc(struct tegra_drm *tegra, size_t size, dma_addr_t *iova);
void tegra_drm_free(struct tegra_drm *tegra, size_t size, void *virt,
dma_addr_t iova);
struct cec_notifier;
struct tegra_output {
struct device_node *of_node;
struct device *dev;
struct drm_bridge *bridge;
struct drm_panel *panel;
struct i2c_adapter *ddc;
const struct drm_edid *drm_edid;
struct cec_notifier *cec;
unsigned int hpd_irq;
struct gpio_desc *hpd_gpio;
struct drm_encoder encoder;
struct drm_connector connector;
};
static inline struct tegra_output *encoder_to_output(struct drm_encoder *e)
{
return container_of(e, struct tegra_output, encoder);
}
static inline struct tegra_output *connector_to_output(struct drm_connector *c)
{
return container_of(c, struct tegra_output, connector);
}
/* from output.c */
int tegra_output_probe(struct tegra_output *output);
void tegra_output_remove(struct tegra_output *output);
int tegra_output_init(struct drm_device *drm, struct tegra_output *output);
void tegra_output_exit(struct tegra_output *output);
void tegra_output_find_possible_crtcs(struct tegra_output *output,
struct drm_device *drm);
int tegra_output_suspend(struct tegra_output *output);
int tegra_output_resume(struct tegra_output *output);
int tegra_output_connector_get_modes(struct drm_connector *connector);
enum drm_connector_status
tegra_output_connector_detect(struct drm_connector *connector, bool force);
void tegra_output_connector_destroy(struct drm_connector *connector);
/* from dpaux.c */
struct drm_dp_aux *drm_dp_aux_find_by_of_node(struct device_node *np);
enum drm_connector_status drm_dp_aux_detect(struct drm_dp_aux *aux);
int drm_dp_aux_attach(struct drm_dp_aux *aux, struct tegra_output *output);
int drm_dp_aux_detach(struct drm_dp_aux *aux);
int drm_dp_aux_enable(struct drm_dp_aux *aux);
int drm_dp_aux_disable(struct drm_dp_aux *aux);
/* from fb.c */
struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer,
unsigned int index);
bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer);
int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer,
struct tegra_bo_tiling *tiling);
struct drm_framebuffer *tegra_fb_create(struct drm_device *drm,
struct drm_file *file,
const struct drm_format_info *info,
const struct drm_mode_fb_cmd2 *cmd);
#ifdef CONFIG_DRM_FBDEV_EMULATION
int tegra_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes);
#define TEGRA_FBDEV_DRIVER_OPS \
.fbdev_probe = tegra_fbdev_driver_fbdev_probe
#else
#define TEGRA_FBDEV_DRIVER_OPS \
.fbdev_probe = NULL
#endif
extern struct platform_driver tegra_display_hub_driver;
extern struct platform_driver tegra_dc_driver;
extern struct platform_driver tegra_hdmi_driver;
extern struct platform_driver tegra_dsi_driver;
extern struct platform_driver tegra_dpaux_driver;
extern struct platform_driver tegra_sor_driver;
extern struct platform_driver tegra_gr2d_driver;
extern struct platform_driver tegra_gr3d_driver;
extern struct platform_driver tegra_vic_driver;
extern struct platform_driver tegra_nvdec_driver;
extern struct platform_driver tegra_nvjpg_driver;
#endif /* HOST1X_DRM_H */
File diff suppressed because it is too large Load Diff
+143
View File
@@ -0,0 +1,143 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2013 NVIDIA Corporation
*/
#ifndef DRM_TEGRA_DSI_H
#define DRM_TEGRA_DSI_H
#define DSI_INCR_SYNCPT 0x00
#define DSI_INCR_SYNCPT_CONTROL 0x01
#define DSI_INCR_SYNCPT_ERROR 0x02
#define DSI_CTXSW 0x08
#define DSI_RD_DATA 0x09
#define DSI_WR_DATA 0x0a
#define DSI_POWER_CONTROL 0x0b
#define DSI_POWER_CONTROL_ENABLE (1 << 0)
#define DSI_INT_ENABLE 0x0c
#define DSI_INT_STATUS 0x0d
#define DSI_INT_MASK 0x0e
#define DSI_HOST_CONTROL 0x0f
#define DSI_HOST_CONTROL_FIFO_RESET (1 << 21)
#define DSI_HOST_CONTROL_CRC_RESET (1 << 20)
#define DSI_HOST_CONTROL_TX_TRIG_SOL (0 << 12)
#define DSI_HOST_CONTROL_TX_TRIG_FIFO (1 << 12)
#define DSI_HOST_CONTROL_TX_TRIG_HOST (2 << 12)
#define DSI_HOST_CONTROL_RAW (1 << 6)
#define DSI_HOST_CONTROL_HS (1 << 5)
#define DSI_HOST_CONTROL_FIFO_SEL (1 << 4)
#define DSI_HOST_CONTROL_IMM_BTA (1 << 3)
#define DSI_HOST_CONTROL_PKT_BTA (1 << 2)
#define DSI_HOST_CONTROL_CS (1 << 1)
#define DSI_HOST_CONTROL_ECC (1 << 0)
#define DSI_CONTROL 0x10
#define DSI_CONTROL_HS_CLK_CTRL (1 << 20)
#define DSI_CONTROL_CHANNEL(c) (((c) & 0x3) << 16)
#define DSI_CONTROL_FORMAT(f) (((f) & 0x3) << 12)
#define DSI_CONTROL_TX_TRIG(x) (((x) & 0x3) << 8)
#define DSI_CONTROL_LANES(n) (((n) & 0x3) << 4)
#define DSI_CONTROL_DCS_ENABLE (1 << 3)
#define DSI_CONTROL_SOURCE(s) (((s) & 0x1) << 2)
#define DSI_CONTROL_VIDEO_ENABLE (1 << 1)
#define DSI_CONTROL_HOST_ENABLE (1 << 0)
#define DSI_SOL_DELAY 0x11
#define DSI_MAX_THRESHOLD 0x12
#define DSI_TRIGGER 0x13
#define DSI_TRIGGER_HOST (1 << 1)
#define DSI_TRIGGER_VIDEO (1 << 0)
#define DSI_TX_CRC 0x14
#define DSI_STATUS 0x15
#define DSI_STATUS_IDLE (1 << 10)
#define DSI_STATUS_UNDERFLOW (1 << 9)
#define DSI_STATUS_OVERFLOW (1 << 8)
#define DSI_INIT_SEQ_CONTROL 0x1a
#define DSI_INIT_SEQ_DATA_0 0x1b
#define DSI_INIT_SEQ_DATA_1 0x1c
#define DSI_INIT_SEQ_DATA_2 0x1d
#define DSI_INIT_SEQ_DATA_3 0x1e
#define DSI_INIT_SEQ_DATA_4 0x1f
#define DSI_INIT_SEQ_DATA_5 0x20
#define DSI_INIT_SEQ_DATA_6 0x21
#define DSI_INIT_SEQ_DATA_7 0x22
#define DSI_PKT_SEQ_0_LO 0x23
#define DSI_PKT_SEQ_0_HI 0x24
#define DSI_PKT_SEQ_1_LO 0x25
#define DSI_PKT_SEQ_1_HI 0x26
#define DSI_PKT_SEQ_2_LO 0x27
#define DSI_PKT_SEQ_2_HI 0x28
#define DSI_PKT_SEQ_3_LO 0x29
#define DSI_PKT_SEQ_3_HI 0x2a
#define DSI_PKT_SEQ_4_LO 0x2b
#define DSI_PKT_SEQ_4_HI 0x2c
#define DSI_PKT_SEQ_5_LO 0x2d
#define DSI_PKT_SEQ_5_HI 0x2e
#define DSI_DCS_CMDS 0x33
#define DSI_PKT_LEN_0_1 0x34
#define DSI_PKT_LEN_2_3 0x35
#define DSI_PKT_LEN_4_5 0x36
#define DSI_PKT_LEN_6_7 0x37
#define DSI_PHY_TIMING_0 0x3c
#define DSI_PHY_TIMING_1 0x3d
#define DSI_PHY_TIMING_2 0x3e
#define DSI_BTA_TIMING 0x3f
#define DSI_TIMING_FIELD(value, period, hwinc) \
((DIV_ROUND_CLOSEST(value, period) - (hwinc)) & 0xff)
#define DSI_TIMEOUT_0 0x44
#define DSI_TIMEOUT_LRX(x) (((x) & 0xffff) << 16)
#define DSI_TIMEOUT_HTX(x) (((x) & 0xffff) << 0)
#define DSI_TIMEOUT_1 0x45
#define DSI_TIMEOUT_PR(x) (((x) & 0xffff) << 16)
#define DSI_TIMEOUT_TA(x) (((x) & 0xffff) << 0)
#define DSI_TO_TALLY 0x46
#define DSI_TALLY_TA(x) (((x) & 0xff) << 16)
#define DSI_TALLY_LRX(x) (((x) & 0xff) << 8)
#define DSI_TALLY_HTX(x) (((x) & 0xff) << 0)
#define DSI_PAD_CONTROL_0 0x4b
#define DSI_PAD_CONTROL_VS1_PDIO(x) (((x) & 0xf) << 0)
#define DSI_PAD_CONTROL_VS1_PDIO_CLK (1 << 8)
#define DSI_PAD_CONTROL_VS1_PULLDN(x) (((x) & 0xf) << 16)
#define DSI_PAD_CONTROL_VS1_PULLDN_CLK (1 << 24)
#define DSI_PAD_CONTROL_CD 0x4c
#define DSI_PAD_CD_STATUS 0x4d
#define DSI_VIDEO_MODE_CONTROL 0x4e
#define DSI_PAD_CONTROL_1 0x4f
#define DSI_PAD_CONTROL_2 0x50
#define DSI_PAD_OUT_CLK(x) (((x) & 0x7) << 0)
#define DSI_PAD_LP_DN(x) (((x) & 0x7) << 4)
#define DSI_PAD_LP_UP(x) (((x) & 0x7) << 8)
#define DSI_PAD_SLEW_DN(x) (((x) & 0x7) << 12)
#define DSI_PAD_SLEW_UP(x) (((x) & 0x7) << 16)
#define DSI_PAD_CONTROL_3 0x51
#define DSI_PAD_PREEMP_PD_CLK(x) (((x) & 0x3) << 12)
#define DSI_PAD_PREEMP_PU_CLK(x) (((x) & 0x3) << 8)
#define DSI_PAD_PREEMP_PD(x) (((x) & 0x3) << 4)
#define DSI_PAD_PREEMP_PU(x) (((x) & 0x3) << 0)
#define DSI_PAD_CONTROL_4 0x52
#define DSI_GANGED_MODE_CONTROL 0x53
#define DSI_GANGED_MODE_CONTROL_ENABLE (1 << 0)
#define DSI_GANGED_MODE_START 0x54
#define DSI_GANGED_MODE_SIZE 0x55
#define DSI_RAW_DATA_BYTE_COUNT 0x56
#define DSI_ULTRA_LOW_POWER_CONTROL 0x57
#define DSI_INIT_SEQ_DATA_8 0x58
#define DSI_INIT_SEQ_DATA_9 0x59
#define DSI_INIT_SEQ_DATA_10 0x5a
#define DSI_INIT_SEQ_DATA_11 0x5b
#define DSI_INIT_SEQ_DATA_12 0x5c
#define DSI_INIT_SEQ_DATA_13 0x5d
#define DSI_INIT_SEQ_DATA_14 0x5e
#define DSI_INIT_SEQ_DATA_15 0x5f
/*
* pixel format as used in the DSI_CONTROL_FORMAT field
*/
enum tegra_dsi_format {
TEGRA_DSI_FORMAT_16P,
TEGRA_DSI_FORMAT_18NP,
TEGRA_DSI_FORMAT_18P,
TEGRA_DSI_FORMAT_24P,
};
#endif
+249
View File
@@ -0,0 +1,249 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015, NVIDIA Corporation.
*/
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/firmware.h>
#include <linux/pci_ids.h>
#include <linux/iopoll.h>
#include "falcon.h"
#include "drm.h"
enum falcon_memory {
FALCON_MEMORY_IMEM,
FALCON_MEMORY_DATA,
};
static void falcon_writel(struct falcon *falcon, u32 value, u32 offset)
{
writel(value, falcon->regs + offset);
}
int falcon_wait_idle(struct falcon *falcon)
{
u32 value;
return readl_poll_timeout(falcon->regs + FALCON_IDLESTATE, value,
(value == 0), 10, 100000);
}
static int falcon_dma_wait_not_full(struct falcon *falcon)
{
u32 value;
return readl_poll_timeout(falcon->regs + FALCON_DMATRFCMD, value,
!(value & FALCON_DMATRFCMD_FULL), 10, 100000);
}
static int falcon_dma_wait_idle(struct falcon *falcon)
{
u32 value;
return readl_poll_timeout(falcon->regs + FALCON_DMATRFCMD, value,
(value & FALCON_DMATRFCMD_IDLE), 10, 100000);
}
static int falcon_copy_chunk(struct falcon *falcon,
phys_addr_t base,
unsigned long offset,
enum falcon_memory target)
{
u32 cmd = FALCON_DMATRFCMD_SIZE_256B;
int err;
if (target == FALCON_MEMORY_IMEM)
cmd |= FALCON_DMATRFCMD_IMEM;
/*
* Use second DMA context (i.e. the one for firmware). Strictly
* speaking, at this point both DMA contexts point to the firmware
* stream ID, but this register's value will be reused by the firmware
* for later DMA transactions, so we need to use the correct value.
*/
cmd |= FALCON_DMATRFCMD_DMACTX(1);
err = falcon_dma_wait_not_full(falcon);
if (err < 0)
return err;
falcon_writel(falcon, offset, FALCON_DMATRFMOFFS);
falcon_writel(falcon, base, FALCON_DMATRFFBOFFS);
falcon_writel(falcon, cmd, FALCON_DMATRFCMD);
return 0;
}
static void falcon_copy_firmware_image(struct falcon *falcon,
const struct firmware *firmware)
{
u32 *virt = falcon->firmware.virt;
size_t i;
/* copy the whole thing taking into account endianness */
for (i = 0; i < firmware->size / sizeof(u32); i++)
virt[i] = le32_to_cpu(((__le32 *)firmware->data)[i]);
}
static int falcon_parse_firmware_image(struct falcon *falcon)
{
struct falcon_fw_bin_header_v1 *bin = (void *)falcon->firmware.virt;
struct falcon_fw_os_header_v1 *os;
/* endian problems would show up right here */
if (bin->magic != PCI_VENDOR_ID_NVIDIA && bin->magic != 0x10fe) {
dev_err(falcon->dev, "incorrect firmware magic\n");
return -EINVAL;
}
/* currently only version 1 is supported */
if (bin->version != 1) {
dev_err(falcon->dev, "unsupported firmware version\n");
return -EINVAL;
}
/* check that the firmware size is consistent */
if (bin->size > falcon->firmware.size) {
dev_err(falcon->dev, "firmware image size inconsistency\n");
return -EINVAL;
}
os = falcon->firmware.virt + bin->os_header_offset;
falcon->firmware.bin_data.size = bin->os_size;
falcon->firmware.bin_data.offset = bin->os_data_offset;
falcon->firmware.code.offset = os->code_offset;
falcon->firmware.code.size = os->code_size;
falcon->firmware.data.offset = os->data_offset;
falcon->firmware.data.size = os->data_size;
return 0;
}
int falcon_read_firmware(struct falcon *falcon, const char *name)
{
int err;
/* request_firmware prints error if it fails */
err = request_firmware(&falcon->firmware.firmware, name, falcon->dev);
if (err < 0)
return err;
falcon->firmware.size = falcon->firmware.firmware->size;
return 0;
}
int falcon_load_firmware(struct falcon *falcon)
{
const struct firmware *firmware = falcon->firmware.firmware;
int err;
/* copy firmware image into local area. this also ensures endianness */
falcon_copy_firmware_image(falcon, firmware);
/* parse the image data */
err = falcon_parse_firmware_image(falcon);
if (err < 0) {
dev_err(falcon->dev, "failed to parse firmware image\n");
return err;
}
release_firmware(firmware);
falcon->firmware.firmware = NULL;
return 0;
}
int falcon_init(struct falcon *falcon)
{
falcon->firmware.virt = NULL;
return 0;
}
void falcon_exit(struct falcon *falcon)
{
if (falcon->firmware.firmware)
release_firmware(falcon->firmware.firmware);
}
int falcon_boot(struct falcon *falcon)
{
unsigned long offset;
u32 value;
int err;
if (!falcon->firmware.virt)
return -EINVAL;
err = readl_poll_timeout(falcon->regs + FALCON_DMACTL, value,
(value & (FALCON_DMACTL_IMEM_SCRUBBING |
FALCON_DMACTL_DMEM_SCRUBBING)) == 0,
10, 10000);
if (err < 0)
return err;
falcon_writel(falcon, 0, FALCON_DMACTL);
/* setup the address of the binary data so Falcon can access it later */
falcon_writel(falcon, (falcon->firmware.iova +
falcon->firmware.bin_data.offset) >> 8,
FALCON_DMATRFBASE);
/* copy the data segment into Falcon internal memory */
for (offset = 0; offset < falcon->firmware.data.size; offset += 256)
falcon_copy_chunk(falcon,
falcon->firmware.data.offset + offset,
offset, FALCON_MEMORY_DATA);
/* copy the code segment into Falcon internal memory */
for (offset = 0; offset < falcon->firmware.code.size; offset += 256)
falcon_copy_chunk(falcon, falcon->firmware.code.offset + offset,
offset, FALCON_MEMORY_IMEM);
/* wait for DMA to complete */
err = falcon_dma_wait_idle(falcon);
if (err < 0)
return err;
/* setup falcon interrupts */
falcon_writel(falcon, FALCON_IRQMSET_EXT(0xff) |
FALCON_IRQMSET_SWGEN1 |
FALCON_IRQMSET_SWGEN0 |
FALCON_IRQMSET_EXTERR |
FALCON_IRQMSET_HALT |
FALCON_IRQMSET_WDTMR,
FALCON_IRQMSET);
falcon_writel(falcon, FALCON_IRQDEST_EXT(0xff) |
FALCON_IRQDEST_SWGEN1 |
FALCON_IRQDEST_SWGEN0 |
FALCON_IRQDEST_EXTERR |
FALCON_IRQDEST_HALT,
FALCON_IRQDEST);
/* enable interface */
falcon_writel(falcon, FALCON_ITFEN_MTHDEN |
FALCON_ITFEN_CTXEN,
FALCON_ITFEN);
/* boot falcon */
falcon_writel(falcon, 0x00000000, FALCON_BOOTVEC);
falcon_writel(falcon, FALCON_CPUCTL_STARTCPU, FALCON_CPUCTL);
err = falcon_wait_idle(falcon);
if (err < 0) {
dev_err(falcon->dev, "Falcon boot failed due to timeout\n");
return err;
}
return 0;
}
void falcon_execute_method(struct falcon *falcon, u32 method, u32 data)
{
falcon_writel(falcon, method >> 2, FALCON_UCLASS_METHOD_OFFSET);
falcon_writel(falcon, data, FALCON_UCLASS_METHOD_DATA);
}
+116
View File
@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2015, NVIDIA Corporation.
*/
#ifndef _FALCON_H_
#define _FALCON_H_
#include <linux/types.h>
#define FALCON_UCLASS_METHOD_OFFSET 0x00000040
#define FALCON_UCLASS_METHOD_DATA 0x00000044
#define FALCON_IRQMSET 0x00001010
#define FALCON_IRQMSET_WDTMR (1 << 1)
#define FALCON_IRQMSET_HALT (1 << 4)
#define FALCON_IRQMSET_EXTERR (1 << 5)
#define FALCON_IRQMSET_SWGEN0 (1 << 6)
#define FALCON_IRQMSET_SWGEN1 (1 << 7)
#define FALCON_IRQMSET_EXT(v) (((v) & 0xff) << 8)
#define FALCON_IRQDEST 0x0000101c
#define FALCON_IRQDEST_HALT (1 << 4)
#define FALCON_IRQDEST_EXTERR (1 << 5)
#define FALCON_IRQDEST_SWGEN0 (1 << 6)
#define FALCON_IRQDEST_SWGEN1 (1 << 7)
#define FALCON_IRQDEST_EXT(v) (((v) & 0xff) << 8)
#define FALCON_ITFEN 0x00001048
#define FALCON_ITFEN_CTXEN (1 << 0)
#define FALCON_ITFEN_MTHDEN (1 << 1)
#define FALCON_IDLESTATE 0x0000104c
#define FALCON_CPUCTL 0x00001100
#define FALCON_CPUCTL_STARTCPU (1 << 1)
#define FALCON_BOOTVEC 0x00001104
#define FALCON_DMACTL 0x0000110c
#define FALCON_DMACTL_DMEM_SCRUBBING (1 << 1)
#define FALCON_DMACTL_IMEM_SCRUBBING (1 << 2)
#define FALCON_DMATRFBASE 0x00001110
#define FALCON_DMATRFMOFFS 0x00001114
#define FALCON_DMATRFCMD 0x00001118
#define FALCON_DMATRFCMD_FULL (1 << 0)
#define FALCON_DMATRFCMD_IDLE (1 << 1)
#define FALCON_DMATRFCMD_IMEM (1 << 4)
#define FALCON_DMATRFCMD_SIZE_256B (6 << 8)
#define FALCON_DMATRFCMD_DMACTX(v) (((v) & 0x7) << 12)
#define FALCON_DMATRFFBOFFS 0x0000111c
struct falcon_fw_bin_header_v1 {
u32 magic; /* 0x10de */
u32 version; /* version of bin format (1) */
u32 size; /* entire image size including this header */
u32 os_header_offset;
u32 os_data_offset;
u32 os_size;
};
struct falcon_fw_os_app_v1 {
u32 offset;
u32 size;
};
struct falcon_fw_os_header_v1 {
u32 code_offset;
u32 code_size;
u32 data_offset;
u32 data_size;
};
struct falcon_firmware_section {
unsigned long offset;
size_t size;
};
struct falcon_firmware {
/* Firmware after it is read but not loaded */
const struct firmware *firmware;
/* Raw firmware data */
dma_addr_t iova;
dma_addr_t phys;
void *virt;
size_t size;
/* Parsed firmware information */
struct falcon_firmware_section bin_data;
struct falcon_firmware_section data;
struct falcon_firmware_section code;
};
struct falcon {
/* Set by falcon client */
struct device *dev;
void __iomem *regs;
struct falcon_firmware firmware;
};
int falcon_init(struct falcon *falcon);
void falcon_exit(struct falcon *falcon);
int falcon_read_firmware(struct falcon *falcon, const char *firmware_name);
int falcon_load_firmware(struct falcon *falcon);
int falcon_boot(struct falcon *falcon);
void falcon_execute_method(struct falcon *falcon, u32 method, u32 data);
int falcon_wait_idle(struct falcon *falcon);
#endif /* _FALCON_H_ */
+185
View File
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012-2013 Avionic Design GmbH
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
*
* Based on the KMS/FB DMA helpers
* Copyright (C) 2012 Analog Devices Inc.
*/
#include <linux/console.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_modeset_helper.h>
#include <drm/drm_print.h>
#include "drm.h"
#include "gem.h"
struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer,
unsigned int index)
{
return to_tegra_bo(drm_gem_fb_get_obj(framebuffer, index));
}
bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer)
{
struct tegra_bo *bo = tegra_fb_get_plane(framebuffer, 0);
if (bo->flags & TEGRA_BO_BOTTOM_UP)
return true;
return false;
}
int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer,
struct tegra_bo_tiling *tiling)
{
uint64_t modifier = framebuffer->modifier;
if (fourcc_mod_is_vendor(modifier, NVIDIA)) {
if ((modifier & DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT) == 0)
tiling->sector_layout = TEGRA_BO_SECTOR_LAYOUT_TEGRA;
else
tiling->sector_layout = TEGRA_BO_SECTOR_LAYOUT_GPU;
modifier &= ~DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT;
}
switch (modifier) {
case DRM_FORMAT_MOD_LINEAR:
tiling->mode = TEGRA_BO_TILING_MODE_PITCH;
tiling->value = 0;
break;
case DRM_FORMAT_MOD_NVIDIA_TEGRA_TILED:
tiling->mode = TEGRA_BO_TILING_MODE_TILED;
tiling->value = 0;
break;
case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(0):
tiling->mode = TEGRA_BO_TILING_MODE_BLOCK;
tiling->value = 0;
break;
case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(1):
tiling->mode = TEGRA_BO_TILING_MODE_BLOCK;
tiling->value = 1;
break;
case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(2):
tiling->mode = TEGRA_BO_TILING_MODE_BLOCK;
tiling->value = 2;
break;
case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(3):
tiling->mode = TEGRA_BO_TILING_MODE_BLOCK;
tiling->value = 3;
break;
case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(4):
tiling->mode = TEGRA_BO_TILING_MODE_BLOCK;
tiling->value = 4;
break;
case DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(5):
tiling->mode = TEGRA_BO_TILING_MODE_BLOCK;
tiling->value = 5;
break;
default:
DRM_DEBUG_KMS("unknown format modifier: %llx\n", modifier);
return -EINVAL;
}
return 0;
}
static const struct drm_framebuffer_funcs tegra_fb_funcs = {
.destroy = drm_gem_fb_destroy,
.create_handle = drm_gem_fb_create_handle,
};
static
struct drm_framebuffer *tegra_fb_alloc(struct drm_device *drm,
const struct drm_format_info *info,
const struct drm_mode_fb_cmd2 *mode_cmd,
struct tegra_bo **planes,
unsigned int num_planes)
{
struct drm_framebuffer *fb;
unsigned int i;
int err;
fb = kzalloc_obj(*fb);
if (!fb)
return ERR_PTR(-ENOMEM);
drm_helper_mode_fill_fb_struct(drm, fb, info, mode_cmd);
for (i = 0; i < fb->format->num_planes; i++)
fb->obj[i] = &planes[i]->gem;
err = drm_framebuffer_init(drm, fb, &tegra_fb_funcs);
if (err < 0) {
dev_err(drm->dev, "failed to initialize framebuffer: %d\n",
err);
kfree(fb);
return ERR_PTR(err);
}
return fb;
}
struct drm_framebuffer *tegra_fb_create(struct drm_device *drm,
struct drm_file *file,
const struct drm_format_info *info,
const struct drm_mode_fb_cmd2 *cmd)
{
struct tegra_bo *planes[4];
struct drm_gem_object *gem;
struct drm_framebuffer *fb;
unsigned int i;
int err;
for (i = 0; i < info->num_planes; i++) {
unsigned int width = cmd->width / (i ? info->hsub : 1);
unsigned int height = cmd->height / (i ? info->vsub : 1);
unsigned int size, bpp;
gem = drm_gem_object_lookup(file, cmd->handles[i]);
if (!gem) {
err = -ENXIO;
goto unreference;
}
bpp = info->cpp[i];
size = (height - 1) * cmd->pitches[i] +
width * bpp + cmd->offsets[i];
if (gem->size < size) {
err = -EINVAL;
drm_gem_object_put(gem);
goto unreference;
}
planes[i] = to_tegra_bo(gem);
}
fb = tegra_fb_alloc(drm, info, cmd, planes, i);
if (IS_ERR(fb)) {
err = PTR_ERR(fb);
goto unreference;
}
return fb;
unreference:
while (i--)
drm_gem_object_put(&planes[i]->gem);
return ERR_PTR(err);
}
+147
View File
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012-2013 Avionic Design GmbH
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
*
* Based on the KMS/FB DMA helpers
* Copyright (C) 2012 Analog Devices Inc.
*/
#include <linux/console.h>
#include <linux/fb.h>
#include <linux/vmalloc.h>
#include <drm/drm_drv.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_modeset_helper.h>
#include "drm.h"
#include "gem.h"
static int tegra_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct drm_fb_helper *helper = info->par;
struct tegra_bo *bo;
int err;
bo = tegra_fb_get_plane(helper->fb, 0);
err = drm_gem_mmap_obj(&bo->gem, bo->gem.size, vma);
if (err < 0)
return err;
return __tegra_gem_mmap(&bo->gem, vma);
}
static void tegra_fbdev_fb_destroy(struct fb_info *info)
{
struct drm_fb_helper *helper = info->par;
struct tegra_bo *bo = tegra_fb_get_plane(helper->fb, 0);
drm_fb_helper_fini(helper);
/* Undo the special mapping we made in fbdev probe. */
if (bo->pages) {
vunmap(bo->vaddr);
bo->vaddr = NULL;
}
drm_client_buffer_delete(helper->buffer);
drm_client_release(&helper->client);
}
static const struct fb_ops tegra_fb_ops = {
.owner = THIS_MODULE,
__FB_DEFAULT_DMAMEM_OPS_RDWR,
DRM_FB_HELPER_DEFAULT_OPS,
__FB_DEFAULT_DMAMEM_OPS_DRAW,
.fb_mmap = tegra_fb_mmap,
.fb_destroy = tegra_fbdev_fb_destroy,
};
static const struct drm_fb_helper_funcs tegra_fbdev_helper_funcs = {
};
int tegra_fbdev_driver_fbdev_probe(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes)
{
struct drm_client_dev *client = &helper->client;
struct drm_device *drm = client->dev;
struct drm_file *file = client->file;
struct tegra_drm *tegra = drm->dev_private;
struct fb_info *info = helper->info;
u32 fourcc, pitch;
u64 size;
const struct drm_format_info *format;
struct tegra_bo *bo;
struct drm_gem_object *gem;
u32 handle;
struct drm_client_buffer *buffer;
int err;
fourcc = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth);
format = drm_get_format_info(drm, fourcc, DRM_FORMAT_MOD_LINEAR);
pitch = round_up(drm_format_info_min_pitch(format, 0, sizes->surface_width),
tegra->pitch_align);
size = ALIGN(pitch * sizes->surface_height, PAGE_SIZE);
bo = tegra_bo_create(drm, size, 0);
if (IS_ERR(bo))
return PTR_ERR(bo);
gem = &bo->gem;
err = drm_gem_handle_create(file, gem, &handle);
if (err)
goto err_drm_gem_object_put;
buffer = drm_client_buffer_create(client, sizes->surface_width, sizes->surface_height,
fourcc, handle, pitch);
if (IS_ERR(buffer)) {
err = PTR_ERR(buffer);
goto err_drm_gem_handle_delete;
}
helper->funcs = &tegra_fbdev_helper_funcs;
helper->buffer = buffer;
helper->fb = buffer->fb;
info->fbops = &tegra_fb_ops;
drm_fb_helper_fill_info(info, helper, sizes);
if (bo->pages) {
bo->vaddr = vmap(bo->pages, bo->num_pages, VM_MAP,
pgprot_writecombine(PAGE_KERNEL));
if (!bo->vaddr) {
dev_err(drm->dev, "failed to vmap() framebuffer\n");
err = -ENOMEM;
goto err_drm_client_buffer_delete;
}
}
info->flags |= FBINFO_VIRTFB;
info->screen_buffer = bo->vaddr;
info->screen_size = gem->size;
info->fix.smem_start = (unsigned long)(bo->iova);
info->fix.smem_len = gem->size;
/* The handle is only needed for creating the framebuffer. */
drm_gem_handle_delete(file, handle);
/* The framebuffer still holds a reference on the GEM object. */
drm_gem_object_put(gem);
return 0;
err_drm_client_buffer_delete:
drm_client_buffer_delete(buffer);
err_drm_gem_handle_delete:
drm_gem_handle_delete(file, handle);
err_drm_gem_object_put:
drm_gem_object_put(gem);
return err;
}
@@ -0,0 +1,257 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2010-2020 NVIDIA Corporation */
#include "drm.h"
#include "submit.h"
#include "uapi.h"
struct tegra_drm_firewall {
struct tegra_drm_submit_data *submit;
struct tegra_drm_client *client;
u32 *data;
u32 pos;
u32 end;
u32 class;
};
static int fw_next(struct tegra_drm_firewall *fw, u32 *word)
{
if (fw->pos == fw->end)
return -EINVAL;
*word = fw->data[fw->pos++];
return 0;
}
static bool fw_check_addr_valid(struct tegra_drm_firewall *fw, u32 offset)
{
u32 i;
for (i = 0; i < fw->submit->num_used_mappings; i++) {
struct tegra_drm_mapping *m = fw->submit->used_mappings[i].mapping;
if (offset >= m->iova && offset <= m->iova_end)
return true;
}
return false;
}
static int fw_check_reg(struct tegra_drm_firewall *fw, u32 offset)
{
bool is_addr;
u32 word;
int err;
err = fw_next(fw, &word);
if (err)
return err;
if (!fw->client->ops->is_addr_reg)
return 0;
is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev, fw->class,
offset);
if (!is_addr)
return 0;
if (!fw_check_addr_valid(fw, word))
return -EINVAL;
return 0;
}
static int fw_check_regs_seq(struct tegra_drm_firewall *fw, u32 offset,
u32 count, bool incr)
{
u32 i;
for (i = 0; i < count; i++) {
if (fw_check_reg(fw, offset))
return -EINVAL;
if (incr)
offset++;
}
return 0;
}
static int fw_check_regs_mask(struct tegra_drm_firewall *fw, u32 offset,
u16 mask)
{
unsigned long bmask = mask;
unsigned int bit;
for_each_set_bit(bit, &bmask, 16) {
if (fw_check_reg(fw, offset+bit))
return -EINVAL;
}
return 0;
}
static int fw_check_regs_imm(struct tegra_drm_firewall *fw, u32 offset)
{
bool is_addr;
if (!fw->client->ops->is_addr_reg)
return 0;
is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev, fw->class,
offset);
if (is_addr)
return -EINVAL;
return 0;
}
static int fw_check_class(struct tegra_drm_firewall *fw, u32 class)
{
if (!fw->client->ops->is_valid_class) {
if (class == fw->client->base.class)
return 0;
else
return -EINVAL;
}
if (!fw->client->ops->is_valid_class(class))
return -EINVAL;
return 0;
}
enum {
HOST1X_OPCODE_SETCLASS = 0x00,
HOST1X_OPCODE_INCR = 0x01,
HOST1X_OPCODE_NONINCR = 0x02,
HOST1X_OPCODE_MASK = 0x03,
HOST1X_OPCODE_IMM = 0x04,
HOST1X_OPCODE_RESTART = 0x05,
HOST1X_OPCODE_GATHER = 0x06,
HOST1X_OPCODE_SETSTRMID = 0x07,
HOST1X_OPCODE_SETAPPID = 0x08,
HOST1X_OPCODE_SETPYLD = 0x09,
HOST1X_OPCODE_INCR_W = 0x0a,
HOST1X_OPCODE_NONINCR_W = 0x0b,
HOST1X_OPCODE_GATHER_W = 0x0c,
HOST1X_OPCODE_RESTART_W = 0x0d,
HOST1X_OPCODE_EXTEND = 0x0e,
};
int tegra_drm_fw_validate(struct tegra_drm_client *client, u32 *data, u32 start,
u32 words, struct tegra_drm_submit_data *submit,
u32 *job_class)
{
struct tegra_drm_firewall fw = {
.submit = submit,
.client = client,
.data = data,
.pos = start,
.end = start+words,
.class = *job_class,
};
bool payload_valid = false;
u32 payload;
int err;
while (fw.pos != fw.end) {
u32 word, opcode, offset, count, mask, class;
err = fw_next(&fw, &word);
if (err)
return err;
opcode = (word & 0xf0000000) >> 28;
switch (opcode) {
case HOST1X_OPCODE_SETCLASS:
offset = word >> 16 & 0xfff;
mask = word & 0x3f;
class = (word >> 6) & 0x3ff;
err = fw_check_class(&fw, class);
fw.class = class;
*job_class = class;
if (!err)
err = fw_check_regs_mask(&fw, offset, mask);
if (err)
dev_warn(client->base.dev,
"illegal SETCLASS(offset=0x%x, mask=0x%x, class=0x%x) at word %u",
offset, mask, class, fw.pos-1);
break;
case HOST1X_OPCODE_INCR:
offset = (word >> 16) & 0xfff;
count = word & 0xffff;
err = fw_check_regs_seq(&fw, offset, count, true);
if (err)
dev_warn(client->base.dev,
"illegal INCR(offset=0x%x, count=%u) in class 0x%x at word %u",
offset, count, fw.class, fw.pos-1);
break;
case HOST1X_OPCODE_NONINCR:
offset = (word >> 16) & 0xfff;
count = word & 0xffff;
err = fw_check_regs_seq(&fw, offset, count, false);
if (err)
dev_warn(client->base.dev,
"illegal NONINCR(offset=0x%x, count=%u) in class 0x%x at word %u",
offset, count, fw.class, fw.pos-1);
break;
case HOST1X_OPCODE_MASK:
offset = (word >> 16) & 0xfff;
mask = word & 0xffff;
err = fw_check_regs_mask(&fw, offset, mask);
if (err)
dev_warn(client->base.dev,
"illegal MASK(offset=0x%x, mask=0x%x) in class 0x%x at word %u",
offset, mask, fw.class, fw.pos-1);
break;
case HOST1X_OPCODE_IMM:
/* IMM cannot reasonably be used to write a pointer */
offset = (word >> 16) & 0xfff;
err = fw_check_regs_imm(&fw, offset);
if (err)
dev_warn(client->base.dev,
"illegal IMM(offset=0x%x) in class 0x%x at word %u",
offset, fw.class, fw.pos-1);
break;
case HOST1X_OPCODE_SETPYLD:
payload = word & 0xffff;
payload_valid = true;
break;
case HOST1X_OPCODE_INCR_W:
if (!payload_valid)
return -EINVAL;
offset = word & 0x3fffff;
err = fw_check_regs_seq(&fw, offset, payload, true);
if (err)
dev_warn(client->base.dev,
"illegal INCR_W(offset=0x%x) in class 0x%x at word %u",
offset, fw.class, fw.pos-1);
break;
case HOST1X_OPCODE_NONINCR_W:
if (!payload_valid)
return -EINVAL;
offset = word & 0x3fffff;
err = fw_check_regs_seq(&fw, offset, payload, false);
if (err)
dev_warn(client->base.dev,
"illegal NONINCR(offset=0x%x) in class 0x%x at word %u",
offset, fw.class, fw.pos-1);
break;
default:
dev_warn(client->base.dev, "illegal opcode at word %u",
fw.pos-1);
return -EINVAL;
}
if (err)
return err;
}
return 0;
}
+799
View File
@@ -0,0 +1,799 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NVIDIA Tegra DRM GEM helper functions
*
* Copyright (C) 2012 Sascha Hauer, Pengutronix
* Copyright (C) 2013-2015 NVIDIA CORPORATION, All rights reserved.
*
* Based on the GEM/CMA helpers
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
*/
#include <linux/dma-buf.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <drm/drm_drv.h>
#include <drm/drm_dumb_buffers.h>
#include <drm/drm_prime.h>
#include "drm.h"
#include "gem.h"
MODULE_IMPORT_NS("DMA_BUF");
static unsigned int sg_dma_count_chunks(struct scatterlist *sgl, unsigned int nents)
{
dma_addr_t next = ~(dma_addr_t)0;
unsigned int count = 0, i;
struct scatterlist *s;
for_each_sg(sgl, s, nents, i) {
/* sg_dma_address(s) is only valid for entries that have sg_dma_len(s) != 0. */
if (!sg_dma_len(s))
continue;
if (sg_dma_address(s) != next) {
next = sg_dma_address(s) + sg_dma_len(s);
count++;
}
}
return count;
}
static inline unsigned int sgt_dma_count_chunks(struct sg_table *sgt)
{
return sg_dma_count_chunks(sgt->sgl, sgt->nents);
}
static void tegra_bo_put(struct host1x_bo *bo)
{
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
drm_gem_object_put(&obj->gem);
}
static struct host1x_bo_mapping *tegra_bo_pin(struct device *dev, struct host1x_bo *bo,
enum dma_data_direction direction)
{
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
struct drm_gem_object *gem = &obj->gem;
struct host1x_bo_mapping *map;
int err;
map = kzalloc_obj(*map);
if (!map)
return ERR_PTR(-ENOMEM);
kref_init(&map->ref);
map->bo = bo;
map->direction = direction;
map->dev = dev;
/*
* Imported buffers need special treatment to satisfy the semantics of DMA-BUF.
*/
if (obj->dma_buf) {
struct dma_buf *buf = obj->dma_buf;
map->attach = dma_buf_attach(buf, dev);
if (IS_ERR(map->attach)) {
err = PTR_ERR(map->attach);
goto free;
}
map->sgt = dma_buf_map_attachment_unlocked(map->attach, direction);
if (IS_ERR(map->sgt)) {
dma_buf_detach(buf, map->attach);
err = PTR_ERR(map->sgt);
map->sgt = NULL;
goto free;
}
err = sgt_dma_count_chunks(map->sgt);
map->size = gem->size;
goto out;
}
/*
* If we don't have a mapping for this buffer yet, return an SG table
* so that host1x can do the mapping for us via the DMA API.
*/
map->sgt = kzalloc_obj(*map->sgt);
if (!map->sgt) {
err = -ENOMEM;
goto free;
}
if (obj->pages) {
/*
* If the buffer object was allocated from the explicit IOMMU
* API code paths, construct an SG table from the pages.
*/
err = sg_alloc_table_from_pages(map->sgt, obj->pages, obj->num_pages, 0, gem->size,
GFP_KERNEL);
if (err < 0)
goto free;
} else {
/*
* If the buffer object had no pages allocated and if it was
* not imported, it had to be allocated with the DMA API, so
* the DMA API helper can be used.
*/
err = dma_get_sgtable(dev, map->sgt, obj->vaddr, obj->iova, gem->size);
if (err < 0)
goto free;
}
err = dma_map_sgtable(dev, map->sgt, direction, 0);
if (err)
goto free_sgt;
out:
/*
* If we've manually mapped the buffer object through the IOMMU, make sure to return the
* existing IOVA address of our mapping.
*/
if (!obj->mm) {
map->phys = sg_dma_address(map->sgt->sgl);
map->chunks = err;
} else {
map->phys = obj->iova;
map->chunks = 1;
}
map->size = gem->size;
return map;
free_sgt:
sg_free_table(map->sgt);
free:
kfree(map->sgt);
kfree(map);
return ERR_PTR(err);
}
static void tegra_bo_unpin(struct host1x_bo_mapping *map)
{
if (map->attach) {
dma_buf_unmap_attachment_unlocked(map->attach, map->sgt,
map->direction);
dma_buf_detach(map->attach->dmabuf, map->attach);
} else {
dma_unmap_sgtable(map->dev, map->sgt, map->direction, 0);
sg_free_table(map->sgt);
kfree(map->sgt);
}
kfree(map);
}
static void *tegra_bo_mmap(struct host1x_bo *bo)
{
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
struct iosys_map map = { 0 };
void *vaddr;
int ret;
if (obj->vaddr)
return obj->vaddr;
if (obj->dma_buf) {
ret = dma_buf_vmap_unlocked(obj->dma_buf, &map);
if (ret < 0)
return ERR_PTR(ret);
return map.vaddr;
}
vaddr = vmap(obj->pages, obj->num_pages, VM_MAP,
pgprot_writecombine(PAGE_KERNEL));
if (!vaddr)
return ERR_PTR(-ENOMEM);
return vaddr;
}
static void tegra_bo_munmap(struct host1x_bo *bo, void *addr)
{
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
struct iosys_map map = IOSYS_MAP_INIT_VADDR(addr);
if (obj->vaddr)
return;
if (obj->dma_buf)
return dma_buf_vunmap_unlocked(obj->dma_buf, &map);
vunmap(addr);
}
static struct host1x_bo *tegra_bo_get(struct host1x_bo *bo)
{
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
drm_gem_object_get(&obj->gem);
return bo;
}
static const struct host1x_bo_ops tegra_bo_ops = {
.get = tegra_bo_get,
.put = tegra_bo_put,
.pin = tegra_bo_pin,
.unpin = tegra_bo_unpin,
.mmap = tegra_bo_mmap,
.munmap = tegra_bo_munmap,
};
static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo)
{
int prot = IOMMU_READ | IOMMU_WRITE;
ssize_t size;
int err;
if (bo->mm)
return -EBUSY;
bo->mm = kzalloc_obj(*bo->mm);
if (!bo->mm)
return -ENOMEM;
mutex_lock(&tegra->mm_lock);
err = drm_mm_insert_node_generic(&tegra->mm,
bo->mm, bo->gem.size, PAGE_SIZE, 0, 0);
if (err < 0) {
dev_err(tegra->drm->dev, "out of I/O virtual memory: %d\n",
err);
goto unlock;
}
bo->iova = bo->mm->start;
size = iommu_map_sgtable(tegra->domain, bo->iova, bo->sgt, prot);
if (size < 0) {
dev_err(tegra->drm->dev, "failed to map buffer\n");
err = size;
goto remove;
}
bo->size = size;
mutex_unlock(&tegra->mm_lock);
return 0;
remove:
drm_mm_remove_node(bo->mm);
unlock:
mutex_unlock(&tegra->mm_lock);
kfree(bo->mm);
return err;
}
static int tegra_bo_iommu_unmap(struct tegra_drm *tegra, struct tegra_bo *bo)
{
if (!bo->mm)
return 0;
mutex_lock(&tegra->mm_lock);
iommu_unmap(tegra->domain, bo->iova, bo->size);
drm_mm_remove_node(bo->mm);
mutex_unlock(&tegra->mm_lock);
kfree(bo->mm);
return 0;
}
static const struct drm_gem_object_funcs tegra_gem_object_funcs = {
.free = tegra_bo_free_object,
.export = tegra_gem_prime_export,
.vm_ops = &tegra_bo_vm_ops,
};
static struct tegra_bo *tegra_bo_alloc_object(struct drm_device *drm,
size_t size)
{
struct tegra_bo *bo;
int err;
bo = kzalloc_obj(*bo);
if (!bo)
return ERR_PTR(-ENOMEM);
bo->gem.funcs = &tegra_gem_object_funcs;
host1x_bo_init(&bo->base, &tegra_bo_ops);
size = round_up(size, PAGE_SIZE);
err = drm_gem_object_init(drm, &bo->gem, size);
if (err < 0)
goto free;
err = drm_gem_create_mmap_offset(&bo->gem);
if (err < 0)
goto release;
return bo;
release:
drm_gem_object_release(&bo->gem);
free:
kfree(bo);
return ERR_PTR(err);
}
static void tegra_bo_free(struct drm_device *drm, struct tegra_bo *bo)
{
if (bo->pages) {
dma_unmap_sgtable(drm->dev, bo->sgt, DMA_FROM_DEVICE, 0);
drm_gem_put_pages(&bo->gem, bo->pages, true, true);
sg_free_table(bo->sgt);
kfree(bo->sgt);
} else if (bo->vaddr) {
dma_free_wc(drm->dev, bo->gem.size, bo->vaddr, bo->iova);
}
}
static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo)
{
int err;
bo->pages = drm_gem_get_pages(&bo->gem);
if (IS_ERR(bo->pages))
return PTR_ERR(bo->pages);
bo->num_pages = bo->gem.size >> PAGE_SHIFT;
bo->sgt = drm_prime_pages_to_sg(bo->gem.dev, bo->pages, bo->num_pages);
if (IS_ERR(bo->sgt)) {
err = PTR_ERR(bo->sgt);
goto put_pages;
}
err = dma_map_sgtable(drm->dev, bo->sgt, DMA_FROM_DEVICE, 0);
if (err)
goto free_sgt;
return 0;
free_sgt:
sg_free_table(bo->sgt);
kfree(bo->sgt);
put_pages:
drm_gem_put_pages(&bo->gem, bo->pages, false, false);
return err;
}
static int tegra_bo_alloc(struct drm_device *drm, struct tegra_bo *bo)
{
struct tegra_drm *tegra = drm->dev_private;
int err;
if (tegra->domain) {
err = tegra_bo_get_pages(drm, bo);
if (err < 0)
return err;
err = tegra_bo_iommu_map(tegra, bo);
if (err < 0) {
tegra_bo_free(drm, bo);
return err;
}
} else {
size_t size = bo->gem.size;
bo->vaddr = dma_alloc_wc(drm->dev, size, &bo->iova,
GFP_KERNEL | __GFP_NOWARN);
if (!bo->vaddr) {
dev_err(drm->dev,
"failed to allocate buffer of size %zu\n",
size);
return -ENOMEM;
}
}
return 0;
}
struct tegra_bo *tegra_bo_create(struct drm_device *drm, size_t size,
unsigned long flags)
{
struct tegra_bo *bo;
int err;
bo = tegra_bo_alloc_object(drm, size);
if (IS_ERR(bo))
return bo;
err = tegra_bo_alloc(drm, bo);
if (err < 0)
goto release;
if (flags & DRM_TEGRA_GEM_CREATE_TILED)
bo->tiling.mode = TEGRA_BO_TILING_MODE_TILED;
if (flags & DRM_TEGRA_GEM_CREATE_BOTTOM_UP)
bo->flags |= TEGRA_BO_BOTTOM_UP;
return bo;
release:
drm_gem_object_release(&bo->gem);
kfree(bo);
return ERR_PTR(err);
}
struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file,
struct drm_device *drm,
size_t size,
unsigned long flags,
u32 *handle)
{
struct tegra_bo *bo;
int err;
bo = tegra_bo_create(drm, size, flags);
if (IS_ERR(bo))
return bo;
err = drm_gem_handle_create(file, &bo->gem, handle);
if (err) {
tegra_bo_free_object(&bo->gem);
return ERR_PTR(err);
}
drm_gem_object_put(&bo->gem);
return bo;
}
static struct tegra_bo *tegra_bo_import(struct drm_device *drm,
struct dma_buf *buf)
{
struct tegra_drm *tegra = drm->dev_private;
struct dma_buf_attachment *attach;
struct tegra_bo *bo;
int err;
bo = tegra_bo_alloc_object(drm, buf->size);
if (IS_ERR(bo))
return bo;
/*
* If we need to use IOMMU API to map the dma-buf into the internally managed
* domain, map it first to the DRM device to get an sgt.
*/
if (tegra->domain) {
attach = dma_buf_attach(buf, drm->dev);
if (IS_ERR(attach)) {
err = PTR_ERR(attach);
goto free;
}
bo->sgt = dma_buf_map_attachment_unlocked(attach, DMA_TO_DEVICE);
if (IS_ERR(bo->sgt)) {
err = PTR_ERR(bo->sgt);
goto detach;
}
err = tegra_bo_iommu_map(tegra, bo);
if (err < 0)
goto detach;
bo->gem.import_attach = attach;
}
get_dma_buf(buf);
bo->dma_buf = buf;
return bo;
detach:
if (!IS_ERR_OR_NULL(bo->sgt))
dma_buf_unmap_attachment_unlocked(attach, bo->sgt, DMA_TO_DEVICE);
dma_buf_detach(buf, attach);
dma_buf_put(buf);
free:
drm_gem_object_release(&bo->gem);
kfree(bo);
return ERR_PTR(err);
}
void tegra_bo_free_object(struct drm_gem_object *gem)
{
struct tegra_drm *tegra = gem->dev->dev_private;
struct tegra_bo *bo = to_tegra_bo(gem);
host1x_bo_clear_cached_mappings(&bo->base);
if (tegra->domain) {
tegra_bo_iommu_unmap(tegra, bo);
if (drm_gem_is_imported(gem)) {
dma_buf_unmap_attachment_unlocked(gem->import_attach, bo->sgt,
DMA_TO_DEVICE);
dma_buf_detach(gem->import_attach->dmabuf, gem->import_attach);
}
}
tegra_bo_free(gem->dev, bo);
if (bo->dma_buf)
dma_buf_put(bo->dma_buf);
drm_gem_object_release(gem);
kfree(bo);
}
int tegra_bo_dumb_create(struct drm_file *file, struct drm_device *drm,
struct drm_mode_create_dumb *args)
{
struct tegra_drm *tegra = drm->dev_private;
struct tegra_bo *bo;
int ret;
ret = drm_mode_size_dumb(drm, args, tegra->pitch_align, 0);
if (ret)
return ret;
bo = tegra_bo_create_with_handle(file, drm, args->size, 0,
&args->handle);
if (IS_ERR(bo))
return PTR_ERR(bo);
return 0;
}
static vm_fault_t tegra_bo_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct drm_gem_object *gem = vma->vm_private_data;
struct tegra_bo *bo = to_tegra_bo(gem);
struct page *page;
pgoff_t offset;
if (!bo->pages)
return VM_FAULT_SIGBUS;
offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
page = bo->pages[offset];
return vmf_insert_page(vma, vmf->address, page);
}
const struct vm_operations_struct tegra_bo_vm_ops = {
.fault = tegra_bo_fault,
.open = drm_gem_vm_open,
.close = drm_gem_vm_close,
};
int __tegra_gem_mmap(struct drm_gem_object *gem, struct vm_area_struct *vma)
{
struct tegra_bo *bo = to_tegra_bo(gem);
if (!bo->pages) {
unsigned long vm_pgoff = vma->vm_pgoff;
int err;
/*
* Clear the VM_PFNMAP flag that was set by drm_gem_mmap(),
* and set the vm_pgoff (used as a fake buffer offset by DRM)
* to 0 as we want to map the whole buffer.
*/
vm_flags_clear(vma, VM_PFNMAP);
vma->vm_pgoff = 0;
err = dma_mmap_wc(gem->dev->dev, vma, bo->vaddr, bo->iova,
gem->size);
if (err < 0) {
drm_gem_vm_close(vma);
return err;
}
vma->vm_pgoff = vm_pgoff;
} else {
pgprot_t prot = vm_get_page_prot(vma->vm_flags);
vm_flags_mod(vma, VM_MIXEDMAP, VM_PFNMAP);
vma->vm_page_prot = pgprot_writecombine(prot);
}
return 0;
}
int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma)
{
struct drm_gem_object *gem;
int err;
err = drm_gem_mmap(file, vma);
if (err < 0)
return err;
gem = vma->vm_private_data;
return __tegra_gem_mmap(gem, vma);
}
static struct sg_table *
tegra_gem_prime_map_dma_buf(struct dma_buf_attachment *attach,
enum dma_data_direction dir)
{
struct drm_gem_object *gem = attach->dmabuf->priv;
struct tegra_bo *bo = to_tegra_bo(gem);
struct sg_table *sgt;
sgt = kmalloc_obj(*sgt);
if (!sgt)
return NULL;
if (bo->pages) {
if (sg_alloc_table_from_pages(sgt, bo->pages, bo->num_pages,
0, gem->size, GFP_KERNEL) < 0)
goto free;
} else {
if (dma_get_sgtable(attach->dev, sgt, bo->vaddr, bo->iova,
gem->size) < 0)
goto free;
}
if (dma_map_sgtable(attach->dev, sgt, dir, 0))
goto free;
return sgt;
free:
sg_free_table(sgt);
kfree(sgt);
return NULL;
}
static void tegra_gem_prime_unmap_dma_buf(struct dma_buf_attachment *attach,
struct sg_table *sgt,
enum dma_data_direction dir)
{
struct drm_gem_object *gem = attach->dmabuf->priv;
struct tegra_bo *bo = to_tegra_bo(gem);
if (bo->pages)
dma_unmap_sgtable(attach->dev, sgt, dir, 0);
sg_free_table(sgt);
kfree(sgt);
}
static void tegra_gem_prime_release(struct dma_buf *buf)
{
drm_gem_dmabuf_release(buf);
}
static int tegra_gem_prime_begin_cpu_access(struct dma_buf *buf,
enum dma_data_direction direction)
{
struct drm_gem_object *gem = buf->priv;
struct tegra_bo *bo = to_tegra_bo(gem);
struct drm_device *drm = gem->dev;
if (bo->pages)
dma_sync_sgtable_for_cpu(drm->dev, bo->sgt, DMA_FROM_DEVICE);
return 0;
}
static int tegra_gem_prime_end_cpu_access(struct dma_buf *buf,
enum dma_data_direction direction)
{
struct drm_gem_object *gem = buf->priv;
struct tegra_bo *bo = to_tegra_bo(gem);
struct drm_device *drm = gem->dev;
if (bo->pages)
dma_sync_sgtable_for_device(drm->dev, bo->sgt, DMA_TO_DEVICE);
return 0;
}
static int tegra_gem_prime_mmap(struct dma_buf *buf, struct vm_area_struct *vma)
{
struct drm_gem_object *gem = buf->priv;
int err;
err = drm_gem_mmap_obj(gem, gem->size, vma);
if (err < 0)
return err;
return __tegra_gem_mmap(gem, vma);
}
static int tegra_gem_prime_vmap(struct dma_buf *buf, struct iosys_map *map)
{
struct drm_gem_object *gem = buf->priv;
struct tegra_bo *bo = to_tegra_bo(gem);
void *vaddr;
vaddr = tegra_bo_mmap(&bo->base);
if (IS_ERR(vaddr))
return PTR_ERR(vaddr);
iosys_map_set_vaddr(map, vaddr);
return 0;
}
static void tegra_gem_prime_vunmap(struct dma_buf *buf, struct iosys_map *map)
{
struct drm_gem_object *gem = buf->priv;
struct tegra_bo *bo = to_tegra_bo(gem);
tegra_bo_munmap(&bo->base, map->vaddr);
}
static const struct dma_buf_ops tegra_gem_prime_dmabuf_ops = {
.map_dma_buf = tegra_gem_prime_map_dma_buf,
.unmap_dma_buf = tegra_gem_prime_unmap_dma_buf,
.release = tegra_gem_prime_release,
.begin_cpu_access = tegra_gem_prime_begin_cpu_access,
.end_cpu_access = tegra_gem_prime_end_cpu_access,
.mmap = tegra_gem_prime_mmap,
.vmap = tegra_gem_prime_vmap,
.vunmap = tegra_gem_prime_vunmap,
};
struct dma_buf *tegra_gem_prime_export(struct drm_gem_object *gem,
int flags)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
exp_info.exp_name = KBUILD_MODNAME;
exp_info.owner = gem->dev->driver->fops->owner;
exp_info.ops = &tegra_gem_prime_dmabuf_ops;
exp_info.size = gem->size;
exp_info.flags = flags;
exp_info.priv = gem;
return drm_gem_dmabuf_export(gem->dev, &exp_info);
}
struct drm_gem_object *tegra_gem_prime_import(struct drm_device *drm,
struct dma_buf *buf)
{
struct tegra_bo *bo;
if (buf->ops == &tegra_gem_prime_dmabuf_ops) {
struct drm_gem_object *gem = buf->priv;
if (gem->dev == drm) {
drm_gem_object_get(gem);
return gem;
}
}
bo = tegra_bo_import(drm, buf);
if (IS_ERR(bo))
return ERR_CAST(bo);
return &bo->gem;
}
struct host1x_bo *tegra_gem_lookup(struct drm_file *file, u32 handle)
{
struct drm_gem_object *gem;
struct tegra_bo *bo;
gem = drm_gem_object_lookup(file, handle);
if (!gem)
return NULL;
bo = to_tegra_bo(gem);
return &bo->base;
}
+106
View File
@@ -0,0 +1,106 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Tegra host1x GEM implementation
*
* Copyright (c) 2012-2013, NVIDIA Corporation.
*/
#ifndef __HOST1X_GEM_H
#define __HOST1X_GEM_H
#include <linux/host1x.h>
#include <drm/drm.h>
#include <drm/drm_gem.h>
#define TEGRA_BO_BOTTOM_UP (1 << 0)
enum tegra_bo_tiling_mode {
TEGRA_BO_TILING_MODE_PITCH,
TEGRA_BO_TILING_MODE_TILED,
TEGRA_BO_TILING_MODE_BLOCK,
};
enum tegra_bo_sector_layout {
TEGRA_BO_SECTOR_LAYOUT_TEGRA,
TEGRA_BO_SECTOR_LAYOUT_GPU,
};
struct tegra_bo_tiling {
enum tegra_bo_tiling_mode mode;
unsigned long value;
enum tegra_bo_sector_layout sector_layout;
};
/*
* How memory is referenced within a tegra_bo:
*
* Buffer source | Mapping API(*) | Fields
* ---------------+-----------------+---------------
* Allocated here | DMA API | iova (IOVA mapped to drm->dev), vaddr (CPU VA)
*
* Allocated here | IOMMU API | pages/num_pages (Phys. memory), sgt (Mapped to drm->dev),
* | iova/size (Mapped to domain)
*
* Imported | DMA API | dma_buf (Imported dma_buf)
*
* Imported | IOMMU API | dma_buf (Imported dma_buf),
* | gem->import_attach (Attachment on drm->dev),
* | sgt (Mapped to drm->dev)
* | iova/size (Mapped to domain)
*
* (*) If tegra->domain is set, i.e. TegraDRM IOMMU domain is directly managed through IOMMU API,
* this is IOMMU API. Otherwise DMA API.
*/
struct tegra_bo {
struct drm_gem_object gem;
struct host1x_bo base;
unsigned long flags;
struct sg_table *sgt;
dma_addr_t iova;
void *vaddr;
struct dma_buf *dma_buf;
struct drm_mm_node *mm;
unsigned long num_pages;
struct page **pages;
/* size of IOMMU mapping */
size_t size;
struct tegra_bo_tiling tiling;
};
static inline struct tegra_bo *to_tegra_bo(struct drm_gem_object *gem)
{
return container_of(gem, struct tegra_bo, gem);
}
static inline struct tegra_bo *host1x_to_tegra_bo(struct host1x_bo *bo)
{
return container_of(bo, struct tegra_bo, base);
}
struct tegra_bo *tegra_bo_create(struct drm_device *drm, size_t size,
unsigned long flags);
struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file,
struct drm_device *drm,
size_t size,
unsigned long flags,
u32 *handle);
void tegra_bo_free_object(struct drm_gem_object *gem);
int tegra_bo_dumb_create(struct drm_file *file, struct drm_device *drm,
struct drm_mode_create_dumb *args);
extern const struct vm_operations_struct tegra_bo_vm_ops;
int __tegra_gem_mmap(struct drm_gem_object *gem, struct vm_area_struct *vma);
int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma);
struct dma_buf *tegra_gem_prime_export(struct drm_gem_object *gem,
int flags);
struct drm_gem_object *tegra_gem_prime_import(struct drm_device *drm,
struct dma_buf *buf);
struct host1x_bo *tegra_gem_lookup(struct drm_file *file, u32 handle);
#endif
+397
View File
@@ -0,0 +1,397 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2013, NVIDIA Corporation.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <soc/tegra/common.h>
#include "drm.h"
#include "gem.h"
#include "gr2d.h"
enum {
RST_MC,
RST_GR2D,
RST_GR2D_MAX,
};
struct gr2d_soc {
unsigned int version;
};
struct gr2d {
struct tegra_drm_client client;
struct host1x_channel *channel;
struct clk *clk;
struct reset_control_bulk_data resets[RST_GR2D_MAX];
unsigned int nresets;
const struct gr2d_soc *soc;
DECLARE_BITMAP(addr_regs, GR2D_NUM_REGS);
};
static inline struct gr2d *to_gr2d(struct tegra_drm_client *client)
{
return container_of(client, struct gr2d, client);
}
static int gr2d_init(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
unsigned long flags = HOST1X_SYNCPT_HAS_BASE;
struct gr2d *gr2d = to_gr2d(drm);
int err;
gr2d->channel = host1x_channel_request(client);
if (!gr2d->channel)
return -ENOMEM;
client->syncpts[0] = host1x_syncpt_request(client, flags);
if (!client->syncpts[0]) {
err = -ENOMEM;
dev_err(client->dev, "failed to request syncpoint: %d\n", err);
goto put;
}
err = host1x_client_iommu_attach(client);
if (err < 0) {
dev_err(client->dev, "failed to attach to domain: %d\n", err);
goto free;
}
err = tegra_drm_register_client(dev->dev_private, drm);
if (err < 0) {
dev_err(client->dev, "failed to register client: %d\n", err);
goto detach_iommu;
}
return 0;
detach_iommu:
host1x_client_iommu_detach(client);
free:
host1x_syncpt_put(client->syncpts[0]);
put:
host1x_channel_put(gr2d->channel);
return err;
}
static int gr2d_exit(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
struct tegra_drm *tegra = dev->dev_private;
struct gr2d *gr2d = to_gr2d(drm);
int err;
err = tegra_drm_unregister_client(tegra, drm);
if (err < 0)
return err;
host1x_client_iommu_detach(client);
host1x_syncpt_put(client->syncpts[0]);
host1x_channel_put(gr2d->channel);
gr2d->channel = NULL;
return 0;
}
static const struct host1x_client_ops gr2d_client_ops = {
.init = gr2d_init,
.exit = gr2d_exit,
};
static int gr2d_open_channel(struct tegra_drm_client *client,
struct tegra_drm_context *context)
{
struct gr2d *gr2d = to_gr2d(client);
context->channel = host1x_channel_get(gr2d->channel);
if (!context->channel)
return -ENOMEM;
return 0;
}
static void gr2d_close_channel(struct tegra_drm_context *context)
{
host1x_channel_put(context->channel);
}
static int gr2d_is_addr_reg(struct device *dev, u32 class, u32 offset)
{
struct gr2d *gr2d = dev_get_drvdata(dev);
switch (class) {
case HOST1X_CLASS_HOST1X:
if (offset == 0x2b)
return 1;
break;
case HOST1X_CLASS_GR2D:
case HOST1X_CLASS_GR2D_SB:
if (offset >= GR2D_NUM_REGS)
break;
if (test_bit(offset, gr2d->addr_regs))
return 1;
break;
}
return 0;
}
static int gr2d_is_valid_class(u32 class)
{
return (class == HOST1X_CLASS_GR2D ||
class == HOST1X_CLASS_GR2D_SB);
}
static const struct tegra_drm_client_ops gr2d_ops = {
.open_channel = gr2d_open_channel,
.close_channel = gr2d_close_channel,
.is_addr_reg = gr2d_is_addr_reg,
.is_valid_class = gr2d_is_valid_class,
.submit = tegra_drm_submit,
};
static const struct gr2d_soc tegra20_gr2d_soc = {
.version = 0x20,
};
static const struct gr2d_soc tegra30_gr2d_soc = {
.version = 0x30,
};
static const struct gr2d_soc tegra114_gr2d_soc = {
.version = 0x35,
};
static const struct of_device_id gr2d_match[] = {
{ .compatible = "nvidia,tegra114-gr2d", .data = &tegra114_gr2d_soc },
{ .compatible = "nvidia,tegra30-gr2d", .data = &tegra30_gr2d_soc },
{ .compatible = "nvidia,tegra20-gr2d", .data = &tegra20_gr2d_soc },
{ },
};
MODULE_DEVICE_TABLE(of, gr2d_match);
static const u32 gr2d_addr_regs[] = {
GR2D_UA_BASE_ADDR,
GR2D_VA_BASE_ADDR,
GR2D_PAT_BASE_ADDR,
GR2D_DSTA_BASE_ADDR,
GR2D_DSTB_BASE_ADDR,
GR2D_DSTC_BASE_ADDR,
GR2D_SRCA_BASE_ADDR,
GR2D_SRCB_BASE_ADDR,
GR2D_PATBASE_ADDR,
GR2D_SRC_BASE_ADDR_SB,
GR2D_DSTA_BASE_ADDR_SB,
GR2D_DSTB_BASE_ADDR_SB,
GR2D_UA_BASE_ADDR_SB,
GR2D_VA_BASE_ADDR_SB,
};
static int gr2d_get_resets(struct device *dev, struct gr2d *gr2d)
{
int err;
gr2d->resets[RST_MC].id = "mc";
gr2d->resets[RST_GR2D].id = "2d";
gr2d->nresets = RST_GR2D_MAX;
err = devm_reset_control_bulk_get_optional_exclusive_released(
dev, gr2d->nresets, gr2d->resets);
if (err) {
dev_err(dev, "failed to get reset: %d\n", err);
return err;
}
if (WARN_ON(!gr2d->resets[RST_GR2D].rstc))
return -ENOENT;
return 0;
}
static int gr2d_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct host1x_syncpt **syncpts;
struct gr2d *gr2d;
unsigned int i;
int err;
gr2d = devm_kzalloc(dev, sizeof(*gr2d), GFP_KERNEL);
if (!gr2d)
return -ENOMEM;
platform_set_drvdata(pdev, gr2d);
gr2d->soc = of_device_get_match_data(dev);
syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL);
if (!syncpts)
return -ENOMEM;
gr2d->clk = devm_clk_get(dev, NULL);
if (IS_ERR(gr2d->clk)) {
dev_err(dev, "cannot get clock\n");
return PTR_ERR(gr2d->clk);
}
err = gr2d_get_resets(dev, gr2d);
if (err)
return err;
INIT_LIST_HEAD(&gr2d->client.base.list);
gr2d->client.base.ops = &gr2d_client_ops;
gr2d->client.base.dev = dev;
gr2d->client.base.class = HOST1X_CLASS_GR2D;
gr2d->client.base.syncpts = syncpts;
gr2d->client.base.num_syncpts = 1;
INIT_LIST_HEAD(&gr2d->client.list);
gr2d->client.version = gr2d->soc->version;
gr2d->client.ops = &gr2d_ops;
err = devm_tegra_core_dev_init_opp_table_common(dev);
if (err)
return err;
/* initialize address register map */
for (i = 0; i < ARRAY_SIZE(gr2d_addr_regs); i++)
set_bit(gr2d_addr_regs[i], gr2d->addr_regs);
pm_runtime_enable(dev);
err = host1x_client_register(&gr2d->client.base);
if (err < 0) {
pm_runtime_disable(dev);
dev_err(dev, "failed to register host1x client: %d\n", err);
return err;
}
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, 500);
return 0;
}
static void gr2d_remove(struct platform_device *pdev)
{
struct gr2d *gr2d = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
host1x_client_unregister(&gr2d->client.base);
}
static int __maybe_unused gr2d_runtime_suspend(struct device *dev)
{
struct gr2d *gr2d = dev_get_drvdata(dev);
int err;
host1x_channel_stop(gr2d->channel);
reset_control_bulk_release(gr2d->nresets, gr2d->resets);
/*
* GR2D module shouldn't be reset while hardware is idling, otherwise
* host1x's cmdproc will stuck on trying to access any G2 register
* after reset. GR2D module could be either hot-reset or reset after
* power-gating of the HEG partition. Hence we will put in reset only
* the memory client part of the module, the HEG GENPD will take care
* of resetting GR2D module across power-gating.
*
* On Tegra20 there is no HEG partition, but it's okay to have
* undetermined h/w state since userspace is expected to reprogram
* the state on each job submission anyways.
*/
err = reset_control_acquire(gr2d->resets[RST_MC].rstc);
if (err) {
dev_err(dev, "failed to acquire MC reset: %d\n", err);
goto acquire_reset;
}
err = reset_control_assert(gr2d->resets[RST_MC].rstc);
reset_control_release(gr2d->resets[RST_MC].rstc);
if (err) {
dev_err(dev, "failed to assert MC reset: %d\n", err);
goto acquire_reset;
}
clk_disable_unprepare(gr2d->clk);
return 0;
acquire_reset:
reset_control_bulk_acquire(gr2d->nresets, gr2d->resets);
reset_control_bulk_deassert(gr2d->nresets, gr2d->resets);
return err;
}
static int __maybe_unused gr2d_runtime_resume(struct device *dev)
{
struct gr2d *gr2d = dev_get_drvdata(dev);
int err;
err = reset_control_bulk_acquire(gr2d->nresets, gr2d->resets);
if (err) {
dev_err(dev, "failed to acquire reset: %d\n", err);
return err;
}
err = clk_prepare_enable(gr2d->clk);
if (err) {
dev_err(dev, "failed to enable clock: %d\n", err);
goto release_reset;
}
usleep_range(2000, 4000);
/* this is a reset array which deasserts both 2D MC and 2D itself */
err = reset_control_bulk_deassert(gr2d->nresets, gr2d->resets);
if (err) {
dev_err(dev, "failed to deassert reset: %d\n", err);
goto disable_clk;
}
return 0;
disable_clk:
clk_disable_unprepare(gr2d->clk);
release_reset:
reset_control_bulk_release(gr2d->nresets, gr2d->resets);
return err;
}
static const struct dev_pm_ops tegra_gr2d_pm = {
SET_RUNTIME_PM_OPS(gr2d_runtime_suspend, gr2d_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
};
struct platform_driver tegra_gr2d_driver = {
.driver = {
.name = "tegra-gr2d",
.of_match_table = gr2d_match,
.pm = &tegra_gr2d_pm,
},
.probe = gr2d_probe,
.remove = gr2d_remove,
};
+26
View File
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2013 NVIDIA Corporation
*/
#ifndef TEGRA_GR2D_H
#define TEGRA_GR2D_H
#define GR2D_UA_BASE_ADDR 0x1a
#define GR2D_VA_BASE_ADDR 0x1b
#define GR2D_PAT_BASE_ADDR 0x26
#define GR2D_DSTA_BASE_ADDR 0x2b
#define GR2D_DSTB_BASE_ADDR 0x2c
#define GR2D_DSTC_BASE_ADDR 0x2d
#define GR2D_SRCA_BASE_ADDR 0x31
#define GR2D_SRCB_BASE_ADDR 0x32
#define GR2D_PATBASE_ADDR 0x47
#define GR2D_SRC_BASE_ADDR_SB 0x48
#define GR2D_DSTA_BASE_ADDR_SB 0x49
#define GR2D_DSTB_BASE_ADDR_SB 0x4a
#define GR2D_UA_BASE_ADDR_SB 0x4b
#define GR2D_VA_BASE_ADDR_SB 0x4c
#define GR2D_NUM_REGS 0x4d
#endif
+615
View File
@@ -0,0 +1,615 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Avionic Design GmbH
* Copyright (C) 2013 NVIDIA Corporation
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/host1x.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <soc/tegra/common.h>
#include <soc/tegra/pmc.h>
#include "drm.h"
#include "gem.h"
#include "gr3d.h"
enum {
RST_MC,
RST_GR3D,
RST_MC2,
RST_GR3D2,
RST_GR3D_MAX,
};
struct gr3d_soc {
unsigned int version;
unsigned int num_clocks;
unsigned int num_resets;
};
struct gr3d {
struct tegra_drm_client client;
struct host1x_channel *channel;
const struct gr3d_soc *soc;
struct clk_bulk_data *clocks;
unsigned int nclocks;
struct reset_control_bulk_data resets[RST_GR3D_MAX];
unsigned int nresets;
struct tegra_pmc *pmc;
struct dev_pm_domain_list *pd_list;
DECLARE_BITMAP(addr_regs, GR3D_NUM_REGS);
};
static inline struct gr3d *to_gr3d(struct tegra_drm_client *client)
{
return container_of(client, struct gr3d, client);
}
static int gr3d_init(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
unsigned long flags = HOST1X_SYNCPT_HAS_BASE;
struct gr3d *gr3d = to_gr3d(drm);
int err;
gr3d->channel = host1x_channel_request(client);
if (!gr3d->channel)
return -ENOMEM;
client->syncpts[0] = host1x_syncpt_request(client, flags);
if (!client->syncpts[0]) {
err = -ENOMEM;
dev_err(client->dev, "failed to request syncpoint: %d\n", err);
goto put;
}
err = host1x_client_iommu_attach(client);
if (err < 0) {
dev_err(client->dev, "failed to attach to domain: %d\n", err);
goto free;
}
err = tegra_drm_register_client(dev->dev_private, drm);
if (err < 0) {
dev_err(client->dev, "failed to register client: %d\n", err);
goto detach_iommu;
}
return 0;
detach_iommu:
host1x_client_iommu_detach(client);
free:
host1x_syncpt_put(client->syncpts[0]);
put:
host1x_channel_put(gr3d->channel);
return err;
}
static int gr3d_exit(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
struct gr3d *gr3d = to_gr3d(drm);
int err;
err = tegra_drm_unregister_client(dev->dev_private, drm);
if (err < 0)
return err;
host1x_client_iommu_detach(client);
host1x_syncpt_put(client->syncpts[0]);
host1x_channel_put(gr3d->channel);
gr3d->channel = NULL;
return 0;
}
static const struct host1x_client_ops gr3d_client_ops = {
.init = gr3d_init,
.exit = gr3d_exit,
};
static int gr3d_open_channel(struct tegra_drm_client *client,
struct tegra_drm_context *context)
{
struct gr3d *gr3d = to_gr3d(client);
context->channel = host1x_channel_get(gr3d->channel);
if (!context->channel)
return -ENOMEM;
return 0;
}
static void gr3d_close_channel(struct tegra_drm_context *context)
{
host1x_channel_put(context->channel);
}
static int gr3d_is_addr_reg(struct device *dev, u32 class, u32 offset)
{
struct gr3d *gr3d = dev_get_drvdata(dev);
switch (class) {
case HOST1X_CLASS_HOST1X:
if (offset == 0x2b)
return 1;
break;
case HOST1X_CLASS_GR3D:
if (offset >= GR3D_NUM_REGS)
break;
if (test_bit(offset, gr3d->addr_regs))
return 1;
break;
}
return 0;
}
static const struct tegra_drm_client_ops gr3d_ops = {
.open_channel = gr3d_open_channel,
.close_channel = gr3d_close_channel,
.is_addr_reg = gr3d_is_addr_reg,
.submit = tegra_drm_submit,
};
static const struct gr3d_soc tegra20_gr3d_soc = {
.version = 0x20,
.num_clocks = 1,
.num_resets = 2,
};
static const struct gr3d_soc tegra30_gr3d_soc = {
.version = 0x30,
.num_clocks = 2,
.num_resets = 4,
};
static const struct gr3d_soc tegra114_gr3d_soc = {
.version = 0x35,
.num_clocks = 1,
.num_resets = 2,
};
static const struct of_device_id tegra_gr3d_match[] = {
{ .compatible = "nvidia,tegra114-gr3d", .data = &tegra114_gr3d_soc },
{ .compatible = "nvidia,tegra30-gr3d", .data = &tegra30_gr3d_soc },
{ .compatible = "nvidia,tegra20-gr3d", .data = &tegra20_gr3d_soc },
{ }
};
MODULE_DEVICE_TABLE(of, tegra_gr3d_match);
static const u32 gr3d_addr_regs[] = {
GR3D_IDX_ATTRIBUTE( 0),
GR3D_IDX_ATTRIBUTE( 1),
GR3D_IDX_ATTRIBUTE( 2),
GR3D_IDX_ATTRIBUTE( 3),
GR3D_IDX_ATTRIBUTE( 4),
GR3D_IDX_ATTRIBUTE( 5),
GR3D_IDX_ATTRIBUTE( 6),
GR3D_IDX_ATTRIBUTE( 7),
GR3D_IDX_ATTRIBUTE( 8),
GR3D_IDX_ATTRIBUTE( 9),
GR3D_IDX_ATTRIBUTE(10),
GR3D_IDX_ATTRIBUTE(11),
GR3D_IDX_ATTRIBUTE(12),
GR3D_IDX_ATTRIBUTE(13),
GR3D_IDX_ATTRIBUTE(14),
GR3D_IDX_ATTRIBUTE(15),
GR3D_IDX_INDEX_BASE,
GR3D_QR_ZTAG_ADDR,
GR3D_QR_CTAG_ADDR,
GR3D_QR_CZ_ADDR,
GR3D_TEX_TEX_ADDR( 0),
GR3D_TEX_TEX_ADDR( 1),
GR3D_TEX_TEX_ADDR( 2),
GR3D_TEX_TEX_ADDR( 3),
GR3D_TEX_TEX_ADDR( 4),
GR3D_TEX_TEX_ADDR( 5),
GR3D_TEX_TEX_ADDR( 6),
GR3D_TEX_TEX_ADDR( 7),
GR3D_TEX_TEX_ADDR( 8),
GR3D_TEX_TEX_ADDR( 9),
GR3D_TEX_TEX_ADDR(10),
GR3D_TEX_TEX_ADDR(11),
GR3D_TEX_TEX_ADDR(12),
GR3D_TEX_TEX_ADDR(13),
GR3D_TEX_TEX_ADDR(14),
GR3D_TEX_TEX_ADDR(15),
GR3D_DW_MEMORY_OUTPUT_ADDRESS,
GR3D_GLOBAL_SURFADDR( 0),
GR3D_GLOBAL_SURFADDR( 1),
GR3D_GLOBAL_SURFADDR( 2),
GR3D_GLOBAL_SURFADDR( 3),
GR3D_GLOBAL_SURFADDR( 4),
GR3D_GLOBAL_SURFADDR( 5),
GR3D_GLOBAL_SURFADDR( 6),
GR3D_GLOBAL_SURFADDR( 7),
GR3D_GLOBAL_SURFADDR( 8),
GR3D_GLOBAL_SURFADDR( 9),
GR3D_GLOBAL_SURFADDR(10),
GR3D_GLOBAL_SURFADDR(11),
GR3D_GLOBAL_SURFADDR(12),
GR3D_GLOBAL_SURFADDR(13),
GR3D_GLOBAL_SURFADDR(14),
GR3D_GLOBAL_SURFADDR(15),
GR3D_GLOBAL_SPILLSURFADDR,
GR3D_GLOBAL_SURFOVERADDR( 0),
GR3D_GLOBAL_SURFOVERADDR( 1),
GR3D_GLOBAL_SURFOVERADDR( 2),
GR3D_GLOBAL_SURFOVERADDR( 3),
GR3D_GLOBAL_SURFOVERADDR( 4),
GR3D_GLOBAL_SURFOVERADDR( 5),
GR3D_GLOBAL_SURFOVERADDR( 6),
GR3D_GLOBAL_SURFOVERADDR( 7),
GR3D_GLOBAL_SURFOVERADDR( 8),
GR3D_GLOBAL_SURFOVERADDR( 9),
GR3D_GLOBAL_SURFOVERADDR(10),
GR3D_GLOBAL_SURFOVERADDR(11),
GR3D_GLOBAL_SURFOVERADDR(12),
GR3D_GLOBAL_SURFOVERADDR(13),
GR3D_GLOBAL_SURFOVERADDR(14),
GR3D_GLOBAL_SURFOVERADDR(15),
GR3D_GLOBAL_SAMP01SURFADDR( 0),
GR3D_GLOBAL_SAMP01SURFADDR( 1),
GR3D_GLOBAL_SAMP01SURFADDR( 2),
GR3D_GLOBAL_SAMP01SURFADDR( 3),
GR3D_GLOBAL_SAMP01SURFADDR( 4),
GR3D_GLOBAL_SAMP01SURFADDR( 5),
GR3D_GLOBAL_SAMP01SURFADDR( 6),
GR3D_GLOBAL_SAMP01SURFADDR( 7),
GR3D_GLOBAL_SAMP01SURFADDR( 8),
GR3D_GLOBAL_SAMP01SURFADDR( 9),
GR3D_GLOBAL_SAMP01SURFADDR(10),
GR3D_GLOBAL_SAMP01SURFADDR(11),
GR3D_GLOBAL_SAMP01SURFADDR(12),
GR3D_GLOBAL_SAMP01SURFADDR(13),
GR3D_GLOBAL_SAMP01SURFADDR(14),
GR3D_GLOBAL_SAMP01SURFADDR(15),
GR3D_GLOBAL_SAMP23SURFADDR( 0),
GR3D_GLOBAL_SAMP23SURFADDR( 1),
GR3D_GLOBAL_SAMP23SURFADDR( 2),
GR3D_GLOBAL_SAMP23SURFADDR( 3),
GR3D_GLOBAL_SAMP23SURFADDR( 4),
GR3D_GLOBAL_SAMP23SURFADDR( 5),
GR3D_GLOBAL_SAMP23SURFADDR( 6),
GR3D_GLOBAL_SAMP23SURFADDR( 7),
GR3D_GLOBAL_SAMP23SURFADDR( 8),
GR3D_GLOBAL_SAMP23SURFADDR( 9),
GR3D_GLOBAL_SAMP23SURFADDR(10),
GR3D_GLOBAL_SAMP23SURFADDR(11),
GR3D_GLOBAL_SAMP23SURFADDR(12),
GR3D_GLOBAL_SAMP23SURFADDR(13),
GR3D_GLOBAL_SAMP23SURFADDR(14),
GR3D_GLOBAL_SAMP23SURFADDR(15),
};
static int gr3d_power_up_legacy_domain(struct device *dev, const char *name,
unsigned int id)
{
struct gr3d *gr3d = dev_get_drvdata(dev);
struct reset_control *reset;
struct clk *clk;
unsigned int i;
int err;
/*
* Tegra20 device-tree doesn't specify 3d clock name and there is only
* one clock for Tegra20. Tegra30+ device-trees always specified names
* for the clocks.
*/
if (gr3d->nclocks == 1) {
if (id == TEGRA_POWERGATE_3D1)
return 0;
clk = gr3d->clocks[0].clk;
} else {
for (i = 0; i < gr3d->nclocks; i++) {
if (WARN_ON(!gr3d->clocks[i].id))
continue;
if (!strcmp(gr3d->clocks[i].id, name)) {
clk = gr3d->clocks[i].clk;
break;
}
}
if (WARN_ON(i == gr3d->nclocks))
return -EINVAL;
}
/*
* We use array of resets, which includes MC resets, and MC
* reset shouldn't be asserted while hardware is gated because
* MC flushing will fail for gated hardware. Hence for legacy
* PD we request the individual reset separately.
*/
reset = reset_control_get_exclusive_released(dev, name);
if (IS_ERR(reset))
return PTR_ERR(reset);
err = reset_control_acquire(reset);
if (err) {
dev_err(dev, "failed to acquire %s reset: %d\n", name, err);
} else {
err = tegra_pmc_powergate_sequence_power_up(gr3d->pmc, id,
clk, reset);
reset_control_release(reset);
}
reset_control_put(reset);
if (err)
return err;
/*
* tegra_powergate_sequence_power_up() leaves clocks enabled,
* while GENPD not. Hence keep clock-enable balanced.
*/
clk_disable_unprepare(clk);
return 0;
}
static int gr3d_init_power(struct device *dev, struct gr3d *gr3d)
{
struct dev_pm_domain_attach_data pd_data = {
.pd_names = (const char *[]) { "3d0", "3d1" },
.num_pd_names = 2,
.pd_flags = PD_FLAG_REQUIRED_OPP,
};
int err;
err = of_count_phandle_with_args(dev->of_node, "power-domains",
"#power-domain-cells");
if (err < 0) {
if (err != -ENOENT)
return err;
gr3d->pmc = devm_tegra_pmc_get(dev);
if (IS_ERR(gr3d->pmc))
return dev_err_probe(dev, PTR_ERR(gr3d->pmc),
"failed to get PMC\n");
/*
* Older device-trees don't use GENPD. In this case we should
* toggle power domain manually.
*/
err = gr3d_power_up_legacy_domain(dev, "3d",
TEGRA_POWERGATE_3D);
if (err)
return err;
err = gr3d_power_up_legacy_domain(dev, "3d2",
TEGRA_POWERGATE_3D1);
if (err)
return err;
return 0;
}
/*
* The PM domain core automatically attaches a single power domain,
* otherwise it skips attaching completely. We have a single domain
* on Tegra20 and two domains on Tegra30+.
*/
if (dev->pm_domain)
return 0;
err = devm_pm_domain_attach_list(dev, &pd_data, &gr3d->pd_list);
if (err < 0)
return err;
return 0;
}
static int gr3d_get_clocks(struct device *dev, struct gr3d *gr3d)
{
int err;
err = devm_clk_bulk_get_all(dev, &gr3d->clocks);
if (err < 0) {
dev_err(dev, "failed to get clock: %d\n", err);
return err;
}
gr3d->nclocks = err;
if (gr3d->nclocks != gr3d->soc->num_clocks) {
dev_err(dev, "invalid number of clocks: %u\n", gr3d->nclocks);
return -ENOENT;
}
return 0;
}
static int gr3d_get_resets(struct device *dev, struct gr3d *gr3d)
{
int err;
gr3d->resets[RST_MC].id = "mc";
gr3d->resets[RST_MC2].id = "mc2";
gr3d->resets[RST_GR3D].id = "3d";
gr3d->resets[RST_GR3D2].id = "3d2";
gr3d->nresets = gr3d->soc->num_resets;
err = devm_reset_control_bulk_get_optional_exclusive_released(
dev, gr3d->nresets, gr3d->resets);
if (err) {
dev_err(dev, "failed to get reset: %d\n", err);
return err;
}
if (WARN_ON(!gr3d->resets[RST_GR3D].rstc) ||
WARN_ON(!gr3d->resets[RST_GR3D2].rstc && gr3d->nresets == 4))
return -ENOENT;
return 0;
}
static int gr3d_probe(struct platform_device *pdev)
{
struct host1x_syncpt **syncpts;
struct gr3d *gr3d;
unsigned int i;
int err;
gr3d = devm_kzalloc(&pdev->dev, sizeof(*gr3d), GFP_KERNEL);
if (!gr3d)
return -ENOMEM;
platform_set_drvdata(pdev, gr3d);
gr3d->soc = of_device_get_match_data(&pdev->dev);
syncpts = devm_kzalloc(&pdev->dev, sizeof(*syncpts), GFP_KERNEL);
if (!syncpts)
return -ENOMEM;
err = gr3d_get_clocks(&pdev->dev, gr3d);
if (err)
return err;
err = gr3d_get_resets(&pdev->dev, gr3d);
if (err)
return err;
err = gr3d_init_power(&pdev->dev, gr3d);
if (err)
return err;
INIT_LIST_HEAD(&gr3d->client.base.list);
gr3d->client.base.ops = &gr3d_client_ops;
gr3d->client.base.dev = &pdev->dev;
gr3d->client.base.class = HOST1X_CLASS_GR3D;
gr3d->client.base.syncpts = syncpts;
gr3d->client.base.num_syncpts = 1;
INIT_LIST_HEAD(&gr3d->client.list);
gr3d->client.version = gr3d->soc->version;
gr3d->client.ops = &gr3d_ops;
err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
if (err)
return err;
/* initialize address register map */
for (i = 0; i < ARRAY_SIZE(gr3d_addr_regs); i++)
set_bit(gr3d_addr_regs[i], gr3d->addr_regs);
pm_runtime_enable(&pdev->dev);
err = host1x_client_register(&gr3d->client.base);
if (err < 0) {
pm_runtime_disable(&pdev->dev);
dev_err(&pdev->dev, "failed to register host1x client: %d\n",
err);
return err;
}
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 500);
return 0;
}
static void gr3d_remove(struct platform_device *pdev)
{
struct gr3d *gr3d = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
host1x_client_unregister(&gr3d->client.base);
}
static int __maybe_unused gr3d_runtime_suspend(struct device *dev)
{
struct gr3d *gr3d = dev_get_drvdata(dev);
int err;
host1x_channel_stop(gr3d->channel);
err = reset_control_bulk_assert(gr3d->nresets, gr3d->resets);
if (err) {
dev_err(dev, "failed to assert reset: %d\n", err);
return err;
}
usleep_range(10, 20);
/*
* Older device-trees don't specify MC resets and power-gating can't
* be done safely in that case. Hence we will keep the power ungated
* for older DTBs. For newer DTBs, GENPD will perform the power-gating.
*/
clk_bulk_disable_unprepare(gr3d->nclocks, gr3d->clocks);
reset_control_bulk_release(gr3d->nresets, gr3d->resets);
return 0;
}
static int __maybe_unused gr3d_runtime_resume(struct device *dev)
{
struct gr3d *gr3d = dev_get_drvdata(dev);
int err;
err = reset_control_bulk_acquire(gr3d->nresets, gr3d->resets);
if (err) {
dev_err(dev, "failed to acquire reset: %d\n", err);
return err;
}
err = clk_bulk_prepare_enable(gr3d->nclocks, gr3d->clocks);
if (err) {
dev_err(dev, "failed to enable clock: %d\n", err);
goto release_reset;
}
err = reset_control_bulk_deassert(gr3d->nresets, gr3d->resets);
if (err) {
dev_err(dev, "failed to deassert reset: %d\n", err);
goto disable_clk;
}
return 0;
disable_clk:
clk_bulk_disable_unprepare(gr3d->nclocks, gr3d->clocks);
release_reset:
reset_control_bulk_release(gr3d->nresets, gr3d->resets);
return err;
}
static const struct dev_pm_ops tegra_gr3d_pm = {
SET_RUNTIME_PM_OPS(gr3d_runtime_suspend, gr3d_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
};
struct platform_driver tegra_gr3d_driver = {
.driver = {
.name = "tegra-gr3d",
.of_match_table = tegra_gr3d_match,
.pm = &tegra_gr3d_pm,
},
.probe = gr3d_probe,
.remove = gr3d_remove,
};
+24
View File
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2013 NVIDIA Corporation
*/
#ifndef TEGRA_GR3D_H
#define TEGRA_GR3D_H
#define GR3D_IDX_ATTRIBUTE(x) (0x100 + (x) * 2)
#define GR3D_IDX_INDEX_BASE 0x121
#define GR3D_QR_ZTAG_ADDR 0x415
#define GR3D_QR_CTAG_ADDR 0x417
#define GR3D_QR_CZ_ADDR 0x419
#define GR3D_TEX_TEX_ADDR(x) (0x710 + (x))
#define GR3D_DW_MEMORY_OUTPUT_ADDRESS 0x904
#define GR3D_GLOBAL_SURFADDR(x) (0xe00 + (x))
#define GR3D_GLOBAL_SPILLSURFADDR 0xe2a
#define GR3D_GLOBAL_SURFOVERADDR(x) (0xe30 + (x))
#define GR3D_GLOBAL_SAMP01SURFADDR(x) (0xe50 + (x))
#define GR3D_GLOBAL_SAMP23SURFADDR(x) (0xe60 + (x))
#define GR3D_NUM_REGS 0xe88
#endif
+63
View File
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2019 NVIDIA Corporation
*/
#include <linux/bug.h>
#include <sound/hda_verbs.h>
#include "hda.h"
void tegra_hda_parse_format(unsigned int format, struct tegra_hda_format *fmt)
{
unsigned int mul, div, bits, channels;
if (format & AC_FMT_TYPE_NON_PCM)
fmt->pcm = false;
else
fmt->pcm = true;
if (format & AC_FMT_BASE_44K)
fmt->sample_rate = 44100;
else
fmt->sample_rate = 48000;
mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT;
div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT;
fmt->sample_rate *= (mul + 1) / (div + 1);
switch (format & AC_FMT_BITS_MASK) {
case AC_FMT_BITS_8:
fmt->bits = 8;
break;
case AC_FMT_BITS_16:
fmt->bits = 16;
break;
case AC_FMT_BITS_20:
fmt->bits = 20;
break;
case AC_FMT_BITS_24:
fmt->bits = 24;
break;
case AC_FMT_BITS_32:
fmt->bits = 32;
break;
default:
bits = (format & AC_FMT_BITS_MASK) >> AC_FMT_BITS_SHIFT;
WARN(1, "invalid number of bits: %#x\n", bits);
fmt->bits = 8;
break;
}
channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT;
/* channels are encoded as n - 1 */
fmt->channels = channels + 1;
}
+20
View File
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2019 NVIDIA Corporation
*/
#ifndef DRM_TEGRA_HDA_H
#define DRM_TEGRA_HDA_H 1
#include <linux/types.h>
struct tegra_hda_format {
unsigned int sample_rate;
unsigned int channels;
unsigned int bits;
bool pcm;
};
void tegra_hda_parse_format(unsigned int format, struct tegra_hda_format *fmt);
#endif
File diff suppressed because it is too large Load Diff
+557
View File
@@ -0,0 +1,557 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2012 Avionic Design GmbH
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
*/
#ifndef TEGRA_HDMI_H
#define TEGRA_HDMI_H 1
/* register definitions */
#define HDMI_CTXSW 0x00
#define HDMI_NV_PDISP_SOR_STATE0 0x01
#define SOR_STATE_UPDATE (1 << 0)
#define HDMI_NV_PDISP_SOR_STATE1 0x02
#define SOR_STATE_ASY_HEAD_OPMODE_AWAKE (2 << 0)
#define SOR_STATE_ASY_ORMODE_NORMAL (1 << 2)
#define SOR_STATE_ATTACHED (1 << 3)
#define HDMI_NV_PDISP_SOR_STATE2 0x03
#define SOR_STATE_ASY_OWNER_NONE (0 << 0)
#define SOR_STATE_ASY_OWNER_HEAD0 (1 << 0)
#define SOR_STATE_ASY_SUBOWNER_NONE (0 << 4)
#define SOR_STATE_ASY_SUBOWNER_SUBHEAD0 (1 << 4)
#define SOR_STATE_ASY_SUBOWNER_SUBHEAD1 (2 << 4)
#define SOR_STATE_ASY_SUBOWNER_BOTH (3 << 4)
#define SOR_STATE_ASY_CRCMODE_ACTIVE (0 << 6)
#define SOR_STATE_ASY_CRCMODE_COMPLETE (1 << 6)
#define SOR_STATE_ASY_CRCMODE_NON_ACTIVE (2 << 6)
#define SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A (1 << 8)
#define SOR_STATE_ASY_PROTOCOL_CUSTOM (15 << 8)
#define SOR_STATE_ASY_HSYNCPOL_POS (0 << 12)
#define SOR_STATE_ASY_HSYNCPOL_NEG (1 << 12)
#define SOR_STATE_ASY_VSYNCPOL_POS (0 << 13)
#define SOR_STATE_ASY_VSYNCPOL_NEG (1 << 13)
#define SOR_STATE_ASY_DEPOL_POS (0 << 14)
#define SOR_STATE_ASY_DEPOL_NEG (1 << 14)
#define HDMI_NV_PDISP_RG_HDCP_AN_MSB 0x04
#define HDMI_NV_PDISP_RG_HDCP_AN_LSB 0x05
#define HDMI_NV_PDISP_RG_HDCP_CN_MSB 0x06
#define HDMI_NV_PDISP_RG_HDCP_CN_LSB 0x07
#define HDMI_NV_PDISP_RG_HDCP_AKSV_MSB 0x08
#define HDMI_NV_PDISP_RG_HDCP_AKSV_LSB 0x09
#define HDMI_NV_PDISP_RG_HDCP_BKSV_MSB 0x0a
#define HDMI_NV_PDISP_RG_HDCP_BKSV_LSB 0x0b
#define HDMI_NV_PDISP_RG_HDCP_CKSV_MSB 0x0c
#define HDMI_NV_PDISP_RG_HDCP_CKSV_LSB 0x0d
#define HDMI_NV_PDISP_RG_HDCP_DKSV_MSB 0x0e
#define HDMI_NV_PDISP_RG_HDCP_DKSV_LSB 0x0f
#define HDMI_NV_PDISP_RG_HDCP_CTRL 0x10
#define HDMI_NV_PDISP_RG_HDCP_CMODE 0x11
#define HDMI_NV_PDISP_RG_HDCP_MPRIME_MSB 0x12
#define HDMI_NV_PDISP_RG_HDCP_MPRIME_LSB 0x13
#define HDMI_NV_PDISP_RG_HDCP_SPRIME_MSB 0x14
#define HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB2 0x15
#define HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB1 0x16
#define HDMI_NV_PDISP_RG_HDCP_RI 0x17
#define HDMI_NV_PDISP_RG_HDCP_CS_MSB 0x18
#define HDMI_NV_PDISP_RG_HDCP_CS_LSB 0x19
#define HDMI_NV_PDISP_HDMI_AUDIO_EMU0 0x1a
#define HDMI_NV_PDISP_HDMI_AUDIO_EMU_RDATA0 0x1b
#define HDMI_NV_PDISP_HDMI_AUDIO_EMU1 0x1c
#define HDMI_NV_PDISP_HDMI_AUDIO_EMU2 0x1d
#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL 0x1e
#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_STATUS 0x1f
#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_HEADER 0x20
#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_LOW 0x21
#define HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_SUBPACK0_HIGH 0x22
#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL 0x23
#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_STATUS 0x24
#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_HEADER 0x25
#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_LOW 0x26
#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH 0x27
#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_LOW 0x28
#define HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_SUBPACK1_HIGH 0x29
#define INFOFRAME_CTRL_ENABLE (1 << 0)
#define INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0)
#define INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8)
#define INFOFRAME_HEADER_LEN(x) (((x) & 0x0f) << 16)
#define HDMI_NV_PDISP_HDMI_GENERIC_CTRL 0x2a
#define GENERIC_CTRL_ENABLE (1 << 0)
#define GENERIC_CTRL_OTHER (1 << 4)
#define GENERIC_CTRL_SINGLE (1 << 8)
#define GENERIC_CTRL_HBLANK (1 << 12)
#define GENERIC_CTRL_AUDIO (1 << 16)
#define HDMI_NV_PDISP_HDMI_GENERIC_STATUS 0x2b
#define HDMI_NV_PDISP_HDMI_GENERIC_HEADER 0x2c
#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_LOW 0x2d
#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK0_HIGH 0x2e
#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_LOW 0x2f
#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK1_HIGH 0x30
#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_LOW 0x31
#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK2_HIGH 0x32
#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_LOW 0x33
#define HDMI_NV_PDISP_HDMI_GENERIC_SUBPACK3_HIGH 0x34
#define HDMI_NV_PDISP_HDMI_ACR_CTRL 0x35
#define HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_LOW 0x36
#define HDMI_NV_PDISP_HDMI_ACR_0320_SUBPACK_HIGH 0x37
#define HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW 0x38
#define HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH 0x39
#define HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_LOW 0x3a
#define HDMI_NV_PDISP_HDMI_ACR_0882_SUBPACK_HIGH 0x3b
#define HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_LOW 0x3c
#define HDMI_NV_PDISP_HDMI_ACR_1764_SUBPACK_HIGH 0x3d
#define HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_LOW 0x3e
#define HDMI_NV_PDISP_HDMI_ACR_0480_SUBPACK_HIGH 0x3f
#define HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_LOW 0x40
#define HDMI_NV_PDISP_HDMI_ACR_0960_SUBPACK_HIGH 0x41
#define HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_LOW 0x42
#define HDMI_NV_PDISP_HDMI_ACR_1920_SUBPACK_HIGH 0x43
#define ACR_SUBPACK_CTS(x) (((x) & 0xffffff) << 8)
#define ACR_SUBPACK_N(x) (((x) & 0xffffff) << 0)
#define ACR_ENABLE (1 << 31)
#define HDMI_NV_PDISP_HDMI_CTRL 0x44
#define HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0)
#define HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16)
#define HDMI_CTRL_ENABLE (1 << 30)
#define HDMI_NV_PDISP_HDMI_VSYNC_KEEPOUT 0x45
#define HDMI_NV_PDISP_HDMI_VSYNC_WINDOW 0x46
#define VSYNC_WINDOW_END(x) (((x) & 0x3ff) << 0)
#define VSYNC_WINDOW_START(x) (((x) & 0x3ff) << 16)
#define VSYNC_WINDOW_ENABLE (1 << 31)
#define HDMI_NV_PDISP_HDMI_GCP_CTRL 0x47
#define HDMI_NV_PDISP_HDMI_GCP_STATUS 0x48
#define HDMI_NV_PDISP_HDMI_GCP_SUBPACK 0x49
#define HDMI_NV_PDISP_HDMI_CHANNEL_STATUS1 0x4a
#define HDMI_NV_PDISP_HDMI_CHANNEL_STATUS2 0x4b
#define HDMI_NV_PDISP_HDMI_EMU0 0x4c
#define HDMI_NV_PDISP_HDMI_EMU1 0x4d
#define HDMI_NV_PDISP_HDMI_EMU1_RDATA 0x4e
#define HDMI_NV_PDISP_HDMI_SPARE 0x4f
#define SPARE_HW_CTS (1 << 0)
#define SPARE_FORCE_SW_CTS (1 << 1)
#define SPARE_CTS_RESET_VAL(x) (((x) & 0x7) << 16)
#define HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS1 0x50
#define HDMI_NV_PDISP_HDMI_SPDIF_CHN_STATUS2 0x51
#define HDMI_NV_PDISP_HDMI_HDCPRIF_ROM_CTRL 0x53
#define HDMI_NV_PDISP_SOR_CAP 0x54
#define HDMI_NV_PDISP_SOR_PWR 0x55
#define SOR_PWR_NORMAL_STATE_PD (0 << 0)
#define SOR_PWR_NORMAL_STATE_PU (1 << 0)
#define SOR_PWR_NORMAL_START_NORMAL (0 << 1)
#define SOR_PWR_NORMAL_START_ALT (1 << 1)
#define SOR_PWR_SAFE_STATE_PD (0 << 16)
#define SOR_PWR_SAFE_STATE_PU (1 << 16)
#define SOR_PWR_SETTING_NEW_DONE (0 << 31)
#define SOR_PWR_SETTING_NEW_PENDING (1 << 31)
#define SOR_PWR_SETTING_NEW_TRIGGER (1 << 31)
#define HDMI_NV_PDISP_SOR_TEST 0x56
#define HDMI_NV_PDISP_SOR_PLL0 0x57
#define SOR_PLL_PWR (1 << 0)
#define SOR_PLL_PDBG (1 << 1)
#define SOR_PLL_VCAPD (1 << 2)
#define SOR_PLL_PDPORT (1 << 3)
#define SOR_PLL_RESISTORSEL (1 << 4)
#define SOR_PLL_PULLDOWN (1 << 5)
#define SOR_PLL_VCOCAP(x) (((x) & 0xf) << 8)
#define SOR_PLL_BG_V17_S(x) (((x) & 0xf) << 12)
#define SOR_PLL_FILTER(x) (((x) & 0xf) << 16)
#define SOR_PLL_ICHPMP(x) (((x) & 0xf) << 24)
#define SOR_PLL_TX_REG_LOAD(x) (((x) & 0xf) << 28)
#define HDMI_NV_PDISP_SOR_PLL1 0x58
#define SOR_PLL_TMDS_TERM_ENABLE (1 << 8)
#define SOR_PLL_TMDS_TERMADJ(x) (((x) & 0xf) << 9)
#define SOR_PLL_LOADADJ(x) (((x) & 0xf) << 20)
#define SOR_PLL_PE_EN (1 << 28)
#define SOR_PLL_HALF_FULL_PE (1 << 29)
#define SOR_PLL_S_D_PIN_PE (1 << 30)
#define HDMI_NV_PDISP_SOR_PLL2 0x59
#define HDMI_NV_PDISP_SOR_CSTM 0x5a
#define SOR_CSTM_ROTCLK(x) (((x) & 0xf) << 24)
#define SOR_CSTM_PLLDIV (1 << 21)
#define SOR_CSTM_LVDS_ENABLE (1 << 16)
#define SOR_CSTM_MODE_LVDS (0 << 12)
#define SOR_CSTM_MODE_TMDS (1 << 12)
#define SOR_CSTM_MODE_MASK (3 << 12)
#define HDMI_NV_PDISP_SOR_LVDS 0x5b
#define HDMI_NV_PDISP_SOR_CRCA 0x5c
#define HDMI_NV_PDISP_SOR_CRCB 0x5d
#define HDMI_NV_PDISP_SOR_BLANK 0x5e
#define HDMI_NV_PDISP_SOR_SEQ_CTL 0x5f
#define SOR_SEQ_PU_PC(x) (((x) & 0xf) << 0)
#define SOR_SEQ_PU_PC_ALT(x) (((x) & 0xf) << 4)
#define SOR_SEQ_PD_PC(x) (((x) & 0xf) << 8)
#define SOR_SEQ_PD_PC_ALT(x) (((x) & 0xf) << 12)
#define SOR_SEQ_PC(x) (((x) & 0xf) << 16)
#define SOR_SEQ_STATUS (1 << 28)
#define SOR_SEQ_SWITCH (1 << 30)
#define HDMI_NV_PDISP_SOR_SEQ_INST(x) (0x60 + (x))
#define SOR_SEQ_INST_WAIT_TIME(x) (((x) & 0x3ff) << 0)
#define SOR_SEQ_INST_WAIT_UNITS_VSYNC (2 << 12)
#define SOR_SEQ_INST_HALT (1 << 15)
#define SOR_SEQ_INST_PIN_A_LOW (0 << 21)
#define SOR_SEQ_INST_PIN_A_HIGH (1 << 21)
#define SOR_SEQ_INST_PIN_B_LOW (0 << 22)
#define SOR_SEQ_INST_PIN_B_HIGH (1 << 22)
#define SOR_SEQ_INST_DRIVE_PWM_OUT_LO (1 << 23)
#define HDMI_NV_PDISP_SOR_VCRCA0 0x72
#define HDMI_NV_PDISP_SOR_VCRCA1 0x73
#define HDMI_NV_PDISP_SOR_CCRCA0 0x74
#define HDMI_NV_PDISP_SOR_CCRCA1 0x75
#define HDMI_NV_PDISP_SOR_EDATAA0 0x76
#define HDMI_NV_PDISP_SOR_EDATAA1 0x77
#define HDMI_NV_PDISP_SOR_COUNTA0 0x78
#define HDMI_NV_PDISP_SOR_COUNTA1 0x79
#define HDMI_NV_PDISP_SOR_DEBUGA0 0x7a
#define HDMI_NV_PDISP_SOR_DEBUGA1 0x7b
#define HDMI_NV_PDISP_SOR_TRIG 0x7c
#define HDMI_NV_PDISP_SOR_MSCHECK 0x7d
#define HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT 0x7e
#define DRIVE_CURRENT_LANE0(x) (((x) & 0x3f) << 0)
#define DRIVE_CURRENT_LANE1(x) (((x) & 0x3f) << 8)
#define DRIVE_CURRENT_LANE2(x) (((x) & 0x3f) << 16)
#define DRIVE_CURRENT_LANE3(x) (((x) & 0x3f) << 24)
#define DRIVE_CURRENT_LANE0_T114(x) (((x) & 0x7f) << 0)
#define DRIVE_CURRENT_LANE1_T114(x) (((x) & 0x7f) << 8)
#define DRIVE_CURRENT_LANE2_T114(x) (((x) & 0x7f) << 16)
#define DRIVE_CURRENT_LANE3_T114(x) (((x) & 0x7f) << 24)
#define DRIVE_CURRENT_1_500_mA 0x00
#define DRIVE_CURRENT_1_875_mA 0x01
#define DRIVE_CURRENT_2_250_mA 0x02
#define DRIVE_CURRENT_2_625_mA 0x03
#define DRIVE_CURRENT_3_000_mA 0x04
#define DRIVE_CURRENT_3_375_mA 0x05
#define DRIVE_CURRENT_3_750_mA 0x06
#define DRIVE_CURRENT_4_125_mA 0x07
#define DRIVE_CURRENT_4_500_mA 0x08
#define DRIVE_CURRENT_4_875_mA 0x09
#define DRIVE_CURRENT_5_250_mA 0x0a
#define DRIVE_CURRENT_5_625_mA 0x0b
#define DRIVE_CURRENT_6_000_mA 0x0c
#define DRIVE_CURRENT_6_375_mA 0x0d
#define DRIVE_CURRENT_6_750_mA 0x0e
#define DRIVE_CURRENT_7_125_mA 0x0f
#define DRIVE_CURRENT_7_500_mA 0x10
#define DRIVE_CURRENT_7_875_mA 0x11
#define DRIVE_CURRENT_8_250_mA 0x12
#define DRIVE_CURRENT_8_625_mA 0x13
#define DRIVE_CURRENT_9_000_mA 0x14
#define DRIVE_CURRENT_9_375_mA 0x15
#define DRIVE_CURRENT_9_750_mA 0x16
#define DRIVE_CURRENT_10_125_mA 0x17
#define DRIVE_CURRENT_10_500_mA 0x18
#define DRIVE_CURRENT_10_875_mA 0x19
#define DRIVE_CURRENT_11_250_mA 0x1a
#define DRIVE_CURRENT_11_625_mA 0x1b
#define DRIVE_CURRENT_12_000_mA 0x1c
#define DRIVE_CURRENT_12_375_mA 0x1d
#define DRIVE_CURRENT_12_750_mA 0x1e
#define DRIVE_CURRENT_13_125_mA 0x1f
#define DRIVE_CURRENT_13_500_mA 0x20
#define DRIVE_CURRENT_13_875_mA 0x21
#define DRIVE_CURRENT_14_250_mA 0x22
#define DRIVE_CURRENT_14_625_mA 0x23
#define DRIVE_CURRENT_15_000_mA 0x24
#define DRIVE_CURRENT_15_375_mA 0x25
#define DRIVE_CURRENT_15_750_mA 0x26
#define DRIVE_CURRENT_16_125_mA 0x27
#define DRIVE_CURRENT_16_500_mA 0x28
#define DRIVE_CURRENT_16_875_mA 0x29
#define DRIVE_CURRENT_17_250_mA 0x2a
#define DRIVE_CURRENT_17_625_mA 0x2b
#define DRIVE_CURRENT_18_000_mA 0x2c
#define DRIVE_CURRENT_18_375_mA 0x2d
#define DRIVE_CURRENT_18_750_mA 0x2e
#define DRIVE_CURRENT_19_125_mA 0x2f
#define DRIVE_CURRENT_19_500_mA 0x30
#define DRIVE_CURRENT_19_875_mA 0x31
#define DRIVE_CURRENT_20_250_mA 0x32
#define DRIVE_CURRENT_20_625_mA 0x33
#define DRIVE_CURRENT_21_000_mA 0x34
#define DRIVE_CURRENT_21_375_mA 0x35
#define DRIVE_CURRENT_21_750_mA 0x36
#define DRIVE_CURRENT_22_125_mA 0x37
#define DRIVE_CURRENT_22_500_mA 0x38
#define DRIVE_CURRENT_22_875_mA 0x39
#define DRIVE_CURRENT_23_250_mA 0x3a
#define DRIVE_CURRENT_23_625_mA 0x3b
#define DRIVE_CURRENT_24_000_mA 0x3c
#define DRIVE_CURRENT_24_375_mA 0x3d
#define DRIVE_CURRENT_24_750_mA 0x3e
#define DRIVE_CURRENT_0_000_mA_T114 0x00
#define DRIVE_CURRENT_0_400_mA_T114 0x01
#define DRIVE_CURRENT_0_800_mA_T114 0x02
#define DRIVE_CURRENT_1_200_mA_T114 0x03
#define DRIVE_CURRENT_1_600_mA_T114 0x04
#define DRIVE_CURRENT_2_000_mA_T114 0x05
#define DRIVE_CURRENT_2_400_mA_T114 0x06
#define DRIVE_CURRENT_2_800_mA_T114 0x07
#define DRIVE_CURRENT_3_200_mA_T114 0x08
#define DRIVE_CURRENT_3_600_mA_T114 0x09
#define DRIVE_CURRENT_4_000_mA_T114 0x0a
#define DRIVE_CURRENT_4_400_mA_T114 0x0b
#define DRIVE_CURRENT_4_800_mA_T114 0x0c
#define DRIVE_CURRENT_5_200_mA_T114 0x0d
#define DRIVE_CURRENT_5_600_mA_T114 0x0e
#define DRIVE_CURRENT_6_000_mA_T114 0x0f
#define DRIVE_CURRENT_6_400_mA_T114 0x10
#define DRIVE_CURRENT_6_800_mA_T114 0x11
#define DRIVE_CURRENT_7_200_mA_T114 0x12
#define DRIVE_CURRENT_7_600_mA_T114 0x13
#define DRIVE_CURRENT_8_000_mA_T114 0x14
#define DRIVE_CURRENT_8_400_mA_T114 0x15
#define DRIVE_CURRENT_8_800_mA_T114 0x16
#define DRIVE_CURRENT_9_200_mA_T114 0x17
#define DRIVE_CURRENT_9_600_mA_T114 0x18
#define DRIVE_CURRENT_10_000_mA_T114 0x19
#define DRIVE_CURRENT_10_400_mA_T114 0x1a
#define DRIVE_CURRENT_10_800_mA_T114 0x1b
#define DRIVE_CURRENT_11_200_mA_T114 0x1c
#define DRIVE_CURRENT_11_600_mA_T114 0x1d
#define DRIVE_CURRENT_12_000_mA_T114 0x1e
#define DRIVE_CURRENT_12_400_mA_T114 0x1f
#define DRIVE_CURRENT_12_800_mA_T114 0x20
#define DRIVE_CURRENT_13_200_mA_T114 0x21
#define DRIVE_CURRENT_13_600_mA_T114 0x22
#define DRIVE_CURRENT_14_000_mA_T114 0x23
#define DRIVE_CURRENT_14_400_mA_T114 0x24
#define DRIVE_CURRENT_14_800_mA_T114 0x25
#define DRIVE_CURRENT_15_200_mA_T114 0x26
#define DRIVE_CURRENT_15_600_mA_T114 0x27
#define DRIVE_CURRENT_16_000_mA_T114 0x28
#define DRIVE_CURRENT_16_400_mA_T114 0x29
#define DRIVE_CURRENT_16_800_mA_T114 0x2a
#define DRIVE_CURRENT_17_200_mA_T114 0x2b
#define DRIVE_CURRENT_17_600_mA_T114 0x2c
#define DRIVE_CURRENT_18_000_mA_T114 0x2d
#define DRIVE_CURRENT_18_400_mA_T114 0x2e
#define DRIVE_CURRENT_18_800_mA_T114 0x2f
#define DRIVE_CURRENT_19_200_mA_T114 0x30
#define DRIVE_CURRENT_19_600_mA_T114 0x31
#define DRIVE_CURRENT_20_000_mA_T114 0x32
#define DRIVE_CURRENT_20_400_mA_T114 0x33
#define DRIVE_CURRENT_20_800_mA_T114 0x34
#define DRIVE_CURRENT_21_200_mA_T114 0x35
#define DRIVE_CURRENT_21_600_mA_T114 0x36
#define DRIVE_CURRENT_22_000_mA_T114 0x37
#define DRIVE_CURRENT_22_400_mA_T114 0x38
#define DRIVE_CURRENT_22_800_mA_T114 0x39
#define DRIVE_CURRENT_23_200_mA_T114 0x3a
#define DRIVE_CURRENT_23_600_mA_T114 0x3b
#define DRIVE_CURRENT_24_000_mA_T114 0x3c
#define DRIVE_CURRENT_24_400_mA_T114 0x3d
#define DRIVE_CURRENT_24_800_mA_T114 0x3e
#define DRIVE_CURRENT_25_200_mA_T114 0x3f
#define DRIVE_CURRENT_25_400_mA_T114 0x40
#define DRIVE_CURRENT_25_800_mA_T114 0x41
#define DRIVE_CURRENT_26_200_mA_T114 0x42
#define DRIVE_CURRENT_26_600_mA_T114 0x43
#define DRIVE_CURRENT_27_000_mA_T114 0x44
#define DRIVE_CURRENT_27_400_mA_T114 0x45
#define DRIVE_CURRENT_27_800_mA_T114 0x46
#define DRIVE_CURRENT_28_200_mA_T114 0x47
#define HDMI_NV_PDISP_AUDIO_DEBUG0 0x7f
#define HDMI_NV_PDISP_AUDIO_DEBUG1 0x80
#define HDMI_NV_PDISP_AUDIO_DEBUG2 0x81
#define HDMI_NV_PDISP_AUDIO_FS(x) (0x82 + (x))
#define AUDIO_FS_LOW(x) (((x) & 0xfff) << 0)
#define AUDIO_FS_HIGH(x) (((x) & 0xfff) << 16)
#define HDMI_NV_PDISP_AUDIO_PULSE_WIDTH 0x89
#define HDMI_NV_PDISP_AUDIO_THRESHOLD 0x8a
#define HDMI_NV_PDISP_AUDIO_CNTRL0 0x8b
#define AUDIO_CNTRL0_ERROR_TOLERANCE(x) (((x) & 0xff) << 0)
#define AUDIO_CNTRL0_SOURCE_SELECT_AUTO (0 << 20)
#define AUDIO_CNTRL0_SOURCE_SELECT_SPDIF (1 << 20)
#define AUDIO_CNTRL0_SOURCE_SELECT_HDAL (2 << 20)
#define AUDIO_CNTRL0_FRAMES_PER_BLOCK(x) (((x) & 0xff) << 24)
#define HDMI_NV_PDISP_AUDIO_N 0x8c
#define AUDIO_N_VALUE(x) (((x) & 0xfffff) << 0)
#define AUDIO_N_RESETF (1 << 20)
#define AUDIO_N_GENERATE_NORMAL (0 << 24)
#define AUDIO_N_GENERATE_ALTERNATE (1 << 24)
#define HDMI_NV_PDISP_HDCPRIF_ROM_TIMING 0x94
#define HDMI_NV_PDISP_SOR_REFCLK 0x95
#define SOR_REFCLK_DIV_INT(x) (((x) & 0xff) << 8)
#define SOR_REFCLK_DIV_FRAC(x) (((x) & 0x03) << 6)
#define HDMI_NV_PDISP_CRC_CONTROL 0x96
#define HDMI_NV_PDISP_INPUT_CONTROL 0x97
#define HDMI_SRC_DISPLAYA (0 << 0)
#define HDMI_SRC_DISPLAYB (1 << 0)
#define ARM_VIDEO_RANGE_FULL (0 << 1)
#define ARM_VIDEO_RANGE_LIMITED (1 << 1)
#define HDMI_NV_PDISP_SCRATCH 0x98
#define HDMI_NV_PDISP_PE_CURRENT 0x99
#define PE_CURRENT0(x) (((x) & 0xf) << 0)
#define PE_CURRENT1(x) (((x) & 0xf) << 8)
#define PE_CURRENT2(x) (((x) & 0xf) << 16)
#define PE_CURRENT3(x) (((x) & 0xf) << 24)
#define PE_CURRENT_0_0_mA 0x0
#define PE_CURRENT_0_5_mA 0x1
#define PE_CURRENT_1_0_mA 0x2
#define PE_CURRENT_1_5_mA 0x3
#define PE_CURRENT_2_0_mA 0x4
#define PE_CURRENT_2_5_mA 0x5
#define PE_CURRENT_3_0_mA 0x6
#define PE_CURRENT_3_5_mA 0x7
#define PE_CURRENT_4_0_mA 0x8
#define PE_CURRENT_4_5_mA 0x9
#define PE_CURRENT_5_0_mA 0xa
#define PE_CURRENT_5_5_mA 0xb
#define PE_CURRENT_6_0_mA 0xc
#define PE_CURRENT_6_5_mA 0xd
#define PE_CURRENT_7_0_mA 0xe
#define PE_CURRENT_7_5_mA 0xf
#define PE_CURRENT_0_mA_T114 0x0
#define PE_CURRENT_1_mA_T114 0x1
#define PE_CURRENT_2_mA_T114 0x2
#define PE_CURRENT_3_mA_T114 0x3
#define PE_CURRENT_4_mA_T114 0x4
#define PE_CURRENT_5_mA_T114 0x5
#define PE_CURRENT_6_mA_T114 0x6
#define PE_CURRENT_7_mA_T114 0x7
#define PE_CURRENT_8_mA_T114 0x8
#define PE_CURRENT_9_mA_T114 0x9
#define PE_CURRENT_10_mA_T114 0xa
#define PE_CURRENT_11_mA_T114 0xb
#define PE_CURRENT_12_mA_T114 0xc
#define PE_CURRENT_13_mA_T114 0xd
#define PE_CURRENT_14_mA_T114 0xe
#define PE_CURRENT_15_mA_T114 0xf
#define HDMI_NV_PDISP_KEY_CTRL 0x9a
#define HDMI_NV_PDISP_KEY_DEBUG0 0x9b
#define HDMI_NV_PDISP_KEY_DEBUG1 0x9c
#define HDMI_NV_PDISP_KEY_DEBUG2 0x9d
#define HDMI_NV_PDISP_KEY_HDCP_KEY_0 0x9e
#define HDMI_NV_PDISP_KEY_HDCP_KEY_1 0x9f
#define HDMI_NV_PDISP_KEY_HDCP_KEY_2 0xa0
#define HDMI_NV_PDISP_KEY_HDCP_KEY_3 0xa1
#define HDMI_NV_PDISP_KEY_HDCP_KEY_TRIG 0xa2
#define HDMI_NV_PDISP_KEY_SKEY_INDEX 0xa3
#define HDMI_NV_PDISP_SOR_AUDIO_CNTRL0 0xac
#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_AUTO (0 << 20)
#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_SPDIF (1 << 20)
#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_HDAL (2 << 20)
#define SOR_AUDIO_CNTRL0_INJECT_NULLSMPL (1 << 29)
#define HDMI_NV_PDISP_SOR_AUDIO_SPARE0 0xae
#define SOR_AUDIO_SPARE0_HBR_ENABLE (1 << 27)
#define HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0 0xba
#define SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID (1 << 30)
#define SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff
#define HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH1 0xbb
#define HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR 0xbc
#define HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE 0xbd
#define SOR_AUDIO_HDA_PRESENSE_VALID (1 << 1)
#define SOR_AUDIO_HDA_PRESENSE_PRESENT (1 << 0)
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320 0xbf
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441 0xc0
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882 0xc1
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764 0xc2
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480 0xc3
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960 0xc4
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920 0xc5
#define HDMI_NV_PDISP_SOR_AUDIO_AVAL_DEFAULT 0xc5
#define HDMI_NV_PDISP_INT_STATUS 0xcc
#define INT_SCRATCH (1 << 3)
#define INT_CP_REQUEST (1 << 2)
#define INT_CODEC_SCRATCH1 (1 << 1)
#define INT_CODEC_SCRATCH0 (1 << 0)
#define HDMI_NV_PDISP_INT_MASK 0xcd
#define HDMI_NV_PDISP_INT_ENABLE 0xce
#define HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT 0xd1
#define PEAK_CURRENT_LANE0(x) (((x) & 0x7f) << 0)
#define PEAK_CURRENT_LANE1(x) (((x) & 0x7f) << 8)
#define PEAK_CURRENT_LANE2(x) (((x) & 0x7f) << 16)
#define PEAK_CURRENT_LANE3(x) (((x) & 0x7f) << 24)
#define PEAK_CURRENT_0_000_mA 0x00
#define PEAK_CURRENT_0_200_mA 0x01
#define PEAK_CURRENT_0_400_mA 0x02
#define PEAK_CURRENT_0_600_mA 0x03
#define PEAK_CURRENT_0_800_mA 0x04
#define PEAK_CURRENT_1_000_mA 0x05
#define PEAK_CURRENT_1_200_mA 0x06
#define PEAK_CURRENT_1_400_mA 0x07
#define PEAK_CURRENT_1_600_mA 0x08
#define PEAK_CURRENT_1_800_mA 0x09
#define PEAK_CURRENT_2_000_mA 0x0a
#define PEAK_CURRENT_2_200_mA 0x0b
#define PEAK_CURRENT_2_400_mA 0x0c
#define PEAK_CURRENT_2_600_mA 0x0d
#define PEAK_CURRENT_2_800_mA 0x0e
#define PEAK_CURRENT_3_000_mA 0x0f
#define PEAK_CURRENT_3_200_mA 0x10
#define PEAK_CURRENT_3_400_mA 0x11
#define PEAK_CURRENT_3_600_mA 0x12
#define PEAK_CURRENT_3_800_mA 0x13
#define PEAK_CURRENT_4_000_mA 0x14
#define PEAK_CURRENT_4_200_mA 0x15
#define PEAK_CURRENT_4_400_mA 0x16
#define PEAK_CURRENT_4_600_mA 0x17
#define PEAK_CURRENT_4_800_mA 0x18
#define PEAK_CURRENT_5_000_mA 0x19
#define PEAK_CURRENT_5_200_mA 0x1a
#define PEAK_CURRENT_5_400_mA 0x1b
#define PEAK_CURRENT_5_600_mA 0x1c
#define PEAK_CURRENT_5_800_mA 0x1d
#define PEAK_CURRENT_6_000_mA 0x1e
#define PEAK_CURRENT_6_200_mA 0x1f
#define PEAK_CURRENT_6_400_mA 0x20
#define PEAK_CURRENT_6_600_mA 0x21
#define PEAK_CURRENT_6_800_mA 0x22
#define PEAK_CURRENT_7_000_mA 0x23
#define PEAK_CURRENT_7_200_mA 0x24
#define PEAK_CURRENT_7_400_mA 0x25
#define PEAK_CURRENT_7_600_mA 0x26
#define PEAK_CURRENT_7_800_mA 0x27
#define PEAK_CURRENT_8_000_mA 0x28
#define PEAK_CURRENT_8_200_mA 0x29
#define PEAK_CURRENT_8_400_mA 0x2a
#define PEAK_CURRENT_8_600_mA 0x2b
#define PEAK_CURRENT_8_800_mA 0x2c
#define PEAK_CURRENT_9_000_mA 0x2d
#define PEAK_CURRENT_9_200_mA 0x2e
#define PEAK_CURRENT_9_400_mA 0x2f
#define HDMI_NV_PDISP_SOR_PAD_CTLS0 0xd2
#endif /* TEGRA_HDMI_H */
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved.
*/
#ifndef TEGRA_HUB_H
#define TEGRA_HUB_H 1
#include <drm/drm_plane.h>
#include "plane.h"
struct tegra_dc;
struct tegra_windowgroup {
unsigned int usecount;
struct mutex lock;
unsigned int index;
struct host1x_client *parent;
struct reset_control *rst;
};
struct tegra_shared_plane {
struct tegra_plane base;
struct tegra_windowgroup *wgrp;
};
static inline struct tegra_shared_plane *
to_tegra_shared_plane(struct drm_plane *plane)
{
return container_of(plane, struct tegra_shared_plane, base.base);
}
struct tegra_display_hub_soc {
unsigned int num_wgrps;
bool supports_dsc;
};
struct tegra_display_hub {
struct drm_private_obj base;
struct host1x_client client;
struct clk *clk_disp;
struct clk *clk_dsc;
struct clk *clk_hub;
struct reset_control *rst;
unsigned int num_heads;
struct clk **clk_heads;
const struct tegra_display_hub_soc *soc;
struct tegra_windowgroup *wgrps;
};
static inline struct tegra_display_hub *
to_tegra_display_hub(struct host1x_client *client)
{
return container_of(client, struct tegra_display_hub, client);
}
struct tegra_display_hub_state {
struct drm_private_state base;
struct tegra_dc *dc;
unsigned long rate;
struct clk *clk;
};
static inline struct tegra_display_hub_state *
to_tegra_display_hub_state(struct drm_private_state *priv)
{
return container_of(priv, struct tegra_display_hub_state, base);
}
struct tegra_plane;
int tegra_display_hub_prepare(struct tegra_display_hub *hub);
void tegra_display_hub_cleanup(struct tegra_display_hub *hub);
struct drm_plane *tegra_shared_plane_create(struct drm_device *drm,
struct tegra_dc *dc,
unsigned int wgrp,
unsigned int index,
enum drm_plane_type type);
int tegra_display_hub_atomic_check(struct drm_device *drm,
struct drm_atomic_commit *state);
void tegra_display_hub_atomic_commit(struct drm_device *drm,
struct drm_atomic_commit *state);
#define DC_CMD_IHUB_COMMON_MISC_CTL 0x068
#define LATENCY_EVENT (1 << 3)
#define DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER 0x451
#define CURS_SLOTS(x) (((x) & 0xff) << 8)
#define WGRP_SLOTS(x) (((x) & 0xff) << 0)
#endif /* TEGRA_HUB_H */
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 NVIDIA Corporation
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include "mipi-phy.h"
/*
* Default D-PHY timings based on MIPI D-PHY specification. Derived from the
* valid ranges specified in Section 6.9, Table 14, Page 40 of the D-PHY
* specification (v1.2) with minor adjustments.
*/
int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
unsigned long period)
{
timing->clkmiss = 0;
timing->clkpost = 70 + 52 * period;
timing->clkpre = 8;
timing->clkprepare = 65;
timing->clksettle = 95;
timing->clktermen = 0;
timing->clktrail = 80;
timing->clkzero = 260;
timing->dtermen = 0;
timing->eot = 0;
timing->hsexit = 120;
timing->hsprepare = 65 + 5 * period;
timing->hszero = 145 + 5 * period;
timing->hssettle = 85 + 6 * period;
timing->hsskip = 40;
/*
* The MIPI D-PHY specification (Section 6.9, v1.2, Table 14, Page 40)
* contains this formula as:
*
* T_HS-TRAIL = max(n * 8 * period, 60 + n * 4 * period)
*
* where n = 1 for forward-direction HS mode and n = 4 for reverse-
* direction HS mode. There's only one setting and this function does
* not parameterize on anything other that period, so this code will
* assumes that reverse-direction HS mode is supported and uses n = 4.
*/
timing->hstrail = max(4 * 8 * period, 60 + 4 * 4 * period);
timing->init = 100000;
timing->lpx = 60;
timing->taget = 5 * timing->lpx;
timing->tago = 4 * timing->lpx;
timing->tasure = 2 * timing->lpx;
timing->wakeup = 1000000;
return 0;
}
/*
* Validate D-PHY timing according to MIPI D-PHY specification (v1.2, Section
* Section 6.9 "Global Operation Timing Parameters").
*/
int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing,
unsigned long period)
{
if (timing->clkmiss > 60)
return -EINVAL;
if (timing->clkpost < (60 + 52 * period))
return -EINVAL;
if (timing->clkpre < 8)
return -EINVAL;
if (timing->clkprepare < 38 || timing->clkprepare > 95)
return -EINVAL;
if (timing->clksettle < 95 || timing->clksettle > 300)
return -EINVAL;
if (timing->clktermen > 38)
return -EINVAL;
if (timing->clktrail < 60)
return -EINVAL;
if (timing->clkprepare + timing->clkzero < 300)
return -EINVAL;
if (timing->dtermen > 35 + 4 * period)
return -EINVAL;
if (timing->eot > 105 + 12 * period)
return -EINVAL;
if (timing->hsexit < 100)
return -EINVAL;
if (timing->hsprepare < 40 + 4 * period ||
timing->hsprepare > 85 + 6 * period)
return -EINVAL;
if (timing->hsprepare + timing->hszero < 145 + 10 * period)
return -EINVAL;
if ((timing->hssettle < 85 + 6 * period) ||
(timing->hssettle > 145 + 10 * period))
return -EINVAL;
if (timing->hsskip < 40 || timing->hsskip > 55 + 4 * period)
return -EINVAL;
if (timing->hstrail < max(8 * period, 60 + 4 * period))
return -EINVAL;
if (timing->init < 100000)
return -EINVAL;
if (timing->lpx < 50)
return -EINVAL;
if (timing->taget != 5 * timing->lpx)
return -EINVAL;
if (timing->tago != 4 * timing->lpx)
return -EINVAL;
if (timing->tasure < timing->lpx || timing->tasure > 2 * timing->lpx)
return -EINVAL;
if (timing->wakeup < 1000000)
return -EINVAL;
return 0;
}
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2013 NVIDIA Corporation
*/
#ifndef DRM_TEGRA_MIPI_PHY_H
#define DRM_TEGRA_MIPI_PHY_H
/*
* D-PHY timing parameters
*
* A detailed description of these parameters can be found in the MIPI
* Alliance Specification for D-PHY, Section 5.9 "Global Operation Timing
* Parameters".
*
* All parameters are specified in nanoseconds.
*/
struct mipi_dphy_timing {
unsigned int clkmiss;
unsigned int clkpost;
unsigned int clkpre;
unsigned int clkprepare;
unsigned int clksettle;
unsigned int clktermen;
unsigned int clktrail;
unsigned int clkzero;
unsigned int dtermen;
unsigned int eot;
unsigned int hsexit;
unsigned int hsprepare;
unsigned int hszero;
unsigned int hssettle;
unsigned int hsskip;
unsigned int hstrail;
unsigned int init;
unsigned int lpx;
unsigned int taget;
unsigned int tago;
unsigned int tasure;
unsigned int wakeup;
};
int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing,
unsigned long period);
int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing,
unsigned long period);
#endif
+578
View File
@@ -0,0 +1,578 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015-2022, NVIDIA Corporation.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/host1x.h>
#include <linux/iommu.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <soc/tegra/mc.h>
#include "drm.h"
#include "falcon.h"
#include "riscv.h"
#include "vic.h"
#define NVDEC_FALCON_DEBUGINFO 0x1094
#define NVDEC_TFBIF_TRANSCFG 0x2c44
struct nvdec_config {
const char *firmware;
unsigned int version;
bool supports_sid;
bool has_riscv;
bool has_extra_clocks;
};
struct nvdec {
struct falcon falcon;
void __iomem *regs;
struct tegra_drm_client client;
struct host1x_channel *channel;
struct device *dev;
struct clk_bulk_data clks[3];
unsigned int num_clks;
struct reset_control *reset;
/* Platform configuration */
const struct nvdec_config *config;
/* RISC-V specific data */
struct tegra_drm_riscv riscv;
phys_addr_t carveout_base;
};
static inline struct nvdec *to_nvdec(struct tegra_drm_client *client)
{
return container_of(client, struct nvdec, client);
}
static inline void nvdec_writel(struct nvdec *nvdec, u32 value,
unsigned int offset)
{
writel(value, nvdec->regs + offset);
}
static int nvdec_boot_falcon(struct nvdec *nvdec)
{
u32 stream_id;
int err;
if (nvdec->config->supports_sid && tegra_dev_iommu_get_stream_id(nvdec->dev, &stream_id)) {
u32 value;
value = TRANSCFG_ATT(1, TRANSCFG_SID_FALCON) | TRANSCFG_ATT(0, TRANSCFG_SID_HW);
nvdec_writel(nvdec, value, NVDEC_TFBIF_TRANSCFG);
nvdec_writel(nvdec, stream_id, VIC_THI_STREAMID0);
nvdec_writel(nvdec, stream_id, VIC_THI_STREAMID1);
}
err = falcon_boot(&nvdec->falcon);
if (err < 0)
return err;
err = falcon_wait_idle(&nvdec->falcon);
if (err < 0) {
dev_err(nvdec->dev, "falcon boot timed out\n");
return err;
}
return 0;
}
static int nvdec_wait_debuginfo(struct nvdec *nvdec, const char *phase)
{
int err;
u32 val;
err = readl_poll_timeout(nvdec->regs + NVDEC_FALCON_DEBUGINFO, val, val == 0x0, 10, 100000);
if (err) {
dev_err(nvdec->dev, "failed to boot %s, debuginfo=0x%x\n", phase, val);
return err;
}
return 0;
}
static int nvdec_boot_riscv(struct nvdec *nvdec)
{
int err;
err = reset_control_acquire(nvdec->reset);
if (err)
return err;
nvdec_writel(nvdec, 0xabcd1234, NVDEC_FALCON_DEBUGINFO);
err = tegra_drm_riscv_boot_bootrom(&nvdec->riscv, nvdec->carveout_base, 1,
&nvdec->riscv.bl_desc);
if (err) {
dev_err(nvdec->dev, "failed to execute bootloader\n");
goto release_reset;
}
err = nvdec_wait_debuginfo(nvdec, "bootloader");
if (err)
goto release_reset;
err = reset_control_reset(nvdec->reset);
if (err)
goto release_reset;
nvdec_writel(nvdec, 0xabcd1234, NVDEC_FALCON_DEBUGINFO);
err = tegra_drm_riscv_boot_bootrom(&nvdec->riscv, nvdec->carveout_base, 1,
&nvdec->riscv.os_desc);
if (err) {
dev_err(nvdec->dev, "failed to execute firmware\n");
goto release_reset;
}
err = nvdec_wait_debuginfo(nvdec, "firmware");
if (err)
goto release_reset;
release_reset:
reset_control_release(nvdec->reset);
return err;
}
static int nvdec_init(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
struct tegra_drm *tegra = dev->dev_private;
struct nvdec *nvdec = to_nvdec(drm);
int err;
err = host1x_client_iommu_attach(client);
if (err < 0 && err != -ENODEV) {
dev_err(nvdec->dev, "failed to attach to domain: %d\n", err);
return err;
}
nvdec->channel = host1x_channel_request(client);
if (!nvdec->channel) {
err = -ENOMEM;
goto detach;
}
client->syncpts[0] = host1x_syncpt_request(client, 0);
if (!client->syncpts[0]) {
err = -ENOMEM;
goto free_channel;
}
err = tegra_drm_register_client(tegra, drm);
if (err < 0)
goto free_syncpt;
/*
* Inherit the DMA parameters (such as maximum segment size) from the
* parent host1x device.
*/
client->dev->dma_parms = client->host->dma_parms;
return 0;
free_syncpt:
host1x_syncpt_put(client->syncpts[0]);
free_channel:
host1x_channel_put(nvdec->channel);
detach:
host1x_client_iommu_detach(client);
return err;
}
static int nvdec_exit(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
struct tegra_drm *tegra = dev->dev_private;
struct nvdec *nvdec = to_nvdec(drm);
int err;
/* avoid a dangling pointer just in case this disappears */
client->dev->dma_parms = NULL;
err = tegra_drm_unregister_client(tegra, drm);
if (err < 0)
return err;
pm_runtime_dont_use_autosuspend(client->dev);
pm_runtime_force_suspend(client->dev);
host1x_syncpt_put(client->syncpts[0]);
host1x_channel_put(nvdec->channel);
host1x_client_iommu_detach(client);
nvdec->channel = NULL;
if (client->group) {
dma_unmap_single(nvdec->dev, nvdec->falcon.firmware.phys,
nvdec->falcon.firmware.size, DMA_TO_DEVICE);
tegra_drm_free(tegra, nvdec->falcon.firmware.size,
nvdec->falcon.firmware.virt,
nvdec->falcon.firmware.iova);
} else {
dma_free_coherent(nvdec->dev, nvdec->falcon.firmware.size,
nvdec->falcon.firmware.virt,
nvdec->falcon.firmware.iova);
}
return 0;
}
static const struct host1x_client_ops nvdec_client_ops = {
.init = nvdec_init,
.exit = nvdec_exit,
};
static int nvdec_load_falcon_firmware(struct nvdec *nvdec)
{
struct host1x_client *client = &nvdec->client.base;
struct tegra_drm *tegra = nvdec->client.drm;
dma_addr_t iova;
size_t size;
void *virt;
int err;
if (nvdec->falcon.firmware.virt)
return 0;
err = falcon_read_firmware(&nvdec->falcon, nvdec->config->firmware);
if (err < 0)
return err;
size = nvdec->falcon.firmware.size;
if (!client->group) {
virt = dma_alloc_coherent(nvdec->dev, size, &iova, GFP_KERNEL);
if (!virt)
return -ENOMEM;
} else {
virt = tegra_drm_alloc(tegra, size, &iova);
if (IS_ERR(virt))
return PTR_ERR(virt);
}
nvdec->falcon.firmware.virt = virt;
nvdec->falcon.firmware.iova = iova;
err = falcon_load_firmware(&nvdec->falcon);
if (err < 0)
goto cleanup;
/*
* In this case we have received an IOVA from the shared domain, so we
* need to make sure to get the physical address so that the DMA API
* knows what memory pages to flush the cache for.
*/
if (client->group) {
dma_addr_t phys;
phys = dma_map_single(nvdec->dev, virt, size, DMA_TO_DEVICE);
err = dma_mapping_error(nvdec->dev, phys);
if (err < 0)
goto cleanup;
nvdec->falcon.firmware.phys = phys;
}
return 0;
cleanup:
if (!client->group)
dma_free_coherent(nvdec->dev, size, virt, iova);
else
tegra_drm_free(tegra, size, virt, iova);
return err;
}
static __maybe_unused int nvdec_runtime_resume(struct device *dev)
{
struct nvdec *nvdec = dev_get_drvdata(dev);
int err;
err = clk_bulk_prepare_enable(nvdec->num_clks, nvdec->clks);
if (err < 0)
return err;
usleep_range(10, 20);
if (nvdec->config->has_riscv) {
err = nvdec_boot_riscv(nvdec);
if (err < 0)
goto disable;
} else {
err = nvdec_load_falcon_firmware(nvdec);
if (err < 0)
goto disable;
err = nvdec_boot_falcon(nvdec);
if (err < 0)
goto disable;
}
return 0;
disable:
clk_bulk_disable_unprepare(nvdec->num_clks, nvdec->clks);
return err;
}
static __maybe_unused int nvdec_runtime_suspend(struct device *dev)
{
struct nvdec *nvdec = dev_get_drvdata(dev);
host1x_channel_stop(nvdec->channel);
clk_bulk_disable_unprepare(nvdec->num_clks, nvdec->clks);
return 0;
}
static int nvdec_open_channel(struct tegra_drm_client *client,
struct tegra_drm_context *context)
{
struct nvdec *nvdec = to_nvdec(client);
context->channel = host1x_channel_get(nvdec->channel);
if (!context->channel)
return -ENOMEM;
return 0;
}
static void nvdec_close_channel(struct tegra_drm_context *context)
{
host1x_channel_put(context->channel);
}
static int nvdec_can_use_memory_ctx(struct tegra_drm_client *client, bool *supported)
{
*supported = true;
return 0;
}
static const struct tegra_drm_client_ops nvdec_ops = {
.open_channel = nvdec_open_channel,
.close_channel = nvdec_close_channel,
.submit = tegra_drm_submit,
.get_streamid_offset = tegra_drm_get_streamid_offset_thi,
.can_use_memory_ctx = nvdec_can_use_memory_ctx,
};
#define NVIDIA_TEGRA_210_NVDEC_FIRMWARE "nvidia/tegra210/nvdec.bin"
static const struct nvdec_config nvdec_t210_config = {
.firmware = NVIDIA_TEGRA_210_NVDEC_FIRMWARE,
.version = 0x21,
.supports_sid = false,
};
#define NVIDIA_TEGRA_186_NVDEC_FIRMWARE "nvidia/tegra186/nvdec.bin"
static const struct nvdec_config nvdec_t186_config = {
.firmware = NVIDIA_TEGRA_186_NVDEC_FIRMWARE,
.version = 0x18,
.supports_sid = true,
};
#define NVIDIA_TEGRA_194_NVDEC_FIRMWARE "nvidia/tegra194/nvdec.bin"
static const struct nvdec_config nvdec_t194_config = {
.firmware = NVIDIA_TEGRA_194_NVDEC_FIRMWARE,
.version = 0x19,
.supports_sid = true,
};
static const struct nvdec_config nvdec_t234_config = {
.version = 0x23,
.supports_sid = true,
.has_riscv = true,
.has_extra_clocks = true,
};
static const struct of_device_id tegra_nvdec_of_match[] = {
{ .compatible = "nvidia,tegra210-nvdec", .data = &nvdec_t210_config },
{ .compatible = "nvidia,tegra186-nvdec", .data = &nvdec_t186_config },
{ .compatible = "nvidia,tegra194-nvdec", .data = &nvdec_t194_config },
{ .compatible = "nvidia,tegra234-nvdec", .data = &nvdec_t234_config },
{ },
};
MODULE_DEVICE_TABLE(of, tegra_nvdec_of_match);
static int nvdec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct host1x_syncpt **syncpts;
struct nvdec *nvdec;
u32 host_class;
int err;
/* inherit DMA mask from host1x parent */
err = dma_coerce_mask_and_coherent(dev, *dev->parent->dma_mask);
if (err < 0) {
dev_err(&pdev->dev, "failed to set DMA mask: %d\n", err);
return err;
}
nvdec = devm_kzalloc(dev, sizeof(*nvdec), GFP_KERNEL);
if (!nvdec)
return -ENOMEM;
nvdec->config = of_device_get_match_data(dev);
syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL);
if (!syncpts)
return -ENOMEM;
nvdec->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(nvdec->regs))
return PTR_ERR(nvdec->regs);
nvdec->clks[0].id = "nvdec";
nvdec->num_clks = 1;
if (nvdec->config->has_extra_clocks) {
nvdec->num_clks = 3;
nvdec->clks[1].id = "fuse";
nvdec->clks[2].id = "tsec_pka";
}
err = devm_clk_bulk_get(dev, nvdec->num_clks, nvdec->clks);
if (err) {
dev_err(&pdev->dev, "failed to get clock(s)\n");
return err;
}
err = clk_set_rate(nvdec->clks[0].clk, ULONG_MAX);
if (err < 0) {
dev_err(&pdev->dev, "failed to set clock rate\n");
return err;
}
err = of_property_read_u32(dev->of_node, "nvidia,host1x-class", &host_class);
if (err < 0)
host_class = HOST1X_CLASS_NVDEC;
if (nvdec->config->has_riscv) {
struct tegra_mc *mc;
mc = devm_tegra_memory_controller_get(dev);
if (IS_ERR(mc)) {
dev_err_probe(dev, PTR_ERR(mc),
"failed to get memory controller handle\n");
return PTR_ERR(mc);
}
err = tegra_mc_get_carveout_info(mc, 1, &nvdec->carveout_base, NULL);
if (err) {
dev_err(dev, "failed to get carveout info: %d\n", err);
return err;
}
nvdec->reset = devm_reset_control_get_exclusive_released(dev, "nvdec");
if (IS_ERR(nvdec->reset)) {
dev_err_probe(dev, PTR_ERR(nvdec->reset), "failed to get reset\n");
return PTR_ERR(nvdec->reset);
}
nvdec->riscv.dev = dev;
nvdec->riscv.regs = nvdec->regs;
err = tegra_drm_riscv_read_descriptors(&nvdec->riscv);
if (err < 0)
return err;
} else {
nvdec->falcon.dev = dev;
nvdec->falcon.regs = nvdec->regs;
err = falcon_init(&nvdec->falcon);
if (err < 0)
return err;
}
platform_set_drvdata(pdev, nvdec);
INIT_LIST_HEAD(&nvdec->client.base.list);
nvdec->client.base.ops = &nvdec_client_ops;
nvdec->client.base.dev = dev;
nvdec->client.base.class = host_class;
nvdec->client.base.syncpts = syncpts;
nvdec->client.base.num_syncpts = 1;
nvdec->dev = dev;
INIT_LIST_HEAD(&nvdec->client.list);
nvdec->client.version = nvdec->config->version;
nvdec->client.ops = &nvdec_ops;
err = host1x_client_register(&nvdec->client.base);
if (err < 0) {
dev_err(dev, "failed to register host1x client: %d\n", err);
goto exit_falcon;
}
pm_runtime_enable(dev);
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, 500);
return 0;
exit_falcon:
falcon_exit(&nvdec->falcon);
return err;
}
static void nvdec_remove(struct platform_device *pdev)
{
struct nvdec *nvdec = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
host1x_client_unregister(&nvdec->client.base);
falcon_exit(&nvdec->falcon);
}
static const struct dev_pm_ops nvdec_pm_ops = {
SET_RUNTIME_PM_OPS(nvdec_runtime_suspend, nvdec_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
};
struct platform_driver tegra_nvdec_driver = {
.driver = {
.name = "tegra-nvdec",
.of_match_table = tegra_nvdec_of_match,
.pm = &nvdec_pm_ops
},
.probe = nvdec_probe,
.remove = nvdec_remove,
};
#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC)
MODULE_FIRMWARE(NVIDIA_TEGRA_210_NVDEC_FIRMWARE);
#endif
#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC)
MODULE_FIRMWARE(NVIDIA_TEGRA_186_NVDEC_FIRMWARE);
#endif
#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC)
MODULE_FIRMWARE(NVIDIA_TEGRA_194_NVDEC_FIRMWARE);
#endif
+330
View File
@@ -0,0 +1,330 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/host1x.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "drm.h"
#include "falcon.h"
struct nvjpg_config {
const char *firmware;
unsigned int version;
};
struct nvjpg {
struct falcon falcon;
void __iomem *regs;
struct tegra_drm_client client;
struct device *dev;
struct clk *clk;
/* Platform configuration */
const struct nvjpg_config *config;
};
static inline struct nvjpg *to_nvjpg(struct tegra_drm_client *client)
{
return container_of(client, struct nvjpg, client);
}
static int nvjpg_init(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
struct tegra_drm *tegra = dev->dev_private;
struct nvjpg *nvjpg = to_nvjpg(drm);
int err;
err = host1x_client_iommu_attach(client);
if (err < 0 && err != -ENODEV) {
dev_err(nvjpg->dev, "failed to attach to domain: %d\n", err);
return err;
}
err = tegra_drm_register_client(tegra, drm);
if (err < 0)
goto detach;
/*
* Inherit the DMA parameters (such as maximum segment size) from the
* parent host1x device.
*/
client->dev->dma_parms = client->host->dma_parms;
return 0;
detach:
host1x_client_iommu_detach(client);
return err;
}
static int nvjpg_exit(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
struct tegra_drm *tegra = dev->dev_private;
struct nvjpg *nvjpg = to_nvjpg(drm);
int err;
/* avoid a dangling pointer just in case this disappears */
client->dev->dma_parms = NULL;
err = tegra_drm_unregister_client(tegra, drm);
if (err < 0)
return err;
pm_runtime_dont_use_autosuspend(client->dev);
pm_runtime_force_suspend(client->dev);
host1x_client_iommu_detach(client);
if (client->group) {
dma_unmap_single(nvjpg->dev, nvjpg->falcon.firmware.phys,
nvjpg->falcon.firmware.size, DMA_TO_DEVICE);
tegra_drm_free(tegra, nvjpg->falcon.firmware.size,
nvjpg->falcon.firmware.virt,
nvjpg->falcon.firmware.iova);
} else {
dma_free_coherent(nvjpg->dev, nvjpg->falcon.firmware.size,
nvjpg->falcon.firmware.virt,
nvjpg->falcon.firmware.iova);
}
return 0;
}
static const struct host1x_client_ops nvjpg_client_ops = {
.init = nvjpg_init,
.exit = nvjpg_exit,
};
static int nvjpg_load_falcon_firmware(struct nvjpg *nvjpg)
{
struct host1x_client *client = &nvjpg->client.base;
struct tegra_drm *tegra = nvjpg->client.drm;
dma_addr_t iova;
size_t size;
void *virt;
int err;
if (nvjpg->falcon.firmware.virt)
return 0;
err = falcon_read_firmware(&nvjpg->falcon, nvjpg->config->firmware);
if (err < 0)
return err;
size = nvjpg->falcon.firmware.size;
if (!client->group) {
virt = dma_alloc_coherent(nvjpg->dev, size, &iova, GFP_KERNEL);
if (!virt)
return -ENOMEM;
} else {
virt = tegra_drm_alloc(tegra, size, &iova);
if (IS_ERR(virt))
return PTR_ERR(virt);
}
nvjpg->falcon.firmware.virt = virt;
nvjpg->falcon.firmware.iova = iova;
err = falcon_load_firmware(&nvjpg->falcon);
if (err < 0)
goto cleanup;
/*
* In this case we have received an IOVA from the shared domain, so we
* need to make sure to get the physical address so that the DMA API
* knows what memory pages to flush the cache for.
*/
if (client->group) {
dma_addr_t phys;
phys = dma_map_single(nvjpg->dev, virt, size, DMA_TO_DEVICE);
err = dma_mapping_error(nvjpg->dev, phys);
if (err < 0)
goto cleanup;
nvjpg->falcon.firmware.phys = phys;
}
return 0;
cleanup:
if (!client->group)
dma_free_coherent(nvjpg->dev, size, virt, iova);
else
tegra_drm_free(tegra, size, virt, iova);
return err;
}
static __maybe_unused int nvjpg_runtime_resume(struct device *dev)
{
struct nvjpg *nvjpg = dev_get_drvdata(dev);
int err;
err = clk_prepare_enable(nvjpg->clk);
if (err < 0)
return err;
usleep_range(20, 30);
err = nvjpg_load_falcon_firmware(nvjpg);
if (err < 0)
goto disable_clk;
err = falcon_boot(&nvjpg->falcon);
if (err < 0)
goto disable_clk;
return 0;
disable_clk:
clk_disable_unprepare(nvjpg->clk);
return err;
}
static __maybe_unused int nvjpg_runtime_suspend(struct device *dev)
{
struct nvjpg *nvjpg = dev_get_drvdata(dev);
clk_disable_unprepare(nvjpg->clk);
return 0;
}
static int nvjpg_can_use_memory_ctx(struct tegra_drm_client *client, bool *supported)
{
*supported = false;
return 0;
}
static const struct tegra_drm_client_ops nvjpg_ops = {
.get_streamid_offset = NULL,
.can_use_memory_ctx = nvjpg_can_use_memory_ctx,
};
#define NVIDIA_TEGRA_210_NVJPG_FIRMWARE "nvidia/tegra210/nvjpg.bin"
static const struct nvjpg_config tegra210_nvjpg_config = {
.firmware = NVIDIA_TEGRA_210_NVJPG_FIRMWARE,
.version = 0x21,
};
static const struct of_device_id tegra_nvjpg_of_match[] = {
{ .compatible = "nvidia,tegra210-nvjpg", .data = &tegra210_nvjpg_config },
{ },
};
MODULE_DEVICE_TABLE(of, tegra_nvjpg_of_match);
static int nvjpg_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct nvjpg *nvjpg;
int err;
/* inherit DMA mask from host1x parent */
err = dma_coerce_mask_and_coherent(dev, *dev->parent->dma_mask);
if (err < 0) {
dev_err(&pdev->dev, "failed to set DMA mask: %d\n", err);
return err;
}
nvjpg = devm_kzalloc(dev, sizeof(*nvjpg), GFP_KERNEL);
if (!nvjpg)
return -ENOMEM;
nvjpg->config = of_device_get_match_data(dev);
nvjpg->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(nvjpg->regs))
return PTR_ERR(nvjpg->regs);
nvjpg->clk = devm_clk_get(dev, "nvjpg");
if (IS_ERR(nvjpg->clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
return PTR_ERR(nvjpg->clk);
}
err = clk_set_rate(nvjpg->clk, ULONG_MAX);
if (err < 0) {
dev_err(&pdev->dev, "failed to set clock rate\n");
return err;
}
nvjpg->falcon.dev = dev;
nvjpg->falcon.regs = nvjpg->regs;
err = falcon_init(&nvjpg->falcon);
if (err < 0)
return err;
platform_set_drvdata(pdev, nvjpg);
INIT_LIST_HEAD(&nvjpg->client.base.list);
nvjpg->client.base.ops = &nvjpg_client_ops;
nvjpg->client.base.dev = dev;
nvjpg->client.base.class = HOST1X_CLASS_NVJPG;
nvjpg->dev = dev;
INIT_LIST_HEAD(&nvjpg->client.list);
nvjpg->client.version = nvjpg->config->version;
nvjpg->client.ops = &nvjpg_ops;
err = host1x_client_register(&nvjpg->client.base);
if (err < 0) {
dev_err(dev, "failed to register host1x client: %d\n", err);
goto exit_falcon;
}
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, 500);
devm_pm_runtime_enable(dev);
return 0;
exit_falcon:
falcon_exit(&nvjpg->falcon);
return err;
}
static void nvjpg_remove(struct platform_device *pdev)
{
struct nvjpg *nvjpg = platform_get_drvdata(pdev);
host1x_client_unregister(&nvjpg->client.base);
falcon_exit(&nvjpg->falcon);
}
static const struct dev_pm_ops nvjpg_pm_ops = {
RUNTIME_PM_OPS(nvjpg_runtime_suspend, nvjpg_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
struct platform_driver tegra_nvjpg_driver = {
.driver = {
.name = "tegra-nvjpg",
.of_match_table = tegra_nvjpg_of_match,
.pm = &nvjpg_pm_ops
},
.probe = nvjpg_probe,
.remove = nvjpg_remove,
};
#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC)
MODULE_FIRMWARE(NVIDIA_TEGRA_210_NVJPG_FIRMWARE);
#endif
+282
View File
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012 Avionic Design GmbH
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/i2c.h>
#include <linux/of.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_simple_kms_helper.h>
#include "drm.h"
#include "dc.h"
#include <media/cec-notifier.h>
int tegra_output_connector_get_modes(struct drm_connector *connector)
{
struct tegra_output *output = connector_to_output(connector);
const struct drm_edid *drm_edid = NULL;
int err = 0;
/*
* If the panel provides one or more modes, use them exclusively and
* ignore any other means of obtaining a mode.
*/
if (output->panel) {
err = drm_panel_get_modes(output->panel, connector);
if (err > 0)
return err;
}
if (output->drm_edid)
drm_edid = drm_edid_dup(output->drm_edid);
else if (output->ddc)
drm_edid = drm_edid_read_ddc(connector, output->ddc);
drm_edid_connector_update(connector, drm_edid);
cec_notifier_set_phys_addr(output->cec,
connector->display_info.source_physical_address);
err = drm_edid_connector_add_modes(connector);
drm_edid_free(drm_edid);
return err;
}
enum drm_connector_status
tegra_output_connector_detect(struct drm_connector *connector, bool force)
{
struct tegra_output *output = connector_to_output(connector);
enum drm_connector_status status = connector_status_unknown;
if (output->hpd_gpio) {
if (gpiod_get_value(output->hpd_gpio) == 0)
status = connector_status_disconnected;
else
status = connector_status_connected;
} else {
if (!output->panel)
status = connector_status_disconnected;
else
status = connector_status_connected;
}
if (status != connector_status_connected)
cec_notifier_phys_addr_invalidate(output->cec);
return status;
}
void tegra_output_connector_destroy(struct drm_connector *connector)
{
struct tegra_output *output = connector_to_output(connector);
if (output->cec)
cec_notifier_conn_unregister(output->cec);
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static irqreturn_t hpd_irq(int irq, void *data)
{
struct tegra_output *output = data;
if (output->connector.dev)
drm_helper_hpd_irq_event(output->connector.dev);
return IRQ_HANDLED;
}
int tegra_output_probe(struct tegra_output *output)
{
struct device_node *ddc, *panel;
const void *edid;
unsigned long flags;
int err, size;
if (!output->of_node)
output->of_node = output->dev->of_node;
err = drm_of_find_panel_or_bridge(output->of_node, -1, -1,
&output->panel, &output->bridge);
if (err && err != -ENODEV)
return err;
panel = of_parse_phandle(output->of_node, "nvidia,panel", 0);
if (panel) {
/*
* Don't mix nvidia,panel phandle with the graph in a
* device-tree.
*/
WARN_ON(output->panel || output->bridge);
output->panel = of_drm_find_panel(panel);
of_node_put(panel);
if (IS_ERR(output->panel))
return PTR_ERR(output->panel);
}
ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0);
if (ddc) {
output->ddc = of_get_i2c_adapter_by_node(ddc);
of_node_put(ddc);
if (!output->ddc) {
err = -EPROBE_DEFER;
return err;
}
}
edid = of_get_property(output->of_node, "nvidia,edid", &size);
output->drm_edid = drm_edid_alloc(edid, size);
output->hpd_gpio = devm_fwnode_gpiod_get(output->dev,
of_fwnode_handle(output->of_node),
"nvidia,hpd",
GPIOD_IN,
"HDMI hotplug detect");
if (IS_ERR(output->hpd_gpio)) {
if (PTR_ERR(output->hpd_gpio) != -ENOENT) {
err = PTR_ERR(output->hpd_gpio);
goto put_i2c;
}
output->hpd_gpio = NULL;
}
if (output->hpd_gpio) {
err = gpiod_to_irq(output->hpd_gpio);
if (err < 0) {
dev_err(output->dev, "gpiod_to_irq(): %d\n", err);
goto put_i2c;
}
output->hpd_irq = err;
flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
IRQF_ONESHOT;
err = request_threaded_irq(output->hpd_irq, NULL, hpd_irq,
flags, "hpd", output);
if (err < 0) {
dev_err(output->dev, "failed to request IRQ#%u: %d\n",
output->hpd_irq, err);
goto put_i2c;
}
output->connector.polled = DRM_CONNECTOR_POLL_HPD;
/*
* Disable the interrupt until the connector has been
* initialized to avoid a race in the hotplug interrupt
* handler.
*/
disable_irq(output->hpd_irq);
}
return 0;
put_i2c:
if (output->ddc)
i2c_put_adapter(output->ddc);
drm_edid_free(output->drm_edid);
return err;
}
void tegra_output_remove(struct tegra_output *output)
{
if (output->hpd_gpio)
free_irq(output->hpd_irq, output);
if (output->ddc)
i2c_put_adapter(output->ddc);
drm_edid_free(output->drm_edid);
}
int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
{
int connector_type;
/*
* The connector is now registered and ready to receive hotplug events
* so the hotplug interrupt can be enabled.
*/
if (output->hpd_gpio)
enable_irq(output->hpd_irq);
connector_type = output->connector.connector_type;
/*
* Create a CEC notifier for HDMI connector.
*/
if (connector_type == DRM_MODE_CONNECTOR_HDMIA ||
connector_type == DRM_MODE_CONNECTOR_HDMIB) {
struct cec_connector_info conn_info;
cec_fill_conn_info_from_drm(&conn_info, &output->connector);
output->cec = cec_notifier_conn_register(output->dev, NULL,
&conn_info);
if (!output->cec)
return -ENOMEM;
}
return 0;
}
void tegra_output_exit(struct tegra_output *output)
{
/*
* The connector is going away, so the interrupt must be disabled to
* prevent the hotplug interrupt handler from potentially crashing.
*/
if (output->hpd_gpio)
disable_irq(output->hpd_irq);
}
void tegra_output_find_possible_crtcs(struct tegra_output *output,
struct drm_device *drm)
{
struct device *dev = output->dev;
struct drm_crtc *crtc;
unsigned int mask = 0;
drm_for_each_crtc(crtc, drm) {
struct tegra_dc *dc = to_tegra_dc(crtc);
if (tegra_dc_has_output(dc, dev))
mask |= drm_crtc_mask(crtc);
}
if (mask == 0) {
dev_warn(dev, "missing output definition for heads in DT\n");
mask = 0x3;
}
output->encoder.possible_crtcs = mask;
}
int tegra_output_suspend(struct tegra_output *output)
{
if (output->hpd_irq)
disable_irq(output->hpd_irq);
return 0;
}
int tegra_output_resume(struct tegra_output *output)
{
if (output->hpd_irq)
enable_irq(output->hpd_irq);
return 0;
}
+794
View File
@@ -0,0 +1,794 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/dma-mapping.h>
#include <linux/iommu.h>
#include <linux/interconnect.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
#include "dc.h"
#include "plane.h"
static void tegra_plane_destroy(struct drm_plane *plane)
{
struct tegra_plane *p = to_tegra_plane(plane);
drm_plane_cleanup(plane);
kfree(p);
}
static void tegra_plane_reset(struct drm_plane *plane)
{
struct tegra_plane *p = to_tegra_plane(plane);
struct tegra_plane_state *state;
unsigned int i;
if (plane->state)
__drm_atomic_helper_plane_destroy_state(plane->state);
kfree(plane->state);
plane->state = NULL;
state = kzalloc_obj(*state);
if (state) {
plane->state = &state->base;
plane->state->plane = plane;
plane->state->zpos = p->index;
plane->state->normalized_zpos = p->index;
for (i = 0; i < 3; i++)
state->iova[i] = DMA_MAPPING_ERROR;
}
}
static struct drm_plane_state *
tegra_plane_atomic_duplicate_state(struct drm_plane *plane)
{
struct tegra_plane_state *state = to_tegra_plane_state(plane->state);
struct tegra_plane_state *copy;
unsigned int i;
copy = kmalloc_obj(*copy);
if (!copy)
return NULL;
__drm_atomic_helper_plane_duplicate_state(plane, &copy->base);
copy->tiling = state->tiling;
copy->format = state->format;
copy->swap = state->swap;
copy->reflect_x = state->reflect_x;
copy->reflect_y = state->reflect_y;
copy->opaque = state->opaque;
copy->total_peak_memory_bandwidth = state->total_peak_memory_bandwidth;
copy->peak_memory_bandwidth = state->peak_memory_bandwidth;
copy->avg_memory_bandwidth = state->avg_memory_bandwidth;
for (i = 0; i < 2; i++)
copy->blending[i] = state->blending[i];
for (i = 0; i < 3; i++) {
copy->iova[i] = DMA_MAPPING_ERROR;
copy->map[i] = NULL;
}
return &copy->base;
}
static void tegra_plane_atomic_destroy_state(struct drm_plane *plane,
struct drm_plane_state *state)
{
__drm_atomic_helper_plane_destroy_state(state);
kfree(state);
}
static bool tegra_plane_supports_sector_layout(struct drm_plane *plane)
{
struct drm_crtc *crtc;
drm_for_each_crtc(crtc, plane->dev) {
if (plane->possible_crtcs & drm_crtc_mask(crtc)) {
struct tegra_dc *dc = to_tegra_dc(crtc);
if (!dc->soc->supports_sector_layout)
return false;
}
}
return true;
}
static bool tegra_plane_format_mod_supported(struct drm_plane *plane,
uint32_t format,
uint64_t modifier)
{
const struct drm_format_info *info = drm_format_info(format);
if (modifier == DRM_FORMAT_MOD_LINEAR)
return true;
/* check for the sector layout bit */
if (fourcc_mod_is_vendor(modifier, NVIDIA)) {
if (modifier & DRM_FORMAT_MOD_NVIDIA_SECTOR_LAYOUT) {
if (!tegra_plane_supports_sector_layout(plane))
return false;
}
}
if (info->num_planes == 1)
return true;
return false;
}
const struct drm_plane_funcs tegra_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = tegra_plane_destroy,
.reset = tegra_plane_reset,
.atomic_duplicate_state = tegra_plane_atomic_duplicate_state,
.atomic_destroy_state = tegra_plane_atomic_destroy_state,
.format_mod_supported = tegra_plane_format_mod_supported,
};
static int tegra_dc_pin(struct tegra_dc *dc, struct tegra_plane_state *state)
{
unsigned int i;
int err;
for (i = 0; i < state->base.fb->format->num_planes; i++) {
struct tegra_bo *bo = tegra_fb_get_plane(state->base.fb, i);
struct host1x_bo_mapping *map;
map = host1x_bo_pin(dc->dev, &bo->base, DMA_TO_DEVICE, &dc->client.cache);
if (IS_ERR(map)) {
err = PTR_ERR(map);
goto unpin;
}
if (!dc->client.group) {
/*
* The display controller needs contiguous memory, so
* fail if the buffer is discontiguous and we fail to
* map its SG table to a single contiguous chunk of
* I/O virtual memory.
*/
if (map->chunks > 1) {
err = -EINVAL;
goto unpin;
}
state->iova[i] = map->phys;
} else {
state->iova[i] = bo->iova;
}
state->map[i] = map;
}
return 0;
unpin:
dev_err(dc->dev, "failed to map plane %u: %d\n", i, err);
while (i--) {
host1x_bo_unpin(state->map[i]);
state->iova[i] = DMA_MAPPING_ERROR;
state->map[i] = NULL;
}
return err;
}
static void tegra_dc_unpin(struct tegra_dc *dc, struct tegra_plane_state *state)
{
unsigned int i;
for (i = 0; i < state->base.fb->format->num_planes; i++) {
host1x_bo_unpin(state->map[i]);
state->iova[i] = DMA_MAPPING_ERROR;
state->map[i] = NULL;
}
}
int tegra_plane_prepare_fb(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct tegra_dc *dc = to_tegra_dc(state->crtc);
int err;
if (!state->fb)
return 0;
err = drm_gem_plane_helper_prepare_fb(plane, state);
if (err < 0)
return err;
return tegra_dc_pin(dc, to_tegra_plane_state(state));
}
void tegra_plane_cleanup_fb(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct tegra_dc *dc = to_tegra_dc(state->crtc);
if (dc)
tegra_dc_unpin(dc, to_tegra_plane_state(state));
}
static int tegra_plane_calculate_memory_bandwidth(struct drm_plane_state *state)
{
struct tegra_plane_state *tegra_state = to_tegra_plane_state(state);
unsigned int i, bpp, dst_w, dst_h, src_w, src_h, mul;
const struct tegra_dc_soc_info *soc;
const struct drm_format_info *fmt;
struct drm_crtc_state *crtc_state;
u64 avg_bandwidth, peak_bandwidth;
if (!state->visible)
return 0;
crtc_state = drm_atomic_get_new_crtc_state(state->state, state->crtc);
if (!crtc_state)
return -EINVAL;
src_w = drm_rect_width(&state->src) >> 16;
src_h = drm_rect_height(&state->src) >> 16;
dst_w = drm_rect_width(&state->dst);
dst_h = drm_rect_height(&state->dst);
fmt = state->fb->format;
soc = to_tegra_dc(state->crtc)->soc;
/*
* Note that real memory bandwidth vary depending on format and
* memory layout, we are not taking that into account because small
* estimation error isn't important since bandwidth is rounded up
* anyway.
*/
for (i = 0, bpp = 0; i < fmt->num_planes; i++) {
unsigned int bpp_plane = fmt->cpp[i] * 8;
/*
* Sub-sampling is relevant for chroma planes only and vertical
* readouts are not cached, hence only horizontal sub-sampling
* matters.
*/
if (i > 0)
bpp_plane /= fmt->hsub;
bpp += bpp_plane;
}
/* average bandwidth in kbytes/sec */
avg_bandwidth = min(src_w, dst_w) * min(src_h, dst_h);
avg_bandwidth *= drm_mode_vrefresh(&crtc_state->adjusted_mode);
avg_bandwidth = DIV_ROUND_UP(avg_bandwidth * bpp, 8) + 999;
do_div(avg_bandwidth, 1000);
/* mode.clock in kHz, peak bandwidth in kbytes/sec */
peak_bandwidth = DIV_ROUND_UP(crtc_state->adjusted_mode.clock * bpp, 8);
/*
* Tegra30/114 Memory Controller can't interleave DC memory requests
* for the tiled windows because DC uses 16-bytes atom, while DDR3
* uses 32-bytes atom. Hence there is x2 memory overfetch for tiled
* framebuffer and DDR3 on these SoCs.
*/
if (soc->plane_tiled_memory_bandwidth_x2 &&
tegra_state->tiling.mode == TEGRA_BO_TILING_MODE_TILED)
mul = 2;
else
mul = 1;
/* ICC bandwidth in kbytes/sec */
tegra_state->peak_memory_bandwidth = kBps_to_icc(peak_bandwidth) * mul;
tegra_state->avg_memory_bandwidth = kBps_to_icc(avg_bandwidth) * mul;
return 0;
}
int tegra_plane_state_add(struct tegra_plane *plane,
struct drm_plane_state *state)
{
struct drm_crtc_state *crtc_state;
struct tegra_dc_state *tegra;
int err;
/* Propagate errors from allocation or locking failures. */
crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
/* Check plane state for visibility and calculate clipping bounds */
err = drm_atomic_helper_check_plane_state(state, crtc_state,
0, INT_MAX, true, true);
if (err < 0)
return err;
err = tegra_plane_calculate_memory_bandwidth(state);
if (err < 0)
return err;
tegra = to_dc_state(crtc_state);
tegra->planes |= WIN_A_ACT_REQ << plane->index;
return 0;
}
int tegra_plane_format(u32 fourcc, u32 *format, u32 *swap)
{
/* assume no swapping of fetched data */
if (swap)
*swap = BYTE_SWAP_NOSWAP;
switch (fourcc) {
case DRM_FORMAT_ARGB4444:
*format = WIN_COLOR_DEPTH_B4G4R4A4;
break;
case DRM_FORMAT_ARGB1555:
*format = WIN_COLOR_DEPTH_B5G5R5A1;
break;
case DRM_FORMAT_RGB565:
*format = WIN_COLOR_DEPTH_B5G6R5;
break;
case DRM_FORMAT_RGBA5551:
*format = WIN_COLOR_DEPTH_A1B5G5R5;
break;
case DRM_FORMAT_ARGB8888:
*format = WIN_COLOR_DEPTH_B8G8R8A8;
break;
case DRM_FORMAT_ABGR8888:
*format = WIN_COLOR_DEPTH_R8G8B8A8;
break;
case DRM_FORMAT_ABGR4444:
*format = WIN_COLOR_DEPTH_R4G4B4A4;
break;
case DRM_FORMAT_ABGR1555:
*format = WIN_COLOR_DEPTH_R5G5B5A;
break;
case DRM_FORMAT_BGRA5551:
*format = WIN_COLOR_DEPTH_AR5G5B5;
break;
case DRM_FORMAT_XRGB1555:
*format = WIN_COLOR_DEPTH_B5G5R5X1;
break;
case DRM_FORMAT_RGBX5551:
*format = WIN_COLOR_DEPTH_X1B5G5R5;
break;
case DRM_FORMAT_XBGR1555:
*format = WIN_COLOR_DEPTH_R5G5B5X1;
break;
case DRM_FORMAT_BGRX5551:
*format = WIN_COLOR_DEPTH_X1R5G5B5;
break;
case DRM_FORMAT_BGR565:
*format = WIN_COLOR_DEPTH_R5G6B5;
break;
case DRM_FORMAT_BGRA8888:
*format = WIN_COLOR_DEPTH_A8R8G8B8;
break;
case DRM_FORMAT_RGBA8888:
*format = WIN_COLOR_DEPTH_A8B8G8R8;
break;
case DRM_FORMAT_XRGB8888:
*format = WIN_COLOR_DEPTH_B8G8R8X8;
break;
case DRM_FORMAT_XBGR8888:
*format = WIN_COLOR_DEPTH_R8G8B8X8;
break;
case DRM_FORMAT_UYVY:
*format = WIN_COLOR_DEPTH_YCbCr422;
break;
case DRM_FORMAT_YUYV:
if (!swap)
return -EINVAL;
*format = WIN_COLOR_DEPTH_YCbCr422;
*swap = BYTE_SWAP_SWAP2;
break;
case DRM_FORMAT_YVYU:
if (!swap)
return -EINVAL;
*format = WIN_COLOR_DEPTH_YCbCr422;
*swap = BYTE_SWAP_SWAP4;
break;
case DRM_FORMAT_VYUY:
if (!swap)
return -EINVAL;
*format = WIN_COLOR_DEPTH_YCbCr422;
*swap = BYTE_SWAP_SWAP4HW;
break;
case DRM_FORMAT_YUV420:
*format = WIN_COLOR_DEPTH_YCbCr420P;
break;
case DRM_FORMAT_YUV422:
*format = WIN_COLOR_DEPTH_YCbCr422P;
break;
case DRM_FORMAT_YUV444:
*format = WIN_COLOR_DEPTH_YCbCr444P;
break;
case DRM_FORMAT_NV12:
*format = WIN_COLOR_DEPTH_YCbCr420SP;
break;
case DRM_FORMAT_NV21:
*format = WIN_COLOR_DEPTH_YCrCb420SP;
break;
case DRM_FORMAT_NV16:
*format = WIN_COLOR_DEPTH_YCbCr422SP;
break;
case DRM_FORMAT_NV61:
*format = WIN_COLOR_DEPTH_YCrCb422SP;
break;
case DRM_FORMAT_NV24:
*format = WIN_COLOR_DEPTH_YCbCr444SP;
break;
case DRM_FORMAT_NV42:
*format = WIN_COLOR_DEPTH_YCrCb444SP;
break;
default:
return -EINVAL;
}
return 0;
}
bool tegra_plane_format_is_indexed(unsigned int format)
{
switch (format) {
case WIN_COLOR_DEPTH_P1:
case WIN_COLOR_DEPTH_P2:
case WIN_COLOR_DEPTH_P4:
case WIN_COLOR_DEPTH_P8:
return true;
}
return false;
}
bool tegra_plane_format_is_yuv(unsigned int format, unsigned int *planes, unsigned int *bpc)
{
switch (format) {
case WIN_COLOR_DEPTH_YCbCr422:
case WIN_COLOR_DEPTH_YUV422:
if (planes)
*planes = 1;
if (bpc)
*bpc = 8;
return true;
case WIN_COLOR_DEPTH_YCbCr420P:
case WIN_COLOR_DEPTH_YUV420P:
case WIN_COLOR_DEPTH_YCbCr422P:
case WIN_COLOR_DEPTH_YUV422P:
case WIN_COLOR_DEPTH_YCbCr422R:
case WIN_COLOR_DEPTH_YUV422R:
case WIN_COLOR_DEPTH_YCbCr422RA:
case WIN_COLOR_DEPTH_YUV422RA:
case WIN_COLOR_DEPTH_YCbCr444P:
if (planes)
*planes = 3;
if (bpc)
*bpc = 8;
return true;
case WIN_COLOR_DEPTH_YCrCb420SP:
case WIN_COLOR_DEPTH_YCbCr420SP:
case WIN_COLOR_DEPTH_YCrCb422SP:
case WIN_COLOR_DEPTH_YCbCr422SP:
case WIN_COLOR_DEPTH_YCrCb444SP:
case WIN_COLOR_DEPTH_YCbCr444SP:
if (planes)
*planes = 2;
if (bpc)
*bpc = 8;
return true;
}
if (planes)
*planes = 1;
return false;
}
static bool __drm_format_has_alpha(u32 format)
{
switch (format) {
case DRM_FORMAT_ARGB1555:
case DRM_FORMAT_RGBA5551:
case DRM_FORMAT_ABGR8888:
case DRM_FORMAT_ARGB8888:
return true;
}
return false;
}
static int tegra_plane_format_get_alpha(unsigned int opaque,
unsigned int *alpha)
{
if (tegra_plane_format_is_yuv(opaque, NULL, NULL)) {
*alpha = opaque;
return 0;
}
switch (opaque) {
case WIN_COLOR_DEPTH_B5G5R5X1:
*alpha = WIN_COLOR_DEPTH_B5G5R5A1;
return 0;
case WIN_COLOR_DEPTH_X1B5G5R5:
*alpha = WIN_COLOR_DEPTH_A1B5G5R5;
return 0;
case WIN_COLOR_DEPTH_R8G8B8X8:
*alpha = WIN_COLOR_DEPTH_R8G8B8A8;
return 0;
case WIN_COLOR_DEPTH_B8G8R8X8:
*alpha = WIN_COLOR_DEPTH_B8G8R8A8;
return 0;
case WIN_COLOR_DEPTH_B5G6R5:
*alpha = opaque;
return 0;
}
return -EINVAL;
}
/*
* This is applicable to Tegra20 and Tegra30 only where the opaque formats can
* be emulated using the alpha formats and alpha blending disabled.
*/
static int tegra_plane_setup_opacity(struct tegra_plane *tegra,
struct tegra_plane_state *state)
{
unsigned int format;
int err;
switch (state->format) {
case WIN_COLOR_DEPTH_B5G5R5A1:
case WIN_COLOR_DEPTH_A1B5G5R5:
case WIN_COLOR_DEPTH_R8G8B8A8:
case WIN_COLOR_DEPTH_B8G8R8A8:
state->opaque = false;
break;
default:
err = tegra_plane_format_get_alpha(state->format, &format);
if (err < 0)
return err;
state->format = format;
state->opaque = true;
break;
}
return 0;
}
static int tegra_plane_check_transparency(struct tegra_plane *tegra,
struct tegra_plane_state *state)
{
struct drm_plane_state *old, *plane_state;
struct drm_plane *plane;
old = drm_atomic_get_old_plane_state(state->base.state, &tegra->base);
/* check if zpos / transparency changed */
if (old->normalized_zpos == state->base.normalized_zpos &&
to_tegra_plane_state(old)->opaque == state->opaque)
return 0;
/* include all sibling planes into this commit */
drm_for_each_plane(plane, tegra->base.dev) {
struct tegra_plane *p = to_tegra_plane(plane);
/* skip this plane and planes on different CRTCs */
if (p == tegra || p->dc != tegra->dc)
continue;
plane_state = drm_atomic_get_plane_state(state->base.state,
plane);
if (IS_ERR(plane_state))
return PTR_ERR(plane_state);
}
return 1;
}
static unsigned int tegra_plane_get_overlap_index(struct tegra_plane *plane,
struct tegra_plane *other)
{
unsigned int index = 0, i;
WARN_ON(plane == other);
for (i = 0; i < 3; i++) {
if (i == plane->index)
continue;
if (i == other->index)
break;
index++;
}
return index;
}
static void tegra_plane_update_transparency(struct tegra_plane *tegra,
struct tegra_plane_state *state)
{
struct drm_plane_state *new;
struct drm_plane *plane;
unsigned int i;
for_each_new_plane_in_state(state->base.state, plane, new, i) {
struct tegra_plane *p = to_tegra_plane(plane);
unsigned index;
/* skip this plane and planes on different CRTCs */
if (p == tegra || p->dc != tegra->dc)
continue;
index = tegra_plane_get_overlap_index(tegra, p);
if (new->fb && __drm_format_has_alpha(new->fb->format->format))
state->blending[index].alpha = true;
else
state->blending[index].alpha = false;
if (new->normalized_zpos > state->base.normalized_zpos)
state->blending[index].top = true;
else
state->blending[index].top = false;
/*
* Missing framebuffer means that plane is disabled, in this
* case mark B / C window as top to be able to differentiate
* windows indices order in regards to zPos for the middle
* window X / Y registers programming.
*/
if (!new->fb)
state->blending[index].top = (index == 1);
}
}
static int tegra_plane_setup_transparency(struct tegra_plane *tegra,
struct tegra_plane_state *state)
{
struct tegra_plane_state *tegra_state;
struct drm_plane_state *new;
struct drm_plane *plane;
int err;
/*
* If planes zpos / transparency changed, sibling planes blending
* state may require adjustment and in this case they will be included
* into this atom commit, otherwise blending state is unchanged.
*/
err = tegra_plane_check_transparency(tegra, state);
if (err <= 0)
return err;
/*
* All planes are now in the atomic state, walk them up and update
* transparency state for each plane.
*/
drm_for_each_plane(plane, tegra->base.dev) {
struct tegra_plane *p = to_tegra_plane(plane);
/* skip planes on different CRTCs */
if (p->dc != tegra->dc)
continue;
new = drm_atomic_get_new_plane_state(state->base.state, plane);
tegra_state = to_tegra_plane_state(new);
/*
* There is no need to update blending state for the disabled
* plane.
*/
if (new->fb)
tegra_plane_update_transparency(p, tegra_state);
}
return 0;
}
int tegra_plane_setup_legacy_state(struct tegra_plane *tegra,
struct tegra_plane_state *state)
{
int err;
err = tegra_plane_setup_opacity(tegra, state);
if (err < 0)
return err;
err = tegra_plane_setup_transparency(tegra, state);
if (err < 0)
return err;
return 0;
}
static const char * const tegra_plane_icc_names[TEGRA_DC_LEGACY_PLANES_NUM] = {
"wina", "winb", "winc", NULL, NULL, NULL, "cursor",
};
int tegra_plane_interconnect_init(struct tegra_plane *plane)
{
const char *icc_name = tegra_plane_icc_names[plane->index];
struct device *dev = plane->dc->dev;
struct tegra_dc *dc = plane->dc;
int err;
if (WARN_ON(plane->index >= TEGRA_DC_LEGACY_PLANES_NUM) ||
WARN_ON(!tegra_plane_icc_names[plane->index]))
return -EINVAL;
plane->icc_mem = devm_of_icc_get(dev, icc_name);
err = PTR_ERR_OR_ZERO(plane->icc_mem);
if (err)
return dev_err_probe(dev, err, "failed to get %s interconnect\n",
icc_name);
/* plane B on T20/30 has a dedicated memory client for a 6-tap vertical filter */
if (plane->index == 1 && dc->soc->has_win_b_vfilter_mem_client) {
plane->icc_mem_vfilter = devm_of_icc_get(dev, "winb-vfilter");
err = PTR_ERR_OR_ZERO(plane->icc_mem_vfilter);
if (err)
return dev_err_probe(dev, err, "failed to get %s interconnect\n",
"winb-vfilter");
}
return 0;
}
+98
View File
@@ -0,0 +1,98 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved.
*/
#ifndef TEGRA_PLANE_H
#define TEGRA_PLANE_H 1
#include <drm/drm_plane.h>
struct icc_path;
struct tegra_bo;
struct tegra_dc;
struct tegra_plane {
struct drm_plane base;
struct tegra_dc *dc;
unsigned int offset;
unsigned int index;
struct icc_path *icc_mem;
struct icc_path *icc_mem_vfilter;
};
struct tegra_cursor {
struct tegra_plane base;
struct tegra_bo *bo;
unsigned int width;
unsigned int height;
};
static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane)
{
return container_of(plane, struct tegra_plane, base);
}
struct tegra_plane_legacy_blending_state {
bool alpha;
bool top;
};
struct tegra_plane_state {
struct drm_plane_state base;
struct host1x_bo_mapping *map[3];
dma_addr_t iova[3];
struct tegra_bo_tiling tiling;
u32 format;
u32 swap;
bool reflect_x;
bool reflect_y;
/* used for legacy blending support only */
struct tegra_plane_legacy_blending_state blending[2];
bool opaque;
/* bandwidths are in ICC units, i.e. kbytes/sec */
u32 total_peak_memory_bandwidth;
u32 peak_memory_bandwidth;
u32 avg_memory_bandwidth;
};
static inline struct tegra_plane_state *
to_tegra_plane_state(struct drm_plane_state *state)
{
if (state)
return container_of(state, struct tegra_plane_state, base);
return NULL;
}
static inline const struct tegra_plane_state *
to_const_tegra_plane_state(const struct drm_plane_state *state)
{
return to_tegra_plane_state((struct drm_plane_state *)state);
}
extern const struct drm_plane_funcs tegra_plane_funcs;
int tegra_plane_prepare_fb(struct drm_plane *plane,
struct drm_plane_state *state);
void tegra_plane_cleanup_fb(struct drm_plane *plane,
struct drm_plane_state *state);
int tegra_plane_state_add(struct tegra_plane *plane,
struct drm_plane_state *state);
int tegra_plane_format(u32 fourcc, u32 *format, u32 *swap);
bool tegra_plane_format_is_indexed(unsigned int format);
bool tegra_plane_format_is_yuv(unsigned int format, unsigned int *planes, unsigned int *bpc);
int tegra_plane_setup_legacy_state(struct tegra_plane *tegra,
struct tegra_plane_state *state);
int tegra_plane_interconnect_init(struct tegra_plane *plane);
#endif /* TEGRA_PLANE_H */
+382
View File
@@ -0,0 +1,382 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012 Avionic Design GmbH
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/of.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_simple_kms_helper.h>
#include "drm.h"
#include "dc.h"
struct tegra_rgb {
struct tegra_output output;
struct tegra_dc *dc;
struct clk *pll_d_out0;
struct clk *pll_d2_out0;
struct clk *clk_parent;
struct clk *clk;
};
static inline struct tegra_rgb *to_rgb(struct tegra_output *output)
{
return container_of(output, struct tegra_rgb, output);
}
struct reg_entry {
unsigned long offset;
unsigned long value;
};
static const struct reg_entry rgb_enable[] = {
{ DC_COM_PIN_OUTPUT_ENABLE(0), 0x00000000 },
{ DC_COM_PIN_OUTPUT_ENABLE(1), 0x00000000 },
{ DC_COM_PIN_OUTPUT_ENABLE(2), 0x00000000 },
{ DC_COM_PIN_OUTPUT_ENABLE(3), 0x00000000 },
{ DC_COM_PIN_OUTPUT_POLARITY(0), 0x00000000 },
{ DC_COM_PIN_OUTPUT_POLARITY(1), 0x01000000 },
{ DC_COM_PIN_OUTPUT_POLARITY(2), 0x00000000 },
{ DC_COM_PIN_OUTPUT_POLARITY(3), 0x00000000 },
{ DC_COM_PIN_OUTPUT_DATA(0), 0x00000000 },
{ DC_COM_PIN_OUTPUT_DATA(1), 0x00000000 },
{ DC_COM_PIN_OUTPUT_DATA(2), 0x00000000 },
{ DC_COM_PIN_OUTPUT_DATA(3), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(0), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(1), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(2), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(3), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(4), 0x00210222 },
{ DC_COM_PIN_OUTPUT_SELECT(5), 0x00002200 },
{ DC_COM_PIN_OUTPUT_SELECT(6), 0x00020000 },
};
static const struct reg_entry rgb_disable[] = {
{ DC_COM_PIN_OUTPUT_SELECT(6), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(5), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(4), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(3), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(2), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(1), 0x00000000 },
{ DC_COM_PIN_OUTPUT_SELECT(0), 0x00000000 },
{ DC_COM_PIN_OUTPUT_DATA(3), 0xaaaaaaaa },
{ DC_COM_PIN_OUTPUT_DATA(2), 0xaaaaaaaa },
{ DC_COM_PIN_OUTPUT_DATA(1), 0xaaaaaaaa },
{ DC_COM_PIN_OUTPUT_DATA(0), 0xaaaaaaaa },
{ DC_COM_PIN_OUTPUT_POLARITY(3), 0x00000000 },
{ DC_COM_PIN_OUTPUT_POLARITY(2), 0x00000000 },
{ DC_COM_PIN_OUTPUT_POLARITY(1), 0x00000000 },
{ DC_COM_PIN_OUTPUT_POLARITY(0), 0x00000000 },
{ DC_COM_PIN_OUTPUT_ENABLE(3), 0x55555555 },
{ DC_COM_PIN_OUTPUT_ENABLE(2), 0x55555555 },
{ DC_COM_PIN_OUTPUT_ENABLE(1), 0x55150005 },
{ DC_COM_PIN_OUTPUT_ENABLE(0), 0x55555555 },
};
static void tegra_dc_write_regs(struct tegra_dc *dc,
const struct reg_entry *table,
unsigned int num)
{
unsigned int i;
for (i = 0; i < num; i++)
tegra_dc_writel(dc, table[i].value, table[i].offset);
}
static void tegra_rgb_encoder_disable(struct drm_encoder *encoder)
{
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_rgb *rgb = to_rgb(output);
tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable));
tegra_dc_commit(rgb->dc);
}
static void tegra_rgb_encoder_enable(struct drm_encoder *encoder)
{
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_rgb *rgb = to_rgb(output);
u32 value;
tegra_dc_write_regs(rgb->dc, rgb_enable, ARRAY_SIZE(rgb_enable));
value = DE_SELECT_ACTIVE | DE_CONTROL_NORMAL;
tegra_dc_writel(rgb->dc, value, DC_DISP_DATA_ENABLE_OPTIONS);
/* configure H- and V-sync signal polarities */
value = tegra_dc_readl(rgb->dc, DC_COM_PIN_OUTPUT_POLARITY(1));
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
value |= LHS_OUTPUT_POLARITY_LOW;
else
value &= ~LHS_OUTPUT_POLARITY_LOW;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
value |= LVS_OUTPUT_POLARITY_LOW;
else
value &= ~LVS_OUTPUT_POLARITY_LOW;
tegra_dc_writel(rgb->dc, value, DC_COM_PIN_OUTPUT_POLARITY(1));
/* XXX: parameterize? */
value = DISP_DATA_FORMAT_DF1P1C | DISP_ALIGNMENT_MSB |
DISP_ORDER_RED_BLUE;
tegra_dc_writel(rgb->dc, value, DC_DISP_DISP_INTERFACE_CONTROL);
tegra_dc_commit(rgb->dc);
}
static bool tegra_rgb_pll_rate_change_allowed(struct tegra_rgb *rgb)
{
if (!rgb->pll_d2_out0)
return false;
if (!clk_is_match(rgb->clk_parent, rgb->pll_d_out0) &&
!clk_is_match(rgb->clk_parent, rgb->pll_d2_out0))
return false;
return true;
}
static int
tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct tegra_output *output = encoder_to_output(encoder);
struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
unsigned long pclk = crtc_state->mode.clock * 1000;
struct tegra_rgb *rgb = to_rgb(output);
unsigned int div;
int err;
/*
* We may not want to change the frequency of the parent clock, since
* it may be a parent for other peripherals. This is due to the fact
* that on Tegra20 there's only a single clock dedicated to display
* (pll_d_out0), whereas later generations have a second one that can
* be used to independently drive a second output (pll_d2_out0).
*
* As a way to support multiple outputs on Tegra20 as well, pll_p is
* typically used as the parent clock for the display controllers.
* But this comes at a cost: pll_p is the parent of several other
* peripherals, so its frequency shouldn't change out of the blue.
*
* The best we can do at this point is to use the shift clock divider
* and hope that the desired frequency can be matched (or at least
* matched sufficiently close that the panel will still work).
*/
if (tegra_rgb_pll_rate_change_allowed(rgb)) {
/*
* Set display controller clock to x2 of PCLK in order to
* produce higher resolution pulse positions.
*/
div = 2;
pclk *= 2;
} else {
div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2;
pclk = 0;
}
err = tegra_dc_state_setup_clock(dc, crtc_state, rgb->clk_parent,
pclk, div);
if (err < 0) {
dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
return err;
}
return err;
}
static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = {
.disable = tegra_rgb_encoder_disable,
.enable = tegra_rgb_encoder_enable,
.atomic_check = tegra_rgb_encoder_atomic_check,
};
static void tegra_dc_of_node_put(void *data)
{
of_node_put(data);
}
int tegra_dc_rgb_probe(struct tegra_dc *dc)
{
struct device_node *np;
struct tegra_rgb *rgb;
int err;
np = of_get_child_by_name(dc->dev->of_node, "rgb");
if (!np)
return -ENODEV;
err = devm_add_action_or_reset(dc->dev, tegra_dc_of_node_put, np);
if (err < 0)
return err;
if (!of_device_is_available(np))
return -ENODEV;
rgb = devm_kzalloc(dc->dev, sizeof(*rgb), GFP_KERNEL);
if (!rgb)
return -ENOMEM;
rgb->output.dev = dc->dev;
rgb->output.of_node = np;
rgb->dc = dc;
err = tegra_output_probe(&rgb->output);
if (err < 0)
return err;
rgb->clk = devm_clk_get(dc->dev, NULL);
if (IS_ERR(rgb->clk)) {
dev_err(dc->dev, "failed to get clock\n");
err = PTR_ERR(rgb->clk);
goto remove;
}
rgb->clk_parent = devm_clk_get(dc->dev, "parent");
if (IS_ERR(rgb->clk_parent)) {
dev_err(dc->dev, "failed to get parent clock\n");
err = PTR_ERR(rgb->clk_parent);
goto remove;
}
err = clk_set_parent(rgb->clk, rgb->clk_parent);
if (err < 0) {
dev_err(dc->dev, "failed to set parent clock: %d\n", err);
goto remove;
}
rgb->pll_d_out0 = clk_get_sys(NULL, "pll_d_out0");
if (IS_ERR(rgb->pll_d_out0)) {
err = PTR_ERR(rgb->pll_d_out0);
dev_err(dc->dev, "failed to get pll_d_out0: %d\n", err);
goto remove;
}
if (dc->soc->has_pll_d2_out0) {
rgb->pll_d2_out0 = clk_get_sys(NULL, "pll_d2_out0");
if (IS_ERR(rgb->pll_d2_out0)) {
err = PTR_ERR(rgb->pll_d2_out0);
dev_err(dc->dev, "failed to get pll_d2_out0: %d\n", err);
goto put_pll;
}
}
dc->rgb = &rgb->output;
return 0;
put_pll:
clk_put(rgb->pll_d_out0);
remove:
tegra_output_remove(&rgb->output);
return err;
}
void tegra_dc_rgb_remove(struct tegra_dc *dc)
{
struct tegra_rgb *rgb;
if (!dc->rgb)
return;
rgb = to_rgb(dc->rgb);
clk_put(rgb->pll_d2_out0);
clk_put(rgb->pll_d_out0);
tegra_output_remove(dc->rgb);
dc->rgb = NULL;
}
int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc)
{
struct tegra_output *output = dc->rgb;
struct drm_connector *connector;
int err;
if (!dc->rgb)
return -ENODEV;
drm_simple_encoder_init(drm, &output->encoder, DRM_MODE_ENCODER_LVDS);
drm_encoder_helper_add(&output->encoder,
&tegra_rgb_encoder_helper_funcs);
/*
* Wrap directly-connected panel into DRM bridge in order to let
* DRM core to handle panel for us.
*/
if (output->panel) {
output->bridge = devm_drm_panel_bridge_add(output->dev,
output->panel);
if (IS_ERR(output->bridge)) {
dev_err(output->dev,
"failed to wrap panel into bridge: %pe\n",
output->bridge);
return PTR_ERR(output->bridge);
}
output->panel = NULL;
}
/*
* Tegra devices that have LVDS panel utilize LVDS encoder bridge
* for converting up to 28 LCD LVTTL lanes into 5/4 LVDS lanes that
* go to display panel's receiver.
*
* Encoder usually have a power-down control which needs to be enabled
* in order to transmit data to the panel. Historically devices that
* use an older device-tree version didn't model the bridge, assuming
* that encoder is turned ON by default, while today's DRM allows us
* to model LVDS encoder properly.
*
* Newer device-trees utilize LVDS encoder bridge, which provides
* us with a connector and handles the display panel.
*
* For older device-trees we wrapped panel into the panel-bridge.
*/
if (output->bridge) {
err = drm_bridge_attach(&output->encoder, output->bridge,
NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (err)
return err;
connector = drm_bridge_connector_init(drm, &output->encoder);
if (IS_ERR(connector)) {
dev_err(output->dev,
"failed to initialize bridge connector: %pe\n",
connector);
return PTR_ERR(connector);
}
}
err = tegra_output_init(drm, output);
if (err < 0) {
dev_err(output->dev, "failed to initialize output: %d\n", err);
return err;
}
/*
* Other outputs can be attached to either display controller. The RGB
* outputs are an exception and work only with their parent display
* controller.
*/
output->encoder.possible_crtcs = drm_crtc_mask(&dc->base);
return 0;
}
int tegra_dc_rgb_exit(struct tegra_dc *dc)
{
if (dc->rgb)
tegra_output_exit(dc->rgb);
return 0;
}
+106
View File
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022, NVIDIA Corporation.
*/
#include <linux/dev_printk.h>
#include <linux/device.h>
#include <linux/iopoll.h>
#include <linux/of.h>
#include "riscv.h"
#define RISCV_CPUCTL 0x4388
#define RISCV_CPUCTL_STARTCPU_TRUE (1 << 0)
#define RISCV_BR_RETCODE 0x465c
#define RISCV_BR_RETCODE_RESULT_V(x) ((x) & 0x3)
#define RISCV_BR_RETCODE_RESULT_PASS_V 3
#define RISCV_BCR_CTRL 0x4668
#define RISCV_BCR_CTRL_CORE_SELECT_RISCV (1 << 4)
#define RISCV_BCR_DMACFG 0x466c
#define RISCV_BCR_DMACFG_TARGET_LOCAL_FB (0 << 0)
#define RISCV_BCR_DMACFG_LOCK_LOCKED (1 << 31)
#define RISCV_BCR_DMAADDR_PKCPARAM_LO 0x4670
#define RISCV_BCR_DMAADDR_PKCPARAM_HI 0x4674
#define RISCV_BCR_DMAADDR_FMCCODE_LO 0x4678
#define RISCV_BCR_DMAADDR_FMCCODE_HI 0x467c
#define RISCV_BCR_DMAADDR_FMCDATA_LO 0x4680
#define RISCV_BCR_DMAADDR_FMCDATA_HI 0x4684
#define RISCV_BCR_DMACFG_SEC 0x4694
#define RISCV_BCR_DMACFG_SEC_GSCID(v) ((v) << 16)
static void riscv_writel(struct tegra_drm_riscv *riscv, u32 value, u32 offset)
{
writel(value, riscv->regs + offset);
}
int tegra_drm_riscv_read_descriptors(struct tegra_drm_riscv *riscv)
{
struct tegra_drm_riscv_descriptor *bl = &riscv->bl_desc;
struct tegra_drm_riscv_descriptor *os = &riscv->os_desc;
const struct device_node *np = riscv->dev->of_node;
int err;
#define READ_PROP(name, location) \
err = of_property_read_u32(np, name, location); \
if (err) { \
dev_err(riscv->dev, "failed to read " name ": %d\n", err); \
return err; \
}
READ_PROP("nvidia,bl-manifest-offset", &bl->manifest_offset);
READ_PROP("nvidia,bl-code-offset", &bl->code_offset);
READ_PROP("nvidia,bl-data-offset", &bl->data_offset);
READ_PROP("nvidia,os-manifest-offset", &os->manifest_offset);
READ_PROP("nvidia,os-code-offset", &os->code_offset);
READ_PROP("nvidia,os-data-offset", &os->data_offset);
#undef READ_PROP
if (bl->manifest_offset == 0 && bl->code_offset == 0 &&
bl->data_offset == 0 && os->manifest_offset == 0 &&
os->code_offset == 0 && os->data_offset == 0) {
dev_err(riscv->dev, "descriptors not available\n");
return -EINVAL;
}
return 0;
}
int tegra_drm_riscv_boot_bootrom(struct tegra_drm_riscv *riscv, phys_addr_t image_address,
u32 gscid, const struct tegra_drm_riscv_descriptor *desc)
{
phys_addr_t addr;
int err;
u32 val;
riscv_writel(riscv, RISCV_BCR_CTRL_CORE_SELECT_RISCV, RISCV_BCR_CTRL);
addr = image_address + desc->manifest_offset;
riscv_writel(riscv, lower_32_bits(addr >> 8), RISCV_BCR_DMAADDR_PKCPARAM_LO);
riscv_writel(riscv, upper_32_bits(addr >> 8), RISCV_BCR_DMAADDR_PKCPARAM_HI);
addr = image_address + desc->code_offset;
riscv_writel(riscv, lower_32_bits(addr >> 8), RISCV_BCR_DMAADDR_FMCCODE_LO);
riscv_writel(riscv, upper_32_bits(addr >> 8), RISCV_BCR_DMAADDR_FMCCODE_HI);
addr = image_address + desc->data_offset;
riscv_writel(riscv, lower_32_bits(addr >> 8), RISCV_BCR_DMAADDR_FMCDATA_LO);
riscv_writel(riscv, upper_32_bits(addr >> 8), RISCV_BCR_DMAADDR_FMCDATA_HI);
riscv_writel(riscv, RISCV_BCR_DMACFG_SEC_GSCID(gscid), RISCV_BCR_DMACFG_SEC);
riscv_writel(riscv,
RISCV_BCR_DMACFG_TARGET_LOCAL_FB | RISCV_BCR_DMACFG_LOCK_LOCKED, RISCV_BCR_DMACFG);
riscv_writel(riscv, RISCV_CPUCTL_STARTCPU_TRUE, RISCV_CPUCTL);
err = readl_poll_timeout(
riscv->regs + RISCV_BR_RETCODE, val,
RISCV_BR_RETCODE_RESULT_V(val) == RISCV_BR_RETCODE_RESULT_PASS_V,
10, 100000);
if (err) {
dev_err(riscv->dev, "error during bootrom execution. BR_RETCODE=%d\n", val);
return err;
}
return 0;
}
+30
View File
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2022, NVIDIA Corporation.
*/
#ifndef DRM_TEGRA_RISCV_H
#define DRM_TEGRA_RISCV_H
struct tegra_drm_riscv_descriptor {
u32 manifest_offset;
u32 code_offset;
u32 code_size;
u32 data_offset;
u32 data_size;
};
struct tegra_drm_riscv {
/* User initializes */
struct device *dev;
void __iomem *regs;
struct tegra_drm_riscv_descriptor bl_desc;
struct tegra_drm_riscv_descriptor os_desc;
};
int tegra_drm_riscv_read_descriptors(struct tegra_drm_riscv *riscv);
int tegra_drm_riscv_boot_bootrom(struct tegra_drm_riscv *riscv, phys_addr_t image_address,
u32 gscid, const struct tegra_drm_riscv_descriptor *desc);
#endif
File diff suppressed because it is too large Load Diff
+457
View File
@@ -0,0 +1,457 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2013 NVIDIA Corporation
*/
#ifndef DRM_TEGRA_SOR_H
#define DRM_TEGRA_SOR_H
#define SOR_CTXSW 0x00
#define SOR_SUPER_STATE0 0x01
#define SOR_SUPER_STATE1 0x02
#define SOR_SUPER_STATE_ATTACHED (1 << 3)
#define SOR_SUPER_STATE_MODE_NORMAL (1 << 2)
#define SOR_SUPER_STATE_HEAD_MODE_MASK (3 << 0)
#define SOR_SUPER_STATE_HEAD_MODE_AWAKE (2 << 0)
#define SOR_SUPER_STATE_HEAD_MODE_SNOOZE (1 << 0)
#define SOR_SUPER_STATE_HEAD_MODE_SLEEP (0 << 0)
#define SOR_STATE0 0x03
#define SOR_STATE1 0x04
#define SOR_STATE_ASY_PIXELDEPTH_MASK (0xf << 17)
#define SOR_STATE_ASY_PIXELDEPTH_BPP_18_444 (0x2 << 17)
#define SOR_STATE_ASY_PIXELDEPTH_BPP_24_444 (0x5 << 17)
#define SOR_STATE_ASY_PIXELDEPTH_BPP_30_444 (0x6 << 17)
#define SOR_STATE_ASY_PIXELDEPTH_BPP_36_444 (0x8 << 17)
#define SOR_STATE_ASY_PIXELDEPTH_BPP_48_444 (0x9 << 17)
#define SOR_STATE_ASY_VSYNCPOL (1 << 13)
#define SOR_STATE_ASY_HSYNCPOL (1 << 12)
#define SOR_STATE_ASY_PROTOCOL_MASK (0xf << 8)
#define SOR_STATE_ASY_PROTOCOL_CUSTOM (0xf << 8)
#define SOR_STATE_ASY_PROTOCOL_DP_A (0x8 << 8)
#define SOR_STATE_ASY_PROTOCOL_DP_B (0x9 << 8)
#define SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A (0x1 << 8)
#define SOR_STATE_ASY_PROTOCOL_LVDS (0x0 << 8)
#define SOR_STATE_ASY_CRC_MODE_MASK (0x3 << 6)
#define SOR_STATE_ASY_CRC_MODE_NON_ACTIVE (0x2 << 6)
#define SOR_STATE_ASY_CRC_MODE_COMPLETE (0x1 << 6)
#define SOR_STATE_ASY_CRC_MODE_ACTIVE (0x0 << 6)
#define SOR_STATE_ASY_SUBOWNER_MASK (0x3 << 4)
#define SOR_STATE_ASY_OWNER_MASK 0xf
#define SOR_STATE_ASY_OWNER(x) (((x) & 0xf) << 0)
#define SOR_HEAD_STATE0(x) (0x05 + (x))
#define SOR_HEAD_STATE_RANGECOMPRESS_MASK (0x1 << 3)
#define SOR_HEAD_STATE_DYNRANGE_MASK (0x1 << 2)
#define SOR_HEAD_STATE_DYNRANGE_VESA (0 << 2)
#define SOR_HEAD_STATE_DYNRANGE_CEA (1 << 2)
#define SOR_HEAD_STATE_COLORSPACE_MASK (0x3 << 0)
#define SOR_HEAD_STATE_COLORSPACE_RGB (0 << 0)
#define SOR_HEAD_STATE1(x) (0x07 + (x))
#define SOR_HEAD_STATE2(x) (0x09 + (x))
#define SOR_HEAD_STATE3(x) (0x0b + (x))
#define SOR_HEAD_STATE4(x) (0x0d + (x))
#define SOR_HEAD_STATE5(x) (0x0f + (x))
#define SOR_CRC_CNTRL 0x11
#define SOR_CRC_CNTRL_ENABLE (1 << 0)
#define SOR_DP_DEBUG_MVID 0x12
#define SOR_CLK_CNTRL 0x13
#define SOR_CLK_CNTRL_DP_LINK_SPEED_MASK (0x1f << 2)
#define SOR_CLK_CNTRL_DP_LINK_SPEED(x) (((x) & 0x1f) << 2)
#define SOR_CLK_CNTRL_DP_LINK_SPEED_G1_62 (0x06 << 2)
#define SOR_CLK_CNTRL_DP_LINK_SPEED_G2_70 (0x0a << 2)
#define SOR_CLK_CNTRL_DP_LINK_SPEED_G5_40 (0x14 << 2)
#define SOR_CLK_CNTRL_DP_CLK_SEL_MASK (3 << 0)
#define SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK (0 << 0)
#define SOR_CLK_CNTRL_DP_CLK_SEL_DIFF_PCLK (1 << 0)
#define SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK (2 << 0)
#define SOR_CLK_CNTRL_DP_CLK_SEL_DIFF_DPCLK (3 << 0)
#define SOR_CAP 0x14
#define SOR_PWR 0x15
#define SOR_PWR_TRIGGER (1 << 31)
#define SOR_PWR_MODE_SAFE (1 << 28)
#define SOR_PWR_NORMAL_STATE_PU (1 << 0)
#define SOR_TEST 0x16
#define SOR_TEST_CRC_POST_SERIALIZE (1 << 23)
#define SOR_TEST_ATTACHED (1 << 10)
#define SOR_TEST_HEAD_MODE_MASK (3 << 8)
#define SOR_TEST_HEAD_MODE_AWAKE (2 << 8)
#define SOR_PLL0 0x17
#define SOR_PLL0_ICHPMP_MASK (0xf << 24)
#define SOR_PLL0_ICHPMP(x) (((x) & 0xf) << 24)
#define SOR_PLL0_FILTER_MASK (0xf << 16)
#define SOR_PLL0_FILTER(x) (((x) & 0xf) << 16)
#define SOR_PLL0_VCOCAP_MASK (0xf << 8)
#define SOR_PLL0_VCOCAP(x) (((x) & 0xf) << 8)
#define SOR_PLL0_VCOCAP_RST SOR_PLL0_VCOCAP(3)
#define SOR_PLL0_PLLREG_MASK (0x3 << 6)
#define SOR_PLL0_PLLREG_LEVEL(x) (((x) & 0x3) << 6)
#define SOR_PLL0_PLLREG_LEVEL_V25 SOR_PLL0_PLLREG_LEVEL(0)
#define SOR_PLL0_PLLREG_LEVEL_V15 SOR_PLL0_PLLREG_LEVEL(1)
#define SOR_PLL0_PLLREG_LEVEL_V35 SOR_PLL0_PLLREG_LEVEL(2)
#define SOR_PLL0_PLLREG_LEVEL_V45 SOR_PLL0_PLLREG_LEVEL(3)
#define SOR_PLL0_PULLDOWN (1 << 5)
#define SOR_PLL0_RESISTOR_EXT (1 << 4)
#define SOR_PLL0_VCOPD (1 << 2)
#define SOR_PLL0_PWR (1 << 0)
#define SOR_PLL1 0x18
/* XXX: read-only bit? */
#define SOR_PLL1_LOADADJ_MASK (0xf << 20)
#define SOR_PLL1_LOADADJ(x) (((x) & 0xf) << 20)
#define SOR_PLL1_TERM_COMPOUT (1 << 15)
#define SOR_PLL1_TMDS_TERMADJ_MASK (0xf << 9)
#define SOR_PLL1_TMDS_TERMADJ(x) (((x) & 0xf) << 9)
#define SOR_PLL1_TMDS_TERM (1 << 8)
#define SOR_PLL2 0x19
#define SOR_PLL2_LVDS_ENABLE (1 << 25)
#define SOR_PLL2_SEQ_PLLCAPPD_ENFORCE (1 << 24)
#define SOR_PLL2_PORT_POWERDOWN (1 << 23)
#define SOR_PLL2_BANDGAP_POWERDOWN (1 << 22)
#define SOR_PLL2_POWERDOWN_OVERRIDE (1 << 18)
#define SOR_PLL2_SEQ_PLLCAPPD (1 << 17)
#define SOR_PLL2_SEQ_PLL_PULLDOWN (1 << 16)
#define SOR_PLL3 0x1a
#define SOR_PLL3_BG_TEMP_COEF_MASK (0xf << 28)
#define SOR_PLL3_BG_TEMP_COEF(x) (((x) & 0xf) << 28)
#define SOR_PLL3_BG_VREF_LEVEL_MASK (0xf << 24)
#define SOR_PLL3_BG_VREF_LEVEL(x) (((x) & 0xf) << 24)
#define SOR_PLL3_PLL_VDD_MODE_1V8 (0 << 13)
#define SOR_PLL3_PLL_VDD_MODE_3V3 (1 << 13)
#define SOR_PLL3_AVDD10_LEVEL_MASK (0xf << 8)
#define SOR_PLL3_AVDD10_LEVEL(x) (((x) & 0xf) << 8)
#define SOR_PLL3_AVDD14_LEVEL_MASK (0xf << 4)
#define SOR_PLL3_AVDD14_LEVEL(x) (((x) & 0xf) << 4)
#define SOR_CSTM 0x1b
#define SOR_CSTM_ROTCLK_MASK (0xf << 24)
#define SOR_CSTM_ROTCLK(x) (((x) & 0xf) << 24)
#define SOR_CSTM_LVDS (1 << 16)
#define SOR_CSTM_LINK_ACT_B (1 << 15)
#define SOR_CSTM_LINK_ACT_A (1 << 14)
#define SOR_CSTM_UPPER (1 << 11)
#define SOR_LVDS 0x1c
#define SOR_CRCA 0x1d
#define SOR_CRCA_VALID (1 << 0)
#define SOR_CRCA_RESET (1 << 0)
#define SOR_CRCB 0x1e
#define SOR_BLANK 0x1f
#define SOR_SEQ_CTL 0x20
#define SOR_SEQ_CTL_PD_PC_ALT(x) (((x) & 0xf) << 12)
#define SOR_SEQ_CTL_PD_PC(x) (((x) & 0xf) << 8)
#define SOR_SEQ_CTL_PU_PC_ALT(x) (((x) & 0xf) << 4)
#define SOR_SEQ_CTL_PU_PC(x) (((x) & 0xf) << 0)
#define SOR_LANE_SEQ_CTL 0x21
#define SOR_LANE_SEQ_CTL_TRIGGER (1 << 31)
#define SOR_LANE_SEQ_CTL_STATE_BUSY (1 << 28)
#define SOR_LANE_SEQ_CTL_SEQUENCE_UP (0 << 20)
#define SOR_LANE_SEQ_CTL_SEQUENCE_DOWN (1 << 20)
#define SOR_LANE_SEQ_CTL_POWER_STATE_UP (0 << 16)
#define SOR_LANE_SEQ_CTL_POWER_STATE_DOWN (1 << 16)
#define SOR_LANE_SEQ_CTL_DELAY(x) (((x) & 0xf) << 12)
#define SOR_SEQ_INST(x) (0x22 + (x))
#define SOR_SEQ_INST_PLL_PULLDOWN (1 << 31)
#define SOR_SEQ_INST_POWERDOWN_MACRO (1 << 30)
#define SOR_SEQ_INST_ASSERT_PLL_RESET (1 << 29)
#define SOR_SEQ_INST_BLANK_V (1 << 28)
#define SOR_SEQ_INST_BLANK_H (1 << 27)
#define SOR_SEQ_INST_BLANK_DE (1 << 26)
#define SOR_SEQ_INST_BLACK_DATA (1 << 25)
#define SOR_SEQ_INST_TRISTATE_IOS (1 << 24)
#define SOR_SEQ_INST_DRIVE_PWM_OUT_LO (1 << 23)
#define SOR_SEQ_INST_PIN_B_LOW (0 << 22)
#define SOR_SEQ_INST_PIN_B_HIGH (1 << 22)
#define SOR_SEQ_INST_PIN_A_LOW (0 << 21)
#define SOR_SEQ_INST_PIN_A_HIGH (1 << 21)
#define SOR_SEQ_INST_SEQUENCE_UP (0 << 19)
#define SOR_SEQ_INST_SEQUENCE_DOWN (1 << 19)
#define SOR_SEQ_INST_LANE_SEQ_STOP (0 << 18)
#define SOR_SEQ_INST_LANE_SEQ_RUN (1 << 18)
#define SOR_SEQ_INST_PORT_POWERDOWN (1 << 17)
#define SOR_SEQ_INST_PLL_POWERDOWN (1 << 16)
#define SOR_SEQ_INST_HALT (1 << 15)
#define SOR_SEQ_INST_WAIT_US (0 << 12)
#define SOR_SEQ_INST_WAIT_MS (1 << 12)
#define SOR_SEQ_INST_WAIT_VSYNC (2 << 12)
#define SOR_SEQ_INST_WAIT(x) (((x) & 0x3ff) << 0)
#define SOR_PWM_DIV 0x32
#define SOR_PWM_DIV_MASK 0xffffff
#define SOR_PWM_CTL 0x33
#define SOR_PWM_CTL_TRIGGER (1 << 31)
#define SOR_PWM_CTL_CLK_SEL (1 << 30)
#define SOR_PWM_CTL_DUTY_CYCLE_MASK 0xffffff
#define SOR_VCRC_A0 0x34
#define SOR_VCRC_A1 0x35
#define SOR_VCRC_B0 0x36
#define SOR_VCRC_B1 0x37
#define SOR_CCRC_A0 0x38
#define SOR_CCRC_A1 0x39
#define SOR_CCRC_B0 0x3a
#define SOR_CCRC_B1 0x3b
#define SOR_EDATA_A0 0x3c
#define SOR_EDATA_A1 0x3d
#define SOR_EDATA_B0 0x3e
#define SOR_EDATA_B1 0x3f
#define SOR_COUNT_A0 0x40
#define SOR_COUNT_A1 0x41
#define SOR_COUNT_B0 0x42
#define SOR_COUNT_B1 0x43
#define SOR_DEBUG_A0 0x44
#define SOR_DEBUG_A1 0x45
#define SOR_DEBUG_B0 0x46
#define SOR_DEBUG_B1 0x47
#define SOR_TRIG 0x48
#define SOR_MSCHECK 0x49
#define SOR_XBAR_CTRL 0x4a
#define SOR_XBAR_CTRL_LINK1_XSEL(channel, value) ((((value) & 0x7) << ((channel) * 3)) << 17)
#define SOR_XBAR_CTRL_LINK0_XSEL(channel, value) ((((value) & 0x7) << ((channel) * 3)) << 2)
#define SOR_XBAR_CTRL_LINK_SWAP (1 << 1)
#define SOR_XBAR_CTRL_BYPASS (1 << 0)
#define SOR_XBAR_POL 0x4b
#define SOR_DP_LINKCTL0 0x4c
#define SOR_DP_LINKCTL_LANE_COUNT_MASK (0x1f << 16)
#define SOR_DP_LINKCTL_LANE_COUNT(x) (((1 << (x)) - 1) << 16)
#define SOR_DP_LINKCTL_ENHANCED_FRAME (1 << 14)
#define SOR_DP_LINKCTL_TU_SIZE_MASK (0x7f << 2)
#define SOR_DP_LINKCTL_TU_SIZE(x) (((x) & 0x7f) << 2)
#define SOR_DP_LINKCTL_ENABLE (1 << 0)
#define SOR_DP_LINKCTL1 0x4d
#define SOR_LANE_DRIVE_CURRENT0 0x4e
#define SOR_LANE_DRIVE_CURRENT1 0x4f
#define SOR_LANE4_DRIVE_CURRENT0 0x50
#define SOR_LANE4_DRIVE_CURRENT1 0x51
#define SOR_LANE_DRIVE_CURRENT_LANE3(x) (((x) & 0xff) << 24)
#define SOR_LANE_DRIVE_CURRENT_LANE2(x) (((x) & 0xff) << 16)
#define SOR_LANE_DRIVE_CURRENT_LANE1(x) (((x) & 0xff) << 8)
#define SOR_LANE_DRIVE_CURRENT_LANE0(x) (((x) & 0xff) << 0)
#define SOR_LANE_PREEMPHASIS0 0x52
#define SOR_LANE_PREEMPHASIS1 0x53
#define SOR_LANE4_PREEMPHASIS0 0x54
#define SOR_LANE4_PREEMPHASIS1 0x55
#define SOR_LANE_PREEMPHASIS_LANE3(x) (((x) & 0xff) << 24)
#define SOR_LANE_PREEMPHASIS_LANE2(x) (((x) & 0xff) << 16)
#define SOR_LANE_PREEMPHASIS_LANE1(x) (((x) & 0xff) << 8)
#define SOR_LANE_PREEMPHASIS_LANE0(x) (((x) & 0xff) << 0)
#define SOR_LANE_POSTCURSOR0 0x56
#define SOR_LANE_POSTCURSOR1 0x57
#define SOR_LANE_POSTCURSOR_LANE3(x) (((x) & 0xff) << 24)
#define SOR_LANE_POSTCURSOR_LANE2(x) (((x) & 0xff) << 16)
#define SOR_LANE_POSTCURSOR_LANE1(x) (((x) & 0xff) << 8)
#define SOR_LANE_POSTCURSOR_LANE0(x) (((x) & 0xff) << 0)
#define SOR_DP_CONFIG0 0x58
#define SOR_DP_CONFIG_DISPARITY_NEGATIVE (1 << 31)
#define SOR_DP_CONFIG_ACTIVE_SYM_ENABLE (1 << 26)
#define SOR_DP_CONFIG_ACTIVE_SYM_POLARITY (1 << 24)
#define SOR_DP_CONFIG_ACTIVE_SYM_FRAC_MASK (0xf << 16)
#define SOR_DP_CONFIG_ACTIVE_SYM_FRAC(x) (((x) & 0xf) << 16)
#define SOR_DP_CONFIG_ACTIVE_SYM_COUNT_MASK (0x7f << 8)
#define SOR_DP_CONFIG_ACTIVE_SYM_COUNT(x) (((x) & 0x7f) << 8)
#define SOR_DP_CONFIG_WATERMARK_MASK (0x3f << 0)
#define SOR_DP_CONFIG_WATERMARK(x) (((x) & 0x3f) << 0)
#define SOR_DP_CONFIG1 0x59
#define SOR_DP_MN0 0x5a
#define SOR_DP_MN1 0x5b
#define SOR_DP_PADCTL0 0x5c
#define SOR_DP_PADCTL_PAD_CAL_PD (1 << 23)
#define SOR_DP_PADCTL_TX_PU_ENABLE (1 << 22)
#define SOR_DP_PADCTL_TX_PU_MASK (0xff << 8)
#define SOR_DP_PADCTL_TX_PU(x) (((x) & 0xff) << 8)
#define SOR_DP_PADCTL_CM_TXD_3 (1 << 7)
#define SOR_DP_PADCTL_CM_TXD_2 (1 << 6)
#define SOR_DP_PADCTL_CM_TXD_1 (1 << 5)
#define SOR_DP_PADCTL_CM_TXD_0 (1 << 4)
#define SOR_DP_PADCTL_CM_TXD(x) (1 << (4 + (x)))
#define SOR_DP_PADCTL_PD_TXD_3 (1 << 3)
#define SOR_DP_PADCTL_PD_TXD_0 (1 << 2)
#define SOR_DP_PADCTL_PD_TXD_1 (1 << 1)
#define SOR_DP_PADCTL_PD_TXD_2 (1 << 0)
#define SOR_DP_PADCTL_PD_TXD(x) (1 << (0 + (x)))
#define SOR_DP_PADCTL1 0x5d
#define SOR_DP_DEBUG0 0x5e
#define SOR_DP_DEBUG1 0x5f
#define SOR_DP_SPARE0 0x60
#define SOR_DP_SPARE_DISP_VIDEO_PREAMBLE (1 << 3)
#define SOR_DP_SPARE_MACRO_SOR_CLK (1 << 2)
#define SOR_DP_SPARE_PANEL_INTERNAL (1 << 1)
#define SOR_DP_SPARE_SEQ_ENABLE (1 << 0)
#define SOR_DP_SPARE1 0x61
#define SOR_DP_AUDIO_CTRL 0x62
#define SOR_DP_AUDIO_HBLANK_SYMBOLS 0x63
#define SOR_DP_AUDIO_HBLANK_SYMBOLS_MASK (0x01ffff << 0)
#define SOR_DP_AUDIO_VBLANK_SYMBOLS 0x64
#define SOR_DP_AUDIO_VBLANK_SYMBOLS_MASK (0x1fffff << 0)
#define SOR_DP_GENERIC_INFOFRAME_HEADER 0x65
#define SOR_DP_GENERIC_INFOFRAME_SUBPACK0 0x66
#define SOR_DP_GENERIC_INFOFRAME_SUBPACK1 0x67
#define SOR_DP_GENERIC_INFOFRAME_SUBPACK2 0x68
#define SOR_DP_GENERIC_INFOFRAME_SUBPACK3 0x69
#define SOR_DP_GENERIC_INFOFRAME_SUBPACK4 0x6a
#define SOR_DP_GENERIC_INFOFRAME_SUBPACK5 0x6b
#define SOR_DP_GENERIC_INFOFRAME_SUBPACK6 0x6c
#define SOR_DP_TPG 0x6d
#define SOR_DP_TPG_CHANNEL_CODING (1 << 6)
#define SOR_DP_TPG_SCRAMBLER_MASK (3 << 4)
#define SOR_DP_TPG_SCRAMBLER_FIBONACCI (2 << 4)
#define SOR_DP_TPG_SCRAMBLER_GALIOS (1 << 4)
#define SOR_DP_TPG_SCRAMBLER_NONE (0 << 4)
#define SOR_DP_TPG_PATTERN_MASK (0xf << 0)
#define SOR_DP_TPG_PATTERN_HBR2 (0x8 << 0)
#define SOR_DP_TPG_PATTERN_CSTM (0x7 << 0)
#define SOR_DP_TPG_PATTERN_PRBS7 (0x6 << 0)
#define SOR_DP_TPG_PATTERN_SBLERRRATE (0x5 << 0)
#define SOR_DP_TPG_PATTERN_D102 (0x4 << 0)
#define SOR_DP_TPG_PATTERN_TRAIN3 (0x3 << 0)
#define SOR_DP_TPG_PATTERN_TRAIN2 (0x2 << 0)
#define SOR_DP_TPG_PATTERN_TRAIN1 (0x1 << 0)
#define SOR_DP_TPG_PATTERN_NONE (0x0 << 0)
#define SOR_DP_TPG_CONFIG 0x6e
#define SOR_DP_LQ_CSTM0 0x6f
#define SOR_DP_LQ_CSTM1 0x70
#define SOR_DP_LQ_CSTM2 0x71
#define SOR_DP_PADCTL2 0x73
#define SOR_DP_PADCTL_SPAREPLL_MASK (0xff << 24)
#define SOR_DP_PADCTL_SPAREPLL(x) (((x) & 0xff) << 24)
#define SOR_HDMI_AUDIO_INFOFRAME_CTRL 0x9a
#define SOR_HDMI_AUDIO_INFOFRAME_STATUS 0x9b
#define SOR_HDMI_AUDIO_INFOFRAME_HEADER 0x9c
#define SOR_HDMI_AVI_INFOFRAME_CTRL 0x9f
#define INFOFRAME_CTRL_CHECKSUM_ENABLE (1 << 9)
#define INFOFRAME_CTRL_SINGLE (1 << 8)
#define INFOFRAME_CTRL_OTHER (1 << 4)
#define INFOFRAME_CTRL_ENABLE (1 << 0)
#define SOR_HDMI_AVI_INFOFRAME_STATUS 0xa0
#define INFOFRAME_STATUS_DONE (1 << 0)
#define SOR_HDMI_AVI_INFOFRAME_HEADER 0xa1
#define INFOFRAME_HEADER_LEN(x) (((x) & 0xff) << 16)
#define INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8)
#define INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0)
#define SOR_HDMI_ACR_CTRL 0xb1
#define SOR_HDMI_ACR_0320_SUBPACK_LOW 0xb2
#define SOR_HDMI_ACR_SUBPACK_LOW_SB1(x) (((x) & 0xff) << 24)
#define SOR_HDMI_ACR_0320_SUBPACK_HIGH 0xb3
#define SOR_HDMI_ACR_SUBPACK_HIGH_ENABLE (1 << 31)
#define SOR_HDMI_ACR_0441_SUBPACK_LOW 0xb4
#define SOR_HDMI_ACR_0441_SUBPACK_HIGH 0xb5
#define SOR_HDMI_CTRL 0xc0
#define SOR_HDMI_CTRL_ENABLE (1 << 30)
#define SOR_HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16)
#define SOR_HDMI_CTRL_AUDIO_LAYOUT (1 << 10)
#define SOR_HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0)
#define SOR_HDMI_SPARE 0xcb
#define SOR_HDMI_SPARE_ACR_PRIORITY_HIGH (1 << 31)
#define SOR_HDMI_SPARE_CTS_RESET(x) (((x) & 0x7) << 16)
#define SOR_HDMI_SPARE_HW_CTS_ENABLE (1 << 0)
#define SOR_REFCLK 0xe6
#define SOR_REFCLK_DIV_INT(x) ((((x) >> 2) & 0xff) << 8)
#define SOR_REFCLK_DIV_FRAC(x) (((x) & 0x3) << 6)
#define SOR_INPUT_CONTROL 0xe8
#define SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED (1 << 1)
#define SOR_INPUT_CONTROL_HDMI_SRC_SELECT(x) (((x) & 0x1) << 0)
#define SOR_AUDIO_CNTRL 0xfc
#define SOR_AUDIO_CNTRL_INJECT_NULLSMPL (1 << 29)
#define SOR_AUDIO_CNTRL_SOURCE_SELECT(x) (((x) & 0x3) << 20)
#define SOURCE_SELECT_MASK 0x3
#define SOURCE_SELECT_HDA 0x2
#define SOURCE_SELECT_SPDIF 0x1
#define SOURCE_SELECT_AUTO 0x0
#define SOR_AUDIO_CNTRL_AFIFO_FLUSH (1 << 12)
#define SOR_AUDIO_SPARE 0xfe
#define SOR_AUDIO_SPARE_HBR_ENABLE (1 << 27)
#define SOR_AUDIO_NVAL_0320 0xff
#define SOR_AUDIO_NVAL_0441 0x100
#define SOR_AUDIO_NVAL_0882 0x101
#define SOR_AUDIO_NVAL_1764 0x102
#define SOR_AUDIO_NVAL_0480 0x103
#define SOR_AUDIO_NVAL_0960 0x104
#define SOR_AUDIO_NVAL_1920 0x105
#define SOR_AUDIO_HDA_CODEC_SCRATCH0 0x10a
#define SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID (1 << 30)
#define SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff
#define SOR_AUDIO_HDA_ELD_BUFWR 0x10c
#define SOR_AUDIO_HDA_ELD_BUFWR_INDEX(x) (((x) & 0xff) << 8)
#define SOR_AUDIO_HDA_ELD_BUFWR_DATA(x) (((x) & 0xff) << 0)
#define SOR_AUDIO_HDA_PRESENSE 0x10d
#define SOR_AUDIO_HDA_PRESENSE_ELDV (1 << 1)
#define SOR_AUDIO_HDA_PRESENSE_PD (1 << 0)
#define SOR_AUDIO_AVAL_0320 0x10f
#define SOR_AUDIO_AVAL_0441 0x110
#define SOR_AUDIO_AVAL_0882 0x111
#define SOR_AUDIO_AVAL_1764 0x112
#define SOR_AUDIO_AVAL_0480 0x113
#define SOR_AUDIO_AVAL_0960 0x114
#define SOR_AUDIO_AVAL_1920 0x115
#define SOR_INT_STATUS 0x11c
#define SOR_INT_CODEC_CP_REQUEST (1 << 2)
#define SOR_INT_CODEC_SCRATCH1 (1 << 1)
#define SOR_INT_CODEC_SCRATCH0 (1 << 0)
#define SOR_INT_MASK 0x11d
#define SOR_INT_ENABLE 0x11e
#define SOR_HDMI_VSI_INFOFRAME_CTRL 0x123
#define SOR_HDMI_VSI_INFOFRAME_STATUS 0x124
#define SOR_HDMI_VSI_INFOFRAME_HEADER 0x125
#define SOR_HDMI_AUDIO_N 0x13c
#define SOR_HDMI_AUDIO_N_LOOKUP (1 << 28)
#define SOR_HDMI_AUDIO_N_RESET (1 << 20)
#define SOR_HDMI2_CTRL 0x13e
#define SOR_HDMI2_CTRL_CLOCK_MODE_DIV_BY_4 (1 << 1)
#define SOR_HDMI2_CTRL_SCRAMBLE (1 << 0)
#endif
+683
View File
@@ -0,0 +1,683 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020 NVIDIA Corporation */
#include <linux/dma-fence-array.h>
#include <linux/dma-mapping.h>
#include <linux/file.h>
#include <linux/host1x.h>
#include <linux/iommu.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/nospec.h>
#include <linux/pm_runtime.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/sync_file.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_syncobj.h>
#include "drm.h"
#include "gem.h"
#include "submit.h"
#include "uapi.h"
#define SUBMIT_ERR(context, fmt, ...) \
dev_err_ratelimited(context->client->base.dev, \
"%s: job submission failed: " fmt "\n", \
current->comm, ##__VA_ARGS__)
struct gather_bo {
struct host1x_bo base;
struct kref ref;
struct device *dev;
u32 *gather_data;
dma_addr_t gather_data_dma;
size_t gather_data_words;
};
static struct host1x_bo *gather_bo_get(struct host1x_bo *host_bo)
{
struct gather_bo *bo = container_of(host_bo, struct gather_bo, base);
kref_get(&bo->ref);
return host_bo;
}
static void gather_bo_release(struct kref *ref)
{
struct gather_bo *bo = container_of(ref, struct gather_bo, ref);
dma_free_attrs(bo->dev, bo->gather_data_words * 4, bo->gather_data, bo->gather_data_dma,
0);
kfree(bo);
}
static void gather_bo_put(struct host1x_bo *host_bo)
{
struct gather_bo *bo = container_of(host_bo, struct gather_bo, base);
kref_put(&bo->ref, gather_bo_release);
}
static struct host1x_bo_mapping *
gather_bo_pin(struct device *dev, struct host1x_bo *bo, enum dma_data_direction direction)
{
struct gather_bo *gather = container_of(bo, struct gather_bo, base);
struct host1x_bo_mapping *map;
int err;
map = kzalloc_obj(*map);
if (!map)
return ERR_PTR(-ENOMEM);
kref_init(&map->ref);
map->bo = bo;
map->direction = direction;
map->dev = dev;
map->sgt = kzalloc_obj(*map->sgt);
if (!map->sgt) {
err = -ENOMEM;
goto free;
}
err = dma_get_sgtable(gather->dev, map->sgt, gather->gather_data, gather->gather_data_dma,
gather->gather_data_words * 4);
if (err)
goto free_sgt;
err = dma_map_sgtable(dev, map->sgt, direction, 0);
if (err)
goto free_sgt;
map->phys = sg_dma_address(map->sgt->sgl);
map->size = gather->gather_data_words * 4;
map->chunks = err;
return map;
free_sgt:
sg_free_table(map->sgt);
kfree(map->sgt);
free:
kfree(map);
return ERR_PTR(err);
}
static void gather_bo_unpin(struct host1x_bo_mapping *map)
{
if (!map)
return;
dma_unmap_sgtable(map->dev, map->sgt, map->direction, 0);
sg_free_table(map->sgt);
kfree(map->sgt);
kfree(map);
}
static void *gather_bo_mmap(struct host1x_bo *host_bo)
{
struct gather_bo *bo = container_of(host_bo, struct gather_bo, base);
return bo->gather_data;
}
static void gather_bo_munmap(struct host1x_bo *host_bo, void *addr)
{
}
static const struct host1x_bo_ops gather_bo_ops = {
.get = gather_bo_get,
.put = gather_bo_put,
.pin = gather_bo_pin,
.unpin = gather_bo_unpin,
.mmap = gather_bo_mmap,
.munmap = gather_bo_munmap,
};
static struct tegra_drm_mapping *
tegra_drm_mapping_get(struct tegra_drm_context *context, u32 id)
{
struct tegra_drm_mapping *mapping;
xa_lock(&context->mappings);
mapping = xa_load(&context->mappings, id);
if (mapping)
kref_get(&mapping->ref);
xa_unlock(&context->mappings);
return mapping;
}
static void *alloc_copy_user_array(void __user *from, size_t count, size_t size)
{
size_t copy_len;
void *data;
if (check_mul_overflow(count, size, &copy_len))
return ERR_PTR(-EINVAL);
if (copy_len > 0x4000)
return ERR_PTR(-E2BIG);
data = vmemdup_user(from, copy_len);
if (IS_ERR(data))
return ERR_CAST(data);
return data;
}
static int submit_copy_gather_data(struct gather_bo **pbo, struct device *dev,
struct tegra_drm_context *context,
struct drm_tegra_channel_submit *args)
{
struct gather_bo *bo;
size_t copy_len;
if (args->gather_data_words == 0) {
SUBMIT_ERR(context, "gather_data_words cannot be zero");
return -EINVAL;
}
if (check_mul_overflow((size_t)args->gather_data_words, (size_t)4, &copy_len)) {
SUBMIT_ERR(context, "gather_data_words is too large");
return -EINVAL;
}
bo = kzalloc_obj(*bo);
if (!bo) {
SUBMIT_ERR(context, "failed to allocate memory for bo info");
return -ENOMEM;
}
host1x_bo_init(&bo->base, &gather_bo_ops);
kref_init(&bo->ref);
bo->dev = dev;
bo->gather_data = dma_alloc_attrs(dev, copy_len, &bo->gather_data_dma,
GFP_KERNEL | __GFP_NOWARN, 0);
if (!bo->gather_data) {
SUBMIT_ERR(context, "failed to allocate memory for gather data");
kfree(bo);
return -ENOMEM;
}
if (copy_from_user(bo->gather_data, u64_to_user_ptr(args->gather_data_ptr), copy_len)) {
SUBMIT_ERR(context, "failed to copy gather data from userspace");
dma_free_attrs(dev, copy_len, bo->gather_data, bo->gather_data_dma, 0);
kfree(bo);
return -EFAULT;
}
bo->gather_data_words = args->gather_data_words;
*pbo = bo;
return 0;
}
static int submit_write_reloc(struct tegra_drm_context *context, struct gather_bo *bo,
struct drm_tegra_submit_buf *buf, struct tegra_drm_mapping *mapping)
{
/* TODO check that target_offset is within bounds */
dma_addr_t iova = mapping->iova + buf->reloc.target_offset;
u32 written_ptr;
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
if (buf->flags & DRM_TEGRA_SUBMIT_RELOC_SECTOR_LAYOUT)
iova |= BIT_ULL(39);
#endif
written_ptr = iova >> buf->reloc.shift;
if (buf->reloc.gather_offset_words >= bo->gather_data_words) {
SUBMIT_ERR(context,
"relocation has too large gather offset (%u vs gather length %zu)",
buf->reloc.gather_offset_words, bo->gather_data_words);
return -EINVAL;
}
buf->reloc.gather_offset_words = array_index_nospec(buf->reloc.gather_offset_words,
bo->gather_data_words);
bo->gather_data[buf->reloc.gather_offset_words] = written_ptr;
return 0;
}
static int submit_process_bufs(struct tegra_drm_context *context, struct gather_bo *bo,
struct drm_tegra_channel_submit *args,
struct tegra_drm_submit_data *job_data)
{
struct tegra_drm_used_mapping *mappings;
struct drm_tegra_submit_buf *bufs;
int err;
u32 i;
bufs = alloc_copy_user_array(u64_to_user_ptr(args->bufs_ptr), args->num_bufs,
sizeof(*bufs));
if (IS_ERR(bufs)) {
SUBMIT_ERR(context, "failed to copy bufs array from userspace");
return PTR_ERR(bufs);
}
mappings = kzalloc_objs(*mappings, args->num_bufs);
if (!mappings) {
SUBMIT_ERR(context, "failed to allocate memory for mapping info");
err = -ENOMEM;
goto done;
}
for (i = 0; i < args->num_bufs; i++) {
struct drm_tegra_submit_buf *buf = &bufs[i];
struct tegra_drm_mapping *mapping;
if (buf->flags & ~DRM_TEGRA_SUBMIT_RELOC_SECTOR_LAYOUT) {
SUBMIT_ERR(context, "invalid flag specified for buffer");
err = -EINVAL;
goto drop_refs;
}
mapping = tegra_drm_mapping_get(context, buf->mapping);
if (!mapping) {
SUBMIT_ERR(context, "invalid mapping ID '%u' for buffer", buf->mapping);
err = -EINVAL;
goto drop_refs;
}
err = submit_write_reloc(context, bo, buf, mapping);
if (err) {
tegra_drm_mapping_put(mapping);
goto drop_refs;
}
mappings[i].mapping = mapping;
mappings[i].flags = buf->flags;
}
job_data->used_mappings = mappings;
job_data->num_used_mappings = i;
err = 0;
goto done;
drop_refs:
while (i--)
tegra_drm_mapping_put(mappings[i].mapping);
kfree(mappings);
job_data->used_mappings = NULL;
done:
kvfree(bufs);
return err;
}
static int submit_get_syncpt(struct tegra_drm_context *context, struct host1x_job *job,
struct xarray *syncpoints, struct drm_tegra_channel_submit *args)
{
struct host1x_syncpt *sp;
if (args->syncpt.flags) {
SUBMIT_ERR(context, "invalid flag specified for syncpt");
return -EINVAL;
}
/* Syncpt ref will be dropped on job release */
sp = xa_load(syncpoints, args->syncpt.id);
if (!sp) {
SUBMIT_ERR(context, "syncpoint specified in syncpt was not allocated");
return -EINVAL;
}
job->syncpt = host1x_syncpt_get(sp);
job->syncpt_incrs = args->syncpt.increments;
return 0;
}
static int submit_job_add_gather(struct host1x_job *job, struct tegra_drm_context *context,
struct drm_tegra_submit_cmd_gather_uptr *cmd,
struct gather_bo *bo, u32 *offset,
struct tegra_drm_submit_data *job_data,
u32 *class)
{
u32 next_offset;
if (cmd->reserved[0] || cmd->reserved[1] || cmd->reserved[2]) {
SUBMIT_ERR(context, "non-zero reserved field in GATHER_UPTR command");
return -EINVAL;
}
/* Check for maximum gather size */
if (cmd->words > 16383) {
SUBMIT_ERR(context, "too many words in GATHER_UPTR command");
return -EINVAL;
}
if (check_add_overflow(*offset, cmd->words, &next_offset)) {
SUBMIT_ERR(context, "too many total words in job");
return -EINVAL;
}
if (next_offset > bo->gather_data_words) {
SUBMIT_ERR(context, "GATHER_UPTR command overflows gather data");
return -EINVAL;
}
if (tegra_drm_fw_validate(context->client, bo->gather_data, *offset,
cmd->words, job_data, class)) {
SUBMIT_ERR(context, "job was rejected by firewall");
return -EINVAL;
}
host1x_job_add_gather(job, &bo->base, cmd->words, *offset * 4);
*offset = next_offset;
return 0;
}
static struct host1x_job *
submit_create_job(struct tegra_drm_context *context, struct gather_bo *bo,
struct drm_tegra_channel_submit *args, struct tegra_drm_submit_data *job_data,
struct xarray *syncpoints)
{
struct drm_tegra_submit_cmd *cmds;
u32 i, gather_offset = 0, class;
struct host1x_job *job;
int err;
/* Set initial class for firewall. */
class = context->client->base.class;
cmds = alloc_copy_user_array(u64_to_user_ptr(args->cmds_ptr), args->num_cmds,
sizeof(*cmds));
if (IS_ERR(cmds)) {
SUBMIT_ERR(context, "failed to copy cmds array from userspace");
return ERR_CAST(cmds);
}
job = host1x_job_alloc(context->channel, args->num_cmds, 0, true);
if (!job) {
SUBMIT_ERR(context, "failed to allocate memory for job");
job = ERR_PTR(-ENOMEM);
goto done;
}
err = submit_get_syncpt(context, job, syncpoints, args);
if (err < 0)
goto free_job;
job->client = &context->client->base;
job->class = context->client->base.class;
job->serialize = true;
for (i = 0; i < args->num_cmds; i++) {
struct drm_tegra_submit_cmd *cmd = &cmds[i];
if (cmd->flags) {
SUBMIT_ERR(context, "unknown flags given for cmd");
err = -EINVAL;
goto free_job;
}
if (cmd->type == DRM_TEGRA_SUBMIT_CMD_GATHER_UPTR) {
err = submit_job_add_gather(job, context, &cmd->gather_uptr, bo,
&gather_offset, job_data, &class);
if (err)
goto free_job;
} else if (cmd->type == DRM_TEGRA_SUBMIT_CMD_WAIT_SYNCPT) {
if (cmd->wait_syncpt.reserved[0] || cmd->wait_syncpt.reserved[1]) {
SUBMIT_ERR(context, "non-zero reserved value");
err = -EINVAL;
goto free_job;
}
host1x_job_add_wait(job, cmd->wait_syncpt.id, cmd->wait_syncpt.value,
false, class);
} else if (cmd->type == DRM_TEGRA_SUBMIT_CMD_WAIT_SYNCPT_RELATIVE) {
if (cmd->wait_syncpt.reserved[0] || cmd->wait_syncpt.reserved[1]) {
SUBMIT_ERR(context, "non-zero reserved value");
err = -EINVAL;
goto free_job;
}
if (cmd->wait_syncpt.id != args->syncpt.id) {
SUBMIT_ERR(context, "syncpoint ID in CMD_WAIT_SYNCPT_RELATIVE is not used by the job");
err = -EINVAL;
goto free_job;
}
host1x_job_add_wait(job, cmd->wait_syncpt.id, cmd->wait_syncpt.value,
true, class);
} else {
SUBMIT_ERR(context, "unknown cmd type");
err = -EINVAL;
goto free_job;
}
}
if (gather_offset == 0) {
SUBMIT_ERR(context, "job must have at least one gather");
err = -EINVAL;
goto free_job;
}
goto done;
free_job:
host1x_job_put(job);
job = ERR_PTR(err);
done:
kvfree(cmds);
return job;
}
static void release_job(struct host1x_job *job)
{
struct tegra_drm_client *client = container_of(job->client, struct tegra_drm_client, base);
struct tegra_drm_submit_data *job_data = job->user_data;
u32 i;
if (job->memory_context)
host1x_memory_context_put(job->memory_context);
for (i = 0; i < job_data->num_used_mappings; i++)
tegra_drm_mapping_put(job_data->used_mappings[i].mapping);
kfree(job_data->used_mappings);
kfree(job_data);
pm_runtime_mark_last_busy(client->base.dev);
pm_runtime_put_autosuspend(client->base.dev);
}
int tegra_drm_ioctl_channel_submit(struct drm_device *drm, void *data,
struct drm_file *file)
{
struct tegra_drm_file *fpriv = file->driver_priv;
struct drm_tegra_channel_submit *args = data;
struct tegra_drm_submit_data *job_data;
struct drm_syncobj *syncobj = NULL;
struct tegra_drm_context *context;
struct host1x_job *job;
struct gather_bo *bo;
u32 i;
int err;
mutex_lock(&fpriv->lock);
context = xa_load(&fpriv->contexts, args->context);
if (!context) {
mutex_unlock(&fpriv->lock);
pr_err_ratelimited("%s: %s: invalid channel context '%#x'", __func__,
current->comm, args->context);
return -EINVAL;
}
if (args->syncobj_in) {
struct dma_fence *fence;
err = drm_syncobj_find_fence(file, args->syncobj_in, 0, 0, &fence);
if (err) {
SUBMIT_ERR(context, "invalid syncobj_in '%#x'", args->syncobj_in);
goto unlock;
}
err = dma_fence_wait_timeout(fence, true, msecs_to_jiffies(10000));
dma_fence_put(fence);
if (err) {
SUBMIT_ERR(context, "wait for syncobj_in timed out");
goto unlock;
}
}
if (args->syncobj_out) {
syncobj = drm_syncobj_find(file, args->syncobj_out);
if (!syncobj) {
SUBMIT_ERR(context, "invalid syncobj_out '%#x'", args->syncobj_out);
err = -ENOENT;
goto unlock;
}
}
/* Allocate gather BO and copy gather words in. */
err = submit_copy_gather_data(&bo, drm->dev, context, args);
if (err)
goto unlock;
job_data = kzalloc_obj(*job_data);
if (!job_data) {
SUBMIT_ERR(context, "failed to allocate memory for job data");
err = -ENOMEM;
goto put_bo;
}
/* Get data buffer mappings and do relocation patching. */
err = submit_process_bufs(context, bo, args, job_data);
if (err)
goto free_job_data;
/* Allocate host1x_job and add gathers and waits to it. */
job = submit_create_job(context, bo, args, job_data, &fpriv->syncpoints);
if (IS_ERR(job)) {
err = PTR_ERR(job);
goto free_job_data;
}
/* Map gather data for Host1x. */
err = host1x_job_pin(job, context->client->base.dev);
if (err) {
SUBMIT_ERR(context, "failed to pin job: %d", err);
goto put_job;
}
if (context->client->ops->get_streamid_offset) {
err = context->client->ops->get_streamid_offset(
context->client, &job->engine_streamid_offset);
if (err) {
SUBMIT_ERR(context, "failed to get streamid offset: %d", err);
goto unpin_job;
}
}
if (context->memory_context && context->client->ops->can_use_memory_ctx) {
bool supported;
err = context->client->ops->can_use_memory_ctx(context->client, &supported);
if (err) {
SUBMIT_ERR(context, "failed to detect if engine can use memory context: %d", err);
goto unpin_job;
}
if (supported) {
job->memory_context = context->memory_context;
host1x_memory_context_get(job->memory_context);
}
} else if (context->client->ops->get_streamid_offset) {
/*
* Job submission will need to temporarily change stream ID,
* so need to tell it what to change it back to.
*/
if (!tegra_dev_iommu_get_stream_id(context->client->base.dev,
&job->engine_fallback_streamid))
job->engine_fallback_streamid = TEGRA_STREAM_ID_BYPASS;
}
/* Boot engine. */
err = pm_runtime_resume_and_get(context->client->base.dev);
if (err < 0) {
SUBMIT_ERR(context, "could not power up engine: %d", err);
goto put_memory_context;
}
job->user_data = job_data;
job->release = release_job;
job->timeout = 10000;
/*
* job_data is now part of job reference counting, so don't release
* it from here.
*/
job_data = NULL;
/* Submit job to hardware. */
err = host1x_job_submit(job);
if (err) {
SUBMIT_ERR(context, "host1x job submission failed: %d", err);
goto unpin_job;
}
/* Return postfences to userspace and add fences to DMA reservations. */
args->syncpt.value = job->syncpt_end;
if (syncobj) {
struct dma_fence *fence = host1x_fence_create(job->syncpt, job->syncpt_end, true);
if (IS_ERR(fence)) {
err = PTR_ERR(fence);
SUBMIT_ERR(context, "failed to create postfence: %d", err);
}
drm_syncobj_replace_fence(syncobj, fence);
}
goto put_job;
put_memory_context:
if (job->memory_context)
host1x_memory_context_put(job->memory_context);
unpin_job:
host1x_job_unpin(job);
put_job:
host1x_job_put(job);
free_job_data:
if (job_data && job_data->used_mappings) {
for (i = 0; i < job_data->num_used_mappings; i++)
tegra_drm_mapping_put(job_data->used_mappings[i].mapping);
kfree(job_data->used_mappings);
}
kfree(job_data);
put_bo:
gather_bo_put(&bo->base);
unlock:
if (syncobj)
drm_syncobj_put(syncobj);
mutex_unlock(&fpriv->lock);
return err;
}
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2020 NVIDIA Corporation */
#ifndef _TEGRA_DRM_UAPI_SUBMIT_H
#define _TEGRA_DRM_UAPI_SUBMIT_H
struct tegra_drm_used_mapping {
struct tegra_drm_mapping *mapping;
u32 flags;
};
struct tegra_drm_submit_data {
struct tegra_drm_used_mapping *used_mappings;
u32 num_used_mappings;
};
int tegra_drm_fw_validate(struct tegra_drm_client *client, u32 *data, u32 start,
u32 words, struct tegra_drm_submit_data *submit,
u32 *job_class);
#endif
@@ -0,0 +1,2 @@
#define CREATE_TRACE_POINTS
#include "trace.h"
+68
View File
@@ -0,0 +1,68 @@
#undef TRACE_SYSTEM
#define TRACE_SYSTEM tegra
#if !defined(DRM_TEGRA_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define DRM_TEGRA_TRACE_H 1
#include <linux/device.h>
#include <linux/tracepoint.h>
DECLARE_EVENT_CLASS(register_access,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value),
TP_STRUCT__entry(
__field(struct device *, dev)
__field(unsigned int, offset)
__field(u32, value)
),
TP_fast_assign(
__entry->dev = dev;
__entry->offset = offset;
__entry->value = value;
),
TP_printk("%s %04x %08x", dev_name(__entry->dev), __entry->offset,
__entry->value)
);
DEFINE_EVENT(register_access, dc_writel,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
DEFINE_EVENT(register_access, dc_readl,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
DEFINE_EVENT(register_access, hdmi_writel,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
DEFINE_EVENT(register_access, hdmi_readl,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
DEFINE_EVENT(register_access, dsi_writel,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
DEFINE_EVENT(register_access, dsi_readl,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
DEFINE_EVENT(register_access, dpaux_writel,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
DEFINE_EVENT(register_access, dpaux_readl,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
DEFINE_EVENT(register_access, sor_writel,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
DEFINE_EVENT(register_access, sor_readl,
TP_PROTO(struct device *dev, unsigned int offset, u32 value),
TP_ARGS(dev, offset, value));
#endif /* DRM_TEGRA_TRACE_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/tegra
#define TRACE_INCLUDE_FILE trace
#include <trace/define_trace.h>
+362
View File
@@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020 NVIDIA Corporation */
#include <linux/host1x.h>
#include <linux/iommu.h>
#include <linux/list.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_utils.h>
#include "drm.h"
#include "uapi.h"
static void tegra_drm_mapping_release(struct kref *ref)
{
struct tegra_drm_mapping *mapping =
container_of(ref, struct tegra_drm_mapping, ref);
host1x_bo_unpin(mapping->map);
host1x_bo_put(mapping->bo);
kfree(mapping);
}
void tegra_drm_mapping_put(struct tegra_drm_mapping *mapping)
{
kref_put(&mapping->ref, tegra_drm_mapping_release);
}
static void tegra_drm_channel_context_close(struct tegra_drm_context *context)
{
struct tegra_drm_mapping *mapping;
unsigned long id;
if (context->memory_context)
host1x_memory_context_put(context->memory_context);
xa_for_each(&context->mappings, id, mapping)
tegra_drm_mapping_put(mapping);
xa_destroy(&context->mappings);
host1x_channel_put(context->channel);
kfree(context);
}
void tegra_drm_uapi_close_file(struct tegra_drm_file *file)
{
struct tegra_drm_context *context;
struct host1x_syncpt *sp;
unsigned long id;
xa_for_each(&file->contexts, id, context)
tegra_drm_channel_context_close(context);
xa_for_each(&file->syncpoints, id, sp)
host1x_syncpt_put(sp);
xa_destroy(&file->contexts);
xa_destroy(&file->syncpoints);
}
static struct tegra_drm_client *tegra_drm_find_client(struct tegra_drm *tegra, u32 class)
{
struct tegra_drm_client *client;
list_for_each_entry(client, &tegra->clients, list)
if (client->base.class == class)
return client;
return NULL;
}
int tegra_drm_ioctl_channel_open(struct drm_device *drm, void *data, struct drm_file *file)
{
struct host1x *host = tegra_drm_to_host1x(drm->dev_private);
struct tegra_drm_file *fpriv = file->driver_priv;
struct tegra_drm *tegra = drm->dev_private;
struct drm_tegra_channel_open *args = data;
struct tegra_drm_client *client = NULL;
struct tegra_drm_context *context;
int err;
if (args->flags)
return -EINVAL;
context = kzalloc_obj(*context);
if (!context)
return -ENOMEM;
client = tegra_drm_find_client(tegra, args->host1x_class);
if (!client) {
err = -ENODEV;
goto free;
}
if (client->shared_channel) {
context->channel = host1x_channel_get(client->shared_channel);
} else {
context->channel = host1x_channel_request(&client->base);
if (!context->channel) {
err = -EBUSY;
goto free;
}
}
/* Only allocate context if the engine supports context isolation. */
if (device_iommu_mapped(client->base.dev) && client->ops->can_use_memory_ctx) {
bool supported;
err = client->ops->can_use_memory_ctx(client, &supported);
if (err)
goto put_channel;
if (supported) {
struct pid *pid = get_task_pid(current, PIDTYPE_TGID);
context->memory_context = host1x_memory_context_alloc(
host, client->base.dev, pid);
put_pid(pid);
}
if (IS_ERR(context->memory_context)) {
if (PTR_ERR(context->memory_context) != -EOPNOTSUPP) {
err = PTR_ERR(context->memory_context);
goto put_channel;
} else {
/*
* OK, HW does not support contexts or contexts
* are disabled.
*/
context->memory_context = NULL;
}
}
}
err = xa_alloc(&fpriv->contexts, &args->context, context, XA_LIMIT(1, U32_MAX),
GFP_KERNEL);
if (err < 0)
goto put_memctx;
context->client = client;
xa_init_flags(&context->mappings, XA_FLAGS_ALLOC1);
args->version = client->version;
args->capabilities = 0;
if (device_get_dma_attr(client->base.dev) == DEV_DMA_COHERENT)
args->capabilities |= DRM_TEGRA_CHANNEL_CAP_CACHE_COHERENT;
return 0;
put_memctx:
if (context->memory_context)
host1x_memory_context_put(context->memory_context);
put_channel:
host1x_channel_put(context->channel);
free:
kfree(context);
return err;
}
int tegra_drm_ioctl_channel_close(struct drm_device *drm, void *data, struct drm_file *file)
{
struct tegra_drm_file *fpriv = file->driver_priv;
struct drm_tegra_channel_close *args = data;
struct tegra_drm_context *context;
mutex_lock(&fpriv->lock);
context = xa_load(&fpriv->contexts, args->context);
if (!context) {
mutex_unlock(&fpriv->lock);
return -EINVAL;
}
xa_erase(&fpriv->contexts, args->context);
mutex_unlock(&fpriv->lock);
tegra_drm_channel_context_close(context);
return 0;
}
int tegra_drm_ioctl_channel_map(struct drm_device *drm, void *data, struct drm_file *file)
{
struct tegra_drm_file *fpriv = file->driver_priv;
struct drm_tegra_channel_map *args = data;
struct tegra_drm_mapping *mapping;
struct tegra_drm_context *context;
enum dma_data_direction direction;
struct device *mapping_dev;
int err = 0;
if (args->flags & ~DRM_TEGRA_CHANNEL_MAP_READ_WRITE)
return -EINVAL;
mutex_lock(&fpriv->lock);
context = xa_load(&fpriv->contexts, args->context);
if (!context) {
mutex_unlock(&fpriv->lock);
return -EINVAL;
}
mapping = kzalloc_obj(*mapping);
if (!mapping) {
err = -ENOMEM;
goto unlock;
}
kref_init(&mapping->ref);
if (context->memory_context)
mapping_dev = &context->memory_context->dev;
else
mapping_dev = context->client->base.dev;
mapping->bo = tegra_gem_lookup(file, args->handle);
if (!mapping->bo) {
err = -EINVAL;
goto free;
}
switch (args->flags & DRM_TEGRA_CHANNEL_MAP_READ_WRITE) {
case DRM_TEGRA_CHANNEL_MAP_READ_WRITE:
direction = DMA_BIDIRECTIONAL;
break;
case DRM_TEGRA_CHANNEL_MAP_WRITE:
direction = DMA_FROM_DEVICE;
break;
case DRM_TEGRA_CHANNEL_MAP_READ:
direction = DMA_TO_DEVICE;
break;
default:
err = -EINVAL;
goto put_gem;
}
mapping->map = host1x_bo_pin(mapping_dev, mapping->bo, direction, NULL);
if (IS_ERR(mapping->map)) {
err = PTR_ERR(mapping->map);
goto put_gem;
}
mapping->iova = mapping->map->phys;
mapping->iova_end = mapping->iova + host1x_to_tegra_bo(mapping->bo)->gem.size;
err = xa_alloc(&context->mappings, &args->mapping, mapping, XA_LIMIT(1, U32_MAX),
GFP_KERNEL);
if (err < 0)
goto unpin;
mutex_unlock(&fpriv->lock);
return 0;
unpin:
host1x_bo_unpin(mapping->map);
put_gem:
host1x_bo_put(mapping->bo);
free:
kfree(mapping);
unlock:
mutex_unlock(&fpriv->lock);
return err;
}
int tegra_drm_ioctl_channel_unmap(struct drm_device *drm, void *data, struct drm_file *file)
{
struct tegra_drm_file *fpriv = file->driver_priv;
struct drm_tegra_channel_unmap *args = data;
struct tegra_drm_mapping *mapping;
struct tegra_drm_context *context;
mutex_lock(&fpriv->lock);
context = xa_load(&fpriv->contexts, args->context);
if (!context) {
mutex_unlock(&fpriv->lock);
return -EINVAL;
}
mapping = xa_erase(&context->mappings, args->mapping);
mutex_unlock(&fpriv->lock);
if (!mapping)
return -EINVAL;
tegra_drm_mapping_put(mapping);
return 0;
}
int tegra_drm_ioctl_syncpoint_allocate(struct drm_device *drm, void *data, struct drm_file *file)
{
struct host1x *host1x = tegra_drm_to_host1x(drm->dev_private);
struct tegra_drm_file *fpriv = file->driver_priv;
struct drm_tegra_syncpoint_allocate *args = data;
struct host1x_syncpt *sp;
int err;
if (args->id)
return -EINVAL;
sp = host1x_syncpt_alloc(host1x, HOST1X_SYNCPT_CLIENT_MANAGED, current->comm);
if (!sp)
return -EBUSY;
args->id = host1x_syncpt_id(sp);
err = xa_insert(&fpriv->syncpoints, args->id, sp, GFP_KERNEL);
if (err) {
host1x_syncpt_put(sp);
return err;
}
return 0;
}
int tegra_drm_ioctl_syncpoint_free(struct drm_device *drm, void *data, struct drm_file *file)
{
struct tegra_drm_file *fpriv = file->driver_priv;
struct drm_tegra_syncpoint_allocate *args = data;
struct host1x_syncpt *sp;
mutex_lock(&fpriv->lock);
sp = xa_erase(&fpriv->syncpoints, args->id);
mutex_unlock(&fpriv->lock);
if (!sp)
return -EINVAL;
host1x_syncpt_put(sp);
return 0;
}
int tegra_drm_ioctl_syncpoint_wait(struct drm_device *drm, void *data, struct drm_file *file)
{
struct host1x *host1x = tegra_drm_to_host1x(drm->dev_private);
struct drm_tegra_syncpoint_wait *args = data;
signed long timeout_jiffies;
struct host1x_syncpt *sp;
if (args->padding != 0)
return -EINVAL;
sp = host1x_syncpt_get_by_id_noref(host1x, args->id);
if (!sp)
return -EINVAL;
timeout_jiffies = drm_timeout_abs_to_jiffies(args->timeout_ns);
return host1x_syncpt_wait(sp, args->threshold, timeout_jiffies, &args->value);
}
+57
View File
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2020 NVIDIA Corporation */
#ifndef _TEGRA_DRM_UAPI_H
#define _TEGRA_DRM_UAPI_H
#include <linux/dma-mapping.h>
#include <linux/idr.h>
#include <linux/kref.h>
#include <linux/xarray.h>
#include <drm/drm.h>
struct drm_file;
struct drm_device;
struct tegra_drm_file {
/* Legacy UAPI state */
struct idr legacy_contexts;
struct mutex lock;
/* New UAPI state */
struct xarray contexts;
struct xarray syncpoints;
};
struct tegra_drm_mapping {
struct kref ref;
struct host1x_bo_mapping *map;
struct host1x_bo *bo;
dma_addr_t iova;
dma_addr_t iova_end;
};
int tegra_drm_ioctl_channel_open(struct drm_device *drm, void *data,
struct drm_file *file);
int tegra_drm_ioctl_channel_close(struct drm_device *drm, void *data,
struct drm_file *file);
int tegra_drm_ioctl_channel_map(struct drm_device *drm, void *data,
struct drm_file *file);
int tegra_drm_ioctl_channel_unmap(struct drm_device *drm, void *data,
struct drm_file *file);
int tegra_drm_ioctl_channel_submit(struct drm_device *drm, void *data,
struct drm_file *file);
int tegra_drm_ioctl_syncpoint_allocate(struct drm_device *drm, void *data,
struct drm_file *file);
int tegra_drm_ioctl_syncpoint_free(struct drm_device *drm, void *data,
struct drm_file *file);
int tegra_drm_ioctl_syncpoint_wait(struct drm_device *drm, void *data,
struct drm_file *file);
void tegra_drm_uapi_close_file(struct tegra_drm_file *file);
void tegra_drm_mapping_put(struct tegra_drm_mapping *mapping);
#endif
+573
View File
@@ -0,0 +1,573 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015, NVIDIA Corporation.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/host1x.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <soc/tegra/pmc.h>
#include "drm.h"
#include "falcon.h"
#include "vic.h"
struct vic_config {
const char *firmware;
unsigned int version;
bool supports_sid;
};
struct vic {
struct falcon falcon;
void __iomem *regs;
struct tegra_drm_client client;
struct host1x_channel *channel;
struct device *dev;
struct clk *clk;
struct reset_control *rst;
bool can_use_context;
/* Platform configuration */
const struct vic_config *config;
};
static inline struct vic *to_vic(struct tegra_drm_client *client)
{
return container_of(client, struct vic, client);
}
static void vic_writel(struct vic *vic, u32 value, unsigned int offset)
{
writel(value, vic->regs + offset);
}
static int vic_boot(struct vic *vic)
{
u32 fce_ucode_size, fce_bin_data_offset, stream_id;
void *hdr;
int err = 0;
if (vic->config->supports_sid && tegra_dev_iommu_get_stream_id(vic->dev, &stream_id)) {
u32 value;
value = TRANSCFG_ATT(1, TRANSCFG_SID_FALCON) |
TRANSCFG_ATT(0, TRANSCFG_SID_HW);
vic_writel(vic, value, VIC_TFBIF_TRANSCFG);
/*
* STREAMID0 is used for input/output buffers. Initialize it to SID_VIC in case
* context isolation is not enabled, and SID_VIC is used for both firmware and
* data buffers.
*
* If context isolation is enabled, it will be overridden by the SETSTREAMID
* opcode as part of each job.
*/
vic_writel(vic, stream_id, VIC_THI_STREAMID0);
/* STREAMID1 is used for firmware loading. */
vic_writel(vic, stream_id, VIC_THI_STREAMID1);
}
/* setup clockgating registers */
vic_writel(vic, CG_IDLE_CG_DLY_CNT(4) |
CG_IDLE_CG_EN |
CG_WAKEUP_DLY_CNT(4),
NV_PVIC_MISC_PRI_VIC_CG);
err = falcon_boot(&vic->falcon);
if (err < 0)
return err;
hdr = vic->falcon.firmware.virt;
fce_bin_data_offset = *(u32 *)(hdr + VIC_UCODE_FCE_DATA_OFFSET);
/* Old VIC firmware needs kernel help with setting up FCE microcode. */
if (fce_bin_data_offset != 0x0 && fce_bin_data_offset != 0xa5a5a5a5) {
hdr = vic->falcon.firmware.virt +
*(u32 *)(hdr + VIC_UCODE_FCE_HEADER_OFFSET);
fce_ucode_size = *(u32 *)(hdr + FCE_UCODE_SIZE_OFFSET);
falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_SIZE,
fce_ucode_size);
falcon_execute_method(
&vic->falcon, VIC_SET_FCE_UCODE_OFFSET,
(vic->falcon.firmware.iova + fce_bin_data_offset) >> 8);
}
err = falcon_wait_idle(&vic->falcon);
if (err < 0) {
dev_err(vic->dev,
"failed to set application ID and FCE base\n");
return err;
}
return 0;
}
static int vic_init(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
struct tegra_drm *tegra = dev->dev_private;
struct vic *vic = to_vic(drm);
int err;
err = host1x_client_iommu_attach(client);
if (err < 0 && err != -ENODEV) {
dev_err(vic->dev, "failed to attach to domain: %d\n", err);
return err;
}
vic->channel = host1x_channel_request(client);
if (!vic->channel) {
err = -ENOMEM;
goto detach;
}
client->syncpts[0] = host1x_syncpt_request(client, 0);
if (!client->syncpts[0]) {
err = -ENOMEM;
goto free_channel;
}
err = tegra_drm_register_client(tegra, drm);
if (err < 0)
goto free_syncpt;
/*
* Inherit the DMA parameters (such as maximum segment size) from the
* parent host1x device.
*/
client->dev->dma_parms = client->host->dma_parms;
return 0;
free_syncpt:
host1x_syncpt_put(client->syncpts[0]);
free_channel:
host1x_channel_put(vic->channel);
detach:
host1x_client_iommu_detach(client);
return err;
}
static int vic_exit(struct host1x_client *client)
{
struct tegra_drm_client *drm = host1x_to_drm_client(client);
struct drm_device *dev = dev_get_drvdata(client->host);
struct tegra_drm *tegra = dev->dev_private;
struct vic *vic = to_vic(drm);
int err;
/* avoid a dangling pointer just in case this disappears */
client->dev->dma_parms = NULL;
err = tegra_drm_unregister_client(tegra, drm);
if (err < 0)
return err;
pm_runtime_dont_use_autosuspend(client->dev);
pm_runtime_force_suspend(client->dev);
host1x_syncpt_put(client->syncpts[0]);
host1x_channel_put(vic->channel);
host1x_client_iommu_detach(client);
vic->channel = NULL;
if (client->group) {
dma_unmap_single(vic->dev, vic->falcon.firmware.phys,
vic->falcon.firmware.size, DMA_TO_DEVICE);
tegra_drm_free(tegra, vic->falcon.firmware.size,
vic->falcon.firmware.virt,
vic->falcon.firmware.iova);
} else {
dma_free_coherent(vic->dev, vic->falcon.firmware.size,
vic->falcon.firmware.virt,
vic->falcon.firmware.iova);
}
return 0;
}
static const struct host1x_client_ops vic_client_ops = {
.init = vic_init,
.exit = vic_exit,
};
static int vic_load_firmware(struct vic *vic)
{
struct host1x_client *client = &vic->client.base;
struct tegra_drm *tegra = vic->client.drm;
static DEFINE_MUTEX(lock);
u32 fce_bin_data_offset;
dma_addr_t iova;
size_t size;
void *virt;
int err;
mutex_lock(&lock);
if (vic->falcon.firmware.virt) {
err = 0;
goto unlock;
}
err = falcon_read_firmware(&vic->falcon, vic->config->firmware);
if (err < 0)
goto unlock;
size = vic->falcon.firmware.size;
if (!client->group) {
virt = dma_alloc_coherent(vic->dev, size, &iova, GFP_KERNEL);
if (!virt) {
err = -ENOMEM;
goto unlock;
}
} else {
virt = tegra_drm_alloc(tegra, size, &iova);
if (IS_ERR(virt)) {
err = PTR_ERR(virt);
goto unlock;
}
}
vic->falcon.firmware.virt = virt;
vic->falcon.firmware.iova = iova;
err = falcon_load_firmware(&vic->falcon);
if (err < 0)
goto cleanup;
/*
* In this case we have received an IOVA from the shared domain, so we
* need to make sure to get the physical address so that the DMA API
* knows what memory pages to flush the cache for.
*/
if (client->group) {
dma_addr_t phys;
phys = dma_map_single(vic->dev, virt, size, DMA_TO_DEVICE);
err = dma_mapping_error(vic->dev, phys);
if (err < 0)
goto cleanup;
vic->falcon.firmware.phys = phys;
}
/*
* Check if firmware is new enough to not require mapping firmware
* to data buffer domains.
*/
fce_bin_data_offset = *(u32 *)(virt + VIC_UCODE_FCE_DATA_OFFSET);
if (!vic->config->supports_sid) {
vic->can_use_context = false;
} else if (fce_bin_data_offset != 0x0 && fce_bin_data_offset != 0xa5a5a5a5) {
/*
* Firmware will access FCE through STREAMID0, so context
* isolation cannot be used.
*/
vic->can_use_context = false;
dev_warn_once(vic->dev, "context isolation disabled due to old firmware\n");
} else {
vic->can_use_context = true;
}
unlock:
mutex_unlock(&lock);
return err;
cleanup:
if (!client->group)
dma_free_coherent(vic->dev, size, virt, iova);
else
tegra_drm_free(tegra, size, virt, iova);
mutex_unlock(&lock);
return err;
}
static int __maybe_unused vic_runtime_resume(struct device *dev)
{
struct vic *vic = dev_get_drvdata(dev);
int err;
err = clk_prepare_enable(vic->clk);
if (err < 0)
return err;
usleep_range(10, 20);
err = reset_control_deassert(vic->rst);
if (err < 0)
goto disable;
usleep_range(10, 20);
err = vic_load_firmware(vic);
if (err < 0)
goto assert;
err = vic_boot(vic);
if (err < 0)
goto assert;
return 0;
assert:
reset_control_assert(vic->rst);
disable:
clk_disable_unprepare(vic->clk);
return err;
}
static int __maybe_unused vic_runtime_suspend(struct device *dev)
{
struct vic *vic = dev_get_drvdata(dev);
int err;
host1x_channel_stop(vic->channel);
err = reset_control_assert(vic->rst);
if (err < 0)
return err;
usleep_range(2000, 4000);
clk_disable_unprepare(vic->clk);
return 0;
}
static int vic_open_channel(struct tegra_drm_client *client,
struct tegra_drm_context *context)
{
struct vic *vic = to_vic(client);
context->channel = host1x_channel_get(vic->channel);
if (!context->channel)
return -ENOMEM;
return 0;
}
static void vic_close_channel(struct tegra_drm_context *context)
{
host1x_channel_put(context->channel);
}
static int vic_can_use_memory_ctx(struct tegra_drm_client *client, bool *supported)
{
struct vic *vic = to_vic(client);
int err;
/* This doesn't access HW so it's safe to call without powering up. */
err = vic_load_firmware(vic);
if (err < 0)
return err;
*supported = vic->can_use_context;
return 0;
}
static const struct tegra_drm_client_ops vic_ops = {
.open_channel = vic_open_channel,
.close_channel = vic_close_channel,
.submit = tegra_drm_submit,
.get_streamid_offset = tegra_drm_get_streamid_offset_thi,
.can_use_memory_ctx = vic_can_use_memory_ctx,
};
#define NVIDIA_TEGRA_124_VIC_FIRMWARE "nvidia/tegra124/vic03_ucode.bin"
static const struct vic_config vic_t124_config = {
.firmware = NVIDIA_TEGRA_124_VIC_FIRMWARE,
.version = 0x40,
.supports_sid = false,
};
#define NVIDIA_TEGRA_210_VIC_FIRMWARE "nvidia/tegra210/vic04_ucode.bin"
static const struct vic_config vic_t210_config = {
.firmware = NVIDIA_TEGRA_210_VIC_FIRMWARE,
.version = 0x21,
.supports_sid = false,
};
#define NVIDIA_TEGRA_186_VIC_FIRMWARE "nvidia/tegra186/vic04_ucode.bin"
static const struct vic_config vic_t186_config = {
.firmware = NVIDIA_TEGRA_186_VIC_FIRMWARE,
.version = 0x18,
.supports_sid = true,
};
#define NVIDIA_TEGRA_194_VIC_FIRMWARE "nvidia/tegra194/vic.bin"
static const struct vic_config vic_t194_config = {
.firmware = NVIDIA_TEGRA_194_VIC_FIRMWARE,
.version = 0x19,
.supports_sid = true,
};
#define NVIDIA_TEGRA_234_VIC_FIRMWARE "nvidia/tegra234/vic.bin"
static const struct vic_config vic_t234_config = {
.firmware = NVIDIA_TEGRA_234_VIC_FIRMWARE,
.version = 0x23,
.supports_sid = true,
};
static const struct of_device_id tegra_vic_of_match[] = {
{ .compatible = "nvidia,tegra124-vic", .data = &vic_t124_config },
{ .compatible = "nvidia,tegra210-vic", .data = &vic_t210_config },
{ .compatible = "nvidia,tegra186-vic", .data = &vic_t186_config },
{ .compatible = "nvidia,tegra194-vic", .data = &vic_t194_config },
{ .compatible = "nvidia,tegra234-vic", .data = &vic_t234_config },
{ },
};
MODULE_DEVICE_TABLE(of, tegra_vic_of_match);
static int vic_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct host1x_syncpt **syncpts;
struct vic *vic;
int err;
/* inherit DMA mask from host1x parent */
err = dma_coerce_mask_and_coherent(dev, *dev->parent->dma_mask);
if (err < 0) {
dev_err(&pdev->dev, "failed to set DMA mask: %d\n", err);
return err;
}
vic = devm_kzalloc(dev, sizeof(*vic), GFP_KERNEL);
if (!vic)
return -ENOMEM;
vic->config = of_device_get_match_data(dev);
syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL);
if (!syncpts)
return -ENOMEM;
vic->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(vic->regs))
return PTR_ERR(vic->regs);
vic->clk = devm_clk_get(dev, NULL);
if (IS_ERR(vic->clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
return PTR_ERR(vic->clk);
}
err = clk_set_rate(vic->clk, ULONG_MAX);
if (err < 0) {
dev_err(&pdev->dev, "failed to set clock rate\n");
return err;
}
if (!dev->pm_domain) {
vic->rst = devm_reset_control_get(dev, "vic");
if (IS_ERR(vic->rst)) {
dev_err(&pdev->dev, "failed to get reset\n");
return PTR_ERR(vic->rst);
}
}
vic->falcon.dev = dev;
vic->falcon.regs = vic->regs;
err = falcon_init(&vic->falcon);
if (err < 0)
return err;
platform_set_drvdata(pdev, vic);
INIT_LIST_HEAD(&vic->client.base.list);
vic->client.base.ops = &vic_client_ops;
vic->client.base.dev = dev;
vic->client.base.class = HOST1X_CLASS_VIC;
vic->client.base.syncpts = syncpts;
vic->client.base.num_syncpts = 1;
vic->dev = dev;
INIT_LIST_HEAD(&vic->client.list);
vic->client.version = vic->config->version;
vic->client.ops = &vic_ops;
err = host1x_client_register(&vic->client.base);
if (err < 0) {
dev_err(dev, "failed to register host1x client: %d\n", err);
goto exit_falcon;
}
pm_runtime_enable(dev);
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, 500);
return 0;
exit_falcon:
falcon_exit(&vic->falcon);
return err;
}
static void vic_remove(struct platform_device *pdev)
{
struct vic *vic = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
host1x_client_unregister(&vic->client.base);
falcon_exit(&vic->falcon);
}
static const struct dev_pm_ops vic_pm_ops = {
RUNTIME_PM_OPS(vic_runtime_suspend, vic_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
struct platform_driver tegra_vic_driver = {
.driver = {
.name = "tegra-vic",
.of_match_table = tegra_vic_of_match,
.pm = &vic_pm_ops
},
.probe = vic_probe,
.remove = vic_remove,
};
#if IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC)
MODULE_FIRMWARE(NVIDIA_TEGRA_124_VIC_FIRMWARE);
#endif
#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC)
MODULE_FIRMWARE(NVIDIA_TEGRA_210_VIC_FIRMWARE);
#endif
#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC)
MODULE_FIRMWARE(NVIDIA_TEGRA_186_VIC_FIRMWARE);
#endif
#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC)
MODULE_FIRMWARE(NVIDIA_TEGRA_194_VIC_FIRMWARE);
#endif
#if IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC)
MODULE_FIRMWARE(NVIDIA_TEGRA_234_VIC_FIRMWARE);
#endif
+36
View File
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2015, NVIDIA Corporation.
*/
#ifndef TEGRA_VIC_H
#define TEGRA_VIC_H
/* VIC methods */
#define VIC_SET_FCE_UCODE_SIZE 0x0000071C
#define VIC_SET_FCE_UCODE_OFFSET 0x0000072C
/* VIC registers */
#define VIC_THI_STREAMID0 0x00000030
#define VIC_THI_STREAMID1 0x00000034
#define NV_PVIC_MISC_PRI_VIC_CG 0x000016d0
#define CG_IDLE_CG_DLY_CNT(val) ((val & 0x3f) << 0)
#define CG_IDLE_CG_EN (1 << 6)
#define CG_WAKEUP_DLY_CNT(val) ((val & 0xf) << 16)
#define VIC_TFBIF_TRANSCFG 0x00002044
#define TRANSCFG_ATT(i, v) (((v) & 0x3) << (i * 4))
#define TRANSCFG_SID_HW 0
#define TRANSCFG_SID_PHY 1
#define TRANSCFG_SID_FALCON 2
/* Firmware offsets */
#define VIC_UCODE_FCE_HEADER_OFFSET (6*4)
#define VIC_UCODE_FCE_DATA_OFFSET (7*4)
#define FCE_UCODE_SIZE_OFFSET (2*4)
#endif /* TEGRA_VIC_H */