mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
drm/tegra: Add upstream tegra-drm driver
Add the upstream tegra-drm driver with the 'Host1x/Tegra UAPI' series [0] applied. This driver will be built as an external module for testing and development with upstream Linux kernels. The following modifications have been made to the series posted upstream 1. Update the Makefile to always build the driver as a module 2. Always enable the tegra_drm_ioctl_xxx in the tegra_drm_ioctls and remove the dependency on CONFIG_DRM_TEGRA_STAGING. 3. Rename the include/uapi/drm/tegra_drm.h to include/uapi/drm/tegra_drm_next.h to avoid conflicts with upstream headers when building as an external module. 5. Rename the module that is built to be tegra-drm-next.ko instead of tegra-drm.ko to avoid any depmod conflicts with the upstream driver. [0] https://patchwork.ozlabs.org/project/linux-tegra/list/?series=215770 Bug 3156385 Change-Id: I19206f989325c9c6ff3c2b9301d964140d52234f Signed-off-by: Jon Hunter <jonathanh@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2435802 Tested-by: mobile promotions <svcmobile_promotions@nvidia.com> Reviewed-by: Mikko Perttunen <mperttunen@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com> GVS: Gerrit_Virtual_Submit
This commit is contained in:
committed by
Laxman Dewangan
parent
46b9df5186
commit
b8c2d943ed
35
drivers/gpu/drm/tegra/Kconfig
Normal file
35
drivers/gpu/drm/tegra/Kconfig
Normal file
@@ -0,0 +1,35 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config DRM_TEGRA_NEXT
|
||||
tristate "NVIDIA Tegra DRM"
|
||||
depends on ARCH_TEGRA || (ARM && COMPILE_TEST)
|
||||
depends on COMMON_CLK
|
||||
depends on DRM
|
||||
depends on OF
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_MIPI_DSI
|
||||
select DRM_PANEL
|
||||
select TEGRA_HOST1X_NEXT
|
||||
select IOMMU_IOVA
|
||||
select CEC_CORE if CEC_NOTIFIER
|
||||
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_NEXT
|
||||
|
||||
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
|
||||
31
drivers/gpu/drm/tegra/Makefile
Normal file
31
drivers/gpu/drm/tegra/Makefile
Normal file
@@ -0,0 +1,31 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
CONFIG_DRM_TEGRA_NEXT := m
|
||||
ccflags-$(CONFIG_DRM_TEGRA_DEBUG) += -DDEBUG
|
||||
ccflags-y += -I$(srctree.tegra-drm)/include
|
||||
|
||||
tegra-drm-next-y := \
|
||||
drm.o \
|
||||
uapi/uapi.o \
|
||||
uapi/submit.o \
|
||||
uapi/firewall.o \
|
||||
uapi/gather_bo.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
|
||||
|
||||
obj-$(CONFIG_DRM_TEGRA_NEXT) += tegra-drm-next.o
|
||||
2660
drivers/gpu/drm/tegra/dc.c
Normal file
2660
drivers/gpu/drm/tegra/dc.c
Normal file
File diff suppressed because it is too large
Load Diff
784
drivers/gpu/drm/tegra/dc.h
Normal file
784
drivers/gpu/drm/tegra/dc.h
Normal file
@@ -0,0 +1,784 @@
|
||||
/* 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-next.h>
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
|
||||
#include "drm.h"
|
||||
|
||||
struct tegra_output;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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 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_c_without_vert_filter;
|
||||
};
|
||||
|
||||
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_dc_stats stats;
|
||||
struct list_head list;
|
||||
|
||||
struct drm_info_list *debugfs_files;
|
||||
|
||||
const struct tegra_dc_soc_info *soc;
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
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));
|
||||
|
||||
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);
|
||||
|
||||
/* from rgb.c */
|
||||
int tegra_dc_rgb_probe(struct tegra_dc *dc);
|
||||
int 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 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_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_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_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_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_PLANAR_STORAGE 0x709
|
||||
#define PITCH(x) (((x) >> 6) & 0x1fff)
|
||||
|
||||
#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_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 */
|
||||
876
drivers/gpu/drm/tegra/dp.c
Normal file
876
drivers/gpu/drm/tegra/dp.c
Normal file
@@ -0,0 +1,876 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
/*
|
||||
* Copyright (C) 2013-2019 NVIDIA Corporation
|
||||
* Copyright (C) 2015 Rob Clark
|
||||
*/
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_dp_helper.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_power_up() - power up 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_power_up(struct drm_dp_aux *aux, struct drm_dp_link *link)
|
||||
{
|
||||
u8 value;
|
||||
int err;
|
||||
|
||||
/* DP_SET_POWER register is only available on DPCD v1.1 and later */
|
||||
if (link->revision < 0x11)
|
||||
return 0;
|
||||
|
||||
err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
value &= ~DP_SET_POWER_MASK;
|
||||
value |= DP_SET_POWER_D0;
|
||||
|
||||
err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* According to the DP 1.1 specification, a "Sink Device must exit the
|
||||
* power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink
|
||||
* Control Field" (register 0x600).
|
||||
*/
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_dp_link_power_down() - power down 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_power_down(struct drm_dp_aux *aux, struct drm_dp_link *link)
|
||||
{
|
||||
u8 value;
|
||||
int err;
|
||||
|
||||
/* DP_SET_POWER register is only available on DPCD v1.1 and later */
|
||||
if (link->revision < 0x11)
|
||||
return 0;
|
||||
|
||||
err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
value &= ~DP_SET_POWER_MASK;
|
||||
value |= DP_SET_POWER_D3;
|
||||
|
||||
err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
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;
|
||||
|
||||
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] =
|
||||
drm_dp_get_adjust_request_post_cursor(status, i);
|
||||
}
|
||||
}
|
||||
|
||||
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, (link->lanes > 1) ? "s" : "",
|
||||
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, (link->lanes > 1) ? "s" : "",
|
||||
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;
|
||||
}
|
||||
177
drivers/gpu/drm/tegra/dp.h
Normal file
177
drivers/gpu/drm/tegra/dp.h
Normal file
@@ -0,0 +1,177 @@
|
||||
/* 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:
|
||||
*/
|
||||
int (*apply_training)(struct drm_dp_link *link);
|
||||
|
||||
/**
|
||||
* @configure:
|
||||
*/
|
||||
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)
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @cr: clock recovery read interval
|
||||
* @ce: channel equalization read interval
|
||||
*/
|
||||
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_power_up(struct drm_dp_aux *aux, struct drm_dp_link *link);
|
||||
int drm_dp_link_power_down(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
|
||||
817
drivers/gpu/drm/tegra/dpaux.c
Normal file
817
drivers/gpu/drm/tegra/dpaux.c
Normal file
@@ -0,0 +1,817 @@
|
||||
// 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_device.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/drm_dp_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include "dp.h"
|
||||
#include "dpaux.h"
|
||||
#include "drm.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));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline void tegra_dpaux_writel(struct tegra_dpaux *dpaux,
|
||||
u32 value, unsigned int offset)
|
||||
{
|
||||
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;
|
||||
irqreturn_t ret = IRQ_HANDLED;
|
||||
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 ret;
|
||||
}
|
||||
|
||||
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;
|
||||
struct resource *regs;
|
||||
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;
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
dpaux->regs = devm_ioremap_resource(&pdev->dev, regs);
|
||||
if (IS_ERR(dpaux->regs))
|
||||
return PTR_ERR(dpaux->regs);
|
||||
|
||||
dpaux->irq = platform_get_irq(pdev, 0);
|
||||
if (dpaux->irq < 0) {
|
||||
dev_err(&pdev->dev, "failed to get IRQ\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (PTR_ERR(dpaux->vdd) != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"failed to get VDD supply: %ld\n",
|
||||
PTR_ERR(dpaux->vdd));
|
||||
|
||||
return PTR_ERR(dpaux->vdd);
|
||||
}
|
||||
|
||||
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);
|
||||
return err;
|
||||
}
|
||||
|
||||
disable_irq(dpaux->irq);
|
||||
|
||||
dpaux->aux.transfer = tegra_dpaux_transfer;
|
||||
dpaux->aux.dev = &pdev->dev;
|
||||
|
||||
err = drm_dp_aux_register(&dpaux->aux);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* 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)
|
||||
return err;
|
||||
|
||||
#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");
|
||||
return PTR_ERR(dpaux->pinctrl);
|
||||
}
|
||||
#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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int 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);
|
||||
|
||||
drm_dp_aux_unregister(&dpaux->aux);
|
||||
|
||||
mutex_lock(&dpaux_lock);
|
||||
list_del(&dpaux->list);
|
||||
mutex_unlock(&dpaux_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops tegra_dpaux_pm_ops = {
|
||||
SET_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 = &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;
|
||||
|
||||
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;
|
||||
|
||||
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
drivers/gpu/drm/tegra/dpaux.h
Normal file
73
drivers/gpu/drm/tegra/dpaux.h
Normal 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
|
||||
1412
drivers/gpu/drm/tegra/drm.c
Normal file
1412
drivers/gpu/drm/tegra/drm.c
Normal file
File diff suppressed because it is too large
Load Diff
195
drivers/gpu/drm/tegra/drm.h
Normal file
195
drivers/gpu/drm/tegra/drm.h
Normal file
@@ -0,0 +1,195 @@
|
||||
/* 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-next.h>
|
||||
#include <linux/iova.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_encoder.h>
|
||||
#include <drm/drm_fb_helper.h>
|
||||
#include <drm/drm_fixed.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <uapi/drm/tegra_drm_next.h>
|
||||
|
||||
#include "gem.h"
|
||||
#include "hub.h"
|
||||
|
||||
struct reset_control;
|
||||
|
||||
#ifdef CONFIG_DRM_FBDEV_EMULATION
|
||||
struct tegra_fbdev {
|
||||
struct drm_fb_helper base;
|
||||
struct drm_framebuffer *fb;
|
||||
};
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
#ifdef CONFIG_DRM_FBDEV_EMULATION
|
||||
struct tegra_fbdev *fbdev;
|
||||
#endif
|
||||
|
||||
unsigned int pitch_align;
|
||||
|
||||
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;
|
||||
unsigned int id;
|
||||
};
|
||||
|
||||
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 tegra_drm_submit(struct tegra_drm_context *context,
|
||||
struct drm_tegra_submit *args, struct drm_device *drm,
|
||||
struct drm_file *file);
|
||||
|
||||
struct tegra_drm_client {
|
||||
struct host1x_client base;
|
||||
struct list_head list;
|
||||
struct tegra_drm *drm;
|
||||
|
||||
/* Set by driver */
|
||||
unsigned int version;
|
||||
const struct tegra_drm_client_ops *ops;
|
||||
|
||||
/* Set by TegraDRM core */
|
||||
struct host1x_channel *shared_channel;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
int tegra_drm_init(struct tegra_drm *tegra, struct drm_device *drm);
|
||||
int tegra_drm_exit(struct tegra_drm *tegra);
|
||||
|
||||
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_panel *panel;
|
||||
struct i2c_adapter *ddc;
|
||||
const struct edid *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_mode_fb_cmd2 *cmd);
|
||||
int tegra_drm_fb_prepare(struct drm_device *drm);
|
||||
void tegra_drm_fb_free(struct drm_device *drm);
|
||||
int tegra_drm_fb_init(struct drm_device *drm);
|
||||
void tegra_drm_fb_exit(struct drm_device *drm);
|
||||
|
||||
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;
|
||||
|
||||
#endif /* HOST1X_DRM_H */
|
||||
1696
drivers/gpu/drm/tegra/dsi.c
Normal file
1696
drivers/gpu/drm/tegra/dsi.c
Normal file
File diff suppressed because it is too large
Load Diff
143
drivers/gpu/drm/tegra/dsi.h
Normal file
143
drivers/gpu/drm/tegra/dsi.h
Normal 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
|
||||
222
drivers/gpu/drm/tegra/falcon.c
Normal file
222
drivers/gpu/drm/tegra/falcon.c
Normal file
@@ -0,0 +1,222 @@
|
||||
// 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_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;
|
||||
|
||||
if (target == FALCON_MEMORY_IMEM)
|
||||
cmd |= FALCON_DMATRFCMD_IMEM;
|
||||
|
||||
falcon_writel(falcon, offset, FALCON_DMATRFMOFFS);
|
||||
falcon_writel(falcon, base, FALCON_DMATRFFBOFFS);
|
||||
falcon_writel(falcon, cmd, FALCON_DMATRFCMD);
|
||||
|
||||
return falcon_dma_wait_idle(falcon);
|
||||
}
|
||||
|
||||
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(((u32 *)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) {
|
||||
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 first code segment into Falcon internal memory */
|
||||
falcon_copy_chunk(falcon, falcon->firmware.code.offset,
|
||||
0, FALCON_MEMORY_IMEM);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
114
drivers/gpu/drm/tegra/falcon.h
Normal file
114
drivers/gpu/drm/tegra/falcon.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/* 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_IDLE (1 << 1)
|
||||
#define FALCON_DMATRFCMD_IMEM (1 << 4)
|
||||
#define FALCON_DMATRFCMD_SIZE_256B (6 << 8)
|
||||
|
||||
#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_ */
|
||||
403
drivers/gpu/drm/tegra/fb.c
Normal file
403
drivers/gpu/drm/tegra/fb.c
Normal file
@@ -0,0 +1,403 @@
|
||||
// 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 CMA helpers
|
||||
* Copyright (C) 2012 Analog Devices Inc.
|
||||
*/
|
||||
|
||||
#include <linux/console.h>
|
||||
|
||||
#include <drm/drm_fourcc.h>
|
||||
#include <drm/drm_gem_framebuffer_helper.h>
|
||||
#include <drm/drm_modeset_helper.h>
|
||||
|
||||
#include "drm.h"
|
||||
#include "gem.h"
|
||||
|
||||
#ifdef CONFIG_DRM_FBDEV_EMULATION
|
||||
static inline struct tegra_fbdev *to_tegra_fbdev(struct drm_fb_helper *helper)
|
||||
{
|
||||
return container_of(helper, struct tegra_fbdev, base);
|
||||
}
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
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:
|
||||
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_mode_fb_cmd2 *mode_cmd,
|
||||
struct tegra_bo **planes,
|
||||
unsigned int num_planes)
|
||||
{
|
||||
struct drm_framebuffer *fb;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
fb = kzalloc(sizeof(*fb), GFP_KERNEL);
|
||||
if (!fb)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
drm_helper_mode_fill_fb_struct(drm, fb, 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_mode_fb_cmd2 *cmd)
|
||||
{
|
||||
const struct drm_format_info *info = drm_get_format_info(drm, 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;
|
||||
goto unreference;
|
||||
}
|
||||
|
||||
planes[i] = to_tegra_bo(gem);
|
||||
}
|
||||
|
||||
fb = tegra_fb_alloc(drm, 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);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DRM_FBDEV_EMULATION
|
||||
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 const struct fb_ops tegra_fb_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
DRM_FB_HELPER_DEFAULT_OPS,
|
||||
.fb_fillrect = drm_fb_helper_sys_fillrect,
|
||||
.fb_copyarea = drm_fb_helper_sys_copyarea,
|
||||
.fb_imageblit = drm_fb_helper_sys_imageblit,
|
||||
.fb_mmap = tegra_fb_mmap,
|
||||
};
|
||||
|
||||
static int tegra_fbdev_probe(struct drm_fb_helper *helper,
|
||||
struct drm_fb_helper_surface_size *sizes)
|
||||
{
|
||||
struct tegra_fbdev *fbdev = to_tegra_fbdev(helper);
|
||||
struct tegra_drm *tegra = helper->dev->dev_private;
|
||||
struct drm_device *drm = helper->dev;
|
||||
struct drm_mode_fb_cmd2 cmd = { 0 };
|
||||
unsigned int bytes_per_pixel;
|
||||
struct drm_framebuffer *fb;
|
||||
unsigned long offset;
|
||||
struct fb_info *info;
|
||||
struct tegra_bo *bo;
|
||||
size_t size;
|
||||
int err;
|
||||
|
||||
bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
|
||||
|
||||
cmd.width = sizes->surface_width;
|
||||
cmd.height = sizes->surface_height;
|
||||
cmd.pitches[0] = round_up(sizes->surface_width * bytes_per_pixel,
|
||||
tegra->pitch_align);
|
||||
|
||||
cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
|
||||
sizes->surface_depth);
|
||||
|
||||
size = cmd.pitches[0] * cmd.height;
|
||||
|
||||
bo = tegra_bo_create(drm, size, 0);
|
||||
if (IS_ERR(bo))
|
||||
return PTR_ERR(bo);
|
||||
|
||||
info = drm_fb_helper_alloc_fbi(helper);
|
||||
if (IS_ERR(info)) {
|
||||
dev_err(drm->dev, "failed to allocate framebuffer info\n");
|
||||
drm_gem_object_put(&bo->gem);
|
||||
return PTR_ERR(info);
|
||||
}
|
||||
|
||||
fbdev->fb = tegra_fb_alloc(drm, &cmd, &bo, 1);
|
||||
if (IS_ERR(fbdev->fb)) {
|
||||
err = PTR_ERR(fbdev->fb);
|
||||
dev_err(drm->dev, "failed to allocate DRM framebuffer: %d\n",
|
||||
err);
|
||||
drm_gem_object_put(&bo->gem);
|
||||
return PTR_ERR(fbdev->fb);
|
||||
}
|
||||
|
||||
fb = fbdev->fb;
|
||||
helper->fb = fb;
|
||||
helper->fbdev = info;
|
||||
|
||||
info->fbops = &tegra_fb_ops;
|
||||
|
||||
drm_fb_helper_fill_info(info, helper, sizes);
|
||||
|
||||
offset = info->var.xoffset * bytes_per_pixel +
|
||||
info->var.yoffset * fb->pitches[0];
|
||||
|
||||
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 destroy;
|
||||
}
|
||||
}
|
||||
|
||||
drm->mode_config.fb_base = (resource_size_t)bo->iova;
|
||||
info->screen_base = (void __iomem *)bo->vaddr + offset;
|
||||
info->screen_size = size;
|
||||
info->fix.smem_start = (unsigned long)(bo->iova + offset);
|
||||
info->fix.smem_len = size;
|
||||
|
||||
return 0;
|
||||
|
||||
destroy:
|
||||
drm_framebuffer_remove(fb);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct drm_fb_helper_funcs tegra_fb_helper_funcs = {
|
||||
.fb_probe = tegra_fbdev_probe,
|
||||
};
|
||||
|
||||
static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm)
|
||||
{
|
||||
struct tegra_fbdev *fbdev;
|
||||
|
||||
fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
|
||||
if (!fbdev) {
|
||||
dev_err(drm->dev, "failed to allocate DRM fbdev\n");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
drm_fb_helper_prepare(drm, &fbdev->base, &tegra_fb_helper_funcs);
|
||||
|
||||
return fbdev;
|
||||
}
|
||||
|
||||
static void tegra_fbdev_free(struct tegra_fbdev *fbdev)
|
||||
{
|
||||
kfree(fbdev);
|
||||
}
|
||||
|
||||
static int tegra_fbdev_init(struct tegra_fbdev *fbdev,
|
||||
unsigned int preferred_bpp,
|
||||
unsigned int num_crtc,
|
||||
unsigned int max_connectors)
|
||||
{
|
||||
struct drm_device *drm = fbdev->base.dev;
|
||||
int err;
|
||||
|
||||
err = drm_fb_helper_init(drm, &fbdev->base);
|
||||
if (err < 0) {
|
||||
dev_err(drm->dev, "failed to initialize DRM FB helper: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = drm_fb_helper_initial_config(&fbdev->base, preferred_bpp);
|
||||
if (err < 0) {
|
||||
dev_err(drm->dev, "failed to set initial configuration: %d\n",
|
||||
err);
|
||||
goto fini;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fini:
|
||||
drm_fb_helper_fini(&fbdev->base);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void tegra_fbdev_exit(struct tegra_fbdev *fbdev)
|
||||
{
|
||||
drm_fb_helper_unregister_fbi(&fbdev->base);
|
||||
|
||||
if (fbdev->fb) {
|
||||
struct tegra_bo *bo = tegra_fb_get_plane(fbdev->fb, 0);
|
||||
|
||||
/* Undo the special mapping we made in fbdev probe. */
|
||||
if (bo && bo->pages) {
|
||||
vunmap(bo->vaddr);
|
||||
bo->vaddr = NULL;
|
||||
}
|
||||
|
||||
drm_framebuffer_remove(fbdev->fb);
|
||||
}
|
||||
|
||||
drm_fb_helper_fini(&fbdev->base);
|
||||
tegra_fbdev_free(fbdev);
|
||||
}
|
||||
#endif
|
||||
|
||||
int tegra_drm_fb_prepare(struct drm_device *drm)
|
||||
{
|
||||
#ifdef CONFIG_DRM_FBDEV_EMULATION
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
|
||||
tegra->fbdev = tegra_fbdev_create(drm);
|
||||
if (IS_ERR(tegra->fbdev))
|
||||
return PTR_ERR(tegra->fbdev);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tegra_drm_fb_free(struct drm_device *drm)
|
||||
{
|
||||
#ifdef CONFIG_DRM_FBDEV_EMULATION
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
|
||||
tegra_fbdev_free(tegra->fbdev);
|
||||
#endif
|
||||
}
|
||||
|
||||
int tegra_drm_fb_init(struct drm_device *drm)
|
||||
{
|
||||
#ifdef CONFIG_DRM_FBDEV_EMULATION
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
int err;
|
||||
|
||||
err = tegra_fbdev_init(tegra->fbdev, 32, drm->mode_config.num_crtc,
|
||||
drm->mode_config.num_connector);
|
||||
if (err < 0)
|
||||
return err;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tegra_drm_fb_exit(struct drm_device *drm)
|
||||
{
|
||||
#ifdef CONFIG_DRM_FBDEV_EMULATION
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
|
||||
tegra_fbdev_exit(tegra->fbdev);
|
||||
#endif
|
||||
}
|
||||
703
drivers/gpu/drm/tegra/gem.c
Normal file
703
drivers/gpu/drm/tegra/gem.c
Normal file
@@ -0,0 +1,703 @@
|
||||
// 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 <drm/drm_drv.h>
|
||||
#include <drm/drm_prime.h>
|
||||
#include <drm/tegra_drm.h>
|
||||
|
||||
#include "drm.h"
|
||||
#include "gem.h"
|
||||
|
||||
static void tegra_bo_put(struct host1x_bo *bo)
|
||||
{
|
||||
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
|
||||
|
||||
drm_gem_object_put(&obj->gem);
|
||||
}
|
||||
|
||||
/* XXX move this into lib/scatterlist.c? */
|
||||
static int sg_alloc_table_from_sg(struct sg_table *sgt, struct scatterlist *sg,
|
||||
unsigned int nents, gfp_t gfp_mask)
|
||||
{
|
||||
struct scatterlist *dst;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
err = sg_alloc_table(sgt, nents, gfp_mask);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
dst = sgt->sgl;
|
||||
|
||||
for (i = 0; i < nents; i++) {
|
||||
sg_set_page(dst, sg_page(sg), sg->length, 0);
|
||||
dst = sg_next(dst);
|
||||
sg = sg_next(sg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sg_table *tegra_bo_pin(struct device *dev, struct host1x_bo *bo,
|
||||
dma_addr_t *phys)
|
||||
{
|
||||
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
|
||||
struct sg_table *sgt;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* If we've manually mapped the buffer object through the IOMMU, make
|
||||
* sure to return the IOVA address of our mapping.
|
||||
*
|
||||
* Similarly, for buffers that have been allocated by the DMA API the
|
||||
* physical address can be used for devices that are not attached to
|
||||
* an IOMMU. For these devices, callers must pass a valid pointer via
|
||||
* the @phys argument.
|
||||
*
|
||||
* Imported buffers were also already mapped at import time, so the
|
||||
* existing mapping can be reused.
|
||||
*/
|
||||
if (phys) {
|
||||
*phys = obj->iova;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
|
||||
if (!sgt)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
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(sgt, obj->pages, obj->num_pages,
|
||||
0, obj->gem.size, GFP_KERNEL);
|
||||
if (err < 0)
|
||||
goto free;
|
||||
} else if (obj->sgt) {
|
||||
/*
|
||||
* If the buffer object already has an SG table but no pages
|
||||
* were allocated for it, it means the buffer was imported and
|
||||
* the SG table needs to be copied to avoid overwriting any
|
||||
* other potential users of the original SG table.
|
||||
*/
|
||||
err = sg_alloc_table_from_sg(sgt, obj->sgt->sgl, obj->sgt->nents,
|
||||
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, sgt, obj->vaddr, obj->iova,
|
||||
obj->gem.size);
|
||||
if (err < 0)
|
||||
goto free;
|
||||
}
|
||||
|
||||
return sgt;
|
||||
|
||||
free:
|
||||
kfree(sgt);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
static void tegra_bo_unpin(struct device *dev, struct sg_table *sgt)
|
||||
{
|
||||
if (sgt) {
|
||||
sg_free_table(sgt);
|
||||
kfree(sgt);
|
||||
}
|
||||
}
|
||||
|
||||
static void *tegra_bo_mmap(struct host1x_bo *bo)
|
||||
{
|
||||
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
|
||||
|
||||
if (obj->vaddr)
|
||||
return obj->vaddr;
|
||||
else if (obj->gem.import_attach)
|
||||
return dma_buf_vmap(obj->gem.import_attach->dmabuf);
|
||||
else
|
||||
return vmap(obj->pages, obj->num_pages, VM_MAP,
|
||||
pgprot_writecombine(PAGE_KERNEL));
|
||||
}
|
||||
|
||||
static void tegra_bo_munmap(struct host1x_bo *bo, void *addr)
|
||||
{
|
||||
struct tegra_bo *obj = host1x_to_tegra_bo(bo);
|
||||
|
||||
if (obj->vaddr)
|
||||
return;
|
||||
else if (obj->gem.import_attach)
|
||||
dma_buf_vunmap(obj->gem.import_attach->dmabuf, addr);
|
||||
else
|
||||
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;
|
||||
int err;
|
||||
|
||||
if (bo->mm)
|
||||
return -EBUSY;
|
||||
|
||||
bo->mm = kzalloc(sizeof(*bo->mm), GFP_KERNEL);
|
||||
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;
|
||||
|
||||
bo->size = iommu_map_sg(tegra->domain, bo->iova, bo->sgt->sgl,
|
||||
bo->sgt->nents, prot);
|
||||
if (!bo->size) {
|
||||
dev_err(tegra->drm->dev, "failed to map buffer\n");
|
||||
err = -ENOMEM;
|
||||
goto remove;
|
||||
}
|
||||
|
||||
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 struct tegra_bo *tegra_bo_alloc_object(struct drm_device *drm,
|
||||
size_t size)
|
||||
{
|
||||
struct tegra_bo *bo;
|
||||
int err;
|
||||
|
||||
bo = kzalloc(sizeof(*bo), GFP_KERNEL);
|
||||
if (!bo)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
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_sg(drm->dev, bo->sgt->sgl, bo->sgt->nents,
|
||||
DMA_FROM_DEVICE);
|
||||
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->pages, bo->num_pages);
|
||||
if (IS_ERR(bo->sgt)) {
|
||||
err = PTR_ERR(bo->sgt);
|
||||
goto put_pages;
|
||||
}
|
||||
|
||||
err = dma_map_sg(drm->dev, bo->sgt->sgl, bo->sgt->nents,
|
||||
DMA_FROM_DEVICE);
|
||||
if (err == 0) {
|
||||
err = -EFAULT;
|
||||
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;
|
||||
|
||||
attach = dma_buf_attach(buf, drm->dev);
|
||||
if (IS_ERR(attach)) {
|
||||
err = PTR_ERR(attach);
|
||||
goto free;
|
||||
}
|
||||
|
||||
get_dma_buf(buf);
|
||||
|
||||
bo->sgt = dma_buf_map_attachment(attach, DMA_TO_DEVICE);
|
||||
if (IS_ERR(bo->sgt)) {
|
||||
err = PTR_ERR(bo->sgt);
|
||||
goto detach;
|
||||
}
|
||||
|
||||
if (tegra->domain) {
|
||||
err = tegra_bo_iommu_map(tegra, bo);
|
||||
if (err < 0)
|
||||
goto detach;
|
||||
}
|
||||
|
||||
bo->gem.import_attach = attach;
|
||||
bo->gem.resv = buf->resv;
|
||||
|
||||
return bo;
|
||||
|
||||
detach:
|
||||
if (!IS_ERR_OR_NULL(bo->sgt))
|
||||
dma_buf_unmap_attachment(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);
|
||||
|
||||
if (tegra->domain)
|
||||
tegra_bo_iommu_unmap(tegra, bo);
|
||||
|
||||
if (gem->import_attach) {
|
||||
dma_buf_unmap_attachment(gem->import_attach, bo->sgt,
|
||||
DMA_TO_DEVICE);
|
||||
drm_prime_gem_destroy(gem, NULL);
|
||||
} else {
|
||||
tegra_bo_free(gem->dev, bo);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
|
||||
struct tegra_drm *tegra = drm->dev_private;
|
||||
struct tegra_bo *bo;
|
||||
|
||||
args->pitch = round_up(min_pitch, tegra->pitch_align);
|
||||
args->size = args->pitch * args->height;
|
||||
|
||||
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.
|
||||
*/
|
||||
vma->vm_flags &= ~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);
|
||||
|
||||
vma->vm_flags |= VM_MIXEDMAP;
|
||||
vma->vm_flags &= ~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(sizeof(*sgt), GFP_KERNEL);
|
||||
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_sg(attach->dev, sgt->sgl, sgt->nents, 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_sg(attach->dev, sgt->sgl, sgt->nents, dir);
|
||||
|
||||
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_sg_for_cpu(drm->dev, bo->sgt->sgl, bo->sgt->nents,
|
||||
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_sg_for_device(drm->dev, bo->sgt->sgl, bo->sgt->nents,
|
||||
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 void *tegra_gem_prime_vmap(struct dma_buf *buf)
|
||||
{
|
||||
struct drm_gem_object *gem = buf->priv;
|
||||
struct tegra_bo *bo = to_tegra_bo(gem);
|
||||
|
||||
return bo->vaddr;
|
||||
}
|
||||
|
||||
static void tegra_gem_prime_vunmap(struct dma_buf *buf, void *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;
|
||||
exp_info.resv = gem->resv;
|
||||
|
||||
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;
|
||||
}
|
||||
77
drivers/gpu/drm/tegra/gem.h
Normal file
77
drivers/gpu/drm/tegra/gem.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/* 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-next.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,
|
||||
};
|
||||
|
||||
struct tegra_bo_tiling {
|
||||
enum tegra_bo_tiling_mode mode;
|
||||
unsigned long value;
|
||||
};
|
||||
|
||||
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 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);
|
||||
|
||||
#endif
|
||||
269
drivers/gpu/drm/tegra/gr2d.c
Normal file
269
drivers/gpu/drm/tegra/gr2d.c
Normal file
@@ -0,0 +1,269 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2012-2013, NVIDIA Corporation.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include "drm.h"
|
||||
#include "gem.h"
|
||||
#include "gr2d.h"
|
||||
|
||||
struct gr2d_soc {
|
||||
unsigned int version;
|
||||
};
|
||||
|
||||
struct gr2d {
|
||||
struct tegra_drm_client client;
|
||||
struct host1x_channel *channel;
|
||||
struct clk *clk;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
detach:
|
||||
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);
|
||||
|
||||
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 of_device_id gr2d_match[] = {
|
||||
{ .compatible = "nvidia,tegra30-gr2d", .data = &tegra20_gr2d_soc },
|
||||
{ .compatible = "nvidia,tegra20-gr2d", .data = &tegra30_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_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;
|
||||
|
||||
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 = clk_prepare_enable(gr2d->clk);
|
||||
if (err) {
|
||||
dev_err(dev, "cannot turn on clock\n");
|
||||
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 = host1x_client_register(&gr2d->client.base);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to register host1x client: %d\n", err);
|
||||
clk_disable_unprepare(gr2d->clk);
|
||||
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);
|
||||
|
||||
platform_set_drvdata(pdev, gr2d);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gr2d_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gr2d *gr2d = platform_get_drvdata(pdev);
|
||||
int err;
|
||||
|
||||
err = host1x_client_unregister(&gr2d->client.base);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
clk_disable_unprepare(gr2d->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct platform_driver tegra_gr2d_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-gr2d",
|
||||
.of_match_table = gr2d_match,
|
||||
},
|
||||
.probe = gr2d_probe,
|
||||
.remove = gr2d_remove,
|
||||
};
|
||||
26
drivers/gpu/drm/tegra/gr2d.h
Normal file
26
drivers/gpu/drm/tegra/gr2d.h
Normal 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
|
||||
403
drivers/gpu/drm/tegra/gr3d.c
Normal file
403
drivers/gpu/drm/tegra/gr3d.c
Normal file
@@ -0,0 +1,403 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2013 Avionic Design GmbH
|
||||
* Copyright (C) 2013 NVIDIA Corporation
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/host1x-next.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <soc/tegra/pmc.h>
|
||||
|
||||
#include "drm.h"
|
||||
#include "gem.h"
|
||||
#include "gr3d.h"
|
||||
|
||||
struct gr3d_soc {
|
||||
unsigned int version;
|
||||
};
|
||||
|
||||
struct gr3d {
|
||||
struct tegra_drm_client client;
|
||||
struct host1x_channel *channel;
|
||||
struct clk *clk_secondary;
|
||||
struct clk *clk;
|
||||
struct reset_control *rst_secondary;
|
||||
struct reset_control *rst;
|
||||
|
||||
const struct gr3d_soc *soc;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
detach:
|
||||
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);
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
static const struct gr3d_soc tegra30_gr3d_soc = {
|
||||
.version = 0x30,
|
||||
};
|
||||
|
||||
static const struct gr3d_soc tegra114_gr3d_soc = {
|
||||
.version = 0x35,
|
||||
};
|
||||
|
||||
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_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
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;
|
||||
|
||||
gr3d->soc = of_device_get_match_data(&pdev->dev);
|
||||
|
||||
syncpts = devm_kzalloc(&pdev->dev, sizeof(*syncpts), GFP_KERNEL);
|
||||
if (!syncpts)
|
||||
return -ENOMEM;
|
||||
|
||||
gr3d->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(gr3d->clk)) {
|
||||
dev_err(&pdev->dev, "cannot get clock\n");
|
||||
return PTR_ERR(gr3d->clk);
|
||||
}
|
||||
|
||||
gr3d->rst = devm_reset_control_get(&pdev->dev, "3d");
|
||||
if (IS_ERR(gr3d->rst)) {
|
||||
dev_err(&pdev->dev, "cannot get reset\n");
|
||||
return PTR_ERR(gr3d->rst);
|
||||
}
|
||||
|
||||
if (of_device_is_compatible(np, "nvidia,tegra30-gr3d")) {
|
||||
gr3d->clk_secondary = devm_clk_get(&pdev->dev, "3d2");
|
||||
if (IS_ERR(gr3d->clk_secondary)) {
|
||||
dev_err(&pdev->dev, "cannot get secondary clock\n");
|
||||
return PTR_ERR(gr3d->clk_secondary);
|
||||
}
|
||||
|
||||
gr3d->rst_secondary = devm_reset_control_get(&pdev->dev,
|
||||
"3d2");
|
||||
if (IS_ERR(gr3d->rst_secondary)) {
|
||||
dev_err(&pdev->dev, "cannot get secondary reset\n");
|
||||
return PTR_ERR(gr3d->rst_secondary);
|
||||
}
|
||||
}
|
||||
|
||||
err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D, gr3d->clk,
|
||||
gr3d->rst);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to power up 3D unit\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (gr3d->clk_secondary) {
|
||||
err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D1,
|
||||
gr3d->clk_secondary,
|
||||
gr3d->rst_secondary);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to power up secondary 3D unit\n");
|
||||
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 = host1x_client_register(&gr3d->client.base);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to register host1x client: %d\n",
|
||||
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);
|
||||
|
||||
platform_set_drvdata(pdev, gr3d);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gr3d_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gr3d *gr3d = platform_get_drvdata(pdev);
|
||||
int err;
|
||||
|
||||
err = host1x_client_unregister(&gr3d->client.base);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (gr3d->clk_secondary) {
|
||||
reset_control_assert(gr3d->rst_secondary);
|
||||
tegra_powergate_power_off(TEGRA_POWERGATE_3D1);
|
||||
clk_disable_unprepare(gr3d->clk_secondary);
|
||||
}
|
||||
|
||||
reset_control_assert(gr3d->rst);
|
||||
tegra_powergate_power_off(TEGRA_POWERGATE_3D);
|
||||
clk_disable_unprepare(gr3d->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct platform_driver tegra_gr3d_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-gr3d",
|
||||
.of_match_table = tegra_gr3d_match,
|
||||
},
|
||||
.probe = gr3d_probe,
|
||||
.remove = gr3d_remove,
|
||||
};
|
||||
24
drivers/gpu/drm/tegra/gr3d.h
Normal file
24
drivers/gpu/drm/tegra/gr3d.h
Normal 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
drivers/gpu/drm/tegra/hda.c
Normal file
63
drivers/gpu/drm/tegra/hda.c
Normal 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
drivers/gpu/drm/tegra/hda.h
Normal file
20
drivers/gpu/drm/tegra/hda.h
Normal 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
|
||||
1773
drivers/gpu/drm/tegra/hdmi.c
Normal file
1773
drivers/gpu/drm/tegra/hdmi.c
Normal file
File diff suppressed because it is too large
Load Diff
557
drivers/gpu/drm/tegra/hdmi.h
Normal file
557
drivers/gpu/drm/tegra/hdmi.h
Normal 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 */
|
||||
1016
drivers/gpu/drm/tegra/hub.c
Normal file
1016
drivers/gpu/drm/tegra/hub.c
Normal file
File diff suppressed because it is too large
Load Diff
98
drivers/gpu/drm/tegra/hub.h
Normal file
98
drivers/gpu/drm/tegra/hub.h
Normal 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_dc;
|
||||
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);
|
||||
|
||||
int tegra_display_hub_atomic_check(struct drm_device *drm,
|
||||
struct drm_atomic_state *state);
|
||||
void tegra_display_hub_atomic_commit(struct drm_device *drm,
|
||||
struct drm_atomic_state *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 */
|
||||
134
drivers/gpu/drm/tegra/mipi-phy.c
Normal file
134
drivers/gpu/drm/tegra/mipi-phy.c
Normal file
@@ -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;
|
||||
}
|
||||
48
drivers/gpu/drm/tegra/mipi-phy.h
Normal file
48
drivers/gpu/drm/tegra/mipi-phy.h
Normal file
@@ -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
|
||||
264
drivers/gpu/drm/tegra/output.c
Normal file
264
drivers/gpu/drm/tegra/output.c
Normal file
@@ -0,0 +1,264 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2012 Avionic Design GmbH
|
||||
* Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <drm/drm_atomic_helper.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);
|
||||
struct edid *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->edid)
|
||||
edid = kmemdup(output->edid, sizeof(*edid), GFP_KERNEL);
|
||||
else if (output->ddc)
|
||||
edid = drm_get_edid(connector, output->ddc);
|
||||
|
||||
cec_notifier_set_phys_addr_from_edid(output->cec, edid);
|
||||
drm_connector_update_edid_property(connector, edid);
|
||||
|
||||
if (edid) {
|
||||
err = drm_add_edid_modes(connector, edid);
|
||||
kfree(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;
|
||||
unsigned long flags;
|
||||
int err, size;
|
||||
|
||||
if (!output->of_node)
|
||||
output->of_node = output->dev->of_node;
|
||||
|
||||
panel = of_parse_phandle(output->of_node, "nvidia,panel", 0);
|
||||
if (panel) {
|
||||
output->panel = of_drm_find_panel(panel);
|
||||
if (IS_ERR(output->panel))
|
||||
return PTR_ERR(output->panel);
|
||||
|
||||
of_node_put(panel);
|
||||
}
|
||||
|
||||
output->edid = of_get_property(output->of_node, "nvidia,edid", &size);
|
||||
|
||||
ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0);
|
||||
if (ddc) {
|
||||
output->ddc = of_find_i2c_adapter_by_node(ddc);
|
||||
if (!output->ddc) {
|
||||
err = -EPROBE_DEFER;
|
||||
of_node_put(ddc);
|
||||
return err;
|
||||
}
|
||||
|
||||
of_node_put(ddc);
|
||||
}
|
||||
|
||||
output->hpd_gpio = devm_gpiod_get_from_of_node(output->dev,
|
||||
output->of_node,
|
||||
"nvidia,hpd-gpio", 0,
|
||||
GPIOD_IN,
|
||||
"HDMI hotplug detect");
|
||||
if (IS_ERR(output->hpd_gpio)) {
|
||||
if (PTR_ERR(output->hpd_gpio) != -ENOENT)
|
||||
return PTR_ERR(output->hpd_gpio);
|
||||
|
||||
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);
|
||||
return err;
|
||||
}
|
||||
|
||||
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);
|
||||
return err;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void tegra_output_remove(struct tegra_output *output)
|
||||
{
|
||||
if (output->hpd_gpio)
|
||||
free_irq(output->hpd_irq, output);
|
||||
|
||||
if (output->ddc)
|
||||
put_device(&output->ddc->dev);
|
||||
}
|
||||
|
||||
int tegra_output_init(struct drm_device *drm, struct tegra_output *output)
|
||||
{
|
||||
int connector_type;
|
||||
int err;
|
||||
|
||||
if (output->panel) {
|
||||
err = drm_panel_attach(output->panel, &output->connector);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_detach(output->panel);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
602
drivers/gpu/drm/tegra/plane.c
Normal file
602
drivers/gpu/drm/tegra/plane.c
Normal file
@@ -0,0 +1,602 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/iommu.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_fourcc.h>
|
||||
#include <drm/drm_gem_framebuffer_helper.h>
|
||||
#include <drm/drm_plane_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(sizeof(*state), GFP_KERNEL);
|
||||
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(sizeof(*copy), GFP_KERNEL);
|
||||
if (!copy)
|
||||
return NULL;
|
||||
|
||||
__drm_atomic_helper_plane_duplicate_state(plane, ©->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;
|
||||
|
||||
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->sgt[i] = NULL;
|
||||
}
|
||||
|
||||
return ©->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_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;
|
||||
|
||||
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)
|
||||
{
|
||||
struct iommu_domain *domain = iommu_get_domain_for_dev(dc->dev);
|
||||
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);
|
||||
dma_addr_t phys_addr, *phys;
|
||||
struct sg_table *sgt;
|
||||
|
||||
if (!domain || dc->client.group)
|
||||
phys = &phys_addr;
|
||||
else
|
||||
phys = NULL;
|
||||
|
||||
sgt = host1x_bo_pin(dc->dev, &bo->base, phys);
|
||||
if (IS_ERR(sgt)) {
|
||||
err = PTR_ERR(sgt);
|
||||
goto unpin;
|
||||
}
|
||||
|
||||
if (sgt) {
|
||||
err = dma_map_sg(dc->dev, sgt->sgl, sgt->nents,
|
||||
DMA_TO_DEVICE);
|
||||
if (err == 0) {
|
||||
err = -ENOMEM;
|
||||
goto unpin;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 (err > 1) {
|
||||
err = -EINVAL;
|
||||
goto unpin;
|
||||
}
|
||||
|
||||
state->iova[i] = sg_dma_address(sgt->sgl);
|
||||
state->sgt[i] = sgt;
|
||||
} else {
|
||||
state->iova[i] = phys_addr;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unpin:
|
||||
dev_err(dc->dev, "failed to map plane %u: %d\n", i, err);
|
||||
|
||||
while (i--) {
|
||||
struct tegra_bo *bo = tegra_fb_get_plane(state->base.fb, i);
|
||||
struct sg_table *sgt = state->sgt[i];
|
||||
|
||||
if (sgt)
|
||||
dma_unmap_sg(dc->dev, sgt->sgl, sgt->nents,
|
||||
DMA_TO_DEVICE);
|
||||
|
||||
host1x_bo_unpin(dc->dev, &bo->base, sgt);
|
||||
state->iova[i] = DMA_MAPPING_ERROR;
|
||||
state->sgt[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++) {
|
||||
struct tegra_bo *bo = tegra_fb_get_plane(state->base.fb, i);
|
||||
struct sg_table *sgt = state->sgt[i];
|
||||
|
||||
if (sgt)
|
||||
dma_unmap_sg(dc->dev, sgt->sgl, sgt->nents,
|
||||
DMA_TO_DEVICE);
|
||||
|
||||
host1x_bo_unpin(dc->dev, &bo->base, sgt);
|
||||
state->iova[i] = DMA_MAPPING_ERROR;
|
||||
state->sgt[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);
|
||||
|
||||
if (!state->fb)
|
||||
return 0;
|
||||
|
||||
drm_gem_fb_prepare_fb(plane, state);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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_YUV420:
|
||||
*format = WIN_COLOR_DEPTH_YCbCr420P;
|
||||
break;
|
||||
|
||||
case DRM_FORMAT_YUV422:
|
||||
*format = WIN_COLOR_DEPTH_YCbCr422P;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool tegra_plane_format_is_yuv(unsigned int format, bool *planar)
|
||||
{
|
||||
switch (format) {
|
||||
case WIN_COLOR_DEPTH_YCbCr422:
|
||||
case WIN_COLOR_DEPTH_YUV422:
|
||||
if (planar)
|
||||
*planar = false;
|
||||
|
||||
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:
|
||||
if (planar)
|
||||
*planar = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (planar)
|
||||
*planar = false;
|
||||
|
||||
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)) {
|
||||
*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;
|
||||
}
|
||||
81
drivers/gpu/drm/tegra/plane.h
Normal file
81
drivers/gpu/drm/tegra/plane.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/* 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 tegra_bo;
|
||||
struct tegra_dc;
|
||||
|
||||
struct tegra_plane {
|
||||
struct drm_plane base;
|
||||
struct tegra_dc *dc;
|
||||
unsigned int offset;
|
||||
unsigned int index;
|
||||
};
|
||||
|
||||
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 sg_table *sgt[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;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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_yuv(unsigned int format, bool *planar);
|
||||
int tegra_plane_setup_legacy_state(struct tegra_plane *tegra,
|
||||
struct tegra_plane_state *state);
|
||||
|
||||
#endif /* TEGRA_PLANE_H */
|
||||
311
drivers/gpu/drm/tegra/rgb.c
Normal file
311
drivers/gpu/drm/tegra/rgb.c
Normal file
@@ -0,0 +1,311 @@
|
||||
// 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 <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_panel.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 *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 const struct drm_connector_funcs tegra_rgb_connector_funcs = {
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.detect = tegra_output_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = tegra_output_connector_destroy,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static enum drm_mode_status
|
||||
tegra_rgb_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
/*
|
||||
* FIXME: For now, always assume that the mode is okay. There are
|
||||
* unresolved issues with clk_round_rate(), which doesn't always
|
||||
* reliably report whether a frequency can be set or not.
|
||||
*/
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs tegra_rgb_connector_helper_funcs = {
|
||||
.get_modes = tegra_output_connector_get_modes,
|
||||
.mode_valid = tegra_rgb_connector_mode_valid,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_disable(output->panel);
|
||||
|
||||
tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable));
|
||||
tegra_dc_commit(rgb->dc);
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_unprepare(output->panel);
|
||||
}
|
||||
|
||||
static void tegra_rgb_encoder_enable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct tegra_output *output = encoder_to_output(encoder);
|
||||
struct tegra_rgb *rgb = to_rgb(output);
|
||||
u32 value;
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_prepare(output->panel);
|
||||
|
||||
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);
|
||||
|
||||
/* XXX: parameterize? */
|
||||
value = tegra_dc_readl(rgb->dc, DC_COM_PIN_OUTPUT_POLARITY(1));
|
||||
value &= ~LVS_OUTPUT_POLARITY_LOW;
|
||||
value &= ~LHS_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);
|
||||
|
||||
/* XXX: parameterize? */
|
||||
value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE;
|
||||
tegra_dc_writel(rgb->dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS);
|
||||
|
||||
tegra_dc_commit(rgb->dc);
|
||||
|
||||
if (output->panel)
|
||||
drm_panel_enable(output->panel);
|
||||
}
|
||||
|
||||
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).
|
||||
*/
|
||||
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,
|
||||
};
|
||||
|
||||
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 || !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");
|
||||
return PTR_ERR(rgb->clk);
|
||||
}
|
||||
|
||||
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");
|
||||
return PTR_ERR(rgb->clk_parent);
|
||||
}
|
||||
|
||||
err = clk_set_parent(rgb->clk, rgb->clk_parent);
|
||||
if (err < 0) {
|
||||
dev_err(dc->dev, "failed to set parent clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
dc->rgb = &rgb->output;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tegra_dc_rgb_remove(struct tegra_dc *dc)
|
||||
{
|
||||
if (!dc->rgb)
|
||||
return 0;
|
||||
|
||||
tegra_output_remove(dc->rgb);
|
||||
dc->rgb = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc)
|
||||
{
|
||||
struct tegra_output *output = dc->rgb;
|
||||
int err;
|
||||
|
||||
if (!dc->rgb)
|
||||
return -ENODEV;
|
||||
|
||||
drm_connector_init(drm, &output->connector, &tegra_rgb_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
drm_connector_helper_add(&output->connector,
|
||||
&tegra_rgb_connector_helper_funcs);
|
||||
output->connector.dpms = DRM_MODE_DPMS_OFF;
|
||||
|
||||
drm_simple_encoder_init(drm, &output->encoder, DRM_MODE_ENCODER_LVDS);
|
||||
drm_encoder_helper_add(&output->encoder,
|
||||
&tegra_rgb_encoder_helper_funcs);
|
||||
|
||||
drm_connector_attach_encoder(&output->connector,
|
||||
&output->encoder);
|
||||
drm_connector_register(&output->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;
|
||||
}
|
||||
4039
drivers/gpu/drm/tegra/sor.c
Normal file
4039
drivers/gpu/drm/tegra/sor.c
Normal file
File diff suppressed because it is too large
Load Diff
457
drivers/gpu/drm/tegra/sor.h
Normal file
457
drivers/gpu/drm/tegra/sor.h
Normal 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
|
||||
63
drivers/gpu/drm/tegra/uapi.h
Normal file
63
drivers/gpu/drm/tegra/uapi.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* 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 tegra_drm_channel_ctx {
|
||||
struct tegra_drm_client *client;
|
||||
struct host1x_channel *channel;
|
||||
struct xarray mappings;
|
||||
};
|
||||
|
||||
struct tegra_drm_mapping {
|
||||
struct kref ref;
|
||||
|
||||
struct device *dev;
|
||||
struct host1x_bo *bo;
|
||||
struct sg_table *sgt;
|
||||
enum dma_data_direction direction;
|
||||
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_gem_create(struct drm_device *drm, void *data,
|
||||
struct drm_file *file);
|
||||
int tegra_drm_ioctl_gem_mmap(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);
|
||||
struct tegra_drm_channel_ctx *
|
||||
tegra_drm_channel_ctx_lock(struct tegra_drm_file *file, u32 id);
|
||||
|
||||
#endif
|
||||
197
drivers/gpu/drm/tegra/uapi/firewall.c
Normal file
197
drivers/gpu/drm/tegra/uapi/firewall.c
Normal file
@@ -0,0 +1,197 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2010-2020 NVIDIA Corporation */
|
||||
|
||||
#include "../drm.h"
|
||||
#include "../uapi.h"
|
||||
|
||||
#include "submit.h"
|
||||
|
||||
struct tegra_drm_firewall {
|
||||
struct tegra_drm_submit_data *submit;
|
||||
struct tegra_drm_client *client;
|
||||
u32 *data;
|
||||
u32 pos;
|
||||
u32 end;
|
||||
};
|
||||
|
||||
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->client->base.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;
|
||||
|
||||
is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev,
|
||||
fw->client->base.class, offset);
|
||||
if (is_addr)
|
||||
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)
|
||||
{
|
||||
struct tegra_drm_firewall fw = {
|
||||
.submit = submit,
|
||||
.client = client,
|
||||
.data = data,
|
||||
.pos = start,
|
||||
.end = start+words,
|
||||
};
|
||||
bool payload_valid = false;
|
||||
u32 payload;
|
||||
int err;
|
||||
|
||||
while (fw.pos != fw.end) {
|
||||
u32 word, opcode, offset, count, mask;
|
||||
|
||||
err = fw_next(&fw, &word);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
opcode = (word & 0xf0000000) >> 28;
|
||||
|
||||
switch (opcode) {
|
||||
case HOST1X_OPCODE_INCR:
|
||||
offset = (word >> 16) & 0xfff;
|
||||
count = word & 0xffff;
|
||||
err = fw_check_regs_seq(&fw, offset, count, true);
|
||||
break;
|
||||
case HOST1X_OPCODE_NONINCR:
|
||||
offset = (word >> 16) & 0xfff;
|
||||
count = word & 0xffff;
|
||||
err = fw_check_regs_seq(&fw, offset, count, false);
|
||||
break;
|
||||
case HOST1X_OPCODE_MASK:
|
||||
offset = (word >> 16) & 0xfff;
|
||||
mask = word & 0xffff;
|
||||
err = fw_check_regs_mask(&fw, offset, mask);
|
||||
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);
|
||||
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);
|
||||
break;
|
||||
case HOST1X_OPCODE_NONINCR_W:
|
||||
if (!payload_valid)
|
||||
return -EINVAL;
|
||||
|
||||
offset = word & 0x3fffff;
|
||||
err = fw_check_regs_seq(&fw, offset, payload, false);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
86
drivers/gpu/drm/tegra/uapi/gather_bo.c
Normal file
86
drivers/gpu/drm/tegra/uapi/gather_bo.c
Normal file
@@ -0,0 +1,86 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2020 NVIDIA Corporation */
|
||||
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "gather_bo.h"
|
||||
|
||||
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);
|
||||
|
||||
kfree(bo->gather_data);
|
||||
kfree(bo);
|
||||
}
|
||||
|
||||
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 sg_table *
|
||||
gather_bo_pin(struct device *dev, struct host1x_bo *host_bo, dma_addr_t *phys)
|
||||
{
|
||||
struct gather_bo *bo = container_of(host_bo, struct gather_bo, base);
|
||||
struct sg_table *sgt;
|
||||
int err;
|
||||
|
||||
if (phys) {
|
||||
*phys = virt_to_phys(bo->gather_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
|
||||
if (!sgt)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
err = sg_alloc_table(sgt, 1, GFP_KERNEL);
|
||||
if (err) {
|
||||
kfree(sgt);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
sg_init_one(sgt->sgl, bo->gather_data, bo->gather_data_words*4);
|
||||
|
||||
return sgt;
|
||||
}
|
||||
|
||||
static void gather_bo_unpin(struct device *dev, struct sg_table *sgt)
|
||||
{
|
||||
if (sgt) {
|
||||
sg_free_table(sgt);
|
||||
kfree(sgt);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
22
drivers/gpu/drm/tegra/uapi/gather_bo.h
Normal file
22
drivers/gpu/drm/tegra/uapi/gather_bo.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright (c) 2020 NVIDIA Corporation */
|
||||
|
||||
#ifndef _TEGRA_DRM_SUBMIT_GATHER_BO_H
|
||||
#define _TEGRA_DRM_SUBMIT_GATHER_BO_H
|
||||
|
||||
#include <linux/host1x-next.h>
|
||||
#include <linux/kref.h>
|
||||
|
||||
struct gather_bo {
|
||||
struct host1x_bo base;
|
||||
|
||||
struct kref ref;
|
||||
|
||||
u32 *gather_data;
|
||||
size_t gather_data_words;
|
||||
};
|
||||
|
||||
extern const struct host1x_bo_ops gather_bo_ops;
|
||||
void gather_bo_put(struct host1x_bo *host_bo);
|
||||
|
||||
#endif
|
||||
427
drivers/gpu/drm/tegra/uapi/submit.c
Normal file
427
drivers/gpu/drm/tegra/uapi/submit.c
Normal file
@@ -0,0 +1,427 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2020 NVIDIA Corporation */
|
||||
|
||||
#include <linux/dma-fence-array.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/host1x-next.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/nospec.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/sync_file.h>
|
||||
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_file.h>
|
||||
|
||||
#include "../uapi.h"
|
||||
#include "../drm.h"
|
||||
#include "../gem.h"
|
||||
|
||||
#include "gather_bo.h"
|
||||
#include "submit.h"
|
||||
|
||||
static struct tegra_drm_mapping *
|
||||
tegra_drm_mapping_get(struct tegra_drm_channel_ctx *ctx, u32 id)
|
||||
{
|
||||
struct tegra_drm_mapping *mapping;
|
||||
|
||||
xa_lock(&ctx->mappings);
|
||||
mapping = xa_load(&ctx->mappings, id);
|
||||
if (mapping)
|
||||
kref_get(&mapping->ref);
|
||||
xa_unlock(&ctx->mappings);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
static void *alloc_copy_user_array(void __user *from, size_t count, size_t size)
|
||||
{
|
||||
unsigned long copy_err;
|
||||
size_t copy_len;
|
||||
void *data;
|
||||
|
||||
if (check_mul_overflow(count, size, ©_len))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
data = kvmalloc(copy_len, GFP_KERNEL);
|
||||
if (!data)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
copy_err = copy_from_user(data, from, copy_len);
|
||||
if (copy_err) {
|
||||
kvfree(data);
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int submit_copy_gather_data(struct drm_device *drm,
|
||||
struct gather_bo **pbo,
|
||||
struct drm_tegra_channel_submit *args)
|
||||
{
|
||||
unsigned long copy_err;
|
||||
struct gather_bo *bo;
|
||||
size_t copy_len;
|
||||
|
||||
if (args->gather_data_words == 0) {
|
||||
drm_info(drm, "gather_data_words can't be 0");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (check_mul_overflow((size_t)args->gather_data_words, (size_t)4, ©_len))
|
||||
return -EINVAL;
|
||||
|
||||
bo = kzalloc(sizeof(*bo), GFP_KERNEL);
|
||||
if (!bo)
|
||||
return -ENOMEM;
|
||||
|
||||
kref_init(&bo->ref);
|
||||
host1x_bo_init(&bo->base, &gather_bo_ops);
|
||||
|
||||
bo->gather_data = kmalloc(copy_len, GFP_KERNEL | __GFP_NOWARN);
|
||||
if (!bo->gather_data) {
|
||||
kfree(bo);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
copy_err = copy_from_user(bo->gather_data,
|
||||
u64_to_user_ptr(args->gather_data_ptr),
|
||||
copy_len);
|
||||
if (copy_err) {
|
||||
kfree(bo->gather_data);
|
||||
kfree(bo);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
bo->gather_data_words = args->gather_data_words;
|
||||
|
||||
*pbo = bo;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int submit_write_reloc(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 = (u32)(iova >> buf->reloc.shift);
|
||||
|
||||
if (buf->flags & DRM_TEGRA_SUBMIT_BUF_RELOC_BLOCKLINEAR)
|
||||
written_ptr |= BIT(39);
|
||||
|
||||
if (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 drm_device *drm, struct gather_bo *bo,
|
||||
struct tegra_drm_submit_data *job_data,
|
||||
struct tegra_drm_channel_ctx *ctx,
|
||||
struct drm_tegra_channel_submit *args)
|
||||
{
|
||||
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))
|
||||
return PTR_ERR(bufs);
|
||||
|
||||
mappings = kcalloc(args->num_bufs, sizeof(*mappings), GFP_KERNEL);
|
||||
if (!mappings) {
|
||||
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_BUF_RELOC_BLOCKLINEAR) {
|
||||
err = -EINVAL;
|
||||
goto drop_refs;
|
||||
}
|
||||
|
||||
mapping = tegra_drm_mapping_get(ctx, buf->mapping_id);
|
||||
if (!mapping) {
|
||||
drm_info(drm, "invalid mapping_id for buf: %u",
|
||||
buf->mapping_id);
|
||||
err = -EINVAL;
|
||||
goto drop_refs;
|
||||
}
|
||||
|
||||
err = submit_write_reloc(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:
|
||||
for (;;) {
|
||||
if (i-- == 0)
|
||||
break;
|
||||
|
||||
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 drm_device *drm, struct host1x_job *job,
|
||||
struct drm_tegra_channel_submit *args)
|
||||
{
|
||||
struct host1x_syncpt *sp;
|
||||
|
||||
if (args->syncpt_incr.flags)
|
||||
return -EINVAL;
|
||||
|
||||
/* Syncpt ref will be dropped on job release */
|
||||
sp = host1x_syncpt_fd_get(args->syncpt_incr.syncpt_fd);
|
||||
if (IS_ERR(sp))
|
||||
return PTR_ERR(sp);
|
||||
|
||||
job->syncpt = sp;
|
||||
job->syncpt_incrs = args->syncpt_incr.num_incrs;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int submit_job_add_gather(struct host1x_job *job,
|
||||
struct tegra_drm_channel_ctx *ctx,
|
||||
struct drm_tegra_submit_cmd_gather_uptr *cmd,
|
||||
struct gather_bo *bo, u32 *offset,
|
||||
struct tegra_drm_submit_data *job_data)
|
||||
{
|
||||
u32 next_offset;
|
||||
|
||||
if (cmd->reserved[0] || cmd->reserved[1] || cmd->reserved[2])
|
||||
return -EINVAL;
|
||||
|
||||
/* Check for maximum gather size */
|
||||
if (cmd->words > 16383)
|
||||
return -EINVAL;
|
||||
|
||||
if (check_add_overflow(*offset, cmd->words, &next_offset))
|
||||
return -EINVAL;
|
||||
|
||||
if (next_offset > bo->gather_data_words)
|
||||
return -EINVAL;
|
||||
|
||||
if (tegra_drm_fw_validate(ctx->client, bo->gather_data, *offset,
|
||||
cmd->words, job_data))
|
||||
return -EINVAL;
|
||||
|
||||
host1x_job_add_gather(job, &bo->base, cmd->words, *offset * 4);
|
||||
|
||||
*offset = next_offset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int submit_create_job(struct drm_device *drm, struct host1x_job **pjob,
|
||||
struct gather_bo *bo,
|
||||
struct tegra_drm_channel_ctx *ctx,
|
||||
struct drm_tegra_channel_submit *args,
|
||||
struct tegra_drm_submit_data *job_data)
|
||||
{
|
||||
struct drm_tegra_submit_cmd *cmds;
|
||||
u32 i, gather_offset = 0;
|
||||
struct host1x_job *job;
|
||||
int err;
|
||||
|
||||
cmds = alloc_copy_user_array(u64_to_user_ptr(args->cmds_ptr),
|
||||
args->num_cmds, sizeof(*cmds));
|
||||
if (IS_ERR(cmds))
|
||||
return PTR_ERR(cmds);
|
||||
|
||||
job = host1x_job_alloc(ctx->channel, args->num_cmds, 0);
|
||||
if (!job) {
|
||||
err = -ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = submit_get_syncpt(drm, job, args);
|
||||
if (err < 0)
|
||||
goto free_job;
|
||||
|
||||
job->client = &ctx->client->base;
|
||||
job->class = ctx->client->base.class;
|
||||
job->serialize = true;
|
||||
|
||||
for (i = 0; i < args->num_cmds; i++) {
|
||||
struct drm_tegra_submit_cmd *cmd = &cmds[i];
|
||||
|
||||
if (cmd->type == DRM_TEGRA_SUBMIT_CMD_GATHER_UPTR) {
|
||||
err = submit_job_add_gather(job, ctx, &cmd->gather_uptr,
|
||||
bo, &gather_offset,
|
||||
job_data);
|
||||
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]) {
|
||||
err = -EINVAL;
|
||||
goto free_job;
|
||||
}
|
||||
|
||||
host1x_job_add_wait(job, cmd->wait_syncpt.id,
|
||||
cmd->wait_syncpt.threshold);
|
||||
} else {
|
||||
err = -EINVAL;
|
||||
goto free_job;
|
||||
}
|
||||
}
|
||||
|
||||
if (gather_offset == 0) {
|
||||
drm_info(drm, "Job must have at least one gather");
|
||||
err = -EINVAL;
|
||||
goto free_job;
|
||||
}
|
||||
|
||||
*pjob = job;
|
||||
|
||||
err = 0;
|
||||
goto done;
|
||||
|
||||
free_job:
|
||||
host1x_job_put(job);
|
||||
|
||||
done:
|
||||
kvfree(cmds);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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_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 tegra_drm_channel_ctx *ctx;
|
||||
struct host1x_job *job;
|
||||
struct gather_bo *bo;
|
||||
u32 i;
|
||||
int err;
|
||||
|
||||
ctx = tegra_drm_channel_ctx_lock(fpriv, args->channel_ctx);
|
||||
if (!ctx)
|
||||
return -EINVAL;
|
||||
|
||||
/* Allocate gather BO and copy gather words in. */
|
||||
err = submit_copy_gather_data(drm, &bo, args);
|
||||
if (err)
|
||||
goto unlock;
|
||||
|
||||
job_data = kzalloc(sizeof(*job_data), GFP_KERNEL);
|
||||
if (!job_data) {
|
||||
err = -ENOMEM;
|
||||
goto put_bo;
|
||||
}
|
||||
|
||||
/* Get data buffer mappings and do relocation patching. */
|
||||
err = submit_process_bufs(drm, bo, job_data, ctx, args);
|
||||
if (err)
|
||||
goto free_job_data;
|
||||
|
||||
/* Allocate host1x_job and add gathers and waits to it. */
|
||||
err = submit_create_job(drm, &job, bo, ctx, args,
|
||||
job_data);
|
||||
if (err)
|
||||
goto free_job_data;
|
||||
|
||||
/* Map gather data for Host1x. */
|
||||
err = host1x_job_pin(job, ctx->client->base.dev);
|
||||
if (err)
|
||||
goto put_job;
|
||||
|
||||
/* Boot engine. */
|
||||
err = pm_runtime_get_sync(ctx->client->base.dev);
|
||||
if (err < 0)
|
||||
goto put_pm_runtime;
|
||||
|
||||
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)
|
||||
goto put_job;
|
||||
|
||||
/* Return postfences to userspace and add fences to DMA reservations. */
|
||||
args->syncpt_incr.fence_value = job->syncpt_end;
|
||||
|
||||
goto put_job;
|
||||
|
||||
put_pm_runtime:
|
||||
if (!job->release)
|
||||
pm_runtime_put(ctx->client->base.dev);
|
||||
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);
|
||||
}
|
||||
if (job_data)
|
||||
kfree(job_data);
|
||||
put_bo:
|
||||
gather_bo_put(&bo->base);
|
||||
unlock:
|
||||
mutex_unlock(&fpriv->lock);
|
||||
return err;
|
||||
}
|
||||
20
drivers/gpu/drm/tegra/uapi/submit.h
Normal file
20
drivers/gpu/drm/tegra/uapi/submit.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/* 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);
|
||||
|
||||
#endif
|
||||
306
drivers/gpu/drm/tegra/uapi/uapi.c
Normal file
306
drivers/gpu/drm/tegra/uapi/uapi.c
Normal file
@@ -0,0 +1,306 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2020 NVIDIA Corporation */
|
||||
|
||||
#include <linux/host1x-next.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_file.h>
|
||||
|
||||
#include "../uapi.h"
|
||||
#include "../drm.h"
|
||||
|
||||
struct tegra_drm_channel_ctx *
|
||||
tegra_drm_channel_ctx_lock(struct tegra_drm_file *file, u32 id)
|
||||
{
|
||||
struct tegra_drm_channel_ctx *ctx;
|
||||
|
||||
mutex_lock(&file->lock);
|
||||
ctx = xa_load(&file->contexts, id);
|
||||
if (!ctx)
|
||||
mutex_unlock(&file->lock);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void tegra_drm_mapping_release(struct kref *ref)
|
||||
{
|
||||
struct tegra_drm_mapping *mapping =
|
||||
container_of(ref, struct tegra_drm_mapping, ref);
|
||||
|
||||
if (mapping->sgt)
|
||||
dma_unmap_sgtable(mapping->dev, mapping->sgt,
|
||||
mapping->direction, DMA_ATTR_SKIP_CPU_SYNC);
|
||||
|
||||
host1x_bo_unpin(mapping->dev, mapping->bo, mapping->sgt);
|
||||
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_ctx_close(struct tegra_drm_channel_ctx *ctx)
|
||||
{
|
||||
unsigned long mapping_id;
|
||||
struct tegra_drm_mapping *mapping;
|
||||
|
||||
xa_for_each(&ctx->mappings, mapping_id, mapping)
|
||||
tegra_drm_mapping_put(mapping);
|
||||
|
||||
xa_destroy(&ctx->mappings);
|
||||
|
||||
host1x_channel_put(ctx->channel);
|
||||
|
||||
kfree(ctx);
|
||||
}
|
||||
|
||||
int close_channel_ctx(int id, void *p, void *data)
|
||||
{
|
||||
struct tegra_drm_channel_ctx *ctx = p;
|
||||
|
||||
tegra_drm_channel_ctx_close(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tegra_drm_uapi_close_file(struct tegra_drm_file *file)
|
||||
{
|
||||
unsigned long ctx_id;
|
||||
struct tegra_drm_channel_ctx *ctx;
|
||||
|
||||
xa_for_each(&file->contexts, ctx_id, ctx)
|
||||
tegra_drm_channel_ctx_close(ctx);
|
||||
|
||||
xa_destroy(&file->contexts);
|
||||
}
|
||||
|
||||
int tegra_drm_ioctl_channel_open(struct drm_device *drm, void *data,
|
||||
struct drm_file *file)
|
||||
{
|
||||
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_channel_ctx *ctx;
|
||||
int err;
|
||||
|
||||
if (args->flags)
|
||||
return -EINVAL;
|
||||
|
||||
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
err = -ENODEV;
|
||||
list_for_each_entry(client, &tegra->clients, list) {
|
||||
if (client->base.class == args->host1x_class) {
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (err)
|
||||
goto free_ctx;
|
||||
|
||||
if (client->shared_channel) {
|
||||
ctx->channel = host1x_channel_get(client->shared_channel);
|
||||
} else {
|
||||
ctx->channel = host1x_channel_request(&client->base);
|
||||
if (!ctx->channel) {
|
||||
err = -EBUSY;
|
||||
goto free_ctx;
|
||||
}
|
||||
}
|
||||
|
||||
err = xa_alloc(&fpriv->contexts, &args->channel_ctx, ctx,
|
||||
XA_LIMIT(1, U32_MAX), GFP_KERNEL);
|
||||
if (err < 0)
|
||||
goto put_channel;
|
||||
|
||||
ctx->client = client;
|
||||
xa_init_flags(&ctx->mappings, XA_FLAGS_ALLOC1);
|
||||
|
||||
args->hardware_version = client->version;
|
||||
|
||||
return 0;
|
||||
|
||||
put_channel:
|
||||
host1x_channel_put(ctx->channel);
|
||||
free_ctx:
|
||||
kfree(ctx);
|
||||
|
||||
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_channel_ctx *ctx;
|
||||
|
||||
ctx = tegra_drm_channel_ctx_lock(fpriv, args->channel_ctx);
|
||||
if (!ctx)
|
||||
return -EINVAL;
|
||||
|
||||
xa_erase(&fpriv->contexts, args->channel_ctx);
|
||||
|
||||
mutex_unlock(&fpriv->lock);
|
||||
|
||||
tegra_drm_channel_ctx_close(ctx);
|
||||
|
||||
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_channel_ctx *ctx;
|
||||
struct tegra_drm_mapping *mapping;
|
||||
struct drm_gem_object *gem;
|
||||
u32 mapping_id;
|
||||
int err = 0;
|
||||
|
||||
if (args->flags & ~DRM_TEGRA_CHANNEL_MAP_READWRITE)
|
||||
return -EINVAL;
|
||||
|
||||
ctx = tegra_drm_channel_ctx_lock(fpriv, args->channel_ctx);
|
||||
if (!ctx)
|
||||
return -EINVAL;
|
||||
|
||||
mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
|
||||
if (!mapping) {
|
||||
err = -ENOMEM;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
kref_init(&mapping->ref);
|
||||
|
||||
gem = drm_gem_object_lookup(file, args->handle);
|
||||
if (!gem) {
|
||||
err = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
mapping->dev = ctx->client->base.dev;
|
||||
mapping->bo = &container_of(gem, struct tegra_bo, gem)->base;
|
||||
|
||||
if (!iommu_get_domain_for_dev(mapping->dev) ||
|
||||
ctx->client->base.group) {
|
||||
host1x_bo_pin(mapping->dev, mapping->bo,
|
||||
&mapping->iova);
|
||||
} else {
|
||||
mapping->direction = DMA_TO_DEVICE;
|
||||
if (args->flags & DRM_TEGRA_CHANNEL_MAP_READWRITE)
|
||||
mapping->direction = DMA_BIDIRECTIONAL;
|
||||
|
||||
mapping->sgt =
|
||||
host1x_bo_pin(mapping->dev, mapping->bo, NULL);
|
||||
if (IS_ERR(mapping->sgt)) {
|
||||
err = PTR_ERR(mapping->sgt);
|
||||
goto put_gem;
|
||||
}
|
||||
|
||||
err = dma_map_sgtable(mapping->dev, mapping->sgt,
|
||||
mapping->direction,
|
||||
DMA_ATTR_SKIP_CPU_SYNC);
|
||||
if (err)
|
||||
goto unpin;
|
||||
|
||||
/* TODO only map the requested part */
|
||||
mapping->iova = sg_dma_address(mapping->sgt->sgl);
|
||||
mapping->iova_end = mapping->iova + gem->size;
|
||||
}
|
||||
|
||||
mutex_unlock(&fpriv->lock);
|
||||
|
||||
err = xa_alloc(&ctx->mappings, &mapping_id, mapping,
|
||||
XA_LIMIT(1, U32_MAX), GFP_KERNEL);
|
||||
if (err < 0)
|
||||
goto unmap;
|
||||
|
||||
args->mapping_id = mapping_id;
|
||||
|
||||
return 0;
|
||||
|
||||
unmap:
|
||||
if (mapping->sgt) {
|
||||
dma_unmap_sgtable(mapping->dev, mapping->sgt,
|
||||
mapping->direction, DMA_ATTR_SKIP_CPU_SYNC);
|
||||
}
|
||||
unpin:
|
||||
host1x_bo_unpin(mapping->dev, mapping->bo, mapping->sgt);
|
||||
put_gem:
|
||||
drm_gem_object_put(gem);
|
||||
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_channel_ctx *ctx;
|
||||
struct tegra_drm_mapping *mapping;
|
||||
|
||||
ctx = tegra_drm_channel_ctx_lock(fpriv, args->channel_ctx);
|
||||
if (!ctx)
|
||||
return -EINVAL;
|
||||
|
||||
mapping = xa_erase(&ctx->mappings, args->mapping_id);
|
||||
|
||||
mutex_unlock(&fpriv->lock);
|
||||
|
||||
if (mapping) {
|
||||
tegra_drm_mapping_put(mapping);
|
||||
return 0;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
int tegra_drm_ioctl_gem_create(struct drm_device *drm, void *data,
|
||||
struct drm_file *file)
|
||||
{
|
||||
struct drm_tegra_gem_create *args = data;
|
||||
struct tegra_bo *bo;
|
||||
|
||||
if (args->flags)
|
||||
return -EINVAL;
|
||||
|
||||
bo = tegra_bo_create_with_handle(file, drm, args->size, args->flags,
|
||||
&args->handle);
|
||||
if (IS_ERR(bo))
|
||||
return PTR_ERR(bo);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tegra_drm_ioctl_gem_mmap(struct drm_device *drm, void *data,
|
||||
struct drm_file *file)
|
||||
{
|
||||
struct drm_tegra_gem_mmap *args = data;
|
||||
struct drm_gem_object *gem;
|
||||
struct tegra_bo *bo;
|
||||
|
||||
gem = drm_gem_object_lookup(file, args->handle);
|
||||
if (!gem)
|
||||
return -EINVAL;
|
||||
|
||||
bo = to_tegra_bo(gem);
|
||||
|
||||
args->offset = drm_vma_node_offset_addr(&bo->gem.vma_node);
|
||||
|
||||
drm_gem_object_put(gem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
530
drivers/gpu/drm/tegra/vic.c
Normal file
530
drivers/gpu/drm/tegra/vic.c
Normal file
@@ -0,0 +1,530 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2015, NVIDIA Corporation.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/host1x-next.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.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;
|
||||
|
||||
/* 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)
|
||||
{
|
||||
#ifdef CONFIG_IOMMU_API
|
||||
struct iommu_fwspec *spec = dev_iommu_fwspec_get(vic->dev);
|
||||
#endif
|
||||
u32 fce_ucode_size, fce_bin_data_offset;
|
||||
void *hdr;
|
||||
int err = 0;
|
||||
|
||||
#ifdef CONFIG_IOMMU_API
|
||||
if (vic->config->supports_sid && spec) {
|
||||
u32 value;
|
||||
|
||||
value = TRANSCFG_ATT(1, TRANSCFG_SID_FALCON) |
|
||||
TRANSCFG_ATT(0, TRANSCFG_SID_HW);
|
||||
vic_writel(vic, value, VIC_TFBIF_TRANSCFG);
|
||||
|
||||
if (spec->num_ids > 0) {
|
||||
value = spec->ids[0] & 0xffff;
|
||||
|
||||
vic_writel(vic, value, VIC_THI_STREAMID0);
|
||||
vic_writel(vic, value, VIC_THI_STREAMID1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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);
|
||||
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_APPLICATION_ID, 1);
|
||||
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;
|
||||
|
||||
host1x_syncpt_put(client->syncpts[0]);
|
||||
host1x_channel_put(vic->channel);
|
||||
host1x_client_iommu_detach(client);
|
||||
|
||||
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;
|
||||
dma_addr_t iova;
|
||||
size_t size;
|
||||
void *virt;
|
||||
int err;
|
||||
|
||||
if (vic->falcon.firmware.virt)
|
||||
return 0;
|
||||
|
||||
err = falcon_read_firmware(&vic->falcon, vic->config->firmware);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
size = vic->falcon.firmware.size;
|
||||
|
||||
if (!client->group) {
|
||||
virt = dma_alloc_coherent(vic->dev, size, &iova, GFP_KERNEL);
|
||||
|
||||
err = dma_mapping_error(vic->dev, iova);
|
||||
if (err < 0)
|
||||
return err;
|
||||
} else {
|
||||
virt = tegra_drm_alloc(tegra, size, &iova);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
if (!client->group)
|
||||
dma_free_coherent(vic->dev, size, virt, iova);
|
||||
else
|
||||
tegra_drm_free(tegra, size, virt, iova);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static int 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 vic_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct vic *vic = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
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);
|
||||
int err;
|
||||
|
||||
err = pm_runtime_get_sync(vic->dev);
|
||||
if (err < 0) {
|
||||
pm_runtime_put(vic->dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
context->channel = host1x_channel_get(vic->channel);
|
||||
if (!context->channel) {
|
||||
pm_runtime_put(vic->dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vic_close_channel(struct tegra_drm_context *context)
|
||||
{
|
||||
struct vic *vic = to_vic(context->client);
|
||||
|
||||
host1x_channel_put(context->channel);
|
||||
pm_runtime_put(vic->dev);
|
||||
}
|
||||
|
||||
static const struct tegra_drm_client_ops vic_ops = {
|
||||
.open_channel = vic_open_channel,
|
||||
.close_channel = vic_close_channel,
|
||||
.submit = tegra_drm_submit,
|
||||
};
|
||||
|
||||
#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,
|
||||
};
|
||||
|
||||
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 },
|
||||
{ },
|
||||
};
|
||||
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 resource *regs;
|
||||
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;
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!regs) {
|
||||
dev_err(&pdev->dev, "failed to get registers\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
vic->regs = devm_ioremap_resource(dev, regs);
|
||||
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);
|
||||
}
|
||||
|
||||
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(&pdev->dev);
|
||||
if (!pm_runtime_enabled(&pdev->dev)) {
|
||||
err = vic_runtime_resume(&pdev->dev);
|
||||
if (err < 0)
|
||||
goto unregister_client;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_client:
|
||||
host1x_client_unregister(&vic->client.base);
|
||||
exit_falcon:
|
||||
falcon_exit(&vic->falcon);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int vic_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct vic *vic = platform_get_drvdata(pdev);
|
||||
int err;
|
||||
|
||||
err = host1x_client_unregister(&vic->client.base);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (pm_runtime_enabled(&pdev->dev))
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
else
|
||||
vic_runtime_suspend(&pdev->dev);
|
||||
|
||||
falcon_exit(&vic->falcon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops vic_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(vic_runtime_suspend, vic_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
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
|
||||
37
drivers/gpu/drm/tegra/vic.h
Normal file
37
drivers/gpu/drm/tegra/vic.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/* 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_APPLICATION_ID 0x00000200
|
||||
#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 */
|
||||
Reference in New Issue
Block a user