mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-29 23:28:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
35
drivers/gpu/drm/msm/Kconfig
Normal file
35
drivers/gpu/drm/msm/Kconfig
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
config DRM_MSM
|
||||
tristate "MSM DRM"
|
||||
depends on DRM
|
||||
depends on ARCH_QCOM || (ARM && COMPILE_TEST)
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_PANEL
|
||||
select SHMEM
|
||||
select TMPFS
|
||||
default y
|
||||
help
|
||||
DRM/KMS driver for MSM/snapdragon.
|
||||
|
||||
config DRM_MSM_FBDEV
|
||||
bool "Enable legacy fbdev support for MSM modesetting driver"
|
||||
depends on DRM_MSM
|
||||
select DRM_KMS_FB_HELPER
|
||||
select FB_SYS_FILLRECT
|
||||
select FB_SYS_COPYAREA
|
||||
select FB_SYS_IMAGEBLIT
|
||||
select FB_SYS_FOPS
|
||||
default y
|
||||
help
|
||||
Choose this option if you have a need for the legacy fbdev
|
||||
support. Note that this support also provide the linux console
|
||||
support on top of the MSM modesetting driver.
|
||||
|
||||
config DRM_MSM_REGISTER_LOGGING
|
||||
bool "MSM DRM register logging"
|
||||
depends on DRM_MSM
|
||||
default n
|
||||
help
|
||||
Compile in support for logging register reads/writes in a format
|
||||
that can be parsed by envytools demsm tool. If enabled, register
|
||||
logging can be switched on via msm.reglog=y module param.
|
||||
47
drivers/gpu/drm/msm/Makefile
Normal file
47
drivers/gpu/drm/msm/Makefile
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/msm
|
||||
ifeq (, $(findstring -W,$(EXTRA_CFLAGS)))
|
||||
ccflags-y += -Werror
|
||||
endif
|
||||
|
||||
msm-y := \
|
||||
adreno/adreno_device.o \
|
||||
adreno/adreno_gpu.o \
|
||||
adreno/a3xx_gpu.o \
|
||||
hdmi/hdmi.o \
|
||||
hdmi/hdmi_audio.o \
|
||||
hdmi/hdmi_bridge.o \
|
||||
hdmi/hdmi_connector.o \
|
||||
hdmi/hdmi_i2c.o \
|
||||
hdmi/hdmi_phy_8960.o \
|
||||
hdmi/hdmi_phy_8x60.o \
|
||||
hdmi/hdmi_phy_8x74.o \
|
||||
mdp/mdp_format.o \
|
||||
mdp/mdp_kms.o \
|
||||
mdp/mdp4/mdp4_crtc.o \
|
||||
mdp/mdp4/mdp4_dtv_encoder.o \
|
||||
mdp/mdp4/mdp4_lcdc_encoder.o \
|
||||
mdp/mdp4/mdp4_lvds_connector.o \
|
||||
mdp/mdp4/mdp4_irq.o \
|
||||
mdp/mdp4/mdp4_kms.o \
|
||||
mdp/mdp4/mdp4_plane.o \
|
||||
mdp/mdp5/mdp5_crtc.o \
|
||||
mdp/mdp5/mdp5_encoder.o \
|
||||
mdp/mdp5/mdp5_irq.o \
|
||||
mdp/mdp5/mdp5_kms.o \
|
||||
mdp/mdp5/mdp5_plane.o \
|
||||
mdp/mdp5/mdp5_smp.o \
|
||||
msm_drv.o \
|
||||
msm_fb.o \
|
||||
msm_gem.o \
|
||||
msm_gem_prime.o \
|
||||
msm_gem_submit.o \
|
||||
msm_gpu.o \
|
||||
msm_iommu.o \
|
||||
msm_perf.o \
|
||||
msm_rd.o \
|
||||
msm_ringbuffer.o
|
||||
|
||||
msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o
|
||||
msm-$(CONFIG_COMMON_CLK) += mdp/mdp4/mdp4_lvds_pll.o
|
||||
|
||||
obj-$(CONFIG_DRM_MSM) += msm.o
|
||||
87
drivers/gpu/drm/msm/NOTES
Normal file
87
drivers/gpu/drm/msm/NOTES
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
NOTES about msm drm/kms driver:
|
||||
|
||||
In the current snapdragon SoC's, we have (at least) 3 different
|
||||
display controller blocks at play:
|
||||
+ MDP3 - ?? seems to be what is on geeksphone peak device
|
||||
+ MDP4 - S3 (APQ8060, touchpad), S4-pro (APQ8064, nexus4 & ifc6410)
|
||||
+ MDP5 - snapdragon 800
|
||||
|
||||
(I don't have a completely clear picture on which display controller
|
||||
maps to which part #)
|
||||
|
||||
Plus a handful of blocks around them for HDMI/DSI/etc output.
|
||||
|
||||
And on gpu side of things:
|
||||
+ zero, one, or two 2d cores (z180)
|
||||
+ and either a2xx or a3xx 3d core.
|
||||
|
||||
But, HDMI/DSI/etc blocks seem like they can be shared across multiple
|
||||
display controller blocks. And I for sure don't want to have to deal
|
||||
with N different kms devices from xf86-video-freedreno. Plus, it
|
||||
seems like we can do some clever tricks like use GPU to trigger
|
||||
pageflip after rendering completes (ie. have the kms/crtc code build
|
||||
up gpu cmdstream to update scanout and write FLUSH register after).
|
||||
|
||||
So, the approach is one drm driver, with some modularity. Different
|
||||
'struct msm_kms' implementations, depending on display controller.
|
||||
And one or more 'struct msm_gpu' for the various different gpu sub-
|
||||
modules.
|
||||
|
||||
(Second part is not implemented yet. So far this is just basic KMS
|
||||
driver, and not exposing any custom ioctls to userspace for now.)
|
||||
|
||||
The kms module provides the plane, crtc, and encoder objects, and
|
||||
loads whatever connectors are appropriate.
|
||||
|
||||
For MDP4, the mapping is:
|
||||
|
||||
plane -> PIPE{RGBn,VGn} \
|
||||
crtc -> OVLP{n} + DMA{P,S,E} (??) |-> MDP "device"
|
||||
encoder -> DTV/LCDC/DSI (within MDP4) /
|
||||
connector -> HDMI/DSI/etc --> other device(s)
|
||||
|
||||
Since the irq's that drm core mostly cares about are vblank/framedone,
|
||||
we'll let msm_mdp4_kms provide the irq install/uninstall/etc functions
|
||||
and treat the MDP4 block's irq as "the" irq. Even though the connectors
|
||||
may have their own irqs which they install themselves. For this reason
|
||||
the display controller is the "master" device.
|
||||
|
||||
For MDP5, the mapping is:
|
||||
|
||||
plane -> PIPE{RGBn,VIGn} \
|
||||
crtc -> LM (layer mixer) |-> MDP "device"
|
||||
encoder -> INTF /
|
||||
connector -> HDMI/DSI/eDP/etc --> other device(s)
|
||||
|
||||
Unlike MDP4, it appears we can get by with a single encoder, rather
|
||||
than needing a different implementation for DTV, DSI, etc. (Ie. the
|
||||
register interface is same, just different bases.)
|
||||
|
||||
Also unlike MDP4, with MDP5 all the IRQs for other blocks (HDMI, DSI,
|
||||
etc) are routed through MDP.
|
||||
|
||||
And finally, MDP5 has this "Shared Memory Pool" (called "SMP"), from
|
||||
which blocks need to be allocated to the active pipes based on fetch
|
||||
stride.
|
||||
|
||||
Each connector probably ends up being a separate device, just for the
|
||||
logistics of finding/mapping io region, irq, etc. Idealy we would
|
||||
have a better way than just stashing the platform device in a global
|
||||
(ie. like DT super-node.. but I don't have any snapdragon hw yet that
|
||||
is using DT).
|
||||
|
||||
Note that so far I've not been able to get any docs on the hw, and it
|
||||
seems that access to such docs would prevent me from working on the
|
||||
freedreno gallium driver. So there may be some mistakes in register
|
||||
names (I had to invent a few, since no sufficient hint was given in
|
||||
the downstream android fbdev driver), bitfield sizes, etc. My current
|
||||
state of understanding the registers is given in the envytools rnndb
|
||||
files at:
|
||||
|
||||
https://github.com/freedreno/envytools/tree/master/rnndb
|
||||
(the mdp4/hdmi/dsi directories)
|
||||
|
||||
These files are used both for a parser tool (in the same tree) to
|
||||
parse logged register reads/writes (both from downstream android fbdev
|
||||
driver, and this driver with register logging enabled), as well as to
|
||||
generate the register level headers.
|
||||
1597
drivers/gpu/drm/msm/adreno/a2xx.xml.h
Normal file
1597
drivers/gpu/drm/msm/adreno/a2xx.xml.h
Normal file
File diff suppressed because it is too large
Load diff
2453
drivers/gpu/drm/msm/adreno/a3xx.xml.h
Normal file
2453
drivers/gpu/drm/msm/adreno/a3xx.xml.h
Normal file
File diff suppressed because it is too large
Load diff
506
drivers/gpu/drm/msm/adreno/a3xx_gpu.c
Normal file
506
drivers/gpu/drm/msm/adreno/a3xx_gpu.c
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_MSM_OCMEM
|
||||
# include <mach/ocmem.h>
|
||||
#endif
|
||||
|
||||
#include "a3xx_gpu.h"
|
||||
|
||||
#define A3XX_INT0_MASK \
|
||||
(A3XX_INT0_RBBM_AHB_ERROR | \
|
||||
A3XX_INT0_RBBM_ATB_BUS_OVERFLOW | \
|
||||
A3XX_INT0_CP_T0_PACKET_IN_IB | \
|
||||
A3XX_INT0_CP_OPCODE_ERROR | \
|
||||
A3XX_INT0_CP_RESERVED_BIT_ERROR | \
|
||||
A3XX_INT0_CP_HW_FAULT | \
|
||||
A3XX_INT0_CP_IB1_INT | \
|
||||
A3XX_INT0_CP_IB2_INT | \
|
||||
A3XX_INT0_CP_RB_INT | \
|
||||
A3XX_INT0_CP_REG_PROTECT_FAULT | \
|
||||
A3XX_INT0_CP_AHB_ERROR_HALT | \
|
||||
A3XX_INT0_UCHE_OOB_ACCESS)
|
||||
|
||||
extern bool hang_debug;
|
||||
|
||||
static void a3xx_dump(struct msm_gpu *gpu);
|
||||
|
||||
static void a3xx_me_init(struct msm_gpu *gpu)
|
||||
{
|
||||
struct msm_ringbuffer *ring = gpu->rb;
|
||||
|
||||
OUT_PKT3(ring, CP_ME_INIT, 17);
|
||||
OUT_RING(ring, 0x000003f7);
|
||||
OUT_RING(ring, 0x00000000);
|
||||
OUT_RING(ring, 0x00000000);
|
||||
OUT_RING(ring, 0x00000000);
|
||||
OUT_RING(ring, 0x00000080);
|
||||
OUT_RING(ring, 0x00000100);
|
||||
OUT_RING(ring, 0x00000180);
|
||||
OUT_RING(ring, 0x00006600);
|
||||
OUT_RING(ring, 0x00000150);
|
||||
OUT_RING(ring, 0x0000014e);
|
||||
OUT_RING(ring, 0x00000154);
|
||||
OUT_RING(ring, 0x00000001);
|
||||
OUT_RING(ring, 0x00000000);
|
||||
OUT_RING(ring, 0x00000000);
|
||||
OUT_RING(ring, 0x00000000);
|
||||
OUT_RING(ring, 0x00000000);
|
||||
OUT_RING(ring, 0x00000000);
|
||||
|
||||
gpu->funcs->flush(gpu);
|
||||
gpu->funcs->idle(gpu);
|
||||
}
|
||||
|
||||
static int a3xx_hw_init(struct msm_gpu *gpu)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
struct a3xx_gpu *a3xx_gpu = to_a3xx_gpu(adreno_gpu);
|
||||
uint32_t *ptr, len;
|
||||
int i, ret;
|
||||
|
||||
DBG("%s", gpu->name);
|
||||
|
||||
if (adreno_is_a305(adreno_gpu)) {
|
||||
/* Set up 16 deep read/write request queues: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_RD_LIM_CONF0, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_RD_LIM_CONF1, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_RD_LIM_CONF0, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_WR_LIM_CONF0, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_DDR_OUT_MAX_BURST, 0x0000303);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_WR_LIM_CONF0, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_WR_LIM_CONF1, 0x10101010);
|
||||
/* Enable WR-REQ: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_GATE_OFF_WRREQ_EN, 0x0000ff);
|
||||
/* Set up round robin arbitration between both AXI ports: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ARB_CTL, 0x00000030);
|
||||
/* Set up AOOO: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_AXI_AOOO_EN, 0x0000003c);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_AXI_AOOO, 0x003c003c);
|
||||
|
||||
} else if (adreno_is_a320(adreno_gpu)) {
|
||||
/* Set up 16 deep read/write request queues: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_RD_LIM_CONF0, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_RD_LIM_CONF1, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_RD_LIM_CONF0, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_WR_LIM_CONF0, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_DDR_OUT_MAX_BURST, 0x0000303);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_WR_LIM_CONF0, 0x10101010);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_WR_LIM_CONF1, 0x10101010);
|
||||
/* Enable WR-REQ: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_GATE_OFF_WRREQ_EN, 0x0000ff);
|
||||
/* Set up round robin arbitration between both AXI ports: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ARB_CTL, 0x00000030);
|
||||
/* Set up AOOO: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_AXI_AOOO_EN, 0x0000003c);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_AXI_AOOO, 0x003c003c);
|
||||
/* Enable 1K sort: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ABIT_SORT, 0x000000ff);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ABIT_SORT_CONF, 0x000000a4);
|
||||
|
||||
} else if (adreno_is_a330v2(adreno_gpu)) {
|
||||
/*
|
||||
* Most of the VBIF registers on 8974v2 have the correct
|
||||
* values at power on, so we won't modify those if we don't
|
||||
* need to
|
||||
*/
|
||||
/* Enable 1k sort: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ABIT_SORT, 0x0001003f);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ABIT_SORT_CONF, 0x000000a4);
|
||||
/* Enable WR-REQ: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_GATE_OFF_WRREQ_EN, 0x00003f);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_DDR_OUT_MAX_BURST, 0x0000303);
|
||||
/* Set up VBIF_ROUND_ROBIN_QOS_ARB: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ROUND_ROBIN_QOS_ARB, 0x0003);
|
||||
|
||||
} else if (adreno_is_a330(adreno_gpu)) {
|
||||
/* Set up 16 deep read/write request queues: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_RD_LIM_CONF0, 0x18181818);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_RD_LIM_CONF1, 0x18181818);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_RD_LIM_CONF0, 0x18181818);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_WR_LIM_CONF0, 0x18181818);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_DDR_OUT_MAX_BURST, 0x0000303);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_WR_LIM_CONF0, 0x18181818);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_IN_WR_LIM_CONF1, 0x18181818);
|
||||
/* Enable WR-REQ: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_GATE_OFF_WRREQ_EN, 0x00003f);
|
||||
/* Set up round robin arbitration between both AXI ports: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ARB_CTL, 0x00000030);
|
||||
/* Set up VBIF_ROUND_ROBIN_QOS_ARB: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ROUND_ROBIN_QOS_ARB, 0x0001);
|
||||
/* Set up AOOO: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_AXI_AOOO_EN, 0x0000003f);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_OUT_AXI_AOOO, 0x003f003f);
|
||||
/* Enable 1K sort: */
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ABIT_SORT, 0x0001003f);
|
||||
gpu_write(gpu, REG_A3XX_VBIF_ABIT_SORT_CONF, 0x000000a4);
|
||||
/* Disable VBIF clock gating. This is to enable AXI running
|
||||
* higher frequency than GPU:
|
||||
*/
|
||||
gpu_write(gpu, REG_A3XX_VBIF_CLKON, 0x00000001);
|
||||
|
||||
} else {
|
||||
BUG();
|
||||
}
|
||||
|
||||
/* Make all blocks contribute to the GPU BUSY perf counter: */
|
||||
gpu_write(gpu, REG_A3XX_RBBM_GPU_BUSY_MASKED, 0xffffffff);
|
||||
|
||||
/* Tune the hystersis counters for SP and CP idle detection: */
|
||||
gpu_write(gpu, REG_A3XX_RBBM_SP_HYST_CNT, 0x10);
|
||||
gpu_write(gpu, REG_A3XX_RBBM_WAIT_IDLE_CLOCKS_CTL, 0x10);
|
||||
|
||||
/* Enable the RBBM error reporting bits. This lets us get
|
||||
* useful information on failure:
|
||||
*/
|
||||
gpu_write(gpu, REG_A3XX_RBBM_AHB_CTL0, 0x00000001);
|
||||
|
||||
/* Enable AHB error reporting: */
|
||||
gpu_write(gpu, REG_A3XX_RBBM_AHB_CTL1, 0xa6ffffff);
|
||||
|
||||
/* Turn on the power counters: */
|
||||
gpu_write(gpu, REG_A3XX_RBBM_RBBM_CTL, 0x00030000);
|
||||
|
||||
/* Turn on hang detection - this spews a lot of useful information
|
||||
* into the RBBM registers on a hang:
|
||||
*/
|
||||
gpu_write(gpu, REG_A3XX_RBBM_INTERFACE_HANG_INT_CTL, 0x00010fff);
|
||||
|
||||
/* Enable 64-byte cacheline size. HW Default is 32-byte (0x000000E0): */
|
||||
gpu_write(gpu, REG_A3XX_UCHE_CACHE_MODE_CONTROL_REG, 0x00000001);
|
||||
|
||||
/* Enable Clock gating: */
|
||||
if (adreno_is_a320(adreno_gpu))
|
||||
gpu_write(gpu, REG_A3XX_RBBM_CLOCK_CTL, 0xbfffffff);
|
||||
else if (adreno_is_a330v2(adreno_gpu))
|
||||
gpu_write(gpu, REG_A3XX_RBBM_CLOCK_CTL, 0xaaaaaaaa);
|
||||
else if (adreno_is_a330(adreno_gpu))
|
||||
gpu_write(gpu, REG_A3XX_RBBM_CLOCK_CTL, 0xbffcffff);
|
||||
|
||||
if (adreno_is_a330v2(adreno_gpu))
|
||||
gpu_write(gpu, REG_A3XX_RBBM_GPR0_CTL, 0x05515455);
|
||||
else if (adreno_is_a330(adreno_gpu))
|
||||
gpu_write(gpu, REG_A3XX_RBBM_GPR0_CTL, 0x00000000);
|
||||
|
||||
/* Set the OCMEM base address for A330, etc */
|
||||
if (a3xx_gpu->ocmem_hdl) {
|
||||
gpu_write(gpu, REG_A3XX_RB_GMEM_BASE_ADDR,
|
||||
(unsigned int)(a3xx_gpu->ocmem_base >> 14));
|
||||
}
|
||||
|
||||
/* Turn on performance counters: */
|
||||
gpu_write(gpu, REG_A3XX_RBBM_PERFCTR_CTL, 0x01);
|
||||
|
||||
/* Enable the perfcntrs that we use.. */
|
||||
for (i = 0; i < gpu->num_perfcntrs; i++) {
|
||||
const struct msm_gpu_perfcntr *perfcntr = &gpu->perfcntrs[i];
|
||||
gpu_write(gpu, perfcntr->select_reg, perfcntr->select_val);
|
||||
}
|
||||
|
||||
gpu_write(gpu, REG_A3XX_RBBM_INT_0_MASK, A3XX_INT0_MASK);
|
||||
|
||||
ret = adreno_hw_init(gpu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* setup access protection: */
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT_CTRL, 0x00000007);
|
||||
|
||||
/* RBBM registers */
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(0), 0x63000040);
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(1), 0x62000080);
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(2), 0x600000cc);
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(3), 0x60000108);
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(4), 0x64000140);
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(5), 0x66000400);
|
||||
|
||||
/* CP registers */
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(6), 0x65000700);
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(7), 0x610007d8);
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(8), 0x620007e0);
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(9), 0x61001178);
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(10), 0x64001180);
|
||||
|
||||
/* RB registers */
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(11), 0x60003300);
|
||||
|
||||
/* VBIF registers */
|
||||
gpu_write(gpu, REG_A3XX_CP_PROTECT(12), 0x6b00c000);
|
||||
|
||||
/* NOTE: PM4/micro-engine firmware registers look to be the same
|
||||
* for a2xx and a3xx.. we could possibly push that part down to
|
||||
* adreno_gpu base class. Or push both PM4 and PFP but
|
||||
* parameterize the pfp ucode addr/data registers..
|
||||
*/
|
||||
|
||||
/* Load PM4: */
|
||||
ptr = (uint32_t *)(adreno_gpu->pm4->data);
|
||||
len = adreno_gpu->pm4->size / 4;
|
||||
DBG("loading PM4 ucode version: %x", ptr[1]);
|
||||
|
||||
gpu_write(gpu, REG_AXXX_CP_DEBUG,
|
||||
AXXX_CP_DEBUG_DYNAMIC_CLK_DISABLE |
|
||||
AXXX_CP_DEBUG_MIU_128BIT_WRITE_ENABLE);
|
||||
gpu_write(gpu, REG_AXXX_CP_ME_RAM_WADDR, 0);
|
||||
for (i = 1; i < len; i++)
|
||||
gpu_write(gpu, REG_AXXX_CP_ME_RAM_DATA, ptr[i]);
|
||||
|
||||
/* Load PFP: */
|
||||
ptr = (uint32_t *)(adreno_gpu->pfp->data);
|
||||
len = adreno_gpu->pfp->size / 4;
|
||||
DBG("loading PFP ucode version: %x", ptr[5]);
|
||||
|
||||
gpu_write(gpu, REG_A3XX_CP_PFP_UCODE_ADDR, 0);
|
||||
for (i = 1; i < len; i++)
|
||||
gpu_write(gpu, REG_A3XX_CP_PFP_UCODE_DATA, ptr[i]);
|
||||
|
||||
/* CP ROQ queue sizes (bytes) - RB:16, ST:16, IB1:32, IB2:64 */
|
||||
if (adreno_is_a305(adreno_gpu) || adreno_is_a320(adreno_gpu)) {
|
||||
gpu_write(gpu, REG_AXXX_CP_QUEUE_THRESHOLDS,
|
||||
AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB1_START(2) |
|
||||
AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB2_START(6) |
|
||||
AXXX_CP_QUEUE_THRESHOLDS_CSQ_ST_START(14));
|
||||
} else if (adreno_is_a330(adreno_gpu)) {
|
||||
/* NOTE: this (value take from downstream android driver)
|
||||
* includes some bits outside of the known bitfields. But
|
||||
* A330 has this "MERCIU queue" thing too, which might
|
||||
* explain a new bitfield or reshuffling:
|
||||
*/
|
||||
gpu_write(gpu, REG_AXXX_CP_QUEUE_THRESHOLDS, 0x003e2008);
|
||||
}
|
||||
|
||||
/* clear ME_HALT to start micro engine */
|
||||
gpu_write(gpu, REG_AXXX_CP_ME_CNTL, 0);
|
||||
|
||||
a3xx_me_init(gpu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void a3xx_recover(struct msm_gpu *gpu)
|
||||
{
|
||||
/* dump registers before resetting gpu, if enabled: */
|
||||
if (hang_debug)
|
||||
a3xx_dump(gpu);
|
||||
gpu_write(gpu, REG_A3XX_RBBM_SW_RESET_CMD, 1);
|
||||
gpu_read(gpu, REG_A3XX_RBBM_SW_RESET_CMD);
|
||||
gpu_write(gpu, REG_A3XX_RBBM_SW_RESET_CMD, 0);
|
||||
adreno_recover(gpu);
|
||||
}
|
||||
|
||||
static void a3xx_destroy(struct msm_gpu *gpu)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
struct a3xx_gpu *a3xx_gpu = to_a3xx_gpu(adreno_gpu);
|
||||
|
||||
DBG("%s", gpu->name);
|
||||
|
||||
adreno_gpu_cleanup(adreno_gpu);
|
||||
|
||||
#ifdef CONFIG_MSM_OCMEM
|
||||
if (a3xx_gpu->ocmem_base)
|
||||
ocmem_free(OCMEM_GRAPHICS, a3xx_gpu->ocmem_hdl);
|
||||
#endif
|
||||
|
||||
kfree(a3xx_gpu);
|
||||
}
|
||||
|
||||
static void a3xx_idle(struct msm_gpu *gpu)
|
||||
{
|
||||
/* wait for ringbuffer to drain: */
|
||||
adreno_idle(gpu);
|
||||
|
||||
/* then wait for GPU to finish: */
|
||||
if (spin_until(!(gpu_read(gpu, REG_A3XX_RBBM_STATUS) &
|
||||
A3XX_RBBM_STATUS_GPU_BUSY)))
|
||||
DRM_ERROR("%s: timeout waiting for GPU to idle!\n", gpu->name);
|
||||
|
||||
/* TODO maybe we need to reset GPU here to recover from hang? */
|
||||
}
|
||||
|
||||
static irqreturn_t a3xx_irq(struct msm_gpu *gpu)
|
||||
{
|
||||
uint32_t status;
|
||||
|
||||
status = gpu_read(gpu, REG_A3XX_RBBM_INT_0_STATUS);
|
||||
DBG("%s: %08x", gpu->name, status);
|
||||
|
||||
// TODO
|
||||
|
||||
gpu_write(gpu, REG_A3XX_RBBM_INT_CLEAR_CMD, status);
|
||||
|
||||
msm_gpu_retire(gpu);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const unsigned int a3xx_registers[] = {
|
||||
0x0000, 0x0002, 0x0010, 0x0012, 0x0018, 0x0018, 0x0020, 0x0027,
|
||||
0x0029, 0x002b, 0x002e, 0x0033, 0x0040, 0x0042, 0x0050, 0x005c,
|
||||
0x0060, 0x006c, 0x0080, 0x0082, 0x0084, 0x0088, 0x0090, 0x00e5,
|
||||
0x00ea, 0x00ed, 0x0100, 0x0100, 0x0110, 0x0123, 0x01c0, 0x01c1,
|
||||
0x01c3, 0x01c5, 0x01c7, 0x01c7, 0x01d5, 0x01d9, 0x01dc, 0x01dd,
|
||||
0x01ea, 0x01ea, 0x01ee, 0x01f1, 0x01f5, 0x01f5, 0x01fc, 0x01ff,
|
||||
0x0440, 0x0440, 0x0443, 0x0443, 0x0445, 0x0445, 0x044d, 0x044f,
|
||||
0x0452, 0x0452, 0x0454, 0x046f, 0x047c, 0x047c, 0x047f, 0x047f,
|
||||
0x0578, 0x057f, 0x0600, 0x0602, 0x0605, 0x0607, 0x060a, 0x060e,
|
||||
0x0612, 0x0614, 0x0c01, 0x0c02, 0x0c06, 0x0c1d, 0x0c3d, 0x0c3f,
|
||||
0x0c48, 0x0c4b, 0x0c80, 0x0c80, 0x0c88, 0x0c8b, 0x0ca0, 0x0cb7,
|
||||
0x0cc0, 0x0cc1, 0x0cc6, 0x0cc7, 0x0ce4, 0x0ce5, 0x0e00, 0x0e05,
|
||||
0x0e0c, 0x0e0c, 0x0e22, 0x0e23, 0x0e41, 0x0e45, 0x0e64, 0x0e65,
|
||||
0x0e80, 0x0e82, 0x0e84, 0x0e89, 0x0ea0, 0x0ea1, 0x0ea4, 0x0ea7,
|
||||
0x0ec4, 0x0ecb, 0x0ee0, 0x0ee0, 0x0f00, 0x0f01, 0x0f03, 0x0f09,
|
||||
0x2040, 0x2040, 0x2044, 0x2044, 0x2048, 0x204d, 0x2068, 0x2069,
|
||||
0x206c, 0x206d, 0x2070, 0x2070, 0x2072, 0x2072, 0x2074, 0x2075,
|
||||
0x2079, 0x207a, 0x20c0, 0x20d3, 0x20e4, 0x20ef, 0x2100, 0x2109,
|
||||
0x210c, 0x210c, 0x210e, 0x210e, 0x2110, 0x2111, 0x2114, 0x2115,
|
||||
0x21e4, 0x21e4, 0x21ea, 0x21ea, 0x21ec, 0x21ed, 0x21f0, 0x21f0,
|
||||
0x2200, 0x2212, 0x2214, 0x2217, 0x221a, 0x221a, 0x2240, 0x227e,
|
||||
0x2280, 0x228b, 0x22c0, 0x22c0, 0x22c4, 0x22ce, 0x22d0, 0x22d8,
|
||||
0x22df, 0x22e6, 0x22e8, 0x22e9, 0x22ec, 0x22ec, 0x22f0, 0x22f7,
|
||||
0x22ff, 0x22ff, 0x2340, 0x2343, 0x2348, 0x2349, 0x2350, 0x2356,
|
||||
0x2360, 0x2360, 0x2440, 0x2440, 0x2444, 0x2444, 0x2448, 0x244d,
|
||||
0x2468, 0x2469, 0x246c, 0x246d, 0x2470, 0x2470, 0x2472, 0x2472,
|
||||
0x2474, 0x2475, 0x2479, 0x247a, 0x24c0, 0x24d3, 0x24e4, 0x24ef,
|
||||
0x2500, 0x2509, 0x250c, 0x250c, 0x250e, 0x250e, 0x2510, 0x2511,
|
||||
0x2514, 0x2515, 0x25e4, 0x25e4, 0x25ea, 0x25ea, 0x25ec, 0x25ed,
|
||||
0x25f0, 0x25f0, 0x2600, 0x2612, 0x2614, 0x2617, 0x261a, 0x261a,
|
||||
0x2640, 0x267e, 0x2680, 0x268b, 0x26c0, 0x26c0, 0x26c4, 0x26ce,
|
||||
0x26d0, 0x26d8, 0x26df, 0x26e6, 0x26e8, 0x26e9, 0x26ec, 0x26ec,
|
||||
0x26f0, 0x26f7, 0x26ff, 0x26ff, 0x2740, 0x2743, 0x2748, 0x2749,
|
||||
0x2750, 0x2756, 0x2760, 0x2760, 0x300c, 0x300e, 0x301c, 0x301d,
|
||||
0x302a, 0x302a, 0x302c, 0x302d, 0x3030, 0x3031, 0x3034, 0x3036,
|
||||
0x303c, 0x303c, 0x305e, 0x305f,
|
||||
~0 /* sentinel */
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static void a3xx_show(struct msm_gpu *gpu, struct seq_file *m)
|
||||
{
|
||||
gpu->funcs->pm_resume(gpu);
|
||||
seq_printf(m, "status: %08x\n",
|
||||
gpu_read(gpu, REG_A3XX_RBBM_STATUS));
|
||||
gpu->funcs->pm_suspend(gpu);
|
||||
adreno_show(gpu, m);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* would be nice to not have to duplicate the _show() stuff with printk(): */
|
||||
static void a3xx_dump(struct msm_gpu *gpu)
|
||||
{
|
||||
printk("status: %08x\n",
|
||||
gpu_read(gpu, REG_A3XX_RBBM_STATUS));
|
||||
adreno_dump(gpu);
|
||||
}
|
||||
|
||||
static const struct adreno_gpu_funcs funcs = {
|
||||
.base = {
|
||||
.get_param = adreno_get_param,
|
||||
.hw_init = a3xx_hw_init,
|
||||
.pm_suspend = msm_gpu_pm_suspend,
|
||||
.pm_resume = msm_gpu_pm_resume,
|
||||
.recover = a3xx_recover,
|
||||
.last_fence = adreno_last_fence,
|
||||
.submit = adreno_submit,
|
||||
.flush = adreno_flush,
|
||||
.idle = a3xx_idle,
|
||||
.irq = a3xx_irq,
|
||||
.destroy = a3xx_destroy,
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
.show = a3xx_show,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
static const struct msm_gpu_perfcntr perfcntrs[] = {
|
||||
{ REG_A3XX_SP_PERFCOUNTER6_SELECT, REG_A3XX_RBBM_PERFCTR_SP_6_LO,
|
||||
SP_ALU_ACTIVE_CYCLES, "ALUACTIVE" },
|
||||
{ REG_A3XX_SP_PERFCOUNTER7_SELECT, REG_A3XX_RBBM_PERFCTR_SP_7_LO,
|
||||
SP_FS_FULL_ALU_INSTRUCTIONS, "ALUFULL" },
|
||||
};
|
||||
|
||||
struct msm_gpu *a3xx_gpu_init(struct drm_device *dev)
|
||||
{
|
||||
struct a3xx_gpu *a3xx_gpu = NULL;
|
||||
struct adreno_gpu *adreno_gpu;
|
||||
struct msm_gpu *gpu;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct platform_device *pdev = priv->gpu_pdev;
|
||||
int ret;
|
||||
|
||||
if (!pdev) {
|
||||
dev_err(dev->dev, "no a3xx device\n");
|
||||
ret = -ENXIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
a3xx_gpu = kzalloc(sizeof(*a3xx_gpu), GFP_KERNEL);
|
||||
if (!a3xx_gpu) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
adreno_gpu = &a3xx_gpu->base;
|
||||
gpu = &adreno_gpu->base;
|
||||
|
||||
a3xx_gpu->pdev = pdev;
|
||||
|
||||
gpu->perfcntrs = perfcntrs;
|
||||
gpu->num_perfcntrs = ARRAY_SIZE(perfcntrs);
|
||||
|
||||
adreno_gpu->registers = a3xx_registers;
|
||||
|
||||
ret = adreno_gpu_init(dev, pdev, adreno_gpu, &funcs);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/* if needed, allocate gmem: */
|
||||
if (adreno_is_a330(adreno_gpu)) {
|
||||
#ifdef CONFIG_MSM_OCMEM
|
||||
/* TODO this is different/missing upstream: */
|
||||
struct ocmem_buf *ocmem_hdl =
|
||||
ocmem_allocate(OCMEM_GRAPHICS, adreno_gpu->gmem);
|
||||
|
||||
a3xx_gpu->ocmem_hdl = ocmem_hdl;
|
||||
a3xx_gpu->ocmem_base = ocmem_hdl->addr;
|
||||
adreno_gpu->gmem = ocmem_hdl->len;
|
||||
DBG("using %dK of OCMEM at 0x%08x", adreno_gpu->gmem / 1024,
|
||||
a3xx_gpu->ocmem_base);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!gpu->mmu) {
|
||||
/* TODO we think it is possible to configure the GPU to
|
||||
* restrict access to VRAM carveout. But the required
|
||||
* registers are unknown. For now just bail out and
|
||||
* limp along with just modesetting. If it turns out
|
||||
* to not be possible to restrict access, then we must
|
||||
* implement a cmdstream validator.
|
||||
*/
|
||||
dev_err(dev->dev, "No memory protection without IOMMU\n");
|
||||
ret = -ENXIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return gpu;
|
||||
|
||||
fail:
|
||||
if (a3xx_gpu)
|
||||
a3xx_destroy(&a3xx_gpu->base.base);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
39
drivers/gpu/drm/msm/adreno/a3xx_gpu.h
Normal file
39
drivers/gpu/drm/msm/adreno/a3xx_gpu.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __A3XX_GPU_H__
|
||||
#define __A3XX_GPU_H__
|
||||
|
||||
#include "adreno_gpu.h"
|
||||
|
||||
/* arrg, somehow fb.h is getting pulled in: */
|
||||
#undef ROP_COPY
|
||||
#undef ROP_XOR
|
||||
|
||||
#include "a3xx.xml.h"
|
||||
|
||||
struct a3xx_gpu {
|
||||
struct adreno_gpu base;
|
||||
struct platform_device *pdev;
|
||||
|
||||
/* if OCMEM is used for GMEM: */
|
||||
uint32_t ocmem_base;
|
||||
void *ocmem_hdl;
|
||||
};
|
||||
#define to_a3xx_gpu(x) container_of(x, struct a3xx_gpu, base)
|
||||
|
||||
#endif /* __A3XX_GPU_H__ */
|
||||
437
drivers/gpu/drm/msm/adreno/adreno_common.xml.h
Normal file
437
drivers/gpu/drm/msm/adreno/adreno_common.xml.h
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
#ifndef ADRENO_COMMON_XML
|
||||
#define ADRENO_COMMON_XML
|
||||
|
||||
/* Autogenerated file, DO NOT EDIT manually!
|
||||
|
||||
This file was generated by the rules-ng-ng headergen tool in this git repository:
|
||||
http://github.com/freedreno/envytools/
|
||||
git clone https://github.com/freedreno/envytools.git
|
||||
|
||||
The rules-ng-ng source files this header was generated from are:
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno.xml ( 364 bytes, from 2013-11-30 14:47:15)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/freedreno_copyright.xml ( 1453 bytes, from 2013-03-31 16:51:27)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/a2xx.xml ( 32901 bytes, from 2014-06-02 15:21:30)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/adreno_common.xml ( 9859 bytes, from 2014-06-02 15:21:30)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/adreno_pm4.xml ( 14960 bytes, from 2014-07-27 17:22:13)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/a3xx.xml ( 58020 bytes, from 2014-08-01 12:22:48)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/a4xx.xml ( 41068 bytes, from 2014-08-01 12:22:48)
|
||||
|
||||
Copyright (C) 2013-2014 by the following authors:
|
||||
- Rob Clark <robdclark@gmail.com> (robclark)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the
|
||||
next paragraph) shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
enum adreno_pa_su_sc_draw {
|
||||
PC_DRAW_POINTS = 0,
|
||||
PC_DRAW_LINES = 1,
|
||||
PC_DRAW_TRIANGLES = 2,
|
||||
};
|
||||
|
||||
enum adreno_compare_func {
|
||||
FUNC_NEVER = 0,
|
||||
FUNC_LESS = 1,
|
||||
FUNC_EQUAL = 2,
|
||||
FUNC_LEQUAL = 3,
|
||||
FUNC_GREATER = 4,
|
||||
FUNC_NOTEQUAL = 5,
|
||||
FUNC_GEQUAL = 6,
|
||||
FUNC_ALWAYS = 7,
|
||||
};
|
||||
|
||||
enum adreno_stencil_op {
|
||||
STENCIL_KEEP = 0,
|
||||
STENCIL_ZERO = 1,
|
||||
STENCIL_REPLACE = 2,
|
||||
STENCIL_INCR_CLAMP = 3,
|
||||
STENCIL_DECR_CLAMP = 4,
|
||||
STENCIL_INVERT = 5,
|
||||
STENCIL_INCR_WRAP = 6,
|
||||
STENCIL_DECR_WRAP = 7,
|
||||
};
|
||||
|
||||
enum adreno_rb_blend_factor {
|
||||
FACTOR_ZERO = 0,
|
||||
FACTOR_ONE = 1,
|
||||
FACTOR_SRC_COLOR = 4,
|
||||
FACTOR_ONE_MINUS_SRC_COLOR = 5,
|
||||
FACTOR_SRC_ALPHA = 6,
|
||||
FACTOR_ONE_MINUS_SRC_ALPHA = 7,
|
||||
FACTOR_DST_COLOR = 8,
|
||||
FACTOR_ONE_MINUS_DST_COLOR = 9,
|
||||
FACTOR_DST_ALPHA = 10,
|
||||
FACTOR_ONE_MINUS_DST_ALPHA = 11,
|
||||
FACTOR_CONSTANT_COLOR = 12,
|
||||
FACTOR_ONE_MINUS_CONSTANT_COLOR = 13,
|
||||
FACTOR_CONSTANT_ALPHA = 14,
|
||||
FACTOR_ONE_MINUS_CONSTANT_ALPHA = 15,
|
||||
FACTOR_SRC_ALPHA_SATURATE = 16,
|
||||
};
|
||||
|
||||
enum adreno_rb_surface_endian {
|
||||
ENDIAN_NONE = 0,
|
||||
ENDIAN_8IN16 = 1,
|
||||
ENDIAN_8IN32 = 2,
|
||||
ENDIAN_16IN32 = 3,
|
||||
ENDIAN_8IN64 = 4,
|
||||
ENDIAN_8IN128 = 5,
|
||||
};
|
||||
|
||||
enum adreno_rb_dither_mode {
|
||||
DITHER_DISABLE = 0,
|
||||
DITHER_ALWAYS = 1,
|
||||
DITHER_IF_ALPHA_OFF = 2,
|
||||
};
|
||||
|
||||
enum adreno_rb_depth_format {
|
||||
DEPTHX_16 = 0,
|
||||
DEPTHX_24_8 = 1,
|
||||
};
|
||||
|
||||
enum adreno_rb_copy_control_mode {
|
||||
RB_COPY_RESOLVE = 1,
|
||||
RB_COPY_CLEAR = 2,
|
||||
RB_COPY_DEPTH_STENCIL = 5,
|
||||
};
|
||||
|
||||
enum a3xx_render_mode {
|
||||
RB_RENDERING_PASS = 0,
|
||||
RB_TILING_PASS = 1,
|
||||
RB_RESOLVE_PASS = 2,
|
||||
RB_COMPUTE_PASS = 3,
|
||||
};
|
||||
|
||||
enum a3xx_msaa_samples {
|
||||
MSAA_ONE = 0,
|
||||
MSAA_TWO = 1,
|
||||
MSAA_FOUR = 2,
|
||||
};
|
||||
|
||||
enum a3xx_threadmode {
|
||||
MULTI = 0,
|
||||
SINGLE = 1,
|
||||
};
|
||||
|
||||
enum a3xx_instrbuffermode {
|
||||
BUFFER = 1,
|
||||
};
|
||||
|
||||
enum a3xx_threadsize {
|
||||
TWO_QUADS = 0,
|
||||
FOUR_QUADS = 1,
|
||||
};
|
||||
|
||||
#define REG_AXXX_CP_RB_BASE 0x000001c0
|
||||
|
||||
#define REG_AXXX_CP_RB_CNTL 0x000001c1
|
||||
#define AXXX_CP_RB_CNTL_BUFSZ__MASK 0x0000003f
|
||||
#define AXXX_CP_RB_CNTL_BUFSZ__SHIFT 0
|
||||
static inline uint32_t AXXX_CP_RB_CNTL_BUFSZ(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_RB_CNTL_BUFSZ__SHIFT) & AXXX_CP_RB_CNTL_BUFSZ__MASK;
|
||||
}
|
||||
#define AXXX_CP_RB_CNTL_BLKSZ__MASK 0x00003f00
|
||||
#define AXXX_CP_RB_CNTL_BLKSZ__SHIFT 8
|
||||
static inline uint32_t AXXX_CP_RB_CNTL_BLKSZ(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_RB_CNTL_BLKSZ__SHIFT) & AXXX_CP_RB_CNTL_BLKSZ__MASK;
|
||||
}
|
||||
#define AXXX_CP_RB_CNTL_BUF_SWAP__MASK 0x00030000
|
||||
#define AXXX_CP_RB_CNTL_BUF_SWAP__SHIFT 16
|
||||
static inline uint32_t AXXX_CP_RB_CNTL_BUF_SWAP(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_RB_CNTL_BUF_SWAP__SHIFT) & AXXX_CP_RB_CNTL_BUF_SWAP__MASK;
|
||||
}
|
||||
#define AXXX_CP_RB_CNTL_POLL_EN 0x00100000
|
||||
#define AXXX_CP_RB_CNTL_NO_UPDATE 0x08000000
|
||||
#define AXXX_CP_RB_CNTL_RPTR_WR_EN 0x80000000
|
||||
|
||||
#define REG_AXXX_CP_RB_RPTR_ADDR 0x000001c3
|
||||
#define AXXX_CP_RB_RPTR_ADDR_SWAP__MASK 0x00000003
|
||||
#define AXXX_CP_RB_RPTR_ADDR_SWAP__SHIFT 0
|
||||
static inline uint32_t AXXX_CP_RB_RPTR_ADDR_SWAP(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_RB_RPTR_ADDR_SWAP__SHIFT) & AXXX_CP_RB_RPTR_ADDR_SWAP__MASK;
|
||||
}
|
||||
#define AXXX_CP_RB_RPTR_ADDR_ADDR__MASK 0xfffffffc
|
||||
#define AXXX_CP_RB_RPTR_ADDR_ADDR__SHIFT 2
|
||||
static inline uint32_t AXXX_CP_RB_RPTR_ADDR_ADDR(uint32_t val)
|
||||
{
|
||||
return ((val >> 2) << AXXX_CP_RB_RPTR_ADDR_ADDR__SHIFT) & AXXX_CP_RB_RPTR_ADDR_ADDR__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_CP_RB_RPTR 0x000001c4
|
||||
|
||||
#define REG_AXXX_CP_RB_WPTR 0x000001c5
|
||||
|
||||
#define REG_AXXX_CP_RB_WPTR_DELAY 0x000001c6
|
||||
|
||||
#define REG_AXXX_CP_RB_RPTR_WR 0x000001c7
|
||||
|
||||
#define REG_AXXX_CP_RB_WPTR_BASE 0x000001c8
|
||||
|
||||
#define REG_AXXX_CP_QUEUE_THRESHOLDS 0x000001d5
|
||||
#define AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB1_START__MASK 0x0000000f
|
||||
#define AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB1_START__SHIFT 0
|
||||
static inline uint32_t AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB1_START(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB1_START__SHIFT) & AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB1_START__MASK;
|
||||
}
|
||||
#define AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB2_START__MASK 0x00000f00
|
||||
#define AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB2_START__SHIFT 8
|
||||
static inline uint32_t AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB2_START(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB2_START__SHIFT) & AXXX_CP_QUEUE_THRESHOLDS_CSQ_IB2_START__MASK;
|
||||
}
|
||||
#define AXXX_CP_QUEUE_THRESHOLDS_CSQ_ST_START__MASK 0x000f0000
|
||||
#define AXXX_CP_QUEUE_THRESHOLDS_CSQ_ST_START__SHIFT 16
|
||||
static inline uint32_t AXXX_CP_QUEUE_THRESHOLDS_CSQ_ST_START(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_QUEUE_THRESHOLDS_CSQ_ST_START__SHIFT) & AXXX_CP_QUEUE_THRESHOLDS_CSQ_ST_START__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_CP_MEQ_THRESHOLDS 0x000001d6
|
||||
#define AXXX_CP_MEQ_THRESHOLDS_MEQ_END__MASK 0x001f0000
|
||||
#define AXXX_CP_MEQ_THRESHOLDS_MEQ_END__SHIFT 16
|
||||
static inline uint32_t AXXX_CP_MEQ_THRESHOLDS_MEQ_END(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_MEQ_THRESHOLDS_MEQ_END__SHIFT) & AXXX_CP_MEQ_THRESHOLDS_MEQ_END__MASK;
|
||||
}
|
||||
#define AXXX_CP_MEQ_THRESHOLDS_ROQ_END__MASK 0x1f000000
|
||||
#define AXXX_CP_MEQ_THRESHOLDS_ROQ_END__SHIFT 24
|
||||
static inline uint32_t AXXX_CP_MEQ_THRESHOLDS_ROQ_END(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_MEQ_THRESHOLDS_ROQ_END__SHIFT) & AXXX_CP_MEQ_THRESHOLDS_ROQ_END__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_CP_CSQ_AVAIL 0x000001d7
|
||||
#define AXXX_CP_CSQ_AVAIL_RING__MASK 0x0000007f
|
||||
#define AXXX_CP_CSQ_AVAIL_RING__SHIFT 0
|
||||
static inline uint32_t AXXX_CP_CSQ_AVAIL_RING(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_CSQ_AVAIL_RING__SHIFT) & AXXX_CP_CSQ_AVAIL_RING__MASK;
|
||||
}
|
||||
#define AXXX_CP_CSQ_AVAIL_IB1__MASK 0x00007f00
|
||||
#define AXXX_CP_CSQ_AVAIL_IB1__SHIFT 8
|
||||
static inline uint32_t AXXX_CP_CSQ_AVAIL_IB1(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_CSQ_AVAIL_IB1__SHIFT) & AXXX_CP_CSQ_AVAIL_IB1__MASK;
|
||||
}
|
||||
#define AXXX_CP_CSQ_AVAIL_IB2__MASK 0x007f0000
|
||||
#define AXXX_CP_CSQ_AVAIL_IB2__SHIFT 16
|
||||
static inline uint32_t AXXX_CP_CSQ_AVAIL_IB2(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_CSQ_AVAIL_IB2__SHIFT) & AXXX_CP_CSQ_AVAIL_IB2__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_CP_STQ_AVAIL 0x000001d8
|
||||
#define AXXX_CP_STQ_AVAIL_ST__MASK 0x0000007f
|
||||
#define AXXX_CP_STQ_AVAIL_ST__SHIFT 0
|
||||
static inline uint32_t AXXX_CP_STQ_AVAIL_ST(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_STQ_AVAIL_ST__SHIFT) & AXXX_CP_STQ_AVAIL_ST__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_CP_MEQ_AVAIL 0x000001d9
|
||||
#define AXXX_CP_MEQ_AVAIL_MEQ__MASK 0x0000001f
|
||||
#define AXXX_CP_MEQ_AVAIL_MEQ__SHIFT 0
|
||||
static inline uint32_t AXXX_CP_MEQ_AVAIL_MEQ(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_MEQ_AVAIL_MEQ__SHIFT) & AXXX_CP_MEQ_AVAIL_MEQ__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_SCRATCH_UMSK 0x000001dc
|
||||
#define AXXX_SCRATCH_UMSK_UMSK__MASK 0x000000ff
|
||||
#define AXXX_SCRATCH_UMSK_UMSK__SHIFT 0
|
||||
static inline uint32_t AXXX_SCRATCH_UMSK_UMSK(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_SCRATCH_UMSK_UMSK__SHIFT) & AXXX_SCRATCH_UMSK_UMSK__MASK;
|
||||
}
|
||||
#define AXXX_SCRATCH_UMSK_SWAP__MASK 0x00030000
|
||||
#define AXXX_SCRATCH_UMSK_SWAP__SHIFT 16
|
||||
static inline uint32_t AXXX_SCRATCH_UMSK_SWAP(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_SCRATCH_UMSK_SWAP__SHIFT) & AXXX_SCRATCH_UMSK_SWAP__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_SCRATCH_ADDR 0x000001dd
|
||||
|
||||
#define REG_AXXX_CP_ME_RDADDR 0x000001ea
|
||||
|
||||
#define REG_AXXX_CP_STATE_DEBUG_INDEX 0x000001ec
|
||||
|
||||
#define REG_AXXX_CP_STATE_DEBUG_DATA 0x000001ed
|
||||
|
||||
#define REG_AXXX_CP_INT_CNTL 0x000001f2
|
||||
|
||||
#define REG_AXXX_CP_INT_STATUS 0x000001f3
|
||||
|
||||
#define REG_AXXX_CP_INT_ACK 0x000001f4
|
||||
|
||||
#define REG_AXXX_CP_ME_CNTL 0x000001f6
|
||||
#define AXXX_CP_ME_CNTL_BUSY 0x20000000
|
||||
#define AXXX_CP_ME_CNTL_HALT 0x10000000
|
||||
|
||||
#define REG_AXXX_CP_ME_STATUS 0x000001f7
|
||||
|
||||
#define REG_AXXX_CP_ME_RAM_WADDR 0x000001f8
|
||||
|
||||
#define REG_AXXX_CP_ME_RAM_RADDR 0x000001f9
|
||||
|
||||
#define REG_AXXX_CP_ME_RAM_DATA 0x000001fa
|
||||
|
||||
#define REG_AXXX_CP_DEBUG 0x000001fc
|
||||
#define AXXX_CP_DEBUG_PREDICATE_DISABLE 0x00800000
|
||||
#define AXXX_CP_DEBUG_PROG_END_PTR_ENABLE 0x01000000
|
||||
#define AXXX_CP_DEBUG_MIU_128BIT_WRITE_ENABLE 0x02000000
|
||||
#define AXXX_CP_DEBUG_PREFETCH_PASS_NOPS 0x04000000
|
||||
#define AXXX_CP_DEBUG_DYNAMIC_CLK_DISABLE 0x08000000
|
||||
#define AXXX_CP_DEBUG_PREFETCH_MATCH_DISABLE 0x10000000
|
||||
#define AXXX_CP_DEBUG_SIMPLE_ME_FLOW_CONTROL 0x40000000
|
||||
#define AXXX_CP_DEBUG_MIU_WRITE_PACK_DISABLE 0x80000000
|
||||
|
||||
#define REG_AXXX_CP_CSQ_RB_STAT 0x000001fd
|
||||
#define AXXX_CP_CSQ_RB_STAT_RPTR__MASK 0x0000007f
|
||||
#define AXXX_CP_CSQ_RB_STAT_RPTR__SHIFT 0
|
||||
static inline uint32_t AXXX_CP_CSQ_RB_STAT_RPTR(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_CSQ_RB_STAT_RPTR__SHIFT) & AXXX_CP_CSQ_RB_STAT_RPTR__MASK;
|
||||
}
|
||||
#define AXXX_CP_CSQ_RB_STAT_WPTR__MASK 0x007f0000
|
||||
#define AXXX_CP_CSQ_RB_STAT_WPTR__SHIFT 16
|
||||
static inline uint32_t AXXX_CP_CSQ_RB_STAT_WPTR(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_CSQ_RB_STAT_WPTR__SHIFT) & AXXX_CP_CSQ_RB_STAT_WPTR__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_CP_CSQ_IB1_STAT 0x000001fe
|
||||
#define AXXX_CP_CSQ_IB1_STAT_RPTR__MASK 0x0000007f
|
||||
#define AXXX_CP_CSQ_IB1_STAT_RPTR__SHIFT 0
|
||||
static inline uint32_t AXXX_CP_CSQ_IB1_STAT_RPTR(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_CSQ_IB1_STAT_RPTR__SHIFT) & AXXX_CP_CSQ_IB1_STAT_RPTR__MASK;
|
||||
}
|
||||
#define AXXX_CP_CSQ_IB1_STAT_WPTR__MASK 0x007f0000
|
||||
#define AXXX_CP_CSQ_IB1_STAT_WPTR__SHIFT 16
|
||||
static inline uint32_t AXXX_CP_CSQ_IB1_STAT_WPTR(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_CSQ_IB1_STAT_WPTR__SHIFT) & AXXX_CP_CSQ_IB1_STAT_WPTR__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_CP_CSQ_IB2_STAT 0x000001ff
|
||||
#define AXXX_CP_CSQ_IB2_STAT_RPTR__MASK 0x0000007f
|
||||
#define AXXX_CP_CSQ_IB2_STAT_RPTR__SHIFT 0
|
||||
static inline uint32_t AXXX_CP_CSQ_IB2_STAT_RPTR(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_CSQ_IB2_STAT_RPTR__SHIFT) & AXXX_CP_CSQ_IB2_STAT_RPTR__MASK;
|
||||
}
|
||||
#define AXXX_CP_CSQ_IB2_STAT_WPTR__MASK 0x007f0000
|
||||
#define AXXX_CP_CSQ_IB2_STAT_WPTR__SHIFT 16
|
||||
static inline uint32_t AXXX_CP_CSQ_IB2_STAT_WPTR(uint32_t val)
|
||||
{
|
||||
return ((val) << AXXX_CP_CSQ_IB2_STAT_WPTR__SHIFT) & AXXX_CP_CSQ_IB2_STAT_WPTR__MASK;
|
||||
}
|
||||
|
||||
#define REG_AXXX_CP_NON_PREFETCH_CNTRS 0x00000440
|
||||
|
||||
#define REG_AXXX_CP_STQ_ST_STAT 0x00000443
|
||||
|
||||
#define REG_AXXX_CP_ST_BASE 0x0000044d
|
||||
|
||||
#define REG_AXXX_CP_ST_BUFSZ 0x0000044e
|
||||
|
||||
#define REG_AXXX_CP_MEQ_STAT 0x0000044f
|
||||
|
||||
#define REG_AXXX_CP_MIU_TAG_STAT 0x00000452
|
||||
|
||||
#define REG_AXXX_CP_BIN_MASK_LO 0x00000454
|
||||
|
||||
#define REG_AXXX_CP_BIN_MASK_HI 0x00000455
|
||||
|
||||
#define REG_AXXX_CP_BIN_SELECT_LO 0x00000456
|
||||
|
||||
#define REG_AXXX_CP_BIN_SELECT_HI 0x00000457
|
||||
|
||||
#define REG_AXXX_CP_IB1_BASE 0x00000458
|
||||
|
||||
#define REG_AXXX_CP_IB1_BUFSZ 0x00000459
|
||||
|
||||
#define REG_AXXX_CP_IB2_BASE 0x0000045a
|
||||
|
||||
#define REG_AXXX_CP_IB2_BUFSZ 0x0000045b
|
||||
|
||||
#define REG_AXXX_CP_STAT 0x0000047f
|
||||
|
||||
#define REG_AXXX_CP_SCRATCH_REG0 0x00000578
|
||||
|
||||
#define REG_AXXX_CP_SCRATCH_REG1 0x00000579
|
||||
|
||||
#define REG_AXXX_CP_SCRATCH_REG2 0x0000057a
|
||||
|
||||
#define REG_AXXX_CP_SCRATCH_REG3 0x0000057b
|
||||
|
||||
#define REG_AXXX_CP_SCRATCH_REG4 0x0000057c
|
||||
|
||||
#define REG_AXXX_CP_SCRATCH_REG5 0x0000057d
|
||||
|
||||
#define REG_AXXX_CP_SCRATCH_REG6 0x0000057e
|
||||
|
||||
#define REG_AXXX_CP_SCRATCH_REG7 0x0000057f
|
||||
|
||||
#define REG_AXXX_CP_ME_VS_EVENT_SRC 0x00000600
|
||||
|
||||
#define REG_AXXX_CP_ME_VS_EVENT_ADDR 0x00000601
|
||||
|
||||
#define REG_AXXX_CP_ME_VS_EVENT_DATA 0x00000602
|
||||
|
||||
#define REG_AXXX_CP_ME_VS_EVENT_ADDR_SWM 0x00000603
|
||||
|
||||
#define REG_AXXX_CP_ME_VS_EVENT_DATA_SWM 0x00000604
|
||||
|
||||
#define REG_AXXX_CP_ME_PS_EVENT_SRC 0x00000605
|
||||
|
||||
#define REG_AXXX_CP_ME_PS_EVENT_ADDR 0x00000606
|
||||
|
||||
#define REG_AXXX_CP_ME_PS_EVENT_DATA 0x00000607
|
||||
|
||||
#define REG_AXXX_CP_ME_PS_EVENT_ADDR_SWM 0x00000608
|
||||
|
||||
#define REG_AXXX_CP_ME_PS_EVENT_DATA_SWM 0x00000609
|
||||
|
||||
#define REG_AXXX_CP_ME_CF_EVENT_SRC 0x0000060a
|
||||
|
||||
#define REG_AXXX_CP_ME_CF_EVENT_ADDR 0x0000060b
|
||||
|
||||
#define REG_AXXX_CP_ME_CF_EVENT_DATA 0x0000060c
|
||||
|
||||
#define REG_AXXX_CP_ME_NRT_ADDR 0x0000060d
|
||||
|
||||
#define REG_AXXX_CP_ME_NRT_DATA 0x0000060e
|
||||
|
||||
#define REG_AXXX_CP_ME_VS_FETCH_DONE_SRC 0x00000612
|
||||
|
||||
#define REG_AXXX_CP_ME_VS_FETCH_DONE_ADDR 0x00000613
|
||||
|
||||
#define REG_AXXX_CP_ME_VS_FETCH_DONE_DATA 0x00000614
|
||||
|
||||
|
||||
#endif /* ADRENO_COMMON_XML */
|
||||
285
drivers/gpu/drm/msm/adreno/adreno_device.c
Normal file
285
drivers/gpu/drm/msm/adreno/adreno_device.c
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2014 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "adreno_gpu.h"
|
||||
|
||||
#if defined(CONFIG_MSM_BUS_SCALING) && !defined(CONFIG_OF)
|
||||
# include <mach/kgsl.h>
|
||||
#endif
|
||||
|
||||
#define ANY_ID 0xff
|
||||
|
||||
bool hang_debug = false;
|
||||
MODULE_PARM_DESC(hang_debug, "Dump registers when hang is detected (can be slow!)");
|
||||
module_param_named(hang_debug, hang_debug, bool, 0600);
|
||||
|
||||
struct msm_gpu *a3xx_gpu_init(struct drm_device *dev);
|
||||
|
||||
static const struct adreno_info gpulist[] = {
|
||||
{
|
||||
.rev = ADRENO_REV(3, 0, 5, ANY_ID),
|
||||
.revn = 305,
|
||||
.name = "A305",
|
||||
.pm4fw = "a300_pm4.fw",
|
||||
.pfpfw = "a300_pfp.fw",
|
||||
.gmem = SZ_256K,
|
||||
.init = a3xx_gpu_init,
|
||||
}, {
|
||||
.rev = ADRENO_REV(3, 2, ANY_ID, ANY_ID),
|
||||
.revn = 320,
|
||||
.name = "A320",
|
||||
.pm4fw = "a300_pm4.fw",
|
||||
.pfpfw = "a300_pfp.fw",
|
||||
.gmem = SZ_512K,
|
||||
.init = a3xx_gpu_init,
|
||||
}, {
|
||||
.rev = ADRENO_REV(3, 3, 0, ANY_ID),
|
||||
.revn = 330,
|
||||
.name = "A330",
|
||||
.pm4fw = "a330_pm4.fw",
|
||||
.pfpfw = "a330_pfp.fw",
|
||||
.gmem = SZ_1M,
|
||||
.init = a3xx_gpu_init,
|
||||
},
|
||||
};
|
||||
|
||||
MODULE_FIRMWARE("a300_pm4.fw");
|
||||
MODULE_FIRMWARE("a300_pfp.fw");
|
||||
MODULE_FIRMWARE("a330_pm4.fw");
|
||||
MODULE_FIRMWARE("a330_pfp.fw");
|
||||
|
||||
static inline bool _rev_match(uint8_t entry, uint8_t id)
|
||||
{
|
||||
return (entry == ANY_ID) || (entry == id);
|
||||
}
|
||||
|
||||
const struct adreno_info *adreno_info(struct adreno_rev rev)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* identify gpu: */
|
||||
for (i = 0; i < ARRAY_SIZE(gpulist); i++) {
|
||||
const struct adreno_info *info = &gpulist[i];
|
||||
if (_rev_match(info->rev.core, rev.core) &&
|
||||
_rev_match(info->rev.major, rev.major) &&
|
||||
_rev_match(info->rev.minor, rev.minor) &&
|
||||
_rev_match(info->rev.patchid, rev.patchid))
|
||||
return info;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct msm_gpu *adreno_load_gpu(struct drm_device *dev)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct platform_device *pdev = priv->gpu_pdev;
|
||||
struct adreno_platform_config *config;
|
||||
struct adreno_rev rev;
|
||||
const struct adreno_info *info;
|
||||
struct msm_gpu *gpu = NULL;
|
||||
|
||||
if (!pdev) {
|
||||
dev_err(dev->dev, "no adreno device\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
config = pdev->dev.platform_data;
|
||||
rev = config->rev;
|
||||
info = adreno_info(config->rev);
|
||||
|
||||
if (!info) {
|
||||
dev_warn(dev->dev, "Unknown GPU revision: %u.%u.%u.%u\n",
|
||||
rev.core, rev.major, rev.minor, rev.patchid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DBG("Found GPU: %u.%u.%u.%u", rev.core, rev.major,
|
||||
rev.minor, rev.patchid);
|
||||
|
||||
gpu = info->init(dev);
|
||||
if (IS_ERR(gpu)) {
|
||||
dev_warn(dev->dev, "failed to load adreno gpu\n");
|
||||
gpu = NULL;
|
||||
/* not fatal */
|
||||
}
|
||||
|
||||
if (gpu) {
|
||||
int ret;
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
gpu->funcs->pm_resume(gpu);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
ret = gpu->funcs->hw_init(gpu);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "gpu hw init failed: %d\n", ret);
|
||||
gpu->funcs->destroy(gpu);
|
||||
gpu = NULL;
|
||||
} else {
|
||||
/* give inactive pm a chance to kick in: */
|
||||
msm_gpu_retire(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
return gpu;
|
||||
}
|
||||
|
||||
static void set_gpu_pdev(struct drm_device *dev,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
priv->gpu_pdev = pdev;
|
||||
}
|
||||
|
||||
static int adreno_bind(struct device *dev, struct device *master, void *data)
|
||||
{
|
||||
static struct adreno_platform_config config = {};
|
||||
#ifdef CONFIG_OF
|
||||
struct device_node *child, *node = dev->of_node;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(node, "qcom,chipid", &val);
|
||||
if (ret) {
|
||||
dev_err(dev, "could not find chipid: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
config.rev = ADRENO_REV((val >> 24) & 0xff,
|
||||
(val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff);
|
||||
|
||||
/* find clock rates: */
|
||||
config.fast_rate = 0;
|
||||
config.slow_rate = ~0;
|
||||
for_each_child_of_node(node, child) {
|
||||
if (of_device_is_compatible(child, "qcom,gpu-pwrlevels")) {
|
||||
struct device_node *pwrlvl;
|
||||
for_each_child_of_node(child, pwrlvl) {
|
||||
ret = of_property_read_u32(pwrlvl, "qcom,gpu-freq", &val);
|
||||
if (ret) {
|
||||
dev_err(dev, "could not find gpu-freq: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
config.fast_rate = max(config.fast_rate, val);
|
||||
config.slow_rate = min(config.slow_rate, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.fast_rate) {
|
||||
dev_err(dev, "could not find clk rates\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
#else
|
||||
struct kgsl_device_platform_data *pdata = dev->platform_data;
|
||||
uint32_t version = socinfo_get_version();
|
||||
if (cpu_is_apq8064ab()) {
|
||||
config.fast_rate = 450000000;
|
||||
config.slow_rate = 27000000;
|
||||
config.bus_freq = 4;
|
||||
config.rev = ADRENO_REV(3, 2, 1, 0);
|
||||
} else if (cpu_is_apq8064()) {
|
||||
config.fast_rate = 400000000;
|
||||
config.slow_rate = 27000000;
|
||||
config.bus_freq = 4;
|
||||
|
||||
if (SOCINFO_VERSION_MAJOR(version) == 2)
|
||||
config.rev = ADRENO_REV(3, 2, 0, 2);
|
||||
else if ((SOCINFO_VERSION_MAJOR(version) == 1) &&
|
||||
(SOCINFO_VERSION_MINOR(version) == 1))
|
||||
config.rev = ADRENO_REV(3, 2, 0, 1);
|
||||
else
|
||||
config.rev = ADRENO_REV(3, 2, 0, 0);
|
||||
|
||||
} else if (cpu_is_msm8960ab()) {
|
||||
config.fast_rate = 400000000;
|
||||
config.slow_rate = 320000000;
|
||||
config.bus_freq = 4;
|
||||
|
||||
if (SOCINFO_VERSION_MINOR(version) == 0)
|
||||
config.rev = ADRENO_REV(3, 2, 1, 0);
|
||||
else
|
||||
config.rev = ADRENO_REV(3, 2, 1, 1);
|
||||
|
||||
} else if (cpu_is_msm8930()) {
|
||||
config.fast_rate = 400000000;
|
||||
config.slow_rate = 27000000;
|
||||
config.bus_freq = 3;
|
||||
|
||||
if ((SOCINFO_VERSION_MAJOR(version) == 1) &&
|
||||
(SOCINFO_VERSION_MINOR(version) == 2))
|
||||
config.rev = ADRENO_REV(3, 0, 5, 2);
|
||||
else
|
||||
config.rev = ADRENO_REV(3, 0, 5, 0);
|
||||
|
||||
}
|
||||
# ifdef CONFIG_MSM_BUS_SCALING
|
||||
config.bus_scale_table = pdata->bus_scale_table;
|
||||
# endif
|
||||
#endif
|
||||
dev->platform_data = &config;
|
||||
set_gpu_pdev(dev_get_drvdata(master), to_platform_device(dev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void adreno_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
set_gpu_pdev(dev_get_drvdata(master), NULL);
|
||||
}
|
||||
|
||||
static const struct component_ops a3xx_ops = {
|
||||
.bind = adreno_bind,
|
||||
.unbind = adreno_unbind,
|
||||
};
|
||||
|
||||
static int adreno_probe(struct platform_device *pdev)
|
||||
{
|
||||
return component_add(&pdev->dev, &a3xx_ops);
|
||||
}
|
||||
|
||||
static int adreno_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_del(&pdev->dev, &a3xx_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id dt_match[] = {
|
||||
{ .compatible = "qcom,adreno-3xx" },
|
||||
/* for backwards compat w/ downstream kgsl DT files: */
|
||||
{ .compatible = "qcom,kgsl-3d0" },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver adreno_driver = {
|
||||
.probe = adreno_probe,
|
||||
.remove = adreno_remove,
|
||||
.driver = {
|
||||
.name = "adreno",
|
||||
.of_match_table = dt_match,
|
||||
},
|
||||
};
|
||||
|
||||
void __init adreno_register(void)
|
||||
{
|
||||
platform_driver_register(&adreno_driver);
|
||||
}
|
||||
|
||||
void __exit adreno_unregister(void)
|
||||
{
|
||||
platform_driver_unregister(&adreno_driver);
|
||||
}
|
||||
389
drivers/gpu/drm/msm/adreno/adreno_gpu.c
Normal file
389
drivers/gpu/drm/msm/adreno/adreno_gpu.c
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "adreno_gpu.h"
|
||||
#include "msm_gem.h"
|
||||
#include "msm_mmu.h"
|
||||
|
||||
#define RB_SIZE SZ_32K
|
||||
#define RB_BLKSIZE 16
|
||||
|
||||
int adreno_get_param(struct msm_gpu *gpu, uint32_t param, uint64_t *value)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
|
||||
switch (param) {
|
||||
case MSM_PARAM_GPU_ID:
|
||||
*value = adreno_gpu->info->revn;
|
||||
return 0;
|
||||
case MSM_PARAM_GMEM_SIZE:
|
||||
*value = adreno_gpu->gmem;
|
||||
return 0;
|
||||
case MSM_PARAM_CHIP_ID:
|
||||
*value = adreno_gpu->rev.patchid |
|
||||
(adreno_gpu->rev.minor << 8) |
|
||||
(adreno_gpu->rev.major << 16) |
|
||||
(adreno_gpu->rev.core << 24);
|
||||
return 0;
|
||||
default:
|
||||
DBG("%s: invalid param: %u", gpu->name, param);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
#define rbmemptr(adreno_gpu, member) \
|
||||
((adreno_gpu)->memptrs_iova + offsetof(struct adreno_rbmemptrs, member))
|
||||
|
||||
int adreno_hw_init(struct msm_gpu *gpu)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
int ret;
|
||||
|
||||
DBG("%s", gpu->name);
|
||||
|
||||
ret = msm_gem_get_iova(gpu->rb->bo, gpu->id, &gpu->rb_iova);
|
||||
if (ret) {
|
||||
gpu->rb_iova = 0;
|
||||
dev_err(gpu->dev->dev, "could not map ringbuffer: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Setup REG_CP_RB_CNTL: */
|
||||
gpu_write(gpu, REG_AXXX_CP_RB_CNTL,
|
||||
/* size is log2(quad-words): */
|
||||
AXXX_CP_RB_CNTL_BUFSZ(ilog2(gpu->rb->size / 8)) |
|
||||
AXXX_CP_RB_CNTL_BLKSZ(ilog2(RB_BLKSIZE / 8)));
|
||||
|
||||
/* Setup ringbuffer address: */
|
||||
gpu_write(gpu, REG_AXXX_CP_RB_BASE, gpu->rb_iova);
|
||||
gpu_write(gpu, REG_AXXX_CP_RB_RPTR_ADDR, rbmemptr(adreno_gpu, rptr));
|
||||
|
||||
/* Setup scratch/timestamp: */
|
||||
gpu_write(gpu, REG_AXXX_SCRATCH_ADDR, rbmemptr(adreno_gpu, fence));
|
||||
|
||||
gpu_write(gpu, REG_AXXX_SCRATCH_UMSK, 0x1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t get_wptr(struct msm_ringbuffer *ring)
|
||||
{
|
||||
return ring->cur - ring->start;
|
||||
}
|
||||
|
||||
uint32_t adreno_last_fence(struct msm_gpu *gpu)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
return adreno_gpu->memptrs->fence;
|
||||
}
|
||||
|
||||
void adreno_recover(struct msm_gpu *gpu)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
struct drm_device *dev = gpu->dev;
|
||||
int ret;
|
||||
|
||||
gpu->funcs->pm_suspend(gpu);
|
||||
|
||||
/* reset ringbuffer: */
|
||||
gpu->rb->cur = gpu->rb->start;
|
||||
|
||||
/* reset completed fence seqno, just discard anything pending: */
|
||||
adreno_gpu->memptrs->fence = gpu->submitted_fence;
|
||||
adreno_gpu->memptrs->rptr = 0;
|
||||
adreno_gpu->memptrs->wptr = 0;
|
||||
|
||||
gpu->funcs->pm_resume(gpu);
|
||||
ret = gpu->funcs->hw_init(gpu);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "gpu hw init failed: %d\n", ret);
|
||||
/* hmm, oh well? */
|
||||
}
|
||||
}
|
||||
|
||||
int adreno_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit,
|
||||
struct msm_file_private *ctx)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
struct msm_drm_private *priv = gpu->dev->dev_private;
|
||||
struct msm_ringbuffer *ring = gpu->rb;
|
||||
unsigned i, ibs = 0;
|
||||
|
||||
for (i = 0; i < submit->nr_cmds; i++) {
|
||||
switch (submit->cmd[i].type) {
|
||||
case MSM_SUBMIT_CMD_IB_TARGET_BUF:
|
||||
/* ignore IB-targets */
|
||||
break;
|
||||
case MSM_SUBMIT_CMD_CTX_RESTORE_BUF:
|
||||
/* ignore if there has not been a ctx switch: */
|
||||
if (priv->lastctx == ctx)
|
||||
break;
|
||||
case MSM_SUBMIT_CMD_BUF:
|
||||
OUT_PKT3(ring, CP_INDIRECT_BUFFER_PFD, 2);
|
||||
OUT_RING(ring, submit->cmd[i].iova);
|
||||
OUT_RING(ring, submit->cmd[i].size);
|
||||
ibs++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* on a320, at least, we seem to need to pad things out to an
|
||||
* even number of qwords to avoid issue w/ CP hanging on wrap-
|
||||
* around:
|
||||
*/
|
||||
if (ibs % 2)
|
||||
OUT_PKT2(ring);
|
||||
|
||||
OUT_PKT0(ring, REG_AXXX_CP_SCRATCH_REG2, 1);
|
||||
OUT_RING(ring, submit->fence);
|
||||
|
||||
if (adreno_is_a3xx(adreno_gpu)) {
|
||||
/* Flush HLSQ lazy updates to make sure there is nothing
|
||||
* pending for indirect loads after the timestamp has
|
||||
* passed:
|
||||
*/
|
||||
OUT_PKT3(ring, CP_EVENT_WRITE, 1);
|
||||
OUT_RING(ring, HLSQ_FLUSH);
|
||||
|
||||
OUT_PKT3(ring, CP_WAIT_FOR_IDLE, 1);
|
||||
OUT_RING(ring, 0x00000000);
|
||||
}
|
||||
|
||||
OUT_PKT3(ring, CP_EVENT_WRITE, 3);
|
||||
OUT_RING(ring, CACHE_FLUSH_TS);
|
||||
OUT_RING(ring, rbmemptr(adreno_gpu, fence));
|
||||
OUT_RING(ring, submit->fence);
|
||||
|
||||
/* we could maybe be clever and only CP_COND_EXEC the interrupt: */
|
||||
OUT_PKT3(ring, CP_INTERRUPT, 1);
|
||||
OUT_RING(ring, 0x80000000);
|
||||
|
||||
#if 0
|
||||
if (adreno_is_a3xx(adreno_gpu)) {
|
||||
/* Dummy set-constant to trigger context rollover */
|
||||
OUT_PKT3(ring, CP_SET_CONSTANT, 2);
|
||||
OUT_RING(ring, CP_REG(REG_A3XX_HLSQ_CL_KERNEL_GROUP_X_REG));
|
||||
OUT_RING(ring, 0x00000000);
|
||||
}
|
||||
#endif
|
||||
|
||||
gpu->funcs->flush(gpu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void adreno_flush(struct msm_gpu *gpu)
|
||||
{
|
||||
uint32_t wptr = get_wptr(gpu->rb);
|
||||
|
||||
/* ensure writes to ringbuffer have hit system memory: */
|
||||
mb();
|
||||
|
||||
gpu_write(gpu, REG_AXXX_CP_RB_WPTR, wptr);
|
||||
}
|
||||
|
||||
void adreno_idle(struct msm_gpu *gpu)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
uint32_t wptr = get_wptr(gpu->rb);
|
||||
|
||||
/* wait for CP to drain ringbuffer: */
|
||||
if (spin_until(adreno_gpu->memptrs->rptr == wptr))
|
||||
DRM_ERROR("%s: timeout waiting to drain ringbuffer!\n", gpu->name);
|
||||
|
||||
/* TODO maybe we need to reset GPU here to recover from hang? */
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void adreno_show(struct msm_gpu *gpu, struct seq_file *m)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
int i;
|
||||
|
||||
seq_printf(m, "revision: %d (%d.%d.%d.%d)\n",
|
||||
adreno_gpu->info->revn, adreno_gpu->rev.core,
|
||||
adreno_gpu->rev.major, adreno_gpu->rev.minor,
|
||||
adreno_gpu->rev.patchid);
|
||||
|
||||
seq_printf(m, "fence: %d/%d\n", adreno_gpu->memptrs->fence,
|
||||
gpu->submitted_fence);
|
||||
seq_printf(m, "rptr: %d\n", adreno_gpu->memptrs->rptr);
|
||||
seq_printf(m, "wptr: %d\n", adreno_gpu->memptrs->wptr);
|
||||
seq_printf(m, "rb wptr: %d\n", get_wptr(gpu->rb));
|
||||
|
||||
gpu->funcs->pm_resume(gpu);
|
||||
|
||||
/* dump these out in a form that can be parsed by demsm: */
|
||||
seq_printf(m, "IO:region %s 00000000 00020000\n", gpu->name);
|
||||
for (i = 0; adreno_gpu->registers[i] != ~0; i += 2) {
|
||||
uint32_t start = adreno_gpu->registers[i];
|
||||
uint32_t end = adreno_gpu->registers[i+1];
|
||||
uint32_t addr;
|
||||
|
||||
for (addr = start; addr <= end; addr++) {
|
||||
uint32_t val = gpu_read(gpu, addr);
|
||||
seq_printf(m, "IO:R %08x %08x\n", addr<<2, val);
|
||||
}
|
||||
}
|
||||
|
||||
gpu->funcs->pm_suspend(gpu);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* would be nice to not have to duplicate the _show() stuff with printk(): */
|
||||
void adreno_dump(struct msm_gpu *gpu)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
int i;
|
||||
|
||||
printk("revision: %d (%d.%d.%d.%d)\n",
|
||||
adreno_gpu->info->revn, adreno_gpu->rev.core,
|
||||
adreno_gpu->rev.major, adreno_gpu->rev.minor,
|
||||
adreno_gpu->rev.patchid);
|
||||
|
||||
printk("fence: %d/%d\n", adreno_gpu->memptrs->fence,
|
||||
gpu->submitted_fence);
|
||||
printk("rptr: %d\n", adreno_gpu->memptrs->rptr);
|
||||
printk("wptr: %d\n", adreno_gpu->memptrs->wptr);
|
||||
printk("rb wptr: %d\n", get_wptr(gpu->rb));
|
||||
|
||||
/* dump these out in a form that can be parsed by demsm: */
|
||||
printk("IO:region %s 00000000 00020000\n", gpu->name);
|
||||
for (i = 0; adreno_gpu->registers[i] != ~0; i += 2) {
|
||||
uint32_t start = adreno_gpu->registers[i];
|
||||
uint32_t end = adreno_gpu->registers[i+1];
|
||||
uint32_t addr;
|
||||
|
||||
for (addr = start; addr <= end; addr++) {
|
||||
uint32_t val = gpu_read(gpu, addr);
|
||||
printk("IO:R %08x %08x\n", addr<<2, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t ring_freewords(struct msm_gpu *gpu)
|
||||
{
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
uint32_t size = gpu->rb->size / 4;
|
||||
uint32_t wptr = get_wptr(gpu->rb);
|
||||
uint32_t rptr = adreno_gpu->memptrs->rptr;
|
||||
return (rptr + (size - 1) - wptr) % size;
|
||||
}
|
||||
|
||||
void adreno_wait_ring(struct msm_gpu *gpu, uint32_t ndwords)
|
||||
{
|
||||
if (spin_until(ring_freewords(gpu) >= ndwords))
|
||||
DRM_ERROR("%s: timeout waiting for ringbuffer space\n", gpu->name);
|
||||
}
|
||||
|
||||
static const char *iommu_ports[] = {
|
||||
"gfx3d_user", "gfx3d_priv",
|
||||
"gfx3d1_user", "gfx3d1_priv",
|
||||
};
|
||||
|
||||
int adreno_gpu_init(struct drm_device *drm, struct platform_device *pdev,
|
||||
struct adreno_gpu *adreno_gpu, const struct adreno_gpu_funcs *funcs)
|
||||
{
|
||||
struct adreno_platform_config *config = pdev->dev.platform_data;
|
||||
struct msm_gpu *gpu = &adreno_gpu->base;
|
||||
struct msm_mmu *mmu;
|
||||
int ret;
|
||||
|
||||
adreno_gpu->funcs = funcs;
|
||||
adreno_gpu->info = adreno_info(config->rev);
|
||||
adreno_gpu->gmem = adreno_gpu->info->gmem;
|
||||
adreno_gpu->revn = adreno_gpu->info->revn;
|
||||
adreno_gpu->rev = config->rev;
|
||||
|
||||
gpu->fast_rate = config->fast_rate;
|
||||
gpu->slow_rate = config->slow_rate;
|
||||
gpu->bus_freq = config->bus_freq;
|
||||
#ifdef CONFIG_MSM_BUS_SCALING
|
||||
gpu->bus_scale_table = config->bus_scale_table;
|
||||
#endif
|
||||
|
||||
DBG("fast_rate=%u, slow_rate=%u, bus_freq=%u",
|
||||
gpu->fast_rate, gpu->slow_rate, gpu->bus_freq);
|
||||
|
||||
ret = request_firmware(&adreno_gpu->pm4, adreno_gpu->info->pm4fw, drm->dev);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "failed to load %s PM4 firmware: %d\n",
|
||||
adreno_gpu->info->pm4fw, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = request_firmware(&adreno_gpu->pfp, adreno_gpu->info->pfpfw, drm->dev);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "failed to load %s PFP firmware: %d\n",
|
||||
adreno_gpu->info->pfpfw, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = msm_gpu_init(drm, pdev, &adreno_gpu->base, &funcs->base,
|
||||
adreno_gpu->info->name, "kgsl_3d0_reg_memory", "kgsl_3d0_irq",
|
||||
RB_SIZE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mmu = gpu->mmu;
|
||||
if (mmu) {
|
||||
ret = mmu->funcs->attach(mmu, iommu_ports,
|
||||
ARRAY_SIZE(iommu_ports));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_lock(&drm->struct_mutex);
|
||||
adreno_gpu->memptrs_bo = msm_gem_new(drm, sizeof(*adreno_gpu->memptrs),
|
||||
MSM_BO_UNCACHED);
|
||||
mutex_unlock(&drm->struct_mutex);
|
||||
if (IS_ERR(adreno_gpu->memptrs_bo)) {
|
||||
ret = PTR_ERR(adreno_gpu->memptrs_bo);
|
||||
adreno_gpu->memptrs_bo = NULL;
|
||||
dev_err(drm->dev, "could not allocate memptrs: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
adreno_gpu->memptrs = msm_gem_vaddr(adreno_gpu->memptrs_bo);
|
||||
if (!adreno_gpu->memptrs) {
|
||||
dev_err(drm->dev, "could not vmap memptrs\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = msm_gem_get_iova(adreno_gpu->memptrs_bo, gpu->id,
|
||||
&adreno_gpu->memptrs_iova);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "could not map memptrs: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void adreno_gpu_cleanup(struct adreno_gpu *gpu)
|
||||
{
|
||||
if (gpu->memptrs_bo) {
|
||||
if (gpu->memptrs_iova)
|
||||
msm_gem_put_iova(gpu->memptrs_bo, gpu->base.id);
|
||||
drm_gem_object_unreference(gpu->memptrs_bo);
|
||||
}
|
||||
if (gpu->pm4)
|
||||
release_firmware(gpu->pm4);
|
||||
if (gpu->pfp)
|
||||
release_firmware(gpu->pfp);
|
||||
msm_gpu_cleanup(&gpu->base);
|
||||
}
|
||||
175
drivers/gpu/drm/msm/adreno/adreno_gpu.h
Normal file
175
drivers/gpu/drm/msm/adreno/adreno_gpu.h
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __ADRENO_GPU_H__
|
||||
#define __ADRENO_GPU_H__
|
||||
|
||||
#include <linux/firmware.h>
|
||||
|
||||
#include "msm_gpu.h"
|
||||
|
||||
#include "adreno_common.xml.h"
|
||||
#include "adreno_pm4.xml.h"
|
||||
|
||||
struct adreno_rev {
|
||||
uint8_t core;
|
||||
uint8_t major;
|
||||
uint8_t minor;
|
||||
uint8_t patchid;
|
||||
};
|
||||
|
||||
#define ADRENO_REV(core, major, minor, patchid) \
|
||||
((struct adreno_rev){ core, major, minor, patchid })
|
||||
|
||||
struct adreno_gpu_funcs {
|
||||
struct msm_gpu_funcs base;
|
||||
};
|
||||
|
||||
struct adreno_info {
|
||||
struct adreno_rev rev;
|
||||
uint32_t revn;
|
||||
const char *name;
|
||||
const char *pm4fw, *pfpfw;
|
||||
uint32_t gmem;
|
||||
struct msm_gpu *(*init)(struct drm_device *dev);
|
||||
};
|
||||
|
||||
const struct adreno_info *adreno_info(struct adreno_rev rev);
|
||||
|
||||
struct adreno_rbmemptrs {
|
||||
volatile uint32_t rptr;
|
||||
volatile uint32_t wptr;
|
||||
volatile uint32_t fence;
|
||||
};
|
||||
|
||||
struct adreno_gpu {
|
||||
struct msm_gpu base;
|
||||
struct adreno_rev rev;
|
||||
const struct adreno_info *info;
|
||||
uint32_t gmem; /* actual gmem size */
|
||||
uint32_t revn; /* numeric revision name */
|
||||
const struct adreno_gpu_funcs *funcs;
|
||||
|
||||
/* interesting register offsets to dump: */
|
||||
const unsigned int *registers;
|
||||
|
||||
/* firmware: */
|
||||
const struct firmware *pm4, *pfp;
|
||||
|
||||
/* ringbuffer rptr/wptr: */
|
||||
// TODO should this be in msm_ringbuffer? I think it would be
|
||||
// different for z180..
|
||||
struct adreno_rbmemptrs *memptrs;
|
||||
struct drm_gem_object *memptrs_bo;
|
||||
uint32_t memptrs_iova;
|
||||
};
|
||||
#define to_adreno_gpu(x) container_of(x, struct adreno_gpu, base)
|
||||
|
||||
/* platform config data (ie. from DT, or pdata) */
|
||||
struct adreno_platform_config {
|
||||
struct adreno_rev rev;
|
||||
uint32_t fast_rate, slow_rate, bus_freq;
|
||||
#ifdef CONFIG_MSM_BUS_SCALING
|
||||
struct msm_bus_scale_pdata *bus_scale_table;
|
||||
#endif
|
||||
};
|
||||
|
||||
#define ADRENO_IDLE_TIMEOUT msecs_to_jiffies(1000)
|
||||
|
||||
#define spin_until(X) ({ \
|
||||
int __ret = -ETIMEDOUT; \
|
||||
unsigned long __t = jiffies + ADRENO_IDLE_TIMEOUT; \
|
||||
do { \
|
||||
if (X) { \
|
||||
__ret = 0; \
|
||||
break; \
|
||||
} \
|
||||
} while (time_before(jiffies, __t)); \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
|
||||
static inline bool adreno_is_a3xx(struct adreno_gpu *gpu)
|
||||
{
|
||||
return (gpu->revn >= 300) && (gpu->revn < 400);
|
||||
}
|
||||
|
||||
static inline bool adreno_is_a305(struct adreno_gpu *gpu)
|
||||
{
|
||||
return gpu->revn == 305;
|
||||
}
|
||||
|
||||
static inline bool adreno_is_a320(struct adreno_gpu *gpu)
|
||||
{
|
||||
return gpu->revn == 320;
|
||||
}
|
||||
|
||||
static inline bool adreno_is_a330(struct adreno_gpu *gpu)
|
||||
{
|
||||
return gpu->revn == 330;
|
||||
}
|
||||
|
||||
static inline bool adreno_is_a330v2(struct adreno_gpu *gpu)
|
||||
{
|
||||
return adreno_is_a330(gpu) && (gpu->rev.patchid > 0);
|
||||
}
|
||||
|
||||
int adreno_get_param(struct msm_gpu *gpu, uint32_t param, uint64_t *value);
|
||||
int adreno_hw_init(struct msm_gpu *gpu);
|
||||
uint32_t adreno_last_fence(struct msm_gpu *gpu);
|
||||
void adreno_recover(struct msm_gpu *gpu);
|
||||
int adreno_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit,
|
||||
struct msm_file_private *ctx);
|
||||
void adreno_flush(struct msm_gpu *gpu);
|
||||
void adreno_idle(struct msm_gpu *gpu);
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void adreno_show(struct msm_gpu *gpu, struct seq_file *m);
|
||||
#endif
|
||||
void adreno_dump(struct msm_gpu *gpu);
|
||||
void adreno_wait_ring(struct msm_gpu *gpu, uint32_t ndwords);
|
||||
|
||||
int adreno_gpu_init(struct drm_device *drm, struct platform_device *pdev,
|
||||
struct adreno_gpu *gpu, const struct adreno_gpu_funcs *funcs);
|
||||
void adreno_gpu_cleanup(struct adreno_gpu *gpu);
|
||||
|
||||
|
||||
/* ringbuffer helpers (the parts that are adreno specific) */
|
||||
|
||||
static inline void
|
||||
OUT_PKT0(struct msm_ringbuffer *ring, uint16_t regindx, uint16_t cnt)
|
||||
{
|
||||
adreno_wait_ring(ring->gpu, cnt+1);
|
||||
OUT_RING(ring, CP_TYPE0_PKT | ((cnt-1) << 16) | (regindx & 0x7FFF));
|
||||
}
|
||||
|
||||
/* no-op packet: */
|
||||
static inline void
|
||||
OUT_PKT2(struct msm_ringbuffer *ring)
|
||||
{
|
||||
adreno_wait_ring(ring->gpu, 1);
|
||||
OUT_RING(ring, CP_TYPE2_PKT);
|
||||
}
|
||||
|
||||
static inline void
|
||||
OUT_PKT3(struct msm_ringbuffer *ring, uint8_t opcode, uint16_t cnt)
|
||||
{
|
||||
adreno_wait_ring(ring->gpu, cnt+1);
|
||||
OUT_RING(ring, CP_TYPE3_PKT | ((cnt-1) << 16) | ((opcode & 0xFF) << 8));
|
||||
}
|
||||
|
||||
|
||||
#endif /* __ADRENO_GPU_H__ */
|
||||
497
drivers/gpu/drm/msm/adreno/adreno_pm4.xml.h
Normal file
497
drivers/gpu/drm/msm/adreno/adreno_pm4.xml.h
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
#ifndef ADRENO_PM4_XML
|
||||
#define ADRENO_PM4_XML
|
||||
|
||||
/* Autogenerated file, DO NOT EDIT manually!
|
||||
|
||||
This file was generated by the rules-ng-ng headergen tool in this git repository:
|
||||
http://github.com/freedreno/envytools/
|
||||
git clone https://github.com/freedreno/envytools.git
|
||||
|
||||
The rules-ng-ng source files this header was generated from are:
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno.xml ( 364 bytes, from 2013-11-30 14:47:15)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/freedreno_copyright.xml ( 1453 bytes, from 2013-03-31 16:51:27)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/a2xx.xml ( 32901 bytes, from 2014-06-02 15:21:30)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/adreno_common.xml ( 9859 bytes, from 2014-06-02 15:21:30)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/adreno_pm4.xml ( 14960 bytes, from 2014-07-27 17:22:13)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/a3xx.xml ( 58020 bytes, from 2014-08-01 12:22:48)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/adreno/a4xx.xml ( 41068 bytes, from 2014-08-01 12:22:48)
|
||||
|
||||
Copyright (C) 2013-2014 by the following authors:
|
||||
- Rob Clark <robdclark@gmail.com> (robclark)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the
|
||||
next paragraph) shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
enum vgt_event_type {
|
||||
VS_DEALLOC = 0,
|
||||
PS_DEALLOC = 1,
|
||||
VS_DONE_TS = 2,
|
||||
PS_DONE_TS = 3,
|
||||
CACHE_FLUSH_TS = 4,
|
||||
CONTEXT_DONE = 5,
|
||||
CACHE_FLUSH = 6,
|
||||
HLSQ_FLUSH = 7,
|
||||
VIZQUERY_START = 7,
|
||||
VIZQUERY_END = 8,
|
||||
SC_WAIT_WC = 9,
|
||||
RST_PIX_CNT = 13,
|
||||
RST_VTX_CNT = 14,
|
||||
TILE_FLUSH = 15,
|
||||
CACHE_FLUSH_AND_INV_TS_EVENT = 20,
|
||||
ZPASS_DONE = 21,
|
||||
CACHE_FLUSH_AND_INV_EVENT = 22,
|
||||
PERFCOUNTER_START = 23,
|
||||
PERFCOUNTER_STOP = 24,
|
||||
VS_FETCH_DONE = 27,
|
||||
FACENESS_FLUSH = 28,
|
||||
};
|
||||
|
||||
enum pc_di_primtype {
|
||||
DI_PT_NONE = 0,
|
||||
DI_PT_POINTLIST_A2XX = 1,
|
||||
DI_PT_LINELIST = 2,
|
||||
DI_PT_LINESTRIP = 3,
|
||||
DI_PT_TRILIST = 4,
|
||||
DI_PT_TRIFAN = 5,
|
||||
DI_PT_TRISTRIP = 6,
|
||||
DI_PT_LINELOOP = 7,
|
||||
DI_PT_RECTLIST = 8,
|
||||
DI_PT_POINTLIST_A3XX = 9,
|
||||
DI_PT_QUADLIST = 13,
|
||||
DI_PT_QUADSTRIP = 14,
|
||||
DI_PT_POLYGON = 15,
|
||||
DI_PT_2D_COPY_RECT_LIST_V0 = 16,
|
||||
DI_PT_2D_COPY_RECT_LIST_V1 = 17,
|
||||
DI_PT_2D_COPY_RECT_LIST_V2 = 18,
|
||||
DI_PT_2D_COPY_RECT_LIST_V3 = 19,
|
||||
DI_PT_2D_FILL_RECT_LIST = 20,
|
||||
DI_PT_2D_LINE_STRIP = 21,
|
||||
DI_PT_2D_TRI_STRIP = 22,
|
||||
};
|
||||
|
||||
enum pc_di_src_sel {
|
||||
DI_SRC_SEL_DMA = 0,
|
||||
DI_SRC_SEL_IMMEDIATE = 1,
|
||||
DI_SRC_SEL_AUTO_INDEX = 2,
|
||||
DI_SRC_SEL_RESERVED = 3,
|
||||
};
|
||||
|
||||
enum pc_di_index_size {
|
||||
INDEX_SIZE_IGN = 0,
|
||||
INDEX_SIZE_16_BIT = 0,
|
||||
INDEX_SIZE_32_BIT = 1,
|
||||
INDEX_SIZE_8_BIT = 2,
|
||||
INDEX_SIZE_INVALID = 0,
|
||||
};
|
||||
|
||||
enum pc_di_vis_cull_mode {
|
||||
IGNORE_VISIBILITY = 0,
|
||||
USE_VISIBILITY = 1,
|
||||
};
|
||||
|
||||
enum adreno_pm4_packet_type {
|
||||
CP_TYPE0_PKT = 0,
|
||||
CP_TYPE1_PKT = 0x40000000,
|
||||
CP_TYPE2_PKT = 0x80000000,
|
||||
CP_TYPE3_PKT = 0xc0000000,
|
||||
};
|
||||
|
||||
enum adreno_pm4_type3_packets {
|
||||
CP_ME_INIT = 72,
|
||||
CP_NOP = 16,
|
||||
CP_INDIRECT_BUFFER = 63,
|
||||
CP_INDIRECT_BUFFER_PFD = 55,
|
||||
CP_WAIT_FOR_IDLE = 38,
|
||||
CP_WAIT_REG_MEM = 60,
|
||||
CP_WAIT_REG_EQ = 82,
|
||||
CP_WAIT_REG_GTE = 83,
|
||||
CP_WAIT_UNTIL_READ = 92,
|
||||
CP_WAIT_IB_PFD_COMPLETE = 93,
|
||||
CP_REG_RMW = 33,
|
||||
CP_SET_BIN_DATA = 47,
|
||||
CP_REG_TO_MEM = 62,
|
||||
CP_MEM_WRITE = 61,
|
||||
CP_MEM_WRITE_CNTR = 79,
|
||||
CP_COND_EXEC = 68,
|
||||
CP_COND_WRITE = 69,
|
||||
CP_EVENT_WRITE = 70,
|
||||
CP_EVENT_WRITE_SHD = 88,
|
||||
CP_EVENT_WRITE_CFL = 89,
|
||||
CP_EVENT_WRITE_ZPD = 91,
|
||||
CP_RUN_OPENCL = 49,
|
||||
CP_DRAW_INDX = 34,
|
||||
CP_DRAW_INDX_2 = 54,
|
||||
CP_DRAW_INDX_BIN = 52,
|
||||
CP_DRAW_INDX_2_BIN = 53,
|
||||
CP_VIZ_QUERY = 35,
|
||||
CP_SET_STATE = 37,
|
||||
CP_SET_CONSTANT = 45,
|
||||
CP_IM_LOAD = 39,
|
||||
CP_IM_LOAD_IMMEDIATE = 43,
|
||||
CP_LOAD_CONSTANT_CONTEXT = 46,
|
||||
CP_INVALIDATE_STATE = 59,
|
||||
CP_SET_SHADER_BASES = 74,
|
||||
CP_SET_BIN_MASK = 80,
|
||||
CP_SET_BIN_SELECT = 81,
|
||||
CP_CONTEXT_UPDATE = 94,
|
||||
CP_INTERRUPT = 64,
|
||||
CP_IM_STORE = 44,
|
||||
CP_SET_DRAW_INIT_FLAGS = 75,
|
||||
CP_SET_PROTECTED_MODE = 95,
|
||||
CP_LOAD_STATE = 48,
|
||||
CP_COND_INDIRECT_BUFFER_PFE = 58,
|
||||
CP_COND_INDIRECT_BUFFER_PFD = 50,
|
||||
CP_INDIRECT_BUFFER_PFE = 63,
|
||||
CP_SET_BIN = 76,
|
||||
CP_TEST_TWO_MEMS = 113,
|
||||
CP_REG_WR_NO_CTXT = 120,
|
||||
CP_RECORD_PFP_TIMESTAMP = 17,
|
||||
CP_WAIT_FOR_ME = 19,
|
||||
CP_SET_DRAW_STATE = 67,
|
||||
CP_DRAW_INDX_OFFSET = 56,
|
||||
CP_DRAW_INDIRECT = 40,
|
||||
CP_DRAW_INDX_INDIRECT = 41,
|
||||
CP_DRAW_AUTO = 36,
|
||||
CP_UNKNOWN_1A = 26,
|
||||
CP_WIDE_REG_WRITE = 116,
|
||||
IN_IB_PREFETCH_END = 23,
|
||||
IN_SUBBLK_PREFETCH = 31,
|
||||
IN_INSTR_PREFETCH = 32,
|
||||
IN_INSTR_MATCH = 71,
|
||||
IN_CONST_PREFETCH = 73,
|
||||
IN_INCR_UPDT_STATE = 85,
|
||||
IN_INCR_UPDT_CONST = 86,
|
||||
IN_INCR_UPDT_INSTR = 87,
|
||||
};
|
||||
|
||||
enum adreno_state_block {
|
||||
SB_VERT_TEX = 0,
|
||||
SB_VERT_MIPADDR = 1,
|
||||
SB_FRAG_TEX = 2,
|
||||
SB_FRAG_MIPADDR = 3,
|
||||
SB_VERT_SHADER = 4,
|
||||
SB_FRAG_SHADER = 6,
|
||||
};
|
||||
|
||||
enum adreno_state_type {
|
||||
ST_SHADER = 0,
|
||||
ST_CONSTANTS = 1,
|
||||
};
|
||||
|
||||
enum adreno_state_src {
|
||||
SS_DIRECT = 0,
|
||||
SS_INDIRECT = 4,
|
||||
};
|
||||
|
||||
#define REG_CP_LOAD_STATE_0 0x00000000
|
||||
#define CP_LOAD_STATE_0_DST_OFF__MASK 0x0000ffff
|
||||
#define CP_LOAD_STATE_0_DST_OFF__SHIFT 0
|
||||
static inline uint32_t CP_LOAD_STATE_0_DST_OFF(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_LOAD_STATE_0_DST_OFF__SHIFT) & CP_LOAD_STATE_0_DST_OFF__MASK;
|
||||
}
|
||||
#define CP_LOAD_STATE_0_STATE_SRC__MASK 0x00070000
|
||||
#define CP_LOAD_STATE_0_STATE_SRC__SHIFT 16
|
||||
static inline uint32_t CP_LOAD_STATE_0_STATE_SRC(enum adreno_state_src val)
|
||||
{
|
||||
return ((val) << CP_LOAD_STATE_0_STATE_SRC__SHIFT) & CP_LOAD_STATE_0_STATE_SRC__MASK;
|
||||
}
|
||||
#define CP_LOAD_STATE_0_STATE_BLOCK__MASK 0x00380000
|
||||
#define CP_LOAD_STATE_0_STATE_BLOCK__SHIFT 19
|
||||
static inline uint32_t CP_LOAD_STATE_0_STATE_BLOCK(enum adreno_state_block val)
|
||||
{
|
||||
return ((val) << CP_LOAD_STATE_0_STATE_BLOCK__SHIFT) & CP_LOAD_STATE_0_STATE_BLOCK__MASK;
|
||||
}
|
||||
#define CP_LOAD_STATE_0_NUM_UNIT__MASK 0x7fc00000
|
||||
#define CP_LOAD_STATE_0_NUM_UNIT__SHIFT 22
|
||||
static inline uint32_t CP_LOAD_STATE_0_NUM_UNIT(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_LOAD_STATE_0_NUM_UNIT__SHIFT) & CP_LOAD_STATE_0_NUM_UNIT__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_LOAD_STATE_1 0x00000001
|
||||
#define CP_LOAD_STATE_1_STATE_TYPE__MASK 0x00000003
|
||||
#define CP_LOAD_STATE_1_STATE_TYPE__SHIFT 0
|
||||
static inline uint32_t CP_LOAD_STATE_1_STATE_TYPE(enum adreno_state_type val)
|
||||
{
|
||||
return ((val) << CP_LOAD_STATE_1_STATE_TYPE__SHIFT) & CP_LOAD_STATE_1_STATE_TYPE__MASK;
|
||||
}
|
||||
#define CP_LOAD_STATE_1_EXT_SRC_ADDR__MASK 0xfffffffc
|
||||
#define CP_LOAD_STATE_1_EXT_SRC_ADDR__SHIFT 2
|
||||
static inline uint32_t CP_LOAD_STATE_1_EXT_SRC_ADDR(uint32_t val)
|
||||
{
|
||||
return ((val >> 2) << CP_LOAD_STATE_1_EXT_SRC_ADDR__SHIFT) & CP_LOAD_STATE_1_EXT_SRC_ADDR__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_0 0x00000000
|
||||
#define CP_DRAW_INDX_0_VIZ_QUERY__MASK 0xffffffff
|
||||
#define CP_DRAW_INDX_0_VIZ_QUERY__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_0_VIZ_QUERY(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_0_VIZ_QUERY__SHIFT) & CP_DRAW_INDX_0_VIZ_QUERY__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_1 0x00000001
|
||||
#define CP_DRAW_INDX_1_PRIM_TYPE__MASK 0x0000003f
|
||||
#define CP_DRAW_INDX_1_PRIM_TYPE__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_1_PRIM_TYPE(enum pc_di_primtype val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_1_PRIM_TYPE__SHIFT) & CP_DRAW_INDX_1_PRIM_TYPE__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_1_SOURCE_SELECT__MASK 0x000000c0
|
||||
#define CP_DRAW_INDX_1_SOURCE_SELECT__SHIFT 6
|
||||
static inline uint32_t CP_DRAW_INDX_1_SOURCE_SELECT(enum pc_di_src_sel val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_1_SOURCE_SELECT__SHIFT) & CP_DRAW_INDX_1_SOURCE_SELECT__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_1_VIS_CULL__MASK 0x00000600
|
||||
#define CP_DRAW_INDX_1_VIS_CULL__SHIFT 9
|
||||
static inline uint32_t CP_DRAW_INDX_1_VIS_CULL(enum pc_di_vis_cull_mode val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_1_VIS_CULL__SHIFT) & CP_DRAW_INDX_1_VIS_CULL__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_1_INDEX_SIZE__MASK 0x00000800
|
||||
#define CP_DRAW_INDX_1_INDEX_SIZE__SHIFT 11
|
||||
static inline uint32_t CP_DRAW_INDX_1_INDEX_SIZE(enum pc_di_index_size val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_1_INDEX_SIZE__SHIFT) & CP_DRAW_INDX_1_INDEX_SIZE__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_1_NOT_EOP 0x00001000
|
||||
#define CP_DRAW_INDX_1_SMALL_INDEX 0x00002000
|
||||
#define CP_DRAW_INDX_1_PRE_DRAW_INITIATOR_ENABLE 0x00004000
|
||||
#define CP_DRAW_INDX_1_NUM_INDICES__MASK 0xffff0000
|
||||
#define CP_DRAW_INDX_1_NUM_INDICES__SHIFT 16
|
||||
static inline uint32_t CP_DRAW_INDX_1_NUM_INDICES(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_1_NUM_INDICES__SHIFT) & CP_DRAW_INDX_1_NUM_INDICES__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_2 0x00000002
|
||||
#define CP_DRAW_INDX_2_NUM_INDICES__MASK 0xffffffff
|
||||
#define CP_DRAW_INDX_2_NUM_INDICES__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_2_NUM_INDICES(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_NUM_INDICES__SHIFT) & CP_DRAW_INDX_2_NUM_INDICES__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_2 0x00000002
|
||||
#define CP_DRAW_INDX_2_INDX_BASE__MASK 0xffffffff
|
||||
#define CP_DRAW_INDX_2_INDX_BASE__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_2_INDX_BASE(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_INDX_BASE__SHIFT) & CP_DRAW_INDX_2_INDX_BASE__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_2 0x00000002
|
||||
#define CP_DRAW_INDX_2_INDX_SIZE__MASK 0xffffffff
|
||||
#define CP_DRAW_INDX_2_INDX_SIZE__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_2_INDX_SIZE(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_INDX_SIZE__SHIFT) & CP_DRAW_INDX_2_INDX_SIZE__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_2_0 0x00000000
|
||||
#define CP_DRAW_INDX_2_0_VIZ_QUERY__MASK 0xffffffff
|
||||
#define CP_DRAW_INDX_2_0_VIZ_QUERY__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_2_0_VIZ_QUERY(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_0_VIZ_QUERY__SHIFT) & CP_DRAW_INDX_2_0_VIZ_QUERY__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_2_1 0x00000001
|
||||
#define CP_DRAW_INDX_2_1_PRIM_TYPE__MASK 0x0000003f
|
||||
#define CP_DRAW_INDX_2_1_PRIM_TYPE__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_2_1_PRIM_TYPE(enum pc_di_primtype val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_1_PRIM_TYPE__SHIFT) & CP_DRAW_INDX_2_1_PRIM_TYPE__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_2_1_SOURCE_SELECT__MASK 0x000000c0
|
||||
#define CP_DRAW_INDX_2_1_SOURCE_SELECT__SHIFT 6
|
||||
static inline uint32_t CP_DRAW_INDX_2_1_SOURCE_SELECT(enum pc_di_src_sel val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_1_SOURCE_SELECT__SHIFT) & CP_DRAW_INDX_2_1_SOURCE_SELECT__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_2_1_VIS_CULL__MASK 0x00000600
|
||||
#define CP_DRAW_INDX_2_1_VIS_CULL__SHIFT 9
|
||||
static inline uint32_t CP_DRAW_INDX_2_1_VIS_CULL(enum pc_di_vis_cull_mode val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_1_VIS_CULL__SHIFT) & CP_DRAW_INDX_2_1_VIS_CULL__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_2_1_INDEX_SIZE__MASK 0x00000800
|
||||
#define CP_DRAW_INDX_2_1_INDEX_SIZE__SHIFT 11
|
||||
static inline uint32_t CP_DRAW_INDX_2_1_INDEX_SIZE(enum pc_di_index_size val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_1_INDEX_SIZE__SHIFT) & CP_DRAW_INDX_2_1_INDEX_SIZE__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_2_1_NOT_EOP 0x00001000
|
||||
#define CP_DRAW_INDX_2_1_SMALL_INDEX 0x00002000
|
||||
#define CP_DRAW_INDX_2_1_PRE_DRAW_INITIATOR_ENABLE 0x00004000
|
||||
#define CP_DRAW_INDX_2_1_NUM_INDICES__MASK 0xffff0000
|
||||
#define CP_DRAW_INDX_2_1_NUM_INDICES__SHIFT 16
|
||||
static inline uint32_t CP_DRAW_INDX_2_1_NUM_INDICES(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_1_NUM_INDICES__SHIFT) & CP_DRAW_INDX_2_1_NUM_INDICES__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_2_2 0x00000002
|
||||
#define CP_DRAW_INDX_2_2_NUM_INDICES__MASK 0xffffffff
|
||||
#define CP_DRAW_INDX_2_2_NUM_INDICES__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_2_2_NUM_INDICES(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_2_2_NUM_INDICES__SHIFT) & CP_DRAW_INDX_2_2_NUM_INDICES__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_OFFSET_0 0x00000000
|
||||
#define CP_DRAW_INDX_OFFSET_0_PRIM_TYPE__MASK 0x0000003f
|
||||
#define CP_DRAW_INDX_OFFSET_0_PRIM_TYPE__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_OFFSET_0_PRIM_TYPE(enum pc_di_primtype val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_OFFSET_0_PRIM_TYPE__SHIFT) & CP_DRAW_INDX_OFFSET_0_PRIM_TYPE__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_OFFSET_0_SOURCE_SELECT__MASK 0x000000c0
|
||||
#define CP_DRAW_INDX_OFFSET_0_SOURCE_SELECT__SHIFT 6
|
||||
static inline uint32_t CP_DRAW_INDX_OFFSET_0_SOURCE_SELECT(enum pc_di_src_sel val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_OFFSET_0_SOURCE_SELECT__SHIFT) & CP_DRAW_INDX_OFFSET_0_SOURCE_SELECT__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_OFFSET_0_VIS_CULL__MASK 0x00000700
|
||||
#define CP_DRAW_INDX_OFFSET_0_VIS_CULL__SHIFT 8
|
||||
static inline uint32_t CP_DRAW_INDX_OFFSET_0_VIS_CULL(enum pc_di_vis_cull_mode val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_OFFSET_0_VIS_CULL__SHIFT) & CP_DRAW_INDX_OFFSET_0_VIS_CULL__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_OFFSET_0_INDEX_SIZE__MASK 0x00000800
|
||||
#define CP_DRAW_INDX_OFFSET_0_INDEX_SIZE__SHIFT 11
|
||||
static inline uint32_t CP_DRAW_INDX_OFFSET_0_INDEX_SIZE(enum pc_di_index_size val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_OFFSET_0_INDEX_SIZE__SHIFT) & CP_DRAW_INDX_OFFSET_0_INDEX_SIZE__MASK;
|
||||
}
|
||||
#define CP_DRAW_INDX_OFFSET_0_NOT_EOP 0x00001000
|
||||
#define CP_DRAW_INDX_OFFSET_0_SMALL_INDEX 0x00002000
|
||||
#define CP_DRAW_INDX_OFFSET_0_PRE_DRAW_INITIATOR_ENABLE 0x00004000
|
||||
#define CP_DRAW_INDX_OFFSET_0_NUM_INDICES__MASK 0xffff0000
|
||||
#define CP_DRAW_INDX_OFFSET_0_NUM_INDICES__SHIFT 16
|
||||
static inline uint32_t CP_DRAW_INDX_OFFSET_0_NUM_INDICES(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_OFFSET_0_NUM_INDICES__SHIFT) & CP_DRAW_INDX_OFFSET_0_NUM_INDICES__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_OFFSET_1 0x00000001
|
||||
|
||||
#define REG_CP_DRAW_INDX_OFFSET_2 0x00000002
|
||||
#define CP_DRAW_INDX_OFFSET_2_NUM_INDICES__MASK 0xffffffff
|
||||
#define CP_DRAW_INDX_OFFSET_2_NUM_INDICES__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_OFFSET_2_NUM_INDICES(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_OFFSET_2_NUM_INDICES__SHIFT) & CP_DRAW_INDX_OFFSET_2_NUM_INDICES__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_OFFSET_2 0x00000002
|
||||
#define CP_DRAW_INDX_OFFSET_2_INDX_BASE__MASK 0xffffffff
|
||||
#define CP_DRAW_INDX_OFFSET_2_INDX_BASE__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_OFFSET_2_INDX_BASE(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_OFFSET_2_INDX_BASE__SHIFT) & CP_DRAW_INDX_OFFSET_2_INDX_BASE__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_DRAW_INDX_OFFSET_2 0x00000002
|
||||
#define CP_DRAW_INDX_OFFSET_2_INDX_SIZE__MASK 0xffffffff
|
||||
#define CP_DRAW_INDX_OFFSET_2_INDX_SIZE__SHIFT 0
|
||||
static inline uint32_t CP_DRAW_INDX_OFFSET_2_INDX_SIZE(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_DRAW_INDX_OFFSET_2_INDX_SIZE__SHIFT) & CP_DRAW_INDX_OFFSET_2_INDX_SIZE__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_SET_DRAW_STATE_0 0x00000000
|
||||
#define CP_SET_DRAW_STATE_0_COUNT__MASK 0x0000ffff
|
||||
#define CP_SET_DRAW_STATE_0_COUNT__SHIFT 0
|
||||
static inline uint32_t CP_SET_DRAW_STATE_0_COUNT(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_SET_DRAW_STATE_0_COUNT__SHIFT) & CP_SET_DRAW_STATE_0_COUNT__MASK;
|
||||
}
|
||||
#define CP_SET_DRAW_STATE_0_DIRTY 0x00010000
|
||||
#define CP_SET_DRAW_STATE_0_DISABLE 0x00020000
|
||||
#define CP_SET_DRAW_STATE_0_DISABLE_ALL_GROUPS 0x00040000
|
||||
#define CP_SET_DRAW_STATE_0_LOAD_IMMED 0x00080000
|
||||
#define CP_SET_DRAW_STATE_0_GROUP_ID__MASK 0x1f000000
|
||||
#define CP_SET_DRAW_STATE_0_GROUP_ID__SHIFT 24
|
||||
static inline uint32_t CP_SET_DRAW_STATE_0_GROUP_ID(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_SET_DRAW_STATE_0_GROUP_ID__SHIFT) & CP_SET_DRAW_STATE_0_GROUP_ID__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_SET_DRAW_STATE_1 0x00000001
|
||||
#define CP_SET_DRAW_STATE_1_ADDR__MASK 0xffffffff
|
||||
#define CP_SET_DRAW_STATE_1_ADDR__SHIFT 0
|
||||
static inline uint32_t CP_SET_DRAW_STATE_1_ADDR(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_SET_DRAW_STATE_1_ADDR__SHIFT) & CP_SET_DRAW_STATE_1_ADDR__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_SET_BIN_0 0x00000000
|
||||
|
||||
#define REG_CP_SET_BIN_1 0x00000001
|
||||
#define CP_SET_BIN_1_X1__MASK 0x0000ffff
|
||||
#define CP_SET_BIN_1_X1__SHIFT 0
|
||||
static inline uint32_t CP_SET_BIN_1_X1(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_SET_BIN_1_X1__SHIFT) & CP_SET_BIN_1_X1__MASK;
|
||||
}
|
||||
#define CP_SET_BIN_1_Y1__MASK 0xffff0000
|
||||
#define CP_SET_BIN_1_Y1__SHIFT 16
|
||||
static inline uint32_t CP_SET_BIN_1_Y1(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_SET_BIN_1_Y1__SHIFT) & CP_SET_BIN_1_Y1__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_SET_BIN_2 0x00000002
|
||||
#define CP_SET_BIN_2_X2__MASK 0x0000ffff
|
||||
#define CP_SET_BIN_2_X2__SHIFT 0
|
||||
static inline uint32_t CP_SET_BIN_2_X2(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_SET_BIN_2_X2__SHIFT) & CP_SET_BIN_2_X2__MASK;
|
||||
}
|
||||
#define CP_SET_BIN_2_Y2__MASK 0xffff0000
|
||||
#define CP_SET_BIN_2_Y2__SHIFT 16
|
||||
static inline uint32_t CP_SET_BIN_2_Y2(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_SET_BIN_2_Y2__SHIFT) & CP_SET_BIN_2_Y2__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_SET_BIN_DATA_0 0x00000000
|
||||
#define CP_SET_BIN_DATA_0_BIN_DATA_ADDR__MASK 0xffffffff
|
||||
#define CP_SET_BIN_DATA_0_BIN_DATA_ADDR__SHIFT 0
|
||||
static inline uint32_t CP_SET_BIN_DATA_0_BIN_DATA_ADDR(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_SET_BIN_DATA_0_BIN_DATA_ADDR__SHIFT) & CP_SET_BIN_DATA_0_BIN_DATA_ADDR__MASK;
|
||||
}
|
||||
|
||||
#define REG_CP_SET_BIN_DATA_1 0x00000001
|
||||
#define CP_SET_BIN_DATA_1_BIN_SIZE_ADDRESS__MASK 0xffffffff
|
||||
#define CP_SET_BIN_DATA_1_BIN_SIZE_ADDRESS__SHIFT 0
|
||||
static inline uint32_t CP_SET_BIN_DATA_1_BIN_SIZE_ADDRESS(uint32_t val)
|
||||
{
|
||||
return ((val) << CP_SET_BIN_DATA_1_BIN_SIZE_ADDRESS__SHIFT) & CP_SET_BIN_DATA_1_BIN_SIZE_ADDRESS__MASK;
|
||||
}
|
||||
|
||||
|
||||
#endif /* ADRENO_PM4_XML */
|
||||
504
drivers/gpu/drm/msm/dsi/dsi.xml.h
Normal file
504
drivers/gpu/drm/msm/dsi/dsi.xml.h
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
#ifndef DSI_XML
|
||||
#define DSI_XML
|
||||
|
||||
/* Autogenerated file, DO NOT EDIT manually!
|
||||
|
||||
This file was generated by the rules-ng-ng headergen tool in this git repository:
|
||||
http://github.com/freedreno/envytools/
|
||||
git clone https://github.com/freedreno/envytools.git
|
||||
|
||||
The rules-ng-ng source files this header was generated from are:
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/msm.xml ( 647 bytes, from 2013-11-30 14:45:35)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/freedreno_copyright.xml ( 1453 bytes, from 2013-03-31 16:51:27)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp4.xml ( 20457 bytes, from 2014-08-01 12:22:48)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp_common.xml ( 1615 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp5.xml ( 22517 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/dsi.xml ( 11712 bytes, from 2013-08-17 17:13:43)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/sfpb.xml ( 344 bytes, from 2013-08-11 19:26:32)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/mmss_cc.xml ( 1686 bytes, from 2014-08-01 12:23:53)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2013-07-05 19:21:12)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/hdmi.xml ( 23613 bytes, from 2014-07-17 15:33:30)
|
||||
|
||||
Copyright (C) 2013 by the following authors:
|
||||
- Rob Clark <robdclark@gmail.com> (robclark)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the
|
||||
next paragraph) shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
enum dsi_traffic_mode {
|
||||
NON_BURST_SYNCH_PULSE = 0,
|
||||
NON_BURST_SYNCH_EVENT = 1,
|
||||
BURST_MODE = 2,
|
||||
};
|
||||
|
||||
enum dsi_dst_format {
|
||||
DST_FORMAT_RGB565 = 0,
|
||||
DST_FORMAT_RGB666 = 1,
|
||||
DST_FORMAT_RGB666_LOOSE = 2,
|
||||
DST_FORMAT_RGB888 = 3,
|
||||
};
|
||||
|
||||
enum dsi_rgb_swap {
|
||||
SWAP_RGB = 0,
|
||||
SWAP_RBG = 1,
|
||||
SWAP_BGR = 2,
|
||||
SWAP_BRG = 3,
|
||||
SWAP_GRB = 4,
|
||||
SWAP_GBR = 5,
|
||||
};
|
||||
|
||||
enum dsi_cmd_trigger {
|
||||
TRIGGER_NONE = 0,
|
||||
TRIGGER_TE = 2,
|
||||
TRIGGER_SW = 4,
|
||||
TRIGGER_SW_SEOF = 5,
|
||||
TRIGGER_SW_TE = 6,
|
||||
};
|
||||
|
||||
#define DSI_IRQ_CMD_DMA_DONE 0x00000001
|
||||
#define DSI_IRQ_MASK_CMD_DMA_DONE 0x00000002
|
||||
#define DSI_IRQ_CMD_MDP_DONE 0x00000100
|
||||
#define DSI_IRQ_MASK_CMD_MDP_DONE 0x00000200
|
||||
#define DSI_IRQ_VIDEO_DONE 0x00010000
|
||||
#define DSI_IRQ_MASK_VIDEO_DONE 0x00020000
|
||||
#define DSI_IRQ_ERROR 0x01000000
|
||||
#define DSI_IRQ_MASK_ERROR 0x02000000
|
||||
#define REG_DSI_CTRL 0x00000000
|
||||
#define DSI_CTRL_ENABLE 0x00000001
|
||||
#define DSI_CTRL_VID_MODE_EN 0x00000002
|
||||
#define DSI_CTRL_CMD_MODE_EN 0x00000004
|
||||
#define DSI_CTRL_LANE0 0x00000010
|
||||
#define DSI_CTRL_LANE1 0x00000020
|
||||
#define DSI_CTRL_LANE2 0x00000040
|
||||
#define DSI_CTRL_LANE3 0x00000080
|
||||
#define DSI_CTRL_CLK_EN 0x00000100
|
||||
#define DSI_CTRL_ECC_CHECK 0x00100000
|
||||
#define DSI_CTRL_CRC_CHECK 0x01000000
|
||||
|
||||
#define REG_DSI_STATUS0 0x00000004
|
||||
#define DSI_STATUS0_CMD_MODE_DMA_BUSY 0x00000002
|
||||
#define DSI_STATUS0_VIDEO_MODE_ENGINE_BUSY 0x00000008
|
||||
#define DSI_STATUS0_DSI_BUSY 0x00000010
|
||||
|
||||
#define REG_DSI_FIFO_STATUS 0x00000008
|
||||
|
||||
#define REG_DSI_VID_CFG0 0x0000000c
|
||||
#define DSI_VID_CFG0_VIRT_CHANNEL__MASK 0x00000003
|
||||
#define DSI_VID_CFG0_VIRT_CHANNEL__SHIFT 0
|
||||
static inline uint32_t DSI_VID_CFG0_VIRT_CHANNEL(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_VID_CFG0_VIRT_CHANNEL__SHIFT) & DSI_VID_CFG0_VIRT_CHANNEL__MASK;
|
||||
}
|
||||
#define DSI_VID_CFG0_DST_FORMAT__MASK 0x00000030
|
||||
#define DSI_VID_CFG0_DST_FORMAT__SHIFT 4
|
||||
static inline uint32_t DSI_VID_CFG0_DST_FORMAT(enum dsi_dst_format val)
|
||||
{
|
||||
return ((val) << DSI_VID_CFG0_DST_FORMAT__SHIFT) & DSI_VID_CFG0_DST_FORMAT__MASK;
|
||||
}
|
||||
#define DSI_VID_CFG0_TRAFFIC_MODE__MASK 0x00000300
|
||||
#define DSI_VID_CFG0_TRAFFIC_MODE__SHIFT 8
|
||||
static inline uint32_t DSI_VID_CFG0_TRAFFIC_MODE(enum dsi_traffic_mode val)
|
||||
{
|
||||
return ((val) << DSI_VID_CFG0_TRAFFIC_MODE__SHIFT) & DSI_VID_CFG0_TRAFFIC_MODE__MASK;
|
||||
}
|
||||
#define DSI_VID_CFG0_BLLP_POWER_STOP 0x00001000
|
||||
#define DSI_VID_CFG0_EOF_BLLP_POWER_STOP 0x00008000
|
||||
#define DSI_VID_CFG0_HSA_POWER_STOP 0x00010000
|
||||
#define DSI_VID_CFG0_HBP_POWER_STOP 0x00100000
|
||||
#define DSI_VID_CFG0_HFP_POWER_STOP 0x01000000
|
||||
#define DSI_VID_CFG0_PULSE_MODE_HSA_HE 0x10000000
|
||||
|
||||
#define REG_DSI_VID_CFG1 0x0000001c
|
||||
#define DSI_VID_CFG1_R_SEL 0x00000010
|
||||
#define DSI_VID_CFG1_G_SEL 0x00000100
|
||||
#define DSI_VID_CFG1_B_SEL 0x00001000
|
||||
#define DSI_VID_CFG1_RGB_SWAP__MASK 0x00070000
|
||||
#define DSI_VID_CFG1_RGB_SWAP__SHIFT 16
|
||||
static inline uint32_t DSI_VID_CFG1_RGB_SWAP(enum dsi_rgb_swap val)
|
||||
{
|
||||
return ((val) << DSI_VID_CFG1_RGB_SWAP__SHIFT) & DSI_VID_CFG1_RGB_SWAP__MASK;
|
||||
}
|
||||
#define DSI_VID_CFG1_INTERLEAVE_MAX__MASK 0x00f00000
|
||||
#define DSI_VID_CFG1_INTERLEAVE_MAX__SHIFT 20
|
||||
static inline uint32_t DSI_VID_CFG1_INTERLEAVE_MAX(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_VID_CFG1_INTERLEAVE_MAX__SHIFT) & DSI_VID_CFG1_INTERLEAVE_MAX__MASK;
|
||||
}
|
||||
|
||||
#define REG_DSI_ACTIVE_H 0x00000020
|
||||
#define DSI_ACTIVE_H_START__MASK 0x00000fff
|
||||
#define DSI_ACTIVE_H_START__SHIFT 0
|
||||
static inline uint32_t DSI_ACTIVE_H_START(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_ACTIVE_H_START__SHIFT) & DSI_ACTIVE_H_START__MASK;
|
||||
}
|
||||
#define DSI_ACTIVE_H_END__MASK 0x0fff0000
|
||||
#define DSI_ACTIVE_H_END__SHIFT 16
|
||||
static inline uint32_t DSI_ACTIVE_H_END(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_ACTIVE_H_END__SHIFT) & DSI_ACTIVE_H_END__MASK;
|
||||
}
|
||||
|
||||
#define REG_DSI_ACTIVE_V 0x00000024
|
||||
#define DSI_ACTIVE_V_START__MASK 0x00000fff
|
||||
#define DSI_ACTIVE_V_START__SHIFT 0
|
||||
static inline uint32_t DSI_ACTIVE_V_START(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_ACTIVE_V_START__SHIFT) & DSI_ACTIVE_V_START__MASK;
|
||||
}
|
||||
#define DSI_ACTIVE_V_END__MASK 0x0fff0000
|
||||
#define DSI_ACTIVE_V_END__SHIFT 16
|
||||
static inline uint32_t DSI_ACTIVE_V_END(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_ACTIVE_V_END__SHIFT) & DSI_ACTIVE_V_END__MASK;
|
||||
}
|
||||
|
||||
#define REG_DSI_TOTAL 0x00000028
|
||||
#define DSI_TOTAL_H_TOTAL__MASK 0x00000fff
|
||||
#define DSI_TOTAL_H_TOTAL__SHIFT 0
|
||||
static inline uint32_t DSI_TOTAL_H_TOTAL(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_TOTAL_H_TOTAL__SHIFT) & DSI_TOTAL_H_TOTAL__MASK;
|
||||
}
|
||||
#define DSI_TOTAL_V_TOTAL__MASK 0x0fff0000
|
||||
#define DSI_TOTAL_V_TOTAL__SHIFT 16
|
||||
static inline uint32_t DSI_TOTAL_V_TOTAL(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_TOTAL_V_TOTAL__SHIFT) & DSI_TOTAL_V_TOTAL__MASK;
|
||||
}
|
||||
|
||||
#define REG_DSI_ACTIVE_HSYNC 0x0000002c
|
||||
#define DSI_ACTIVE_HSYNC_START__MASK 0x00000fff
|
||||
#define DSI_ACTIVE_HSYNC_START__SHIFT 0
|
||||
static inline uint32_t DSI_ACTIVE_HSYNC_START(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_ACTIVE_HSYNC_START__SHIFT) & DSI_ACTIVE_HSYNC_START__MASK;
|
||||
}
|
||||
#define DSI_ACTIVE_HSYNC_END__MASK 0x0fff0000
|
||||
#define DSI_ACTIVE_HSYNC_END__SHIFT 16
|
||||
static inline uint32_t DSI_ACTIVE_HSYNC_END(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_ACTIVE_HSYNC_END__SHIFT) & DSI_ACTIVE_HSYNC_END__MASK;
|
||||
}
|
||||
|
||||
#define REG_DSI_ACTIVE_VSYNC 0x00000034
|
||||
#define DSI_ACTIVE_VSYNC_START__MASK 0x00000fff
|
||||
#define DSI_ACTIVE_VSYNC_START__SHIFT 0
|
||||
static inline uint32_t DSI_ACTIVE_VSYNC_START(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_ACTIVE_VSYNC_START__SHIFT) & DSI_ACTIVE_VSYNC_START__MASK;
|
||||
}
|
||||
#define DSI_ACTIVE_VSYNC_END__MASK 0x0fff0000
|
||||
#define DSI_ACTIVE_VSYNC_END__SHIFT 16
|
||||
static inline uint32_t DSI_ACTIVE_VSYNC_END(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_ACTIVE_VSYNC_END__SHIFT) & DSI_ACTIVE_VSYNC_END__MASK;
|
||||
}
|
||||
|
||||
#define REG_DSI_CMD_DMA_CTRL 0x00000038
|
||||
#define DSI_CMD_DMA_CTRL_FROM_FRAME_BUFFER 0x10000000
|
||||
#define DSI_CMD_DMA_CTRL_LOW_POWER 0x04000000
|
||||
|
||||
#define REG_DSI_CMD_CFG0 0x0000003c
|
||||
|
||||
#define REG_DSI_CMD_CFG1 0x00000040
|
||||
|
||||
#define REG_DSI_DMA_BASE 0x00000044
|
||||
|
||||
#define REG_DSI_DMA_LEN 0x00000048
|
||||
|
||||
#define REG_DSI_ACK_ERR_STATUS 0x00000064
|
||||
|
||||
static inline uint32_t REG_DSI_RDBK(uint32_t i0) { return 0x00000068 + 0x4*i0; }
|
||||
|
||||
static inline uint32_t REG_DSI_RDBK_DATA(uint32_t i0) { return 0x00000068 + 0x4*i0; }
|
||||
|
||||
#define REG_DSI_TRIG_CTRL 0x00000080
|
||||
#define DSI_TRIG_CTRL_DMA_TRIGGER__MASK 0x0000000f
|
||||
#define DSI_TRIG_CTRL_DMA_TRIGGER__SHIFT 0
|
||||
static inline uint32_t DSI_TRIG_CTRL_DMA_TRIGGER(enum dsi_cmd_trigger val)
|
||||
{
|
||||
return ((val) << DSI_TRIG_CTRL_DMA_TRIGGER__SHIFT) & DSI_TRIG_CTRL_DMA_TRIGGER__MASK;
|
||||
}
|
||||
#define DSI_TRIG_CTRL_MDP_TRIGGER__MASK 0x000000f0
|
||||
#define DSI_TRIG_CTRL_MDP_TRIGGER__SHIFT 4
|
||||
static inline uint32_t DSI_TRIG_CTRL_MDP_TRIGGER(enum dsi_cmd_trigger val)
|
||||
{
|
||||
return ((val) << DSI_TRIG_CTRL_MDP_TRIGGER__SHIFT) & DSI_TRIG_CTRL_MDP_TRIGGER__MASK;
|
||||
}
|
||||
#define DSI_TRIG_CTRL_STREAM 0x00000100
|
||||
#define DSI_TRIG_CTRL_TE 0x80000000
|
||||
|
||||
#define REG_DSI_TRIG_DMA 0x0000008c
|
||||
|
||||
#define REG_DSI_DLN0_PHY_ERR 0x000000b0
|
||||
|
||||
#define REG_DSI_TIMEOUT_STATUS 0x000000bc
|
||||
|
||||
#define REG_DSI_CLKOUT_TIMING_CTRL 0x000000c0
|
||||
#define DSI_CLKOUT_TIMING_CTRL_T_CLK_PRE__MASK 0x0000003f
|
||||
#define DSI_CLKOUT_TIMING_CTRL_T_CLK_PRE__SHIFT 0
|
||||
static inline uint32_t DSI_CLKOUT_TIMING_CTRL_T_CLK_PRE(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_CLKOUT_TIMING_CTRL_T_CLK_PRE__SHIFT) & DSI_CLKOUT_TIMING_CTRL_T_CLK_PRE__MASK;
|
||||
}
|
||||
#define DSI_CLKOUT_TIMING_CTRL_T_CLK_POST__MASK 0x00003f00
|
||||
#define DSI_CLKOUT_TIMING_CTRL_T_CLK_POST__SHIFT 8
|
||||
static inline uint32_t DSI_CLKOUT_TIMING_CTRL_T_CLK_POST(uint32_t val)
|
||||
{
|
||||
return ((val) << DSI_CLKOUT_TIMING_CTRL_T_CLK_POST__SHIFT) & DSI_CLKOUT_TIMING_CTRL_T_CLK_POST__MASK;
|
||||
}
|
||||
|
||||
#define REG_DSI_EOT_PACKET_CTRL 0x000000c8
|
||||
#define DSI_EOT_PACKET_CTRL_TX_EOT_APPEND 0x00000001
|
||||
#define DSI_EOT_PACKET_CTRL_RX_EOT_IGNORE 0x00000010
|
||||
|
||||
#define REG_DSI_LANE_SWAP_CTRL 0x000000ac
|
||||
|
||||
#define REG_DSI_ERR_INT_MASK0 0x00000108
|
||||
|
||||
#define REG_DSI_INTR_CTRL 0x0000010c
|
||||
|
||||
#define REG_DSI_RESET 0x00000114
|
||||
|
||||
#define REG_DSI_CLK_CTRL 0x00000118
|
||||
|
||||
#define REG_DSI_PHY_RESET 0x00000128
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_0 0x00000200
|
||||
#define DSI_PHY_PLL_CTRL_0_ENABLE 0x00000001
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_1 0x00000204
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_2 0x00000208
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_3 0x0000020c
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_4 0x00000210
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_5 0x00000214
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_6 0x00000218
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_7 0x0000021c
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_8 0x00000220
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_9 0x00000224
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_10 0x00000228
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_11 0x0000022c
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_12 0x00000230
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_13 0x00000234
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_14 0x00000238
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_15 0x0000023c
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_16 0x00000240
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_17 0x00000244
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_18 0x00000248
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_19 0x0000024c
|
||||
|
||||
#define REG_DSI_PHY_PLL_CTRL_20 0x00000250
|
||||
|
||||
#define REG_DSI_PHY_PLL_STATUS 0x00000280
|
||||
#define DSI_PHY_PLL_STATUS_PLL_BUSY 0x00000001
|
||||
|
||||
#define REG_DSI_8x60_PHY_TPA_CTRL_1 0x00000258
|
||||
|
||||
#define REG_DSI_8x60_PHY_TPA_CTRL_2 0x0000025c
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_0 0x00000260
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_1 0x00000264
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_2 0x00000268
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_3 0x0000026c
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_4 0x00000270
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_5 0x00000274
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_6 0x00000278
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_7 0x0000027c
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_8 0x00000280
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_9 0x00000284
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_10 0x00000288
|
||||
|
||||
#define REG_DSI_8x60_PHY_TIMING_CTRL_11 0x0000028c
|
||||
|
||||
#define REG_DSI_8x60_PHY_CTRL_0 0x00000290
|
||||
|
||||
#define REG_DSI_8x60_PHY_CTRL_1 0x00000294
|
||||
|
||||
#define REG_DSI_8x60_PHY_CTRL_2 0x00000298
|
||||
|
||||
#define REG_DSI_8x60_PHY_CTRL_3 0x0000029c
|
||||
|
||||
#define REG_DSI_8x60_PHY_STRENGTH_0 0x000002a0
|
||||
|
||||
#define REG_DSI_8x60_PHY_STRENGTH_1 0x000002a4
|
||||
|
||||
#define REG_DSI_8x60_PHY_STRENGTH_2 0x000002a8
|
||||
|
||||
#define REG_DSI_8x60_PHY_STRENGTH_3 0x000002ac
|
||||
|
||||
#define REG_DSI_8x60_PHY_REGULATOR_CTRL_0 0x000002cc
|
||||
|
||||
#define REG_DSI_8x60_PHY_REGULATOR_CTRL_1 0x000002d0
|
||||
|
||||
#define REG_DSI_8x60_PHY_REGULATOR_CTRL_2 0x000002d4
|
||||
|
||||
#define REG_DSI_8x60_PHY_REGULATOR_CTRL_3 0x000002d8
|
||||
|
||||
#define REG_DSI_8x60_PHY_REGULATOR_CTRL_4 0x000002dc
|
||||
|
||||
#define REG_DSI_8x60_PHY_CAL_HW_TRIGGER 0x000000f0
|
||||
|
||||
#define REG_DSI_8x60_PHY_CAL_CTRL 0x000000f4
|
||||
|
||||
#define REG_DSI_8x60_PHY_CAL_STATUS 0x000000fc
|
||||
#define DSI_8x60_PHY_CAL_STATUS_CAL_BUSY 0x10000000
|
||||
|
||||
static inline uint32_t REG_DSI_8960_LN(uint32_t i0) { return 0x00000300 + 0x40*i0; }
|
||||
|
||||
static inline uint32_t REG_DSI_8960_LN_CFG_0(uint32_t i0) { return 0x00000300 + 0x40*i0; }
|
||||
|
||||
static inline uint32_t REG_DSI_8960_LN_CFG_1(uint32_t i0) { return 0x00000304 + 0x40*i0; }
|
||||
|
||||
static inline uint32_t REG_DSI_8960_LN_CFG_2(uint32_t i0) { return 0x00000308 + 0x40*i0; }
|
||||
|
||||
static inline uint32_t REG_DSI_8960_LN_TEST_DATAPATH(uint32_t i0) { return 0x0000030c + 0x40*i0; }
|
||||
|
||||
static inline uint32_t REG_DSI_8960_LN_TEST_STR_0(uint32_t i0) { return 0x00000314 + 0x40*i0; }
|
||||
|
||||
static inline uint32_t REG_DSI_8960_LN_TEST_STR_1(uint32_t i0) { return 0x00000318 + 0x40*i0; }
|
||||
|
||||
#define REG_DSI_8960_PHY_LNCK_CFG_0 0x00000400
|
||||
|
||||
#define REG_DSI_8960_PHY_LNCK_CFG_1 0x00000404
|
||||
|
||||
#define REG_DSI_8960_PHY_LNCK_CFG_2 0x00000408
|
||||
|
||||
#define REG_DSI_8960_PHY_LNCK_TEST_DATAPATH 0x0000040c
|
||||
|
||||
#define REG_DSI_8960_PHY_LNCK_TEST_STR0 0x00000414
|
||||
|
||||
#define REG_DSI_8960_PHY_LNCK_TEST_STR1 0x00000418
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_0 0x00000440
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_1 0x00000444
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_2 0x00000448
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_3 0x0000044c
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_4 0x00000450
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_5 0x00000454
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_6 0x00000458
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_7 0x0000045c
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_8 0x00000460
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_9 0x00000464
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_10 0x00000468
|
||||
|
||||
#define REG_DSI_8960_PHY_TIMING_CTRL_11 0x0000046c
|
||||
|
||||
#define REG_DSI_8960_PHY_CTRL_0 0x00000470
|
||||
|
||||
#define REG_DSI_8960_PHY_CTRL_1 0x00000474
|
||||
|
||||
#define REG_DSI_8960_PHY_CTRL_2 0x00000478
|
||||
|
||||
#define REG_DSI_8960_PHY_CTRL_3 0x0000047c
|
||||
|
||||
#define REG_DSI_8960_PHY_STRENGTH_0 0x00000480
|
||||
|
||||
#define REG_DSI_8960_PHY_STRENGTH_1 0x00000484
|
||||
|
||||
#define REG_DSI_8960_PHY_STRENGTH_2 0x00000488
|
||||
|
||||
#define REG_DSI_8960_PHY_BIST_CTRL_0 0x0000048c
|
||||
|
||||
#define REG_DSI_8960_PHY_BIST_CTRL_1 0x00000490
|
||||
|
||||
#define REG_DSI_8960_PHY_BIST_CTRL_2 0x00000494
|
||||
|
||||
#define REG_DSI_8960_PHY_BIST_CTRL_3 0x00000498
|
||||
|
||||
#define REG_DSI_8960_PHY_BIST_CTRL_4 0x0000049c
|
||||
|
||||
#define REG_DSI_8960_PHY_LDO_CTRL 0x000004b0
|
||||
|
||||
#define REG_DSI_8960_PHY_REGULATOR_CTRL_0 0x00000500
|
||||
|
||||
#define REG_DSI_8960_PHY_REGULATOR_CTRL_1 0x00000504
|
||||
|
||||
#define REG_DSI_8960_PHY_REGULATOR_CTRL_2 0x00000508
|
||||
|
||||
#define REG_DSI_8960_PHY_REGULATOR_CTRL_3 0x0000050c
|
||||
|
||||
#define REG_DSI_8960_PHY_REGULATOR_CTRL_4 0x00000510
|
||||
|
||||
#define REG_DSI_8960_PHY_REGULATOR_CAL_PWR_CFG 0x00000518
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_HW_TRIGGER 0x00000528
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_SW_CFG_0 0x0000052c
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_SW_CFG_1 0x00000530
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_SW_CFG_2 0x00000534
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_HW_CFG_0 0x00000538
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_HW_CFG_1 0x0000053c
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_HW_CFG_2 0x00000540
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_HW_CFG_3 0x00000544
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_HW_CFG_4 0x00000548
|
||||
|
||||
#define REG_DSI_8960_PHY_CAL_STATUS 0x00000550
|
||||
#define DSI_8960_PHY_CAL_STATUS_CAL_BUSY 0x00000010
|
||||
|
||||
|
||||
#endif /* DSI_XML */
|
||||
122
drivers/gpu/drm/msm/dsi/mmss_cc.xml.h
Normal file
122
drivers/gpu/drm/msm/dsi/mmss_cc.xml.h
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#ifndef MMSS_CC_XML
|
||||
#define MMSS_CC_XML
|
||||
|
||||
/* Autogenerated file, DO NOT EDIT manually!
|
||||
|
||||
This file was generated by the rules-ng-ng headergen tool in this git repository:
|
||||
http://github.com/freedreno/envytools/
|
||||
git clone https://github.com/freedreno/envytools.git
|
||||
|
||||
The rules-ng-ng source files this header was generated from are:
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/msm.xml ( 647 bytes, from 2013-11-30 14:45:35)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/freedreno_copyright.xml ( 1453 bytes, from 2013-03-31 16:51:27)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp4.xml ( 20457 bytes, from 2014-08-01 12:22:48)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp_common.xml ( 1615 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp5.xml ( 22517 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/dsi.xml ( 11712 bytes, from 2013-08-17 17:13:43)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/sfpb.xml ( 344 bytes, from 2013-08-11 19:26:32)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/mmss_cc.xml ( 1686 bytes, from 2014-08-01 12:23:53)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2013-07-05 19:21:12)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/hdmi.xml ( 23613 bytes, from 2014-07-17 15:33:30)
|
||||
|
||||
Copyright (C) 2013-2014 by the following authors:
|
||||
- Rob Clark <robdclark@gmail.com> (robclark)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the
|
||||
next paragraph) shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
enum mmss_cc_clk {
|
||||
CLK = 0,
|
||||
PCLK = 1,
|
||||
};
|
||||
|
||||
#define REG_MMSS_CC_AHB 0x00000008
|
||||
|
||||
static inline uint32_t __offset_CLK(enum mmss_cc_clk idx)
|
||||
{
|
||||
switch (idx) {
|
||||
case CLK: return 0x0000004c;
|
||||
case PCLK: return 0x00000130;
|
||||
default: return INVALID_IDX(idx);
|
||||
}
|
||||
}
|
||||
static inline uint32_t REG_MMSS_CC_CLK(enum mmss_cc_clk i0) { return 0x00000000 + __offset_CLK(i0); }
|
||||
|
||||
static inline uint32_t REG_MMSS_CC_CLK_CC(enum mmss_cc_clk i0) { return 0x00000000 + __offset_CLK(i0); }
|
||||
#define MMSS_CC_CLK_CC_CLK_EN 0x00000001
|
||||
#define MMSS_CC_CLK_CC_ROOT_EN 0x00000004
|
||||
#define MMSS_CC_CLK_CC_MND_EN 0x00000020
|
||||
#define MMSS_CC_CLK_CC_MND_MODE__MASK 0x000000c0
|
||||
#define MMSS_CC_CLK_CC_MND_MODE__SHIFT 6
|
||||
static inline uint32_t MMSS_CC_CLK_CC_MND_MODE(uint32_t val)
|
||||
{
|
||||
return ((val) << MMSS_CC_CLK_CC_MND_MODE__SHIFT) & MMSS_CC_CLK_CC_MND_MODE__MASK;
|
||||
}
|
||||
#define MMSS_CC_CLK_CC_PMXO_SEL__MASK 0x00000300
|
||||
#define MMSS_CC_CLK_CC_PMXO_SEL__SHIFT 8
|
||||
static inline uint32_t MMSS_CC_CLK_CC_PMXO_SEL(uint32_t val)
|
||||
{
|
||||
return ((val) << MMSS_CC_CLK_CC_PMXO_SEL__SHIFT) & MMSS_CC_CLK_CC_PMXO_SEL__MASK;
|
||||
}
|
||||
|
||||
static inline uint32_t REG_MMSS_CC_CLK_MD(enum mmss_cc_clk i0) { return 0x00000004 + __offset_CLK(i0); }
|
||||
#define MMSS_CC_CLK_MD_D__MASK 0x000000ff
|
||||
#define MMSS_CC_CLK_MD_D__SHIFT 0
|
||||
static inline uint32_t MMSS_CC_CLK_MD_D(uint32_t val)
|
||||
{
|
||||
return ((val) << MMSS_CC_CLK_MD_D__SHIFT) & MMSS_CC_CLK_MD_D__MASK;
|
||||
}
|
||||
#define MMSS_CC_CLK_MD_M__MASK 0x0000ff00
|
||||
#define MMSS_CC_CLK_MD_M__SHIFT 8
|
||||
static inline uint32_t MMSS_CC_CLK_MD_M(uint32_t val)
|
||||
{
|
||||
return ((val) << MMSS_CC_CLK_MD_M__SHIFT) & MMSS_CC_CLK_MD_M__MASK;
|
||||
}
|
||||
|
||||
static inline uint32_t REG_MMSS_CC_CLK_NS(enum mmss_cc_clk i0) { return 0x00000008 + __offset_CLK(i0); }
|
||||
#define MMSS_CC_CLK_NS_SRC__MASK 0x0000000f
|
||||
#define MMSS_CC_CLK_NS_SRC__SHIFT 0
|
||||
static inline uint32_t MMSS_CC_CLK_NS_SRC(uint32_t val)
|
||||
{
|
||||
return ((val) << MMSS_CC_CLK_NS_SRC__SHIFT) & MMSS_CC_CLK_NS_SRC__MASK;
|
||||
}
|
||||
#define MMSS_CC_CLK_NS_PRE_DIV_FUNC__MASK 0x00fff000
|
||||
#define MMSS_CC_CLK_NS_PRE_DIV_FUNC__SHIFT 12
|
||||
static inline uint32_t MMSS_CC_CLK_NS_PRE_DIV_FUNC(uint32_t val)
|
||||
{
|
||||
return ((val) << MMSS_CC_CLK_NS_PRE_DIV_FUNC__SHIFT) & MMSS_CC_CLK_NS_PRE_DIV_FUNC__MASK;
|
||||
}
|
||||
#define MMSS_CC_CLK_NS_VAL__MASK 0xff000000
|
||||
#define MMSS_CC_CLK_NS_VAL__SHIFT 24
|
||||
static inline uint32_t MMSS_CC_CLK_NS_VAL(uint32_t val)
|
||||
{
|
||||
return ((val) << MMSS_CC_CLK_NS_VAL__SHIFT) & MMSS_CC_CLK_NS_VAL__MASK;
|
||||
}
|
||||
|
||||
#define REG_MMSS_CC_DSI2_PIXEL_CC 0x00000094
|
||||
|
||||
#define REG_MMSS_CC_DSI2_PIXEL_NS 0x000000e4
|
||||
|
||||
#define REG_MMSS_CC_DSI2_PIXEL_CC2 0x00000264
|
||||
|
||||
|
||||
#endif /* MMSS_CC_XML */
|
||||
50
drivers/gpu/drm/msm/dsi/sfpb.xml.h
Normal file
50
drivers/gpu/drm/msm/dsi/sfpb.xml.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef SFPB_XML
|
||||
#define SFPB_XML
|
||||
|
||||
/* Autogenerated file, DO NOT EDIT manually!
|
||||
|
||||
This file was generated by the rules-ng-ng headergen tool in this git repository:
|
||||
http://github.com/freedreno/envytools/
|
||||
git clone https://github.com/freedreno/envytools.git
|
||||
|
||||
The rules-ng-ng source files this header was generated from are:
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/msm.xml ( 647 bytes, from 2013-11-30 14:45:35)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/freedreno_copyright.xml ( 1453 bytes, from 2013-03-31 16:51:27)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp4.xml ( 20457 bytes, from 2014-08-01 12:22:48)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp_common.xml ( 1615 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp5.xml ( 22517 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/dsi.xml ( 11712 bytes, from 2013-08-17 17:13:43)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/sfpb.xml ( 344 bytes, from 2013-08-11 19:26:32)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/mmss_cc.xml ( 1686 bytes, from 2014-08-01 12:23:53)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2013-07-05 19:21:12)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/hdmi.xml ( 23613 bytes, from 2014-07-17 15:33:30)
|
||||
|
||||
Copyright (C) 2013 by the following authors:
|
||||
- Rob Clark <robdclark@gmail.com> (robclark)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the
|
||||
next paragraph) shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#define REG_SFPB_CFG 0x00000058
|
||||
|
||||
|
||||
#endif /* SFPB_XML */
|
||||
422
drivers/gpu/drm/msm/hdmi/hdmi.c
Normal file
422
drivers/gpu/drm/msm/hdmi/hdmi.c
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hdmi.h"
|
||||
|
||||
void hdmi_set_mode(struct hdmi *hdmi, bool power_on)
|
||||
{
|
||||
uint32_t ctrl = 0;
|
||||
|
||||
if (power_on) {
|
||||
ctrl |= HDMI_CTRL_ENABLE;
|
||||
if (!hdmi->hdmi_mode) {
|
||||
ctrl |= HDMI_CTRL_HDMI;
|
||||
hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
|
||||
ctrl &= ~HDMI_CTRL_HDMI;
|
||||
} else {
|
||||
ctrl |= HDMI_CTRL_HDMI;
|
||||
}
|
||||
} else {
|
||||
ctrl = HDMI_CTRL_HDMI;
|
||||
}
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
|
||||
DBG("HDMI Core: %s, HDMI_CTRL=0x%08x",
|
||||
power_on ? "Enable" : "Disable", ctrl);
|
||||
}
|
||||
|
||||
irqreturn_t hdmi_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct hdmi *hdmi = dev_id;
|
||||
|
||||
/* Process HPD: */
|
||||
hdmi_connector_irq(hdmi->connector);
|
||||
|
||||
/* Process DDC: */
|
||||
hdmi_i2c_irq(hdmi->i2c);
|
||||
|
||||
/* TODO audio.. */
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
void hdmi_destroy(struct kref *kref)
|
||||
{
|
||||
struct hdmi *hdmi = container_of(kref, struct hdmi, refcount);
|
||||
struct hdmi_phy *phy = hdmi->phy;
|
||||
|
||||
if (phy)
|
||||
phy->funcs->destroy(phy);
|
||||
|
||||
if (hdmi->i2c)
|
||||
hdmi_i2c_destroy(hdmi->i2c);
|
||||
|
||||
platform_set_drvdata(hdmi->pdev, NULL);
|
||||
}
|
||||
|
||||
/* initialize connector */
|
||||
struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
|
||||
{
|
||||
struct hdmi *hdmi = NULL;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct platform_device *pdev = priv->hdmi_pdev;
|
||||
struct hdmi_platform_config *config;
|
||||
int i, ret;
|
||||
|
||||
if (!pdev) {
|
||||
dev_err(dev->dev, "no hdmi device\n");
|
||||
ret = -ENXIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
config = pdev->dev.platform_data;
|
||||
|
||||
hdmi = kzalloc(sizeof(*hdmi), GFP_KERNEL);
|
||||
if (!hdmi) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
kref_init(&hdmi->refcount);
|
||||
|
||||
hdmi->dev = dev;
|
||||
hdmi->pdev = pdev;
|
||||
hdmi->config = config;
|
||||
hdmi->encoder = encoder;
|
||||
|
||||
hdmi_audio_infoframe_init(&hdmi->audio.infoframe);
|
||||
|
||||
/* not sure about which phy maps to which msm.. probably I miss some */
|
||||
if (config->phy_init)
|
||||
hdmi->phy = config->phy_init(hdmi);
|
||||
else
|
||||
hdmi->phy = ERR_PTR(-ENXIO);
|
||||
|
||||
if (IS_ERR(hdmi->phy)) {
|
||||
ret = PTR_ERR(hdmi->phy);
|
||||
dev_err(dev->dev, "failed to load phy: %d\n", ret);
|
||||
hdmi->phy = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hdmi->mmio = msm_ioremap(pdev, config->mmio_name, "HDMI");
|
||||
if (IS_ERR(hdmi->mmio)) {
|
||||
ret = PTR_ERR(hdmi->mmio);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
BUG_ON(config->hpd_reg_cnt > ARRAY_SIZE(hdmi->hpd_regs));
|
||||
for (i = 0; i < config->hpd_reg_cnt; i++) {
|
||||
struct regulator *reg;
|
||||
|
||||
reg = devm_regulator_get(&pdev->dev,
|
||||
config->hpd_reg_names[i]);
|
||||
if (IS_ERR(reg)) {
|
||||
ret = PTR_ERR(reg);
|
||||
dev_err(dev->dev, "failed to get hpd regulator: %s (%d)\n",
|
||||
config->hpd_reg_names[i], ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hdmi->hpd_regs[i] = reg;
|
||||
}
|
||||
|
||||
BUG_ON(config->pwr_reg_cnt > ARRAY_SIZE(hdmi->pwr_regs));
|
||||
for (i = 0; i < config->pwr_reg_cnt; i++) {
|
||||
struct regulator *reg;
|
||||
|
||||
reg = devm_regulator_get(&pdev->dev,
|
||||
config->pwr_reg_names[i]);
|
||||
if (IS_ERR(reg)) {
|
||||
ret = PTR_ERR(reg);
|
||||
dev_err(dev->dev, "failed to get pwr regulator: %s (%d)\n",
|
||||
config->pwr_reg_names[i], ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hdmi->pwr_regs[i] = reg;
|
||||
}
|
||||
|
||||
BUG_ON(config->hpd_clk_cnt > ARRAY_SIZE(hdmi->hpd_clks));
|
||||
for (i = 0; i < config->hpd_clk_cnt; i++) {
|
||||
struct clk *clk;
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, config->hpd_clk_names[i]);
|
||||
if (IS_ERR(clk)) {
|
||||
ret = PTR_ERR(clk);
|
||||
dev_err(dev->dev, "failed to get hpd clk: %s (%d)\n",
|
||||
config->hpd_clk_names[i], ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hdmi->hpd_clks[i] = clk;
|
||||
}
|
||||
|
||||
BUG_ON(config->pwr_clk_cnt > ARRAY_SIZE(hdmi->pwr_clks));
|
||||
for (i = 0; i < config->pwr_clk_cnt; i++) {
|
||||
struct clk *clk;
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, config->pwr_clk_names[i]);
|
||||
if (IS_ERR(clk)) {
|
||||
ret = PTR_ERR(clk);
|
||||
dev_err(dev->dev, "failed to get pwr clk: %s (%d)\n",
|
||||
config->pwr_clk_names[i], ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hdmi->pwr_clks[i] = clk;
|
||||
}
|
||||
|
||||
hdmi->i2c = hdmi_i2c_init(hdmi);
|
||||
if (IS_ERR(hdmi->i2c)) {
|
||||
ret = PTR_ERR(hdmi->i2c);
|
||||
dev_err(dev->dev, "failed to get i2c: %d\n", ret);
|
||||
hdmi->i2c = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hdmi->bridge = hdmi_bridge_init(hdmi);
|
||||
if (IS_ERR(hdmi->bridge)) {
|
||||
ret = PTR_ERR(hdmi->bridge);
|
||||
dev_err(dev->dev, "failed to create HDMI bridge: %d\n", ret);
|
||||
hdmi->bridge = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hdmi->connector = hdmi_connector_init(hdmi);
|
||||
if (IS_ERR(hdmi->connector)) {
|
||||
ret = PTR_ERR(hdmi->connector);
|
||||
dev_err(dev->dev, "failed to create HDMI connector: %d\n", ret);
|
||||
hdmi->connector = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!config->shared_irq) {
|
||||
hdmi->irq = platform_get_irq(pdev, 0);
|
||||
if (hdmi->irq < 0) {
|
||||
ret = hdmi->irq;
|
||||
dev_err(dev->dev, "failed to get irq: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, hdmi->irq,
|
||||
NULL, hdmi_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||
"hdmi_isr", hdmi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to request IRQ%u: %d\n",
|
||||
hdmi->irq, ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
encoder->bridge = hdmi->bridge;
|
||||
|
||||
priv->bridges[priv->num_bridges++] = hdmi->bridge;
|
||||
priv->connectors[priv->num_connectors++] = hdmi->connector;
|
||||
|
||||
platform_set_drvdata(pdev, hdmi);
|
||||
|
||||
return hdmi;
|
||||
|
||||
fail:
|
||||
if (hdmi) {
|
||||
/* bridge/connector are normally destroyed by drm: */
|
||||
if (hdmi->bridge)
|
||||
hdmi->bridge->funcs->destroy(hdmi->bridge);
|
||||
if (hdmi->connector)
|
||||
hdmi->connector->funcs->destroy(hdmi->connector);
|
||||
hdmi_destroy(&hdmi->refcount);
|
||||
}
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* The hdmi device:
|
||||
*/
|
||||
|
||||
#include <linux/of_gpio.h>
|
||||
|
||||
static void set_hdmi_pdev(struct drm_device *dev,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
priv->hdmi_pdev = pdev;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int get_gpio(struct device *dev, struct device_node *of_node, const char *name)
|
||||
{
|
||||
int gpio = of_get_named_gpio(of_node, name, 0);
|
||||
if (gpio < 0) {
|
||||
char name2[32];
|
||||
snprintf(name2, sizeof(name2), "%s-gpio", name);
|
||||
gpio = of_get_named_gpio(of_node, name2, 0);
|
||||
if (gpio < 0) {
|
||||
dev_err(dev, "failed to get gpio: %s (%d)\n",
|
||||
name, gpio);
|
||||
gpio = -1;
|
||||
}
|
||||
}
|
||||
return gpio;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int hdmi_bind(struct device *dev, struct device *master, void *data)
|
||||
{
|
||||
static struct hdmi_platform_config config = {};
|
||||
#ifdef CONFIG_OF
|
||||
struct device_node *of_node = dev->of_node;
|
||||
|
||||
if (of_device_is_compatible(of_node, "qcom,hdmi-tx-8074")) {
|
||||
static const char *hpd_reg_names[] = {"hpd-gdsc", "hpd-5v"};
|
||||
static const char *pwr_reg_names[] = {"core-vdda", "core-vcc"};
|
||||
static const char *hpd_clk_names[] = {"iface_clk", "core_clk", "mdp_core_clk"};
|
||||
static unsigned long hpd_clk_freq[] = {0, 19200000, 0};
|
||||
static const char *pwr_clk_names[] = {"extp_clk", "alt_iface_clk"};
|
||||
config.phy_init = hdmi_phy_8x74_init;
|
||||
config.hpd_reg_names = hpd_reg_names;
|
||||
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
|
||||
config.pwr_reg_names = pwr_reg_names;
|
||||
config.pwr_reg_cnt = ARRAY_SIZE(pwr_reg_names);
|
||||
config.hpd_clk_names = hpd_clk_names;
|
||||
config.hpd_freq = hpd_clk_freq;
|
||||
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
|
||||
config.pwr_clk_names = pwr_clk_names;
|
||||
config.pwr_clk_cnt = ARRAY_SIZE(pwr_clk_names);
|
||||
config.shared_irq = true;
|
||||
} else if (of_device_is_compatible(of_node, "qcom,hdmi-tx-8960")) {
|
||||
static const char *hpd_clk_names[] = {"core_clk", "master_iface_clk", "slave_iface_clk"};
|
||||
static const char *hpd_reg_names[] = {"core-vdda", "hdmi-mux"};
|
||||
config.phy_init = hdmi_phy_8960_init;
|
||||
config.hpd_reg_names = hpd_reg_names;
|
||||
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
|
||||
config.hpd_clk_names = hpd_clk_names;
|
||||
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
|
||||
} else if (of_device_is_compatible(of_node, "qcom,hdmi-tx-8660")) {
|
||||
config.phy_init = hdmi_phy_8x60_init;
|
||||
} else {
|
||||
dev_err(dev, "unknown phy: %s\n", of_node->name);
|
||||
}
|
||||
|
||||
config.mmio_name = "core_physical";
|
||||
config.ddc_clk_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-clk");
|
||||
config.ddc_data_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-data");
|
||||
config.hpd_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-hpd");
|
||||
config.mux_en_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-mux-en");
|
||||
config.mux_sel_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-mux-sel");
|
||||
config.mux_lpm_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-mux-lpm");
|
||||
|
||||
#else
|
||||
static const char *hpd_clk_names[] = {
|
||||
"core_clk", "master_iface_clk", "slave_iface_clk",
|
||||
};
|
||||
if (cpu_is_apq8064()) {
|
||||
static const char *hpd_reg_names[] = {"8921_hdmi_mvs"};
|
||||
config.phy_init = hdmi_phy_8960_init;
|
||||
config.mmio_name = "hdmi_msm_hdmi_addr";
|
||||
config.hpd_reg_names = hpd_reg_names;
|
||||
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
|
||||
config.hpd_clk_names = hpd_clk_names;
|
||||
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
|
||||
config.ddc_clk_gpio = 70;
|
||||
config.ddc_data_gpio = 71;
|
||||
config.hpd_gpio = 72;
|
||||
config.mux_en_gpio = -1;
|
||||
config.mux_sel_gpio = -1;
|
||||
} else if (cpu_is_msm8960() || cpu_is_msm8960ab()) {
|
||||
static const char *hpd_reg_names[] = {"8921_hdmi_mvs"};
|
||||
config.phy_init = hdmi_phy_8960_init;
|
||||
config.mmio_name = "hdmi_msm_hdmi_addr";
|
||||
config.hpd_reg_names = hpd_reg_names;
|
||||
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
|
||||
config.hpd_clk_names = hpd_clk_names;
|
||||
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
|
||||
config.ddc_clk_gpio = 100;
|
||||
config.ddc_data_gpio = 101;
|
||||
config.hpd_gpio = 102;
|
||||
config.mux_en_gpio = -1;
|
||||
config.mux_sel_gpio = -1;
|
||||
} else if (cpu_is_msm8x60()) {
|
||||
static const char *hpd_reg_names[] = {
|
||||
"8901_hdmi_mvs", "8901_mpp0"
|
||||
};
|
||||
config.phy_init = hdmi_phy_8x60_init;
|
||||
config.mmio_name = "hdmi_msm_hdmi_addr";
|
||||
config.hpd_reg_names = hpd_reg_names;
|
||||
config.hpd_reg_cnt = ARRAY_SIZE(hpd_reg_names);
|
||||
config.hpd_clk_names = hpd_clk_names;
|
||||
config.hpd_clk_cnt = ARRAY_SIZE(hpd_clk_names);
|
||||
config.ddc_clk_gpio = 170;
|
||||
config.ddc_data_gpio = 171;
|
||||
config.hpd_gpio = 172;
|
||||
config.mux_en_gpio = -1;
|
||||
config.mux_sel_gpio = -1;
|
||||
}
|
||||
#endif
|
||||
dev->platform_data = &config;
|
||||
set_hdmi_pdev(dev_get_drvdata(master), to_platform_device(dev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hdmi_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
set_hdmi_pdev(dev_get_drvdata(master), NULL);
|
||||
}
|
||||
|
||||
static const struct component_ops hdmi_ops = {
|
||||
.bind = hdmi_bind,
|
||||
.unbind = hdmi_unbind,
|
||||
};
|
||||
|
||||
static int hdmi_dev_probe(struct platform_device *pdev)
|
||||
{
|
||||
return component_add(&pdev->dev, &hdmi_ops);
|
||||
}
|
||||
|
||||
static int hdmi_dev_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_del(&pdev->dev, &hdmi_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id dt_match[] = {
|
||||
{ .compatible = "qcom,hdmi-tx-8074" },
|
||||
{ .compatible = "qcom,hdmi-tx-8960" },
|
||||
{ .compatible = "qcom,hdmi-tx-8660" },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver hdmi_driver = {
|
||||
.probe = hdmi_dev_probe,
|
||||
.remove = hdmi_dev_remove,
|
||||
.driver = {
|
||||
.name = "hdmi_msm",
|
||||
.of_match_table = dt_match,
|
||||
},
|
||||
};
|
||||
|
||||
void __init hdmi_register(void)
|
||||
{
|
||||
platform_driver_register(&hdmi_driver);
|
||||
}
|
||||
|
||||
void __exit hdmi_unregister(void)
|
||||
{
|
||||
platform_driver_unregister(&hdmi_driver);
|
||||
}
|
||||
182
drivers/gpu/drm/msm/hdmi/hdmi.h
Normal file
182
drivers/gpu/drm/msm/hdmi/hdmi.h
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __HDMI_CONNECTOR_H__
|
||||
#define __HDMI_CONNECTOR_H__
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/hdmi.h>
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "hdmi.xml.h"
|
||||
|
||||
|
||||
struct hdmi_phy;
|
||||
struct hdmi_platform_config;
|
||||
|
||||
struct hdmi_audio {
|
||||
bool enabled;
|
||||
struct hdmi_audio_infoframe infoframe;
|
||||
int rate;
|
||||
};
|
||||
|
||||
struct hdmi {
|
||||
struct kref refcount;
|
||||
|
||||
struct drm_device *dev;
|
||||
struct platform_device *pdev;
|
||||
|
||||
const struct hdmi_platform_config *config;
|
||||
|
||||
/* audio state: */
|
||||
struct hdmi_audio audio;
|
||||
|
||||
/* video state: */
|
||||
bool power_on;
|
||||
unsigned long int pixclock;
|
||||
|
||||
void __iomem *mmio;
|
||||
|
||||
struct regulator *hpd_regs[2];
|
||||
struct regulator *pwr_regs[2];
|
||||
struct clk *hpd_clks[3];
|
||||
struct clk *pwr_clks[2];
|
||||
|
||||
struct hdmi_phy *phy;
|
||||
struct i2c_adapter *i2c;
|
||||
struct drm_connector *connector;
|
||||
struct drm_bridge *bridge;
|
||||
|
||||
/* the encoder we are hooked to (outside of hdmi block) */
|
||||
struct drm_encoder *encoder;
|
||||
|
||||
bool hdmi_mode; /* are we in hdmi mode? */
|
||||
|
||||
int irq;
|
||||
};
|
||||
|
||||
/* platform config data (ie. from DT, or pdata) */
|
||||
struct hdmi_platform_config {
|
||||
struct hdmi_phy *(*phy_init)(struct hdmi *hdmi);
|
||||
const char *mmio_name;
|
||||
|
||||
/* regulators that need to be on for hpd: */
|
||||
const char **hpd_reg_names;
|
||||
int hpd_reg_cnt;
|
||||
|
||||
/* regulators that need to be on for screen pwr: */
|
||||
const char **pwr_reg_names;
|
||||
int pwr_reg_cnt;
|
||||
|
||||
/* clks that need to be on for hpd: */
|
||||
const char **hpd_clk_names;
|
||||
const long unsigned *hpd_freq;
|
||||
int hpd_clk_cnt;
|
||||
|
||||
/* clks that need to be on for screen pwr (ie pixel clk): */
|
||||
const char **pwr_clk_names;
|
||||
int pwr_clk_cnt;
|
||||
|
||||
/* gpio's: */
|
||||
int ddc_clk_gpio, ddc_data_gpio, hpd_gpio, mux_en_gpio, mux_sel_gpio;
|
||||
int mux_lpm_gpio;
|
||||
|
||||
/* older devices had their own irq, mdp5+ it is shared w/ mdp: */
|
||||
bool shared_irq;
|
||||
};
|
||||
|
||||
void hdmi_set_mode(struct hdmi *hdmi, bool power_on);
|
||||
void hdmi_destroy(struct kref *kref);
|
||||
|
||||
static inline void hdmi_write(struct hdmi *hdmi, u32 reg, u32 data)
|
||||
{
|
||||
msm_writel(data, hdmi->mmio + reg);
|
||||
}
|
||||
|
||||
static inline u32 hdmi_read(struct hdmi *hdmi, u32 reg)
|
||||
{
|
||||
return msm_readl(hdmi->mmio + reg);
|
||||
}
|
||||
|
||||
static inline struct hdmi * hdmi_reference(struct hdmi *hdmi)
|
||||
{
|
||||
kref_get(&hdmi->refcount);
|
||||
return hdmi;
|
||||
}
|
||||
|
||||
static inline void hdmi_unreference(struct hdmi *hdmi)
|
||||
{
|
||||
kref_put(&hdmi->refcount, hdmi_destroy);
|
||||
}
|
||||
|
||||
/*
|
||||
* The phy appears to be different, for example between 8960 and 8x60,
|
||||
* so split the phy related functions out and load the correct one at
|
||||
* runtime:
|
||||
*/
|
||||
|
||||
struct hdmi_phy_funcs {
|
||||
void (*destroy)(struct hdmi_phy *phy);
|
||||
void (*reset)(struct hdmi_phy *phy);
|
||||
void (*powerup)(struct hdmi_phy *phy, unsigned long int pixclock);
|
||||
void (*powerdown)(struct hdmi_phy *phy);
|
||||
};
|
||||
|
||||
struct hdmi_phy {
|
||||
const struct hdmi_phy_funcs *funcs;
|
||||
};
|
||||
|
||||
struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi);
|
||||
struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi);
|
||||
struct hdmi_phy *hdmi_phy_8x74_init(struct hdmi *hdmi);
|
||||
|
||||
/*
|
||||
* audio:
|
||||
*/
|
||||
|
||||
int hdmi_audio_update(struct hdmi *hdmi);
|
||||
int hdmi_audio_info_setup(struct hdmi *hdmi, bool enabled,
|
||||
uint32_t num_of_channels, uint32_t channel_allocation,
|
||||
uint32_t level_shift, bool down_mix);
|
||||
void hdmi_audio_set_sample_rate(struct hdmi *hdmi, int rate);
|
||||
|
||||
|
||||
/*
|
||||
* hdmi bridge:
|
||||
*/
|
||||
|
||||
struct drm_bridge *hdmi_bridge_init(struct hdmi *hdmi);
|
||||
|
||||
/*
|
||||
* hdmi connector:
|
||||
*/
|
||||
|
||||
void hdmi_connector_irq(struct drm_connector *connector);
|
||||
struct drm_connector *hdmi_connector_init(struct hdmi *hdmi);
|
||||
|
||||
/*
|
||||
* i2c adapter for ddc:
|
||||
*/
|
||||
|
||||
void hdmi_i2c_irq(struct i2c_adapter *i2c);
|
||||
void hdmi_i2c_destroy(struct i2c_adapter *i2c);
|
||||
struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi);
|
||||
|
||||
#endif /* __HDMI_CONNECTOR_H__ */
|
||||
672
drivers/gpu/drm/msm/hdmi/hdmi.xml.h
Normal file
672
drivers/gpu/drm/msm/hdmi/hdmi.xml.h
Normal file
|
|
@ -0,0 +1,672 @@
|
|||
#ifndef HDMI_XML
|
||||
#define HDMI_XML
|
||||
|
||||
/* Autogenerated file, DO NOT EDIT manually!
|
||||
|
||||
This file was generated by the rules-ng-ng headergen tool in this git repository:
|
||||
http://github.com/freedreno/envytools/
|
||||
git clone https://github.com/freedreno/envytools.git
|
||||
|
||||
The rules-ng-ng source files this header was generated from are:
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/msm.xml ( 647 bytes, from 2013-11-30 14:45:35)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/freedreno_copyright.xml ( 1453 bytes, from 2013-03-31 16:51:27)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp4.xml ( 20457 bytes, from 2014-08-01 12:22:48)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp_common.xml ( 1615 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp5.xml ( 22517 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/dsi.xml ( 11712 bytes, from 2013-08-17 17:13:43)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/sfpb.xml ( 344 bytes, from 2013-08-11 19:26:32)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/mmss_cc.xml ( 1686 bytes, from 2014-08-01 12:23:53)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2013-07-05 19:21:12)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/hdmi.xml ( 23613 bytes, from 2014-07-17 15:33:30)
|
||||
|
||||
Copyright (C) 2013-2014 by the following authors:
|
||||
- Rob Clark <robdclark@gmail.com> (robclark)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the
|
||||
next paragraph) shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
enum hdmi_hdcp_key_state {
|
||||
NO_KEYS = 0,
|
||||
NOT_CHECKED = 1,
|
||||
CHECKING = 2,
|
||||
KEYS_VALID = 3,
|
||||
AKSV_INVALID = 4,
|
||||
CHECKSUM_MISMATCH = 5,
|
||||
};
|
||||
|
||||
enum hdmi_ddc_read_write {
|
||||
DDC_WRITE = 0,
|
||||
DDC_READ = 1,
|
||||
};
|
||||
|
||||
enum hdmi_acr_cts {
|
||||
ACR_NONE = 0,
|
||||
ACR_32 = 1,
|
||||
ACR_44 = 2,
|
||||
ACR_48 = 3,
|
||||
};
|
||||
|
||||
#define REG_HDMI_CTRL 0x00000000
|
||||
#define HDMI_CTRL_ENABLE 0x00000001
|
||||
#define HDMI_CTRL_HDMI 0x00000002
|
||||
#define HDMI_CTRL_ENCRYPTED 0x00000004
|
||||
|
||||
#define REG_HDMI_AUDIO_PKT_CTRL1 0x00000020
|
||||
#define HDMI_AUDIO_PKT_CTRL1_AUDIO_SAMPLE_SEND 0x00000001
|
||||
|
||||
#define REG_HDMI_ACR_PKT_CTRL 0x00000024
|
||||
#define HDMI_ACR_PKT_CTRL_CONT 0x00000001
|
||||
#define HDMI_ACR_PKT_CTRL_SEND 0x00000002
|
||||
#define HDMI_ACR_PKT_CTRL_SELECT__MASK 0x00000030
|
||||
#define HDMI_ACR_PKT_CTRL_SELECT__SHIFT 4
|
||||
static inline uint32_t HDMI_ACR_PKT_CTRL_SELECT(enum hdmi_acr_cts val)
|
||||
{
|
||||
return ((val) << HDMI_ACR_PKT_CTRL_SELECT__SHIFT) & HDMI_ACR_PKT_CTRL_SELECT__MASK;
|
||||
}
|
||||
#define HDMI_ACR_PKT_CTRL_SOURCE 0x00000100
|
||||
#define HDMI_ACR_PKT_CTRL_N_MULTIPLIER__MASK 0x00070000
|
||||
#define HDMI_ACR_PKT_CTRL_N_MULTIPLIER__SHIFT 16
|
||||
static inline uint32_t HDMI_ACR_PKT_CTRL_N_MULTIPLIER(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_ACR_PKT_CTRL_N_MULTIPLIER__SHIFT) & HDMI_ACR_PKT_CTRL_N_MULTIPLIER__MASK;
|
||||
}
|
||||
#define HDMI_ACR_PKT_CTRL_AUDIO_PRIORITY 0x80000000
|
||||
|
||||
#define REG_HDMI_VBI_PKT_CTRL 0x00000028
|
||||
#define HDMI_VBI_PKT_CTRL_GC_ENABLE 0x00000010
|
||||
#define HDMI_VBI_PKT_CTRL_GC_EVERY_FRAME 0x00000020
|
||||
#define HDMI_VBI_PKT_CTRL_ISRC_SEND 0x00000100
|
||||
#define HDMI_VBI_PKT_CTRL_ISRC_CONTINUOUS 0x00000200
|
||||
#define HDMI_VBI_PKT_CTRL_ACP_SEND 0x00001000
|
||||
#define HDMI_VBI_PKT_CTRL_ACP_SRC_SW 0x00002000
|
||||
|
||||
#define REG_HDMI_INFOFRAME_CTRL0 0x0000002c
|
||||
#define HDMI_INFOFRAME_CTRL0_AVI_SEND 0x00000001
|
||||
#define HDMI_INFOFRAME_CTRL0_AVI_CONT 0x00000002
|
||||
#define HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND 0x00000010
|
||||
#define HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT 0x00000020
|
||||
#define HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE 0x00000040
|
||||
#define HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE 0x00000080
|
||||
|
||||
#define REG_HDMI_GEN_PKT_CTRL 0x00000034
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC0_SEND 0x00000001
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC0_CONT 0x00000002
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE__MASK 0x0000000c
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE__SHIFT 2
|
||||
static inline uint32_t HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE__SHIFT) & HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE__MASK;
|
||||
}
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC1_SEND 0x00000010
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC1_CONT 0x00000020
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK 0x003f0000
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC0_LINE__SHIFT 16
|
||||
static inline uint32_t HDMI_GEN_PKT_CTRL_GENERIC0_LINE(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_GEN_PKT_CTRL_GENERIC0_LINE__SHIFT) & HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK;
|
||||
}
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK 0x3f000000
|
||||
#define HDMI_GEN_PKT_CTRL_GENERIC1_LINE__SHIFT 24
|
||||
static inline uint32_t HDMI_GEN_PKT_CTRL_GENERIC1_LINE(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_GEN_PKT_CTRL_GENERIC1_LINE__SHIFT) & HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_GC 0x00000040
|
||||
#define HDMI_GC_MUTE 0x00000001
|
||||
|
||||
#define REG_HDMI_AUDIO_PKT_CTRL2 0x00000044
|
||||
#define HDMI_AUDIO_PKT_CTRL2_OVERRIDE 0x00000001
|
||||
#define HDMI_AUDIO_PKT_CTRL2_LAYOUT 0x00000002
|
||||
|
||||
static inline uint32_t REG_HDMI_AVI_INFO(uint32_t i0) { return 0x0000006c + 0x4*i0; }
|
||||
|
||||
#define REG_HDMI_GENERIC0_HDR 0x00000084
|
||||
|
||||
static inline uint32_t REG_HDMI_GENERIC0(uint32_t i0) { return 0x00000088 + 0x4*i0; }
|
||||
|
||||
#define REG_HDMI_GENERIC1_HDR 0x000000a4
|
||||
|
||||
static inline uint32_t REG_HDMI_GENERIC1(uint32_t i0) { return 0x000000a8 + 0x4*i0; }
|
||||
|
||||
static inline uint32_t REG_HDMI_ACR(enum hdmi_acr_cts i0) { return 0x000000c4 + 0x8*i0; }
|
||||
|
||||
static inline uint32_t REG_HDMI_ACR_0(enum hdmi_acr_cts i0) { return 0x000000c4 + 0x8*i0; }
|
||||
#define HDMI_ACR_0_CTS__MASK 0xfffff000
|
||||
#define HDMI_ACR_0_CTS__SHIFT 12
|
||||
static inline uint32_t HDMI_ACR_0_CTS(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_ACR_0_CTS__SHIFT) & HDMI_ACR_0_CTS__MASK;
|
||||
}
|
||||
|
||||
static inline uint32_t REG_HDMI_ACR_1(enum hdmi_acr_cts i0) { return 0x000000c8 + 0x8*i0; }
|
||||
#define HDMI_ACR_1_N__MASK 0xffffffff
|
||||
#define HDMI_ACR_1_N__SHIFT 0
|
||||
static inline uint32_t HDMI_ACR_1_N(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_ACR_1_N__SHIFT) & HDMI_ACR_1_N__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_AUDIO_INFO0 0x000000e4
|
||||
#define HDMI_AUDIO_INFO0_CHECKSUM__MASK 0x000000ff
|
||||
#define HDMI_AUDIO_INFO0_CHECKSUM__SHIFT 0
|
||||
static inline uint32_t HDMI_AUDIO_INFO0_CHECKSUM(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_AUDIO_INFO0_CHECKSUM__SHIFT) & HDMI_AUDIO_INFO0_CHECKSUM__MASK;
|
||||
}
|
||||
#define HDMI_AUDIO_INFO0_CC__MASK 0x00000700
|
||||
#define HDMI_AUDIO_INFO0_CC__SHIFT 8
|
||||
static inline uint32_t HDMI_AUDIO_INFO0_CC(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_AUDIO_INFO0_CC__SHIFT) & HDMI_AUDIO_INFO0_CC__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_AUDIO_INFO1 0x000000e8
|
||||
#define HDMI_AUDIO_INFO1_CA__MASK 0x000000ff
|
||||
#define HDMI_AUDIO_INFO1_CA__SHIFT 0
|
||||
static inline uint32_t HDMI_AUDIO_INFO1_CA(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_AUDIO_INFO1_CA__SHIFT) & HDMI_AUDIO_INFO1_CA__MASK;
|
||||
}
|
||||
#define HDMI_AUDIO_INFO1_LSV__MASK 0x00007800
|
||||
#define HDMI_AUDIO_INFO1_LSV__SHIFT 11
|
||||
static inline uint32_t HDMI_AUDIO_INFO1_LSV(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_AUDIO_INFO1_LSV__SHIFT) & HDMI_AUDIO_INFO1_LSV__MASK;
|
||||
}
|
||||
#define HDMI_AUDIO_INFO1_DM_INH 0x00008000
|
||||
|
||||
#define REG_HDMI_HDCP_CTRL 0x00000110
|
||||
#define HDMI_HDCP_CTRL_ENABLE 0x00000001
|
||||
#define HDMI_HDCP_CTRL_ENCRYPTION_ENABLE 0x00000100
|
||||
|
||||
#define REG_HDMI_HDCP_INT_CTRL 0x00000118
|
||||
|
||||
#define REG_HDMI_HDCP_LINK0_STATUS 0x0000011c
|
||||
#define HDMI_HDCP_LINK0_STATUS_AN_0_READY 0x00000100
|
||||
#define HDMI_HDCP_LINK0_STATUS_AN_1_READY 0x00000200
|
||||
#define HDMI_HDCP_LINK0_STATUS_KEY_STATE__MASK 0x70000000
|
||||
#define HDMI_HDCP_LINK0_STATUS_KEY_STATE__SHIFT 28
|
||||
static inline uint32_t HDMI_HDCP_LINK0_STATUS_KEY_STATE(enum hdmi_hdcp_key_state val)
|
||||
{
|
||||
return ((val) << HDMI_HDCP_LINK0_STATUS_KEY_STATE__SHIFT) & HDMI_HDCP_LINK0_STATUS_KEY_STATE__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_HDCP_RESET 0x00000130
|
||||
#define HDMI_HDCP_RESET_LINK0_DEAUTHENTICATE 0x00000001
|
||||
|
||||
#define REG_HDMI_VENSPEC_INFO0 0x0000016c
|
||||
|
||||
#define REG_HDMI_VENSPEC_INFO1 0x00000170
|
||||
|
||||
#define REG_HDMI_VENSPEC_INFO2 0x00000174
|
||||
|
||||
#define REG_HDMI_VENSPEC_INFO3 0x00000178
|
||||
|
||||
#define REG_HDMI_VENSPEC_INFO4 0x0000017c
|
||||
|
||||
#define REG_HDMI_VENSPEC_INFO5 0x00000180
|
||||
|
||||
#define REG_HDMI_VENSPEC_INFO6 0x00000184
|
||||
|
||||
#define REG_HDMI_AUDIO_CFG 0x000001d0
|
||||
#define HDMI_AUDIO_CFG_ENGINE_ENABLE 0x00000001
|
||||
#define HDMI_AUDIO_CFG_FIFO_WATERMARK__MASK 0x000000f0
|
||||
#define HDMI_AUDIO_CFG_FIFO_WATERMARK__SHIFT 4
|
||||
static inline uint32_t HDMI_AUDIO_CFG_FIFO_WATERMARK(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_AUDIO_CFG_FIFO_WATERMARK__SHIFT) & HDMI_AUDIO_CFG_FIFO_WATERMARK__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_USEC_REFTIMER 0x00000208
|
||||
|
||||
#define REG_HDMI_DDC_CTRL 0x0000020c
|
||||
#define HDMI_DDC_CTRL_GO 0x00000001
|
||||
#define HDMI_DDC_CTRL_SOFT_RESET 0x00000002
|
||||
#define HDMI_DDC_CTRL_SEND_RESET 0x00000004
|
||||
#define HDMI_DDC_CTRL_SW_STATUS_RESET 0x00000008
|
||||
#define HDMI_DDC_CTRL_TRANSACTION_CNT__MASK 0x00300000
|
||||
#define HDMI_DDC_CTRL_TRANSACTION_CNT__SHIFT 20
|
||||
static inline uint32_t HDMI_DDC_CTRL_TRANSACTION_CNT(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_DDC_CTRL_TRANSACTION_CNT__SHIFT) & HDMI_DDC_CTRL_TRANSACTION_CNT__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_DDC_ARBITRATION 0x00000210
|
||||
#define HDMI_DDC_ARBITRATION_HW_ARBITRATION 0x00000010
|
||||
|
||||
#define REG_HDMI_DDC_INT_CTRL 0x00000214
|
||||
#define HDMI_DDC_INT_CTRL_SW_DONE_INT 0x00000001
|
||||
#define HDMI_DDC_INT_CTRL_SW_DONE_ACK 0x00000002
|
||||
#define HDMI_DDC_INT_CTRL_SW_DONE_MASK 0x00000004
|
||||
|
||||
#define REG_HDMI_DDC_SW_STATUS 0x00000218
|
||||
#define HDMI_DDC_SW_STATUS_NACK0 0x00001000
|
||||
#define HDMI_DDC_SW_STATUS_NACK1 0x00002000
|
||||
#define HDMI_DDC_SW_STATUS_NACK2 0x00004000
|
||||
#define HDMI_DDC_SW_STATUS_NACK3 0x00008000
|
||||
|
||||
#define REG_HDMI_DDC_HW_STATUS 0x0000021c
|
||||
|
||||
#define REG_HDMI_DDC_SPEED 0x00000220
|
||||
#define HDMI_DDC_SPEED_THRESHOLD__MASK 0x00000003
|
||||
#define HDMI_DDC_SPEED_THRESHOLD__SHIFT 0
|
||||
static inline uint32_t HDMI_DDC_SPEED_THRESHOLD(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_DDC_SPEED_THRESHOLD__SHIFT) & HDMI_DDC_SPEED_THRESHOLD__MASK;
|
||||
}
|
||||
#define HDMI_DDC_SPEED_PRESCALE__MASK 0xffff0000
|
||||
#define HDMI_DDC_SPEED_PRESCALE__SHIFT 16
|
||||
static inline uint32_t HDMI_DDC_SPEED_PRESCALE(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_DDC_SPEED_PRESCALE__SHIFT) & HDMI_DDC_SPEED_PRESCALE__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_DDC_SETUP 0x00000224
|
||||
#define HDMI_DDC_SETUP_TIMEOUT__MASK 0xff000000
|
||||
#define HDMI_DDC_SETUP_TIMEOUT__SHIFT 24
|
||||
static inline uint32_t HDMI_DDC_SETUP_TIMEOUT(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_DDC_SETUP_TIMEOUT__SHIFT) & HDMI_DDC_SETUP_TIMEOUT__MASK;
|
||||
}
|
||||
|
||||
static inline uint32_t REG_HDMI_I2C_TRANSACTION(uint32_t i0) { return 0x00000228 + 0x4*i0; }
|
||||
|
||||
static inline uint32_t REG_HDMI_I2C_TRANSACTION_REG(uint32_t i0) { return 0x00000228 + 0x4*i0; }
|
||||
#define HDMI_I2C_TRANSACTION_REG_RW__MASK 0x00000001
|
||||
#define HDMI_I2C_TRANSACTION_REG_RW__SHIFT 0
|
||||
static inline uint32_t HDMI_I2C_TRANSACTION_REG_RW(enum hdmi_ddc_read_write val)
|
||||
{
|
||||
return ((val) << HDMI_I2C_TRANSACTION_REG_RW__SHIFT) & HDMI_I2C_TRANSACTION_REG_RW__MASK;
|
||||
}
|
||||
#define HDMI_I2C_TRANSACTION_REG_STOP_ON_NACK 0x00000100
|
||||
#define HDMI_I2C_TRANSACTION_REG_START 0x00001000
|
||||
#define HDMI_I2C_TRANSACTION_REG_STOP 0x00002000
|
||||
#define HDMI_I2C_TRANSACTION_REG_CNT__MASK 0x00ff0000
|
||||
#define HDMI_I2C_TRANSACTION_REG_CNT__SHIFT 16
|
||||
static inline uint32_t HDMI_I2C_TRANSACTION_REG_CNT(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_I2C_TRANSACTION_REG_CNT__SHIFT) & HDMI_I2C_TRANSACTION_REG_CNT__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_DDC_DATA 0x00000238
|
||||
#define HDMI_DDC_DATA_DATA_RW__MASK 0x00000001
|
||||
#define HDMI_DDC_DATA_DATA_RW__SHIFT 0
|
||||
static inline uint32_t HDMI_DDC_DATA_DATA_RW(enum hdmi_ddc_read_write val)
|
||||
{
|
||||
return ((val) << HDMI_DDC_DATA_DATA_RW__SHIFT) & HDMI_DDC_DATA_DATA_RW__MASK;
|
||||
}
|
||||
#define HDMI_DDC_DATA_DATA__MASK 0x0000ff00
|
||||
#define HDMI_DDC_DATA_DATA__SHIFT 8
|
||||
static inline uint32_t HDMI_DDC_DATA_DATA(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_DDC_DATA_DATA__SHIFT) & HDMI_DDC_DATA_DATA__MASK;
|
||||
}
|
||||
#define HDMI_DDC_DATA_INDEX__MASK 0x00ff0000
|
||||
#define HDMI_DDC_DATA_INDEX__SHIFT 16
|
||||
static inline uint32_t HDMI_DDC_DATA_INDEX(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_DDC_DATA_INDEX__SHIFT) & HDMI_DDC_DATA_INDEX__MASK;
|
||||
}
|
||||
#define HDMI_DDC_DATA_INDEX_WRITE 0x80000000
|
||||
|
||||
#define REG_HDMI_HPD_INT_STATUS 0x00000250
|
||||
#define HDMI_HPD_INT_STATUS_INT 0x00000001
|
||||
#define HDMI_HPD_INT_STATUS_CABLE_DETECTED 0x00000002
|
||||
|
||||
#define REG_HDMI_HPD_INT_CTRL 0x00000254
|
||||
#define HDMI_HPD_INT_CTRL_INT_ACK 0x00000001
|
||||
#define HDMI_HPD_INT_CTRL_INT_CONNECT 0x00000002
|
||||
#define HDMI_HPD_INT_CTRL_INT_EN 0x00000004
|
||||
#define HDMI_HPD_INT_CTRL_RX_INT_ACK 0x00000010
|
||||
#define HDMI_HPD_INT_CTRL_RX_INT_EN 0x00000020
|
||||
#define HDMI_HPD_INT_CTRL_RCV_PLUGIN_DET_MASK 0x00000200
|
||||
|
||||
#define REG_HDMI_HPD_CTRL 0x00000258
|
||||
#define HDMI_HPD_CTRL_TIMEOUT__MASK 0x00001fff
|
||||
#define HDMI_HPD_CTRL_TIMEOUT__SHIFT 0
|
||||
static inline uint32_t HDMI_HPD_CTRL_TIMEOUT(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_HPD_CTRL_TIMEOUT__SHIFT) & HDMI_HPD_CTRL_TIMEOUT__MASK;
|
||||
}
|
||||
#define HDMI_HPD_CTRL_ENABLE 0x10000000
|
||||
|
||||
#define REG_HDMI_DDC_REF 0x0000027c
|
||||
#define HDMI_DDC_REF_REFTIMER_ENABLE 0x00010000
|
||||
#define HDMI_DDC_REF_REFTIMER__MASK 0x0000ffff
|
||||
#define HDMI_DDC_REF_REFTIMER__SHIFT 0
|
||||
static inline uint32_t HDMI_DDC_REF_REFTIMER(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_DDC_REF_REFTIMER__SHIFT) & HDMI_DDC_REF_REFTIMER__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_CEC_STATUS 0x00000298
|
||||
|
||||
#define REG_HDMI_CEC_INT 0x0000029c
|
||||
|
||||
#define REG_HDMI_CEC_ADDR 0x000002a0
|
||||
|
||||
#define REG_HDMI_CEC_TIME 0x000002a4
|
||||
|
||||
#define REG_HDMI_CEC_REFTIMER 0x000002a8
|
||||
|
||||
#define REG_HDMI_CEC_RD_DATA 0x000002ac
|
||||
|
||||
#define REG_HDMI_CEC_RD_FILTER 0x000002b0
|
||||
|
||||
#define REG_HDMI_ACTIVE_HSYNC 0x000002b4
|
||||
#define HDMI_ACTIVE_HSYNC_START__MASK 0x00000fff
|
||||
#define HDMI_ACTIVE_HSYNC_START__SHIFT 0
|
||||
static inline uint32_t HDMI_ACTIVE_HSYNC_START(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_ACTIVE_HSYNC_START__SHIFT) & HDMI_ACTIVE_HSYNC_START__MASK;
|
||||
}
|
||||
#define HDMI_ACTIVE_HSYNC_END__MASK 0x0fff0000
|
||||
#define HDMI_ACTIVE_HSYNC_END__SHIFT 16
|
||||
static inline uint32_t HDMI_ACTIVE_HSYNC_END(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_ACTIVE_HSYNC_END__SHIFT) & HDMI_ACTIVE_HSYNC_END__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_ACTIVE_VSYNC 0x000002b8
|
||||
#define HDMI_ACTIVE_VSYNC_START__MASK 0x00000fff
|
||||
#define HDMI_ACTIVE_VSYNC_START__SHIFT 0
|
||||
static inline uint32_t HDMI_ACTIVE_VSYNC_START(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_ACTIVE_VSYNC_START__SHIFT) & HDMI_ACTIVE_VSYNC_START__MASK;
|
||||
}
|
||||
#define HDMI_ACTIVE_VSYNC_END__MASK 0x0fff0000
|
||||
#define HDMI_ACTIVE_VSYNC_END__SHIFT 16
|
||||
static inline uint32_t HDMI_ACTIVE_VSYNC_END(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_ACTIVE_VSYNC_END__SHIFT) & HDMI_ACTIVE_VSYNC_END__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_VSYNC_ACTIVE_F2 0x000002bc
|
||||
#define HDMI_VSYNC_ACTIVE_F2_START__MASK 0x00000fff
|
||||
#define HDMI_VSYNC_ACTIVE_F2_START__SHIFT 0
|
||||
static inline uint32_t HDMI_VSYNC_ACTIVE_F2_START(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_VSYNC_ACTIVE_F2_START__SHIFT) & HDMI_VSYNC_ACTIVE_F2_START__MASK;
|
||||
}
|
||||
#define HDMI_VSYNC_ACTIVE_F2_END__MASK 0x0fff0000
|
||||
#define HDMI_VSYNC_ACTIVE_F2_END__SHIFT 16
|
||||
static inline uint32_t HDMI_VSYNC_ACTIVE_F2_END(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_VSYNC_ACTIVE_F2_END__SHIFT) & HDMI_VSYNC_ACTIVE_F2_END__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_TOTAL 0x000002c0
|
||||
#define HDMI_TOTAL_H_TOTAL__MASK 0x00000fff
|
||||
#define HDMI_TOTAL_H_TOTAL__SHIFT 0
|
||||
static inline uint32_t HDMI_TOTAL_H_TOTAL(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_TOTAL_H_TOTAL__SHIFT) & HDMI_TOTAL_H_TOTAL__MASK;
|
||||
}
|
||||
#define HDMI_TOTAL_V_TOTAL__MASK 0x0fff0000
|
||||
#define HDMI_TOTAL_V_TOTAL__SHIFT 16
|
||||
static inline uint32_t HDMI_TOTAL_V_TOTAL(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_TOTAL_V_TOTAL__SHIFT) & HDMI_TOTAL_V_TOTAL__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_VSYNC_TOTAL_F2 0x000002c4
|
||||
#define HDMI_VSYNC_TOTAL_F2_V_TOTAL__MASK 0x00000fff
|
||||
#define HDMI_VSYNC_TOTAL_F2_V_TOTAL__SHIFT 0
|
||||
static inline uint32_t HDMI_VSYNC_TOTAL_F2_V_TOTAL(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_VSYNC_TOTAL_F2_V_TOTAL__SHIFT) & HDMI_VSYNC_TOTAL_F2_V_TOTAL__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_FRAME_CTRL 0x000002c8
|
||||
#define HDMI_FRAME_CTRL_RGB_MUX_SEL_BGR 0x00001000
|
||||
#define HDMI_FRAME_CTRL_VSYNC_LOW 0x10000000
|
||||
#define HDMI_FRAME_CTRL_HSYNC_LOW 0x20000000
|
||||
#define HDMI_FRAME_CTRL_INTERLACED_EN 0x80000000
|
||||
|
||||
#define REG_HDMI_AUD_INT 0x000002cc
|
||||
#define HDMI_AUD_INT_AUD_FIFO_URUN_INT 0x00000001
|
||||
#define HDMI_AUD_INT_AUD_FIFO_URAN_MASK 0x00000002
|
||||
#define HDMI_AUD_INT_AUD_SAM_DROP_INT 0x00000004
|
||||
#define HDMI_AUD_INT_AUD_SAM_DROP_MASK 0x00000008
|
||||
|
||||
#define REG_HDMI_PHY_CTRL 0x000002d4
|
||||
#define HDMI_PHY_CTRL_SW_RESET_PLL 0x00000001
|
||||
#define HDMI_PHY_CTRL_SW_RESET_PLL_LOW 0x00000002
|
||||
#define HDMI_PHY_CTRL_SW_RESET 0x00000004
|
||||
#define HDMI_PHY_CTRL_SW_RESET_LOW 0x00000008
|
||||
|
||||
#define REG_HDMI_CEC_WR_RANGE 0x000002dc
|
||||
|
||||
#define REG_HDMI_CEC_RD_RANGE 0x000002e0
|
||||
|
||||
#define REG_HDMI_VERSION 0x000002e4
|
||||
|
||||
#define REG_HDMI_CEC_COMPL_CTL 0x00000360
|
||||
|
||||
#define REG_HDMI_CEC_RD_START_RANGE 0x00000364
|
||||
|
||||
#define REG_HDMI_CEC_RD_TOTAL_RANGE 0x00000368
|
||||
|
||||
#define REG_HDMI_CEC_RD_ERR_RESP_LO 0x0000036c
|
||||
|
||||
#define REG_HDMI_CEC_WR_CHECK_CONFIG 0x00000370
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG0 0x00000300
|
||||
#define HDMI_8x60_PHY_REG0_DESER_DEL_CTRL__MASK 0x0000001c
|
||||
#define HDMI_8x60_PHY_REG0_DESER_DEL_CTRL__SHIFT 2
|
||||
static inline uint32_t HDMI_8x60_PHY_REG0_DESER_DEL_CTRL(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_8x60_PHY_REG0_DESER_DEL_CTRL__SHIFT) & HDMI_8x60_PHY_REG0_DESER_DEL_CTRL__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG1 0x00000304
|
||||
#define HDMI_8x60_PHY_REG1_DTEST_MUX_SEL__MASK 0x000000f0
|
||||
#define HDMI_8x60_PHY_REG1_DTEST_MUX_SEL__SHIFT 4
|
||||
static inline uint32_t HDMI_8x60_PHY_REG1_DTEST_MUX_SEL(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_8x60_PHY_REG1_DTEST_MUX_SEL__SHIFT) & HDMI_8x60_PHY_REG1_DTEST_MUX_SEL__MASK;
|
||||
}
|
||||
#define HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL__MASK 0x0000000f
|
||||
#define HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL__SHIFT 0
|
||||
static inline uint32_t HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL(uint32_t val)
|
||||
{
|
||||
return ((val) << HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL__SHIFT) & HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL__MASK;
|
||||
}
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG2 0x00000308
|
||||
#define HDMI_8x60_PHY_REG2_PD_DESER 0x00000001
|
||||
#define HDMI_8x60_PHY_REG2_PD_DRIVE_1 0x00000002
|
||||
#define HDMI_8x60_PHY_REG2_PD_DRIVE_2 0x00000004
|
||||
#define HDMI_8x60_PHY_REG2_PD_DRIVE_3 0x00000008
|
||||
#define HDMI_8x60_PHY_REG2_PD_DRIVE_4 0x00000010
|
||||
#define HDMI_8x60_PHY_REG2_PD_PLL 0x00000020
|
||||
#define HDMI_8x60_PHY_REG2_PD_PWRGEN 0x00000040
|
||||
#define HDMI_8x60_PHY_REG2_RCV_SENSE_EN 0x00000080
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG3 0x0000030c
|
||||
#define HDMI_8x60_PHY_REG3_PLL_ENABLE 0x00000001
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG4 0x00000310
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG5 0x00000314
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG6 0x00000318
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG7 0x0000031c
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG8 0x00000320
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG9 0x00000324
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG10 0x00000328
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG11 0x0000032c
|
||||
|
||||
#define REG_HDMI_8x60_PHY_REG12 0x00000330
|
||||
#define HDMI_8x60_PHY_REG12_RETIMING_EN 0x00000001
|
||||
#define HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN 0x00000002
|
||||
#define HDMI_8x60_PHY_REG12_FORCE_LOCK 0x00000010
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG0 0x00000400
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG1 0x00000404
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG2 0x00000408
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG3 0x0000040c
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG4 0x00000410
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG5 0x00000414
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG6 0x00000418
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG7 0x0000041c
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG8 0x00000420
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG9 0x00000424
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG10 0x00000428
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG11 0x0000042c
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG12 0x00000430
|
||||
#define HDMI_8960_PHY_REG12_SW_RESET 0x00000020
|
||||
#define HDMI_8960_PHY_REG12_PWRDN_B 0x00000080
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG_BIST_CFG 0x00000434
|
||||
|
||||
#define REG_HDMI_8960_PHY_DEBUG_BUS_SEL 0x00000438
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG_MISC0 0x0000043c
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG13 0x00000440
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG14 0x00000444
|
||||
|
||||
#define REG_HDMI_8960_PHY_REG15 0x00000448
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_REFCLK_CFG 0x00000500
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG 0x00000504
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 0x00000508
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 0x0000050c
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG 0x00000510
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG 0x00000514
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_PWRDN_B 0x00000518
|
||||
#define HDMI_8960_PHY_PLL_PWRDN_B_PD_PLL 0x00000002
|
||||
#define HDMI_8960_PHY_PLL_PWRDN_B_PLL_PWRDN_B 0x00000008
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_SDM_CFG0 0x0000051c
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_SDM_CFG1 0x00000520
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_SDM_CFG2 0x00000524
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_SDM_CFG3 0x00000528
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_SDM_CFG4 0x0000052c
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_SSC_CFG0 0x00000530
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_SSC_CFG1 0x00000534
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_SSC_CFG2 0x00000538
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_SSC_CFG3 0x0000053c
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 0x00000540
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 0x00000544
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 0x00000548
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 0x0000054c
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 0x00000550
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 0x00000554
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 0x00000558
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 0x0000055c
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 0x00000560
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 0x00000564
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 0x00000568
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_DEBUG_SEL 0x0000056c
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_MISC0 0x00000570
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_MISC1 0x00000574
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_MISC2 0x00000578
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_MISC3 0x0000057c
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_MISC4 0x00000580
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_MISC5 0x00000584
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_MISC6 0x00000588
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_DEBUG_BUS0 0x0000058c
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_DEBUG_BUS1 0x00000590
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_DEBUG_BUS2 0x00000594
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_STATUS0 0x00000598
|
||||
#define HDMI_8960_PHY_PLL_STATUS0_PLL_LOCK 0x00000001
|
||||
|
||||
#define REG_HDMI_8960_PHY_PLL_STATUS1 0x0000059c
|
||||
|
||||
#define REG_HDMI_8x74_ANA_CFG0 0x00000000
|
||||
|
||||
#define REG_HDMI_8x74_ANA_CFG1 0x00000004
|
||||
|
||||
#define REG_HDMI_8x74_PD_CTRL0 0x00000010
|
||||
|
||||
#define REG_HDMI_8x74_PD_CTRL1 0x00000014
|
||||
|
||||
#define REG_HDMI_8x74_BIST_CFG0 0x00000034
|
||||
|
||||
#define REG_HDMI_8x74_BIST_PATN0 0x0000003c
|
||||
|
||||
#define REG_HDMI_8x74_BIST_PATN1 0x00000040
|
||||
|
||||
#define REG_HDMI_8x74_BIST_PATN2 0x00000044
|
||||
|
||||
#define REG_HDMI_8x74_BIST_PATN3 0x00000048
|
||||
|
||||
|
||||
#endif /* HDMI_XML */
|
||||
273
drivers/gpu/drm/msm/hdmi/hdmi_audio.c
Normal file
273
drivers/gpu/drm/msm/hdmi/hdmi_audio.c
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/hdmi.h>
|
||||
#include "hdmi.h"
|
||||
|
||||
|
||||
/* Supported HDMI Audio channels */
|
||||
#define MSM_HDMI_AUDIO_CHANNEL_2 0
|
||||
#define MSM_HDMI_AUDIO_CHANNEL_4 1
|
||||
#define MSM_HDMI_AUDIO_CHANNEL_6 2
|
||||
#define MSM_HDMI_AUDIO_CHANNEL_8 3
|
||||
|
||||
/* maps MSM_HDMI_AUDIO_CHANNEL_n consts used by audio driver to # of channels: */
|
||||
static int nchannels[] = { 2, 4, 6, 8 };
|
||||
|
||||
/* Supported HDMI Audio sample rates */
|
||||
#define MSM_HDMI_SAMPLE_RATE_32KHZ 0
|
||||
#define MSM_HDMI_SAMPLE_RATE_44_1KHZ 1
|
||||
#define MSM_HDMI_SAMPLE_RATE_48KHZ 2
|
||||
#define MSM_HDMI_SAMPLE_RATE_88_2KHZ 3
|
||||
#define MSM_HDMI_SAMPLE_RATE_96KHZ 4
|
||||
#define MSM_HDMI_SAMPLE_RATE_176_4KHZ 5
|
||||
#define MSM_HDMI_SAMPLE_RATE_192KHZ 6
|
||||
#define MSM_HDMI_SAMPLE_RATE_MAX 7
|
||||
|
||||
|
||||
struct hdmi_msm_audio_acr {
|
||||
uint32_t n; /* N parameter for clock regeneration */
|
||||
uint32_t cts; /* CTS parameter for clock regeneration */
|
||||
};
|
||||
|
||||
struct hdmi_msm_audio_arcs {
|
||||
unsigned long int pixclock;
|
||||
struct hdmi_msm_audio_acr lut[MSM_HDMI_SAMPLE_RATE_MAX];
|
||||
};
|
||||
|
||||
#define HDMI_MSM_AUDIO_ARCS(pclk, ...) { (1000 * (pclk)), __VA_ARGS__ }
|
||||
|
||||
/* Audio constants lookup table for hdmi_msm_audio_acr_setup */
|
||||
/* Valid Pixel-Clock rates: 25.2MHz, 27MHz, 27.03MHz, 74.25MHz, 148.5MHz */
|
||||
static const struct hdmi_msm_audio_arcs acr_lut[] = {
|
||||
/* 25.200MHz */
|
||||
HDMI_MSM_AUDIO_ARCS(25200, {
|
||||
{4096, 25200}, {6272, 28000}, {6144, 25200}, {12544, 28000},
|
||||
{12288, 25200}, {25088, 28000}, {24576, 25200} }),
|
||||
/* 27.000MHz */
|
||||
HDMI_MSM_AUDIO_ARCS(27000, {
|
||||
{4096, 27000}, {6272, 30000}, {6144, 27000}, {12544, 30000},
|
||||
{12288, 27000}, {25088, 30000}, {24576, 27000} }),
|
||||
/* 27.027MHz */
|
||||
HDMI_MSM_AUDIO_ARCS(27030, {
|
||||
{4096, 27027}, {6272, 30030}, {6144, 27027}, {12544, 30030},
|
||||
{12288, 27027}, {25088, 30030}, {24576, 27027} }),
|
||||
/* 74.250MHz */
|
||||
HDMI_MSM_AUDIO_ARCS(74250, {
|
||||
{4096, 74250}, {6272, 82500}, {6144, 74250}, {12544, 82500},
|
||||
{12288, 74250}, {25088, 82500}, {24576, 74250} }),
|
||||
/* 148.500MHz */
|
||||
HDMI_MSM_AUDIO_ARCS(148500, {
|
||||
{4096, 148500}, {6272, 165000}, {6144, 148500}, {12544, 165000},
|
||||
{12288, 148500}, {25088, 165000}, {24576, 148500} }),
|
||||
};
|
||||
|
||||
static const struct hdmi_msm_audio_arcs *get_arcs(unsigned long int pixclock)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(acr_lut); i++) {
|
||||
const struct hdmi_msm_audio_arcs *arcs = &acr_lut[i];
|
||||
if (arcs->pixclock == pixclock)
|
||||
return arcs;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int hdmi_audio_update(struct hdmi *hdmi)
|
||||
{
|
||||
struct hdmi_audio *audio = &hdmi->audio;
|
||||
struct hdmi_audio_infoframe *info = &audio->infoframe;
|
||||
const struct hdmi_msm_audio_arcs *arcs = NULL;
|
||||
bool enabled = audio->enabled;
|
||||
uint32_t acr_pkt_ctrl, vbi_pkt_ctrl, aud_pkt_ctrl;
|
||||
uint32_t infofrm_ctrl, audio_config;
|
||||
|
||||
DBG("audio: enabled=%d, channels=%d, channel_allocation=0x%x, "
|
||||
"level_shift_value=%d, downmix_inhibit=%d, rate=%d",
|
||||
audio->enabled, info->channels, info->channel_allocation,
|
||||
info->level_shift_value, info->downmix_inhibit, audio->rate);
|
||||
DBG("video: power_on=%d, pixclock=%lu", hdmi->power_on, hdmi->pixclock);
|
||||
|
||||
if (enabled && !(hdmi->power_on && hdmi->pixclock)) {
|
||||
DBG("disabling audio: no video");
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
arcs = get_arcs(hdmi->pixclock);
|
||||
if (!arcs) {
|
||||
DBG("disabling audio: unsupported pixclock: %lu",
|
||||
hdmi->pixclock);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read first before writing */
|
||||
acr_pkt_ctrl = hdmi_read(hdmi, REG_HDMI_ACR_PKT_CTRL);
|
||||
vbi_pkt_ctrl = hdmi_read(hdmi, REG_HDMI_VBI_PKT_CTRL);
|
||||
aud_pkt_ctrl = hdmi_read(hdmi, REG_HDMI_AUDIO_PKT_CTRL1);
|
||||
infofrm_ctrl = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0);
|
||||
audio_config = hdmi_read(hdmi, REG_HDMI_AUDIO_CFG);
|
||||
|
||||
/* Clear N/CTS selection bits */
|
||||
acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_SELECT__MASK;
|
||||
|
||||
if (enabled) {
|
||||
uint32_t n, cts, multiplier;
|
||||
enum hdmi_acr_cts select;
|
||||
uint8_t buf[14];
|
||||
|
||||
n = arcs->lut[audio->rate].n;
|
||||
cts = arcs->lut[audio->rate].cts;
|
||||
|
||||
if ((MSM_HDMI_SAMPLE_RATE_192KHZ == audio->rate) ||
|
||||
(MSM_HDMI_SAMPLE_RATE_176_4KHZ == audio->rate)) {
|
||||
multiplier = 4;
|
||||
n >>= 2; /* divide N by 4 and use multiplier */
|
||||
} else if ((MSM_HDMI_SAMPLE_RATE_96KHZ == audio->rate) ||
|
||||
(MSM_HDMI_SAMPLE_RATE_88_2KHZ == audio->rate)) {
|
||||
multiplier = 2;
|
||||
n >>= 1; /* divide N by 2 and use multiplier */
|
||||
} else {
|
||||
multiplier = 1;
|
||||
}
|
||||
|
||||
DBG("n=%u, cts=%u, multiplier=%u", n, cts, multiplier);
|
||||
|
||||
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_SOURCE;
|
||||
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_AUDIO_PRIORITY;
|
||||
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_N_MULTIPLIER(multiplier);
|
||||
|
||||
if ((MSM_HDMI_SAMPLE_RATE_48KHZ == audio->rate) ||
|
||||
(MSM_HDMI_SAMPLE_RATE_96KHZ == audio->rate) ||
|
||||
(MSM_HDMI_SAMPLE_RATE_192KHZ == audio->rate))
|
||||
select = ACR_48;
|
||||
else if ((MSM_HDMI_SAMPLE_RATE_44_1KHZ == audio->rate) ||
|
||||
(MSM_HDMI_SAMPLE_RATE_88_2KHZ == audio->rate) ||
|
||||
(MSM_HDMI_SAMPLE_RATE_176_4KHZ == audio->rate))
|
||||
select = ACR_44;
|
||||
else /* default to 32k */
|
||||
select = ACR_32;
|
||||
|
||||
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_SELECT(select);
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_ACR_0(select - 1),
|
||||
HDMI_ACR_0_CTS(cts));
|
||||
hdmi_write(hdmi, REG_HDMI_ACR_1(select - 1),
|
||||
HDMI_ACR_1_N(n));
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_AUDIO_PKT_CTRL2,
|
||||
COND(info->channels != 2, HDMI_AUDIO_PKT_CTRL2_LAYOUT) |
|
||||
HDMI_AUDIO_PKT_CTRL2_OVERRIDE);
|
||||
|
||||
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_CONT;
|
||||
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_SEND;
|
||||
|
||||
/* configure infoframe: */
|
||||
hdmi_audio_infoframe_pack(info, buf, sizeof(buf));
|
||||
hdmi_write(hdmi, REG_HDMI_AUDIO_INFO0,
|
||||
(buf[3] << 0) || (buf[4] << 8) ||
|
||||
(buf[5] << 16) || (buf[6] << 24));
|
||||
hdmi_write(hdmi, REG_HDMI_AUDIO_INFO1,
|
||||
(buf[7] << 0) || (buf[8] << 8));
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_GC, 0);
|
||||
|
||||
vbi_pkt_ctrl |= HDMI_VBI_PKT_CTRL_GC_ENABLE;
|
||||
vbi_pkt_ctrl |= HDMI_VBI_PKT_CTRL_GC_EVERY_FRAME;
|
||||
|
||||
aud_pkt_ctrl |= HDMI_AUDIO_PKT_CTRL1_AUDIO_SAMPLE_SEND;
|
||||
|
||||
infofrm_ctrl |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND;
|
||||
infofrm_ctrl |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT;
|
||||
infofrm_ctrl |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE;
|
||||
infofrm_ctrl |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE;
|
||||
|
||||
audio_config &= ~HDMI_AUDIO_CFG_FIFO_WATERMARK__MASK;
|
||||
audio_config |= HDMI_AUDIO_CFG_FIFO_WATERMARK(4);
|
||||
audio_config |= HDMI_AUDIO_CFG_ENGINE_ENABLE;
|
||||
} else {
|
||||
hdmi_write(hdmi, REG_HDMI_GC, HDMI_GC_MUTE);
|
||||
acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_CONT;
|
||||
acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_SEND;
|
||||
vbi_pkt_ctrl &= ~HDMI_VBI_PKT_CTRL_GC_ENABLE;
|
||||
vbi_pkt_ctrl &= ~HDMI_VBI_PKT_CTRL_GC_EVERY_FRAME;
|
||||
aud_pkt_ctrl &= ~HDMI_AUDIO_PKT_CTRL1_AUDIO_SAMPLE_SEND;
|
||||
infofrm_ctrl &= ~HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND;
|
||||
infofrm_ctrl &= ~HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT;
|
||||
infofrm_ctrl &= ~HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE;
|
||||
infofrm_ctrl &= ~HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE;
|
||||
audio_config &= ~HDMI_AUDIO_CFG_ENGINE_ENABLE;
|
||||
}
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_ACR_PKT_CTRL, acr_pkt_ctrl);
|
||||
hdmi_write(hdmi, REG_HDMI_VBI_PKT_CTRL, vbi_pkt_ctrl);
|
||||
hdmi_write(hdmi, REG_HDMI_AUDIO_PKT_CTRL1, aud_pkt_ctrl);
|
||||
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, infofrm_ctrl);
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_AUD_INT,
|
||||
COND(enabled, HDMI_AUD_INT_AUD_FIFO_URUN_INT) |
|
||||
COND(enabled, HDMI_AUD_INT_AUD_SAM_DROP_INT));
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_AUDIO_CFG, audio_config);
|
||||
|
||||
|
||||
DBG("audio %sabled", enabled ? "en" : "dis");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hdmi_audio_info_setup(struct hdmi *hdmi, bool enabled,
|
||||
uint32_t num_of_channels, uint32_t channel_allocation,
|
||||
uint32_t level_shift, bool down_mix)
|
||||
{
|
||||
struct hdmi_audio *audio;
|
||||
|
||||
if (!hdmi)
|
||||
return -ENXIO;
|
||||
|
||||
audio = &hdmi->audio;
|
||||
|
||||
if (num_of_channels >= ARRAY_SIZE(nchannels))
|
||||
return -EINVAL;
|
||||
|
||||
audio->enabled = enabled;
|
||||
audio->infoframe.channels = nchannels[num_of_channels];
|
||||
audio->infoframe.channel_allocation = channel_allocation;
|
||||
audio->infoframe.level_shift_value = level_shift;
|
||||
audio->infoframe.downmix_inhibit = down_mix;
|
||||
|
||||
return hdmi_audio_update(hdmi);
|
||||
}
|
||||
|
||||
void hdmi_audio_set_sample_rate(struct hdmi *hdmi, int rate)
|
||||
{
|
||||
struct hdmi_audio *audio;
|
||||
|
||||
if (!hdmi)
|
||||
return;
|
||||
|
||||
audio = &hdmi->audio;
|
||||
|
||||
if ((rate < 0) || (rate >= MSM_HDMI_SAMPLE_RATE_MAX))
|
||||
return;
|
||||
|
||||
audio->rate = rate;
|
||||
hdmi_audio_update(hdmi);
|
||||
}
|
||||
234
drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
Normal file
234
drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hdmi.h"
|
||||
|
||||
struct hdmi_bridge {
|
||||
struct drm_bridge base;
|
||||
struct hdmi *hdmi;
|
||||
};
|
||||
#define to_hdmi_bridge(x) container_of(x, struct hdmi_bridge, base)
|
||||
|
||||
static void hdmi_bridge_destroy(struct drm_bridge *bridge)
|
||||
{
|
||||
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
|
||||
hdmi_unreference(hdmi_bridge->hdmi);
|
||||
drm_bridge_cleanup(bridge);
|
||||
kfree(hdmi_bridge);
|
||||
}
|
||||
|
||||
static void power_on(struct drm_bridge *bridge)
|
||||
{
|
||||
struct drm_device *dev = bridge->dev;
|
||||
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
|
||||
struct hdmi *hdmi = hdmi_bridge->hdmi;
|
||||
const struct hdmi_platform_config *config = hdmi->config;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < config->pwr_reg_cnt; i++) {
|
||||
ret = regulator_enable(hdmi->pwr_regs[i]);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to enable pwr regulator: %s (%d)\n",
|
||||
config->pwr_reg_names[i], ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (config->pwr_clk_cnt > 0) {
|
||||
DBG("pixclock: %lu", hdmi->pixclock);
|
||||
ret = clk_set_rate(hdmi->pwr_clks[0], hdmi->pixclock);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to set pixel clk: %s (%d)\n",
|
||||
config->pwr_clk_names[0], ret);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < config->pwr_clk_cnt; i++) {
|
||||
ret = clk_prepare_enable(hdmi->pwr_clks[i]);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to enable pwr clk: %s (%d)\n",
|
||||
config->pwr_clk_names[i], ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void power_off(struct drm_bridge *bridge)
|
||||
{
|
||||
struct drm_device *dev = bridge->dev;
|
||||
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
|
||||
struct hdmi *hdmi = hdmi_bridge->hdmi;
|
||||
const struct hdmi_platform_config *config = hdmi->config;
|
||||
int i, ret;
|
||||
|
||||
/* TODO do we need to wait for final vblank somewhere before
|
||||
* cutting the clocks?
|
||||
*/
|
||||
mdelay(16 + 4);
|
||||
|
||||
for (i = 0; i < config->pwr_clk_cnt; i++)
|
||||
clk_disable_unprepare(hdmi->pwr_clks[i]);
|
||||
|
||||
for (i = 0; i < config->pwr_reg_cnt; i++) {
|
||||
ret = regulator_disable(hdmi->pwr_regs[i]);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to disable pwr regulator: %s (%d)\n",
|
||||
config->pwr_reg_names[i], ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void hdmi_bridge_pre_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
|
||||
struct hdmi *hdmi = hdmi_bridge->hdmi;
|
||||
struct hdmi_phy *phy = hdmi->phy;
|
||||
|
||||
DBG("power up");
|
||||
|
||||
if (!hdmi->power_on) {
|
||||
power_on(bridge);
|
||||
hdmi->power_on = true;
|
||||
hdmi_audio_update(hdmi);
|
||||
}
|
||||
|
||||
phy->funcs->powerup(phy, hdmi->pixclock);
|
||||
hdmi_set_mode(hdmi, true);
|
||||
}
|
||||
|
||||
static void hdmi_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
}
|
||||
|
||||
static void hdmi_bridge_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
}
|
||||
|
||||
static void hdmi_bridge_post_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
|
||||
struct hdmi *hdmi = hdmi_bridge->hdmi;
|
||||
struct hdmi_phy *phy = hdmi->phy;
|
||||
|
||||
DBG("power down");
|
||||
hdmi_set_mode(hdmi, false);
|
||||
phy->funcs->powerdown(phy);
|
||||
|
||||
if (hdmi->power_on) {
|
||||
power_off(bridge);
|
||||
hdmi->power_on = false;
|
||||
hdmi_audio_update(hdmi);
|
||||
}
|
||||
}
|
||||
|
||||
static void hdmi_bridge_mode_set(struct drm_bridge *bridge,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
|
||||
struct hdmi *hdmi = hdmi_bridge->hdmi;
|
||||
int hstart, hend, vstart, vend;
|
||||
uint32_t frame_ctrl;
|
||||
|
||||
mode = adjusted_mode;
|
||||
|
||||
hdmi->pixclock = mode->clock * 1000;
|
||||
|
||||
hdmi->hdmi_mode = drm_match_cea_mode(mode) > 1;
|
||||
|
||||
hstart = mode->htotal - mode->hsync_start;
|
||||
hend = mode->htotal - mode->hsync_start + mode->hdisplay;
|
||||
|
||||
vstart = mode->vtotal - mode->vsync_start - 1;
|
||||
vend = mode->vtotal - mode->vsync_start + mode->vdisplay - 1;
|
||||
|
||||
DBG("htotal=%d, vtotal=%d, hstart=%d, hend=%d, vstart=%d, vend=%d",
|
||||
mode->htotal, mode->vtotal, hstart, hend, vstart, vend);
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_TOTAL,
|
||||
HDMI_TOTAL_H_TOTAL(mode->htotal - 1) |
|
||||
HDMI_TOTAL_V_TOTAL(mode->vtotal - 1));
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_ACTIVE_HSYNC,
|
||||
HDMI_ACTIVE_HSYNC_START(hstart) |
|
||||
HDMI_ACTIVE_HSYNC_END(hend));
|
||||
hdmi_write(hdmi, REG_HDMI_ACTIVE_VSYNC,
|
||||
HDMI_ACTIVE_VSYNC_START(vstart) |
|
||||
HDMI_ACTIVE_VSYNC_END(vend));
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
|
||||
hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
|
||||
HDMI_VSYNC_TOTAL_F2_V_TOTAL(mode->vtotal));
|
||||
hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
|
||||
HDMI_VSYNC_ACTIVE_F2_START(vstart + 1) |
|
||||
HDMI_VSYNC_ACTIVE_F2_END(vend + 1));
|
||||
} else {
|
||||
hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
|
||||
HDMI_VSYNC_TOTAL_F2_V_TOTAL(0));
|
||||
hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
|
||||
HDMI_VSYNC_ACTIVE_F2_START(0) |
|
||||
HDMI_VSYNC_ACTIVE_F2_END(0));
|
||||
}
|
||||
|
||||
frame_ctrl = 0;
|
||||
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
||||
frame_ctrl |= HDMI_FRAME_CTRL_HSYNC_LOW;
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
frame_ctrl |= HDMI_FRAME_CTRL_VSYNC_LOW;
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
||||
frame_ctrl |= HDMI_FRAME_CTRL_INTERLACED_EN;
|
||||
DBG("frame_ctrl=%08x", frame_ctrl);
|
||||
hdmi_write(hdmi, REG_HDMI_FRAME_CTRL, frame_ctrl);
|
||||
|
||||
hdmi_audio_update(hdmi);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs hdmi_bridge_funcs = {
|
||||
.pre_enable = hdmi_bridge_pre_enable,
|
||||
.enable = hdmi_bridge_enable,
|
||||
.disable = hdmi_bridge_disable,
|
||||
.post_disable = hdmi_bridge_post_disable,
|
||||
.mode_set = hdmi_bridge_mode_set,
|
||||
.destroy = hdmi_bridge_destroy,
|
||||
};
|
||||
|
||||
|
||||
/* initialize bridge */
|
||||
struct drm_bridge *hdmi_bridge_init(struct hdmi *hdmi)
|
||||
{
|
||||
struct drm_bridge *bridge = NULL;
|
||||
struct hdmi_bridge *hdmi_bridge;
|
||||
int ret;
|
||||
|
||||
hdmi_bridge = kzalloc(sizeof(*hdmi_bridge), GFP_KERNEL);
|
||||
if (!hdmi_bridge) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hdmi_bridge->hdmi = hdmi_reference(hdmi);
|
||||
|
||||
bridge = &hdmi_bridge->base;
|
||||
|
||||
drm_bridge_init(hdmi->dev, bridge, &hdmi_bridge_funcs);
|
||||
|
||||
return bridge;
|
||||
|
||||
fail:
|
||||
if (bridge)
|
||||
hdmi_bridge_destroy(bridge);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
457
drivers/gpu/drm/msm/hdmi/hdmi_connector.c
Normal file
457
drivers/gpu/drm/msm/hdmi/hdmi_connector.c
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include "msm_kms.h"
|
||||
#include "hdmi.h"
|
||||
|
||||
struct hdmi_connector {
|
||||
struct drm_connector base;
|
||||
struct hdmi *hdmi;
|
||||
struct work_struct hpd_work;
|
||||
};
|
||||
#define to_hdmi_connector(x) container_of(x, struct hdmi_connector, base)
|
||||
|
||||
static int gpio_config(struct hdmi *hdmi, bool on)
|
||||
{
|
||||
struct drm_device *dev = hdmi->dev;
|
||||
const struct hdmi_platform_config *config = hdmi->config;
|
||||
int ret;
|
||||
|
||||
if (on) {
|
||||
ret = gpio_request(config->ddc_clk_gpio, "HDMI_DDC_CLK");
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
||||
"HDMI_DDC_CLK", config->ddc_clk_gpio, ret);
|
||||
goto error1;
|
||||
}
|
||||
gpio_set_value_cansleep(config->ddc_clk_gpio, 1);
|
||||
|
||||
ret = gpio_request(config->ddc_data_gpio, "HDMI_DDC_DATA");
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
||||
"HDMI_DDC_DATA", config->ddc_data_gpio, ret);
|
||||
goto error2;
|
||||
}
|
||||
gpio_set_value_cansleep(config->ddc_data_gpio, 1);
|
||||
|
||||
ret = gpio_request(config->hpd_gpio, "HDMI_HPD");
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
||||
"HDMI_HPD", config->hpd_gpio, ret);
|
||||
goto error3;
|
||||
}
|
||||
gpio_direction_input(config->hpd_gpio);
|
||||
gpio_set_value_cansleep(config->hpd_gpio, 1);
|
||||
|
||||
if (config->mux_en_gpio != -1) {
|
||||
ret = gpio_request(config->mux_en_gpio, "HDMI_MUX_EN");
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
||||
"HDMI_MUX_EN", config->mux_en_gpio, ret);
|
||||
goto error4;
|
||||
}
|
||||
gpio_set_value_cansleep(config->mux_en_gpio, 1);
|
||||
}
|
||||
|
||||
if (config->mux_sel_gpio != -1) {
|
||||
ret = gpio_request(config->mux_sel_gpio, "HDMI_MUX_SEL");
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n",
|
||||
"HDMI_MUX_SEL", config->mux_sel_gpio, ret);
|
||||
goto error5;
|
||||
}
|
||||
gpio_set_value_cansleep(config->mux_sel_gpio, 0);
|
||||
}
|
||||
|
||||
if (config->mux_lpm_gpio != -1) {
|
||||
ret = gpio_request(config->mux_lpm_gpio,
|
||||
"HDMI_MUX_LPM");
|
||||
if (ret) {
|
||||
dev_err(dev->dev,
|
||||
"'%s'(%d) gpio_request failed: %d\n",
|
||||
"HDMI_MUX_LPM",
|
||||
config->mux_lpm_gpio, ret);
|
||||
goto error6;
|
||||
}
|
||||
gpio_set_value_cansleep(config->mux_lpm_gpio, 1);
|
||||
}
|
||||
DBG("gpio on");
|
||||
} else {
|
||||
gpio_free(config->ddc_clk_gpio);
|
||||
gpio_free(config->ddc_data_gpio);
|
||||
gpio_free(config->hpd_gpio);
|
||||
|
||||
if (config->mux_en_gpio != -1) {
|
||||
gpio_set_value_cansleep(config->mux_en_gpio, 0);
|
||||
gpio_free(config->mux_en_gpio);
|
||||
}
|
||||
|
||||
if (config->mux_sel_gpio != -1) {
|
||||
gpio_set_value_cansleep(config->mux_sel_gpio, 1);
|
||||
gpio_free(config->mux_sel_gpio);
|
||||
}
|
||||
|
||||
if (config->mux_lpm_gpio != -1) {
|
||||
gpio_set_value_cansleep(config->mux_lpm_gpio, 0);
|
||||
gpio_free(config->mux_lpm_gpio);
|
||||
}
|
||||
DBG("gpio off");
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error6:
|
||||
if (config->mux_sel_gpio != -1)
|
||||
gpio_free(config->mux_sel_gpio);
|
||||
error5:
|
||||
if (config->mux_en_gpio != -1)
|
||||
gpio_free(config->mux_en_gpio);
|
||||
error4:
|
||||
gpio_free(config->hpd_gpio);
|
||||
error3:
|
||||
gpio_free(config->ddc_data_gpio);
|
||||
error2:
|
||||
gpio_free(config->ddc_clk_gpio);
|
||||
error1:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hpd_enable(struct hdmi_connector *hdmi_connector)
|
||||
{
|
||||
struct hdmi *hdmi = hdmi_connector->hdmi;
|
||||
const struct hdmi_platform_config *config = hdmi->config;
|
||||
struct drm_device *dev = hdmi_connector->base.dev;
|
||||
struct hdmi_phy *phy = hdmi->phy;
|
||||
uint32_t hpd_ctrl;
|
||||
int i, ret;
|
||||
|
||||
ret = gpio_config(hdmi, true);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to configure GPIOs: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < config->hpd_clk_cnt; i++) {
|
||||
if (config->hpd_freq && config->hpd_freq[i]) {
|
||||
ret = clk_set_rate(hdmi->hpd_clks[i],
|
||||
config->hpd_freq[i]);
|
||||
if (ret)
|
||||
dev_warn(dev->dev, "failed to set clk %s (%d)\n",
|
||||
config->hpd_clk_names[i], ret);
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(hdmi->hpd_clks[i]);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to enable hpd clk: %s (%d)\n",
|
||||
config->hpd_clk_names[i], ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < config->hpd_reg_cnt; i++) {
|
||||
ret = regulator_enable(hdmi->hpd_regs[i]);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to enable hpd regulator: %s (%d)\n",
|
||||
config->hpd_reg_names[i], ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
hdmi_set_mode(hdmi, false);
|
||||
phy->funcs->reset(phy);
|
||||
hdmi_set_mode(hdmi, true);
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b);
|
||||
|
||||
/* enable HPD events: */
|
||||
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
|
||||
HDMI_HPD_INT_CTRL_INT_CONNECT |
|
||||
HDMI_HPD_INT_CTRL_INT_EN);
|
||||
|
||||
/* set timeout to 4.1ms (max) for hardware debounce */
|
||||
hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
|
||||
hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff);
|
||||
|
||||
/* Toggle HPD circuit to trigger HPD sense */
|
||||
hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
|
||||
~HDMI_HPD_CTRL_ENABLE & hpd_ctrl);
|
||||
hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
|
||||
HDMI_HPD_CTRL_ENABLE | hpd_ctrl);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hdp_disable(struct hdmi_connector *hdmi_connector)
|
||||
{
|
||||
struct hdmi *hdmi = hdmi_connector->hdmi;
|
||||
const struct hdmi_platform_config *config = hdmi->config;
|
||||
struct drm_device *dev = hdmi_connector->base.dev;
|
||||
int i, ret = 0;
|
||||
|
||||
/* Disable HPD interrupt */
|
||||
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);
|
||||
|
||||
hdmi_set_mode(hdmi, false);
|
||||
|
||||
for (i = 0; i < config->hpd_reg_cnt; i++) {
|
||||
ret = regulator_disable(hdmi->hpd_regs[i]);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to disable hpd regulator: %s (%d)\n",
|
||||
config->hpd_reg_names[i], ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < config->hpd_clk_cnt; i++)
|
||||
clk_disable_unprepare(hdmi->hpd_clks[i]);
|
||||
|
||||
ret = gpio_config(hdmi, false);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to unconfigure GPIOs: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
hotplug_work(struct work_struct *work)
|
||||
{
|
||||
struct hdmi_connector *hdmi_connector =
|
||||
container_of(work, struct hdmi_connector, hpd_work);
|
||||
struct drm_connector *connector = &hdmi_connector->base;
|
||||
drm_helper_hpd_irq_event(connector->dev);
|
||||
}
|
||||
|
||||
void hdmi_connector_irq(struct drm_connector *connector)
|
||||
{
|
||||
struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
|
||||
struct msm_drm_private *priv = connector->dev->dev_private;
|
||||
struct hdmi *hdmi = hdmi_connector->hdmi;
|
||||
uint32_t hpd_int_status, hpd_int_ctrl;
|
||||
|
||||
/* Process HPD: */
|
||||
hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
|
||||
hpd_int_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_INT_CTRL);
|
||||
|
||||
if ((hpd_int_ctrl & HDMI_HPD_INT_CTRL_INT_EN) &&
|
||||
(hpd_int_status & HDMI_HPD_INT_STATUS_INT)) {
|
||||
bool detected = !!(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED);
|
||||
|
||||
DBG("status=%04x, ctrl=%04x", hpd_int_status, hpd_int_ctrl);
|
||||
|
||||
/* ack the irq: */
|
||||
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
|
||||
hpd_int_ctrl | HDMI_HPD_INT_CTRL_INT_ACK);
|
||||
|
||||
/* detect disconnect if we are connected or visa versa: */
|
||||
hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN;
|
||||
if (!detected)
|
||||
hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
|
||||
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl);
|
||||
|
||||
queue_work(priv->wq, &hdmi_connector->hpd_work);
|
||||
}
|
||||
}
|
||||
|
||||
static enum drm_connector_status detect_reg(struct hdmi *hdmi)
|
||||
{
|
||||
uint32_t hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
|
||||
return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ?
|
||||
connector_status_connected : connector_status_disconnected;
|
||||
}
|
||||
|
||||
static enum drm_connector_status detect_gpio(struct hdmi *hdmi)
|
||||
{
|
||||
const struct hdmi_platform_config *config = hdmi->config;
|
||||
return gpio_get_value(config->hpd_gpio) ?
|
||||
connector_status_connected :
|
||||
connector_status_disconnected;
|
||||
}
|
||||
|
||||
static enum drm_connector_status hdmi_connector_detect(
|
||||
struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
|
||||
struct hdmi *hdmi = hdmi_connector->hdmi;
|
||||
enum drm_connector_status stat_gpio, stat_reg;
|
||||
int retry = 20;
|
||||
|
||||
do {
|
||||
stat_gpio = detect_gpio(hdmi);
|
||||
stat_reg = detect_reg(hdmi);
|
||||
|
||||
if (stat_gpio == stat_reg)
|
||||
break;
|
||||
|
||||
mdelay(10);
|
||||
} while (--retry);
|
||||
|
||||
/* the status we get from reading gpio seems to be more reliable,
|
||||
* so trust that one the most if we didn't manage to get hdmi and
|
||||
* gpio status to agree:
|
||||
*/
|
||||
if (stat_gpio != stat_reg) {
|
||||
DBG("HDMI_HPD_INT_STATUS tells us: %d", stat_reg);
|
||||
DBG("hpd gpio tells us: %d", stat_gpio);
|
||||
}
|
||||
|
||||
return stat_gpio;
|
||||
}
|
||||
|
||||
static void hdmi_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
|
||||
|
||||
hdp_disable(hdmi_connector);
|
||||
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
|
||||
hdmi_unreference(hdmi_connector->hdmi);
|
||||
|
||||
kfree(hdmi_connector);
|
||||
}
|
||||
|
||||
static int hdmi_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
|
||||
struct hdmi *hdmi = hdmi_connector->hdmi;
|
||||
struct edid *edid;
|
||||
uint32_t hdmi_ctrl;
|
||||
int ret = 0;
|
||||
|
||||
hdmi_ctrl = hdmi_read(hdmi, REG_HDMI_CTRL);
|
||||
hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl | HDMI_CTRL_ENABLE);
|
||||
|
||||
edid = drm_get_edid(connector, hdmi->i2c);
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl);
|
||||
|
||||
drm_mode_connector_update_edid_property(connector, edid);
|
||||
|
||||
if (edid) {
|
||||
ret = drm_add_edid_modes(connector, edid);
|
||||
kfree(edid);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hdmi_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
|
||||
struct hdmi *hdmi = hdmi_connector->hdmi;
|
||||
const struct hdmi_platform_config *config = hdmi->config;
|
||||
struct msm_drm_private *priv = connector->dev->dev_private;
|
||||
struct msm_kms *kms = priv->kms;
|
||||
long actual, requested;
|
||||
|
||||
requested = 1000 * mode->clock;
|
||||
actual = kms->funcs->round_pixclk(kms,
|
||||
requested, hdmi_connector->hdmi->encoder);
|
||||
|
||||
/* for mdp5/apq8074, we manage our own pixel clk (as opposed to
|
||||
* mdp4/dtv stuff where pixel clk is assigned to mdp/encoder
|
||||
* instead):
|
||||
*/
|
||||
if (config->pwr_clk_cnt > 0)
|
||||
actual = clk_round_rate(hdmi->pwr_clks[0], actual);
|
||||
|
||||
DBG("requested=%ld, actual=%ld", requested, actual);
|
||||
|
||||
if (actual != requested)
|
||||
return MODE_CLOCK_RANGE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct drm_encoder *
|
||||
hdmi_connector_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
|
||||
return hdmi_connector->hdmi->encoder;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs hdmi_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.detect = hdmi_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = hdmi_connector_destroy,
|
||||
};
|
||||
|
||||
static const struct drm_connector_helper_funcs hdmi_connector_helper_funcs = {
|
||||
.get_modes = hdmi_connector_get_modes,
|
||||
.mode_valid = hdmi_connector_mode_valid,
|
||||
.best_encoder = hdmi_connector_best_encoder,
|
||||
};
|
||||
|
||||
/* initialize connector */
|
||||
struct drm_connector *hdmi_connector_init(struct hdmi *hdmi)
|
||||
{
|
||||
struct drm_connector *connector = NULL;
|
||||
struct hdmi_connector *hdmi_connector;
|
||||
int ret;
|
||||
|
||||
hdmi_connector = kzalloc(sizeof(*hdmi_connector), GFP_KERNEL);
|
||||
if (!hdmi_connector) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
hdmi_connector->hdmi = hdmi_reference(hdmi);
|
||||
INIT_WORK(&hdmi_connector->hpd_work, hotplug_work);
|
||||
|
||||
connector = &hdmi_connector->base;
|
||||
|
||||
drm_connector_init(hdmi->dev, connector, &hdmi_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_HDMIA);
|
||||
drm_connector_helper_add(connector, &hdmi_connector_helper_funcs);
|
||||
|
||||
connector->polled = DRM_CONNECTOR_POLL_CONNECT |
|
||||
DRM_CONNECTOR_POLL_DISCONNECT;
|
||||
|
||||
connector->interlace_allowed = 1;
|
||||
connector->doublescan_allowed = 0;
|
||||
|
||||
drm_connector_register(connector);
|
||||
|
||||
ret = hpd_enable(hdmi_connector);
|
||||
if (ret) {
|
||||
dev_err(hdmi->dev->dev, "failed to enable HPD: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
drm_mode_connector_attach_encoder(connector, hdmi->encoder);
|
||||
|
||||
return connector;
|
||||
|
||||
fail:
|
||||
if (connector)
|
||||
hdmi_connector_destroy(connector);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
281
drivers/gpu/drm/msm/hdmi/hdmi_i2c.c
Normal file
281
drivers/gpu/drm/msm/hdmi/hdmi_i2c.c
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hdmi.h"
|
||||
|
||||
struct hdmi_i2c_adapter {
|
||||
struct i2c_adapter base;
|
||||
struct hdmi *hdmi;
|
||||
bool sw_done;
|
||||
wait_queue_head_t ddc_event;
|
||||
};
|
||||
#define to_hdmi_i2c_adapter(x) container_of(x, struct hdmi_i2c_adapter, base)
|
||||
|
||||
static void init_ddc(struct hdmi_i2c_adapter *hdmi_i2c)
|
||||
{
|
||||
struct hdmi *hdmi = hdmi_i2c->hdmi;
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
|
||||
HDMI_DDC_CTRL_SW_STATUS_RESET);
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
|
||||
HDMI_DDC_CTRL_SOFT_RESET);
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_SPEED,
|
||||
HDMI_DDC_SPEED_THRESHOLD(2) |
|
||||
HDMI_DDC_SPEED_PRESCALE(10));
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_SETUP,
|
||||
HDMI_DDC_SETUP_TIMEOUT(0xff));
|
||||
|
||||
/* enable reference timer for 27us */
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_REF,
|
||||
HDMI_DDC_REF_REFTIMER_ENABLE |
|
||||
HDMI_DDC_REF_REFTIMER(27));
|
||||
}
|
||||
|
||||
static int ddc_clear_irq(struct hdmi_i2c_adapter *hdmi_i2c)
|
||||
{
|
||||
struct hdmi *hdmi = hdmi_i2c->hdmi;
|
||||
struct drm_device *dev = hdmi->dev;
|
||||
uint32_t retry = 0xffff;
|
||||
uint32_t ddc_int_ctrl;
|
||||
|
||||
do {
|
||||
--retry;
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,
|
||||
HDMI_DDC_INT_CTRL_SW_DONE_ACK |
|
||||
HDMI_DDC_INT_CTRL_SW_DONE_MASK);
|
||||
|
||||
ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);
|
||||
|
||||
} while ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT) && retry);
|
||||
|
||||
if (!retry) {
|
||||
dev_err(dev->dev, "timeout waiting for DDC\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
hdmi_i2c->sw_done = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_TRANSACTIONS 4
|
||||
|
||||
static bool sw_done(struct hdmi_i2c_adapter *hdmi_i2c)
|
||||
{
|
||||
struct hdmi *hdmi = hdmi_i2c->hdmi;
|
||||
|
||||
if (!hdmi_i2c->sw_done) {
|
||||
uint32_t ddc_int_ctrl;
|
||||
|
||||
ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);
|
||||
|
||||
if ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_MASK) &&
|
||||
(ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT)) {
|
||||
hdmi_i2c->sw_done = true;
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,
|
||||
HDMI_DDC_INT_CTRL_SW_DONE_ACK);
|
||||
}
|
||||
}
|
||||
|
||||
return hdmi_i2c->sw_done;
|
||||
}
|
||||
|
||||
static int hdmi_i2c_xfer(struct i2c_adapter *i2c,
|
||||
struct i2c_msg *msgs, int num)
|
||||
{
|
||||
struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
|
||||
struct hdmi *hdmi = hdmi_i2c->hdmi;
|
||||
struct drm_device *dev = hdmi->dev;
|
||||
static const uint32_t nack[] = {
|
||||
HDMI_DDC_SW_STATUS_NACK0, HDMI_DDC_SW_STATUS_NACK1,
|
||||
HDMI_DDC_SW_STATUS_NACK2, HDMI_DDC_SW_STATUS_NACK3,
|
||||
};
|
||||
int indices[MAX_TRANSACTIONS];
|
||||
int ret, i, j, index = 0;
|
||||
uint32_t ddc_status, ddc_data, i2c_trans;
|
||||
|
||||
num = min(num, MAX_TRANSACTIONS);
|
||||
|
||||
WARN_ON(!(hdmi_read(hdmi, REG_HDMI_CTRL) & HDMI_CTRL_ENABLE));
|
||||
|
||||
if (num == 0)
|
||||
return num;
|
||||
|
||||
init_ddc(hdmi_i2c);
|
||||
|
||||
ret = ddc_clear_irq(hdmi_i2c);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
struct i2c_msg *p = &msgs[i];
|
||||
uint32_t raw_addr = p->addr << 1;
|
||||
|
||||
if (p->flags & I2C_M_RD)
|
||||
raw_addr |= 1;
|
||||
|
||||
ddc_data = HDMI_DDC_DATA_DATA(raw_addr) |
|
||||
HDMI_DDC_DATA_DATA_RW(DDC_WRITE);
|
||||
|
||||
if (i == 0) {
|
||||
ddc_data |= HDMI_DDC_DATA_INDEX(0) |
|
||||
HDMI_DDC_DATA_INDEX_WRITE;
|
||||
}
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
|
||||
index++;
|
||||
|
||||
indices[i] = index;
|
||||
|
||||
if (p->flags & I2C_M_RD) {
|
||||
index += p->len;
|
||||
} else {
|
||||
for (j = 0; j < p->len; j++) {
|
||||
ddc_data = HDMI_DDC_DATA_DATA(p->buf[j]) |
|
||||
HDMI_DDC_DATA_DATA_RW(DDC_WRITE);
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
i2c_trans = HDMI_I2C_TRANSACTION_REG_CNT(p->len) |
|
||||
HDMI_I2C_TRANSACTION_REG_RW(
|
||||
(p->flags & I2C_M_RD) ? DDC_READ : DDC_WRITE) |
|
||||
HDMI_I2C_TRANSACTION_REG_START;
|
||||
|
||||
if (i == (num - 1))
|
||||
i2c_trans |= HDMI_I2C_TRANSACTION_REG_STOP;
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_I2C_TRANSACTION(i), i2c_trans);
|
||||
}
|
||||
|
||||
/* trigger the transfer: */
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
|
||||
HDMI_DDC_CTRL_TRANSACTION_CNT(num - 1) |
|
||||
HDMI_DDC_CTRL_GO);
|
||||
|
||||
ret = wait_event_timeout(hdmi_i2c->ddc_event, sw_done(hdmi_i2c), HZ/4);
|
||||
if (ret <= 0) {
|
||||
if (ret == 0)
|
||||
ret = -ETIMEDOUT;
|
||||
dev_warn(dev->dev, "DDC timeout: %d\n", ret);
|
||||
DBG("sw_status=%08x, hw_status=%08x, int_ctrl=%08x",
|
||||
hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS),
|
||||
hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS),
|
||||
hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ddc_status = hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS);
|
||||
|
||||
/* read back results of any read transactions: */
|
||||
for (i = 0; i < num; i++) {
|
||||
struct i2c_msg *p = &msgs[i];
|
||||
|
||||
if (!(p->flags & I2C_M_RD))
|
||||
continue;
|
||||
|
||||
/* check for NACK: */
|
||||
if (ddc_status & nack[i]) {
|
||||
DBG("ddc_status=%08x", ddc_status);
|
||||
break;
|
||||
}
|
||||
|
||||
ddc_data = HDMI_DDC_DATA_DATA_RW(DDC_READ) |
|
||||
HDMI_DDC_DATA_INDEX(indices[i]) |
|
||||
HDMI_DDC_DATA_INDEX_WRITE;
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
|
||||
|
||||
/* discard first byte: */
|
||||
hdmi_read(hdmi, REG_HDMI_DDC_DATA);
|
||||
|
||||
for (j = 0; j < p->len; j++) {
|
||||
ddc_data = hdmi_read(hdmi, REG_HDMI_DDC_DATA);
|
||||
p->buf[j] = FIELD(ddc_data, HDMI_DDC_DATA_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static u32 hdmi_i2c_func(struct i2c_adapter *adapter)
|
||||
{
|
||||
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm hdmi_i2c_algorithm = {
|
||||
.master_xfer = hdmi_i2c_xfer,
|
||||
.functionality = hdmi_i2c_func,
|
||||
};
|
||||
|
||||
void hdmi_i2c_irq(struct i2c_adapter *i2c)
|
||||
{
|
||||
struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
|
||||
|
||||
if (sw_done(hdmi_i2c))
|
||||
wake_up_all(&hdmi_i2c->ddc_event);
|
||||
}
|
||||
|
||||
void hdmi_i2c_destroy(struct i2c_adapter *i2c)
|
||||
{
|
||||
struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
|
||||
i2c_del_adapter(i2c);
|
||||
kfree(hdmi_i2c);
|
||||
}
|
||||
|
||||
struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi)
|
||||
{
|
||||
struct drm_device *dev = hdmi->dev;
|
||||
struct hdmi_i2c_adapter *hdmi_i2c;
|
||||
struct i2c_adapter *i2c = NULL;
|
||||
int ret;
|
||||
|
||||
hdmi_i2c = kzalloc(sizeof(*hdmi_i2c), GFP_KERNEL);
|
||||
if (!hdmi_i2c) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
i2c = &hdmi_i2c->base;
|
||||
|
||||
hdmi_i2c->hdmi = hdmi;
|
||||
init_waitqueue_head(&hdmi_i2c->ddc_event);
|
||||
|
||||
|
||||
i2c->owner = THIS_MODULE;
|
||||
i2c->class = I2C_CLASS_DDC;
|
||||
snprintf(i2c->name, sizeof(i2c->name), "msm hdmi i2c");
|
||||
i2c->dev.parent = &hdmi->pdev->dev;
|
||||
i2c->algo = &hdmi_i2c_algorithm;
|
||||
|
||||
ret = i2c_add_adapter(i2c);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to register hdmi i2c: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return i2c;
|
||||
|
||||
fail:
|
||||
if (i2c)
|
||||
hdmi_i2c_destroy(i2c);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
527
drivers/gpu/drm/msm/hdmi/hdmi_phy_8960.c
Normal file
527
drivers/gpu/drm/msm/hdmi/hdmi_phy_8960.c
Normal file
|
|
@ -0,0 +1,527 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_COMMON_CLK
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#endif
|
||||
|
||||
#include "hdmi.h"
|
||||
|
||||
struct hdmi_phy_8960 {
|
||||
struct hdmi_phy base;
|
||||
struct hdmi *hdmi;
|
||||
#ifdef CONFIG_COMMON_CLK
|
||||
struct clk_hw pll_hw;
|
||||
struct clk *pll;
|
||||
unsigned long pixclk;
|
||||
#endif
|
||||
};
|
||||
#define to_hdmi_phy_8960(x) container_of(x, struct hdmi_phy_8960, base)
|
||||
|
||||
#ifdef CONFIG_COMMON_CLK
|
||||
#define clk_to_phy(x) container_of(x, struct hdmi_phy_8960, pll_hw)
|
||||
|
||||
/*
|
||||
* HDMI PLL:
|
||||
*
|
||||
* To get the parent clock setup properly, we need to plug in hdmi pll
|
||||
* configuration into common-clock-framework.
|
||||
*/
|
||||
|
||||
struct pll_rate {
|
||||
unsigned long rate;
|
||||
struct {
|
||||
uint32_t val;
|
||||
uint32_t reg;
|
||||
} conf[32];
|
||||
};
|
||||
|
||||
/* NOTE: keep sorted highest freq to lowest: */
|
||||
static const struct pll_rate freqtbl[] = {
|
||||
/* 1080p60/1080p50 case */
|
||||
{ 148500000, {
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG },
|
||||
{ 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
|
||||
{ 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
|
||||
{ 0x2c, REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG },
|
||||
{ 0x06, REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG },
|
||||
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
|
||||
{ 0x76, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
|
||||
{ 0x01, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
|
||||
{ 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
|
||||
{ 0xc0, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
|
||||
{ 0x9a, REG_HDMI_8960_PHY_PLL_SSC_CFG0 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG1 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG2 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG3 },
|
||||
{ 0x10, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 },
|
||||
{ 0x1a, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 },
|
||||
{ 0x0d, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 },
|
||||
{ 0xe6, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
|
||||
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
|
||||
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
|
||||
{ 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 },
|
||||
{ 0, 0 } }
|
||||
},
|
||||
{ 108000000, {
|
||||
{ 0x08, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
|
||||
{ 0x21, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
|
||||
{ 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
|
||||
{ 0x1c, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
|
||||
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
|
||||
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
|
||||
{ 0x49, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
|
||||
{ 0x49, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
|
||||
{ 0, 0 } }
|
||||
},
|
||||
/* 720p60/720p50/1080i60/1080i50/1080p24/1080p30/1080p25 */
|
||||
{ 74250000, {
|
||||
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
|
||||
{ 0x12, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
|
||||
{ 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
|
||||
{ 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
|
||||
{ 0x76, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
|
||||
{ 0xe6, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
|
||||
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
|
||||
{ 0, 0 } }
|
||||
},
|
||||
{ 65000000, {
|
||||
{ 0x18, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
|
||||
{ 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
|
||||
{ 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
|
||||
{ 0x8a, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
|
||||
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
|
||||
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
|
||||
{ 0x0b, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
|
||||
{ 0x4b, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
|
||||
{ 0x7b, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
|
||||
{ 0x09, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
|
||||
{ 0, 0 } }
|
||||
},
|
||||
/* 480p60/480i60 */
|
||||
{ 27030000, {
|
||||
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
|
||||
{ 0x38, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG },
|
||||
{ 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
|
||||
{ 0xff, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
|
||||
{ 0x4e, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
|
||||
{ 0xd7, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
|
||||
{ 0x03, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
|
||||
{ 0x2a, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
|
||||
{ 0x03, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
|
||||
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
|
||||
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
|
||||
{ 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 },
|
||||
{ 0, 0 } }
|
||||
},
|
||||
/* 576p50/576i50 */
|
||||
{ 27000000, {
|
||||
{ 0x32, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG },
|
||||
{ 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
|
||||
{ 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
|
||||
{ 0x2c, REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG },
|
||||
{ 0x06, REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG },
|
||||
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
|
||||
{ 0x7b, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
|
||||
{ 0x01, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
|
||||
{ 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
|
||||
{ 0xc0, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
|
||||
{ 0x9a, REG_HDMI_8960_PHY_PLL_SSC_CFG0 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG1 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG2 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG3 },
|
||||
{ 0x10, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 },
|
||||
{ 0x1a, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 },
|
||||
{ 0x0d, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 },
|
||||
{ 0x2a, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
|
||||
{ 0x03, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
|
||||
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
|
||||
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
|
||||
{ 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 },
|
||||
{ 0, 0 } }
|
||||
},
|
||||
/* 640x480p60 */
|
||||
{ 25200000, {
|
||||
{ 0x32, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG },
|
||||
{ 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
|
||||
{ 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
|
||||
{ 0x2c, REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG },
|
||||
{ 0x06, REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG },
|
||||
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
|
||||
{ 0x77, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
|
||||
{ 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
|
||||
{ 0xc0, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
|
||||
{ 0x9a, REG_HDMI_8960_PHY_PLL_SSC_CFG0 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG1 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG2 },
|
||||
{ 0x20, REG_HDMI_8960_PHY_PLL_SSC_CFG3 },
|
||||
{ 0x10, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 },
|
||||
{ 0x1a, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 },
|
||||
{ 0x0d, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 },
|
||||
{ 0xf4, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
|
||||
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
|
||||
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
|
||||
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
|
||||
{ 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 },
|
||||
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 },
|
||||
{ 0, 0 } }
|
||||
},
|
||||
};
|
||||
|
||||
static int hdmi_pll_enable(struct clk_hw *hw)
|
||||
{
|
||||
struct hdmi_phy_8960 *phy_8960 = clk_to_phy(hw);
|
||||
struct hdmi *hdmi = phy_8960->hdmi;
|
||||
int timeout_count, pll_lock_retry = 10;
|
||||
unsigned int val;
|
||||
|
||||
DBG("");
|
||||
|
||||
/* Assert PLL S/W reset */
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x8d);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0, 0x10);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1, 0x1a);
|
||||
|
||||
/* Wait for a short time before de-asserting
|
||||
* to allow the hardware to complete its job.
|
||||
* This much of delay should be fine for hardware
|
||||
* to assert and de-assert.
|
||||
*/
|
||||
udelay(10);
|
||||
|
||||
/* De-assert PLL S/W reset */
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x0d);
|
||||
|
||||
val = hdmi_read(hdmi, REG_HDMI_8960_PHY_REG12);
|
||||
val |= HDMI_8960_PHY_REG12_SW_RESET;
|
||||
/* Assert PHY S/W reset */
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG12, val);
|
||||
val &= ~HDMI_8960_PHY_REG12_SW_RESET;
|
||||
/* Wait for a short time before de-asserting
|
||||
to allow the hardware to complete its job.
|
||||
This much of delay should be fine for hardware
|
||||
to assert and de-assert. */
|
||||
udelay(10);
|
||||
/* De-assert PHY S/W reset */
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG12, val);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG2, 0x3f);
|
||||
|
||||
val = hdmi_read(hdmi, REG_HDMI_8960_PHY_REG12);
|
||||
val |= HDMI_8960_PHY_REG12_PWRDN_B;
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG12, val);
|
||||
/* Wait 10 us for enabling global power for PHY */
|
||||
mb();
|
||||
udelay(10);
|
||||
|
||||
val = hdmi_read(hdmi, REG_HDMI_8960_PHY_PLL_PWRDN_B);
|
||||
val |= HDMI_8960_PHY_PLL_PWRDN_B_PLL_PWRDN_B;
|
||||
val &= ~HDMI_8960_PHY_PLL_PWRDN_B_PD_PLL;
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_PLL_PWRDN_B, val);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG2, 0x80);
|
||||
|
||||
timeout_count = 1000;
|
||||
while (--pll_lock_retry > 0) {
|
||||
|
||||
/* are we there yet? */
|
||||
val = hdmi_read(hdmi, REG_HDMI_8960_PHY_PLL_STATUS0);
|
||||
if (val & HDMI_8960_PHY_PLL_STATUS0_PLL_LOCK)
|
||||
break;
|
||||
|
||||
udelay(1);
|
||||
|
||||
if (--timeout_count > 0)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* PLL has still not locked.
|
||||
* Do a software reset and try again
|
||||
* Assert PLL S/W reset first
|
||||
*/
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x8d);
|
||||
udelay(10);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x0d);
|
||||
|
||||
/*
|
||||
* Wait for a short duration for the PLL calibration
|
||||
* before checking if the PLL gets locked
|
||||
*/
|
||||
udelay(350);
|
||||
|
||||
timeout_count = 1000;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hdmi_pll_disable(struct clk_hw *hw)
|
||||
{
|
||||
struct hdmi_phy_8960 *phy_8960 = clk_to_phy(hw);
|
||||
struct hdmi *hdmi = phy_8960->hdmi;
|
||||
unsigned int val;
|
||||
|
||||
DBG("");
|
||||
|
||||
val = hdmi_read(hdmi, REG_HDMI_8960_PHY_REG12);
|
||||
val &= ~HDMI_8960_PHY_REG12_PWRDN_B;
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG12, val);
|
||||
|
||||
val = hdmi_read(hdmi, REG_HDMI_8960_PHY_PLL_PWRDN_B);
|
||||
val |= HDMI_8960_PHY_REG12_SW_RESET;
|
||||
val &= ~HDMI_8960_PHY_REG12_PWRDN_B;
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_PLL_PWRDN_B, val);
|
||||
/* Make sure HDMI PHY/PLL are powered down */
|
||||
mb();
|
||||
}
|
||||
|
||||
static const struct pll_rate *find_rate(unsigned long rate)
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i < ARRAY_SIZE(freqtbl); i++)
|
||||
if (rate > freqtbl[i].rate)
|
||||
return &freqtbl[i-1];
|
||||
return &freqtbl[i-1];
|
||||
}
|
||||
|
||||
static unsigned long hdmi_pll_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct hdmi_phy_8960 *phy_8960 = clk_to_phy(hw);
|
||||
return phy_8960->pixclk;
|
||||
}
|
||||
|
||||
static long hdmi_pll_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *parent_rate)
|
||||
{
|
||||
const struct pll_rate *pll_rate = find_rate(rate);
|
||||
return pll_rate->rate;
|
||||
}
|
||||
|
||||
static int hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct hdmi_phy_8960 *phy_8960 = clk_to_phy(hw);
|
||||
struct hdmi *hdmi = phy_8960->hdmi;
|
||||
const struct pll_rate *pll_rate = find_rate(rate);
|
||||
int i;
|
||||
|
||||
DBG("rate=%lu", rate);
|
||||
|
||||
for (i = 0; pll_rate->conf[i].reg; i++)
|
||||
hdmi_write(hdmi, pll_rate->conf[i].reg, pll_rate->conf[i].val);
|
||||
|
||||
phy_8960->pixclk = rate;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct clk_ops hdmi_pll_ops = {
|
||||
.enable = hdmi_pll_enable,
|
||||
.disable = hdmi_pll_disable,
|
||||
.recalc_rate = hdmi_pll_recalc_rate,
|
||||
.round_rate = hdmi_pll_round_rate,
|
||||
.set_rate = hdmi_pll_set_rate,
|
||||
};
|
||||
|
||||
static const char *hdmi_pll_parents[] = {
|
||||
"pxo",
|
||||
};
|
||||
|
||||
static struct clk_init_data pll_init = {
|
||||
.name = "hdmi_pll",
|
||||
.ops = &hdmi_pll_ops,
|
||||
.parent_names = hdmi_pll_parents,
|
||||
.num_parents = ARRAY_SIZE(hdmi_pll_parents),
|
||||
};
|
||||
#endif
|
||||
|
||||
/*
|
||||
* HDMI Phy:
|
||||
*/
|
||||
|
||||
static void hdmi_phy_8960_destroy(struct hdmi_phy *phy)
|
||||
{
|
||||
struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy);
|
||||
kfree(phy_8960);
|
||||
}
|
||||
|
||||
static void hdmi_phy_8960_reset(struct hdmi_phy *phy)
|
||||
{
|
||||
struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy);
|
||||
struct hdmi *hdmi = phy_8960->hdmi;
|
||||
unsigned int val;
|
||||
|
||||
val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL);
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET);
|
||||
} else {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET);
|
||||
}
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
|
||||
} else {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET_PLL);
|
||||
}
|
||||
|
||||
msleep(100);
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET);
|
||||
} else {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET);
|
||||
}
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET_PLL);
|
||||
} else {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
|
||||
}
|
||||
}
|
||||
|
||||
static void hdmi_phy_8960_powerup(struct hdmi_phy *phy,
|
||||
unsigned long int pixclock)
|
||||
{
|
||||
struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy);
|
||||
struct hdmi *hdmi = phy_8960->hdmi;
|
||||
|
||||
DBG("pixclock: %lu", pixclock);
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG2, 0x00);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG0, 0x1b);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG1, 0xf2);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG4, 0x00);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG5, 0x00);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG6, 0x00);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG7, 0x00);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG8, 0x00);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG9, 0x00);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG10, 0x00);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG11, 0x00);
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG3, 0x20);
|
||||
}
|
||||
|
||||
static void hdmi_phy_8960_powerdown(struct hdmi_phy *phy)
|
||||
{
|
||||
struct hdmi_phy_8960 *phy_8960 = to_hdmi_phy_8960(phy);
|
||||
struct hdmi *hdmi = phy_8960->hdmi;
|
||||
|
||||
DBG("");
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_8960_PHY_REG2, 0x7f);
|
||||
}
|
||||
|
||||
static const struct hdmi_phy_funcs hdmi_phy_8960_funcs = {
|
||||
.destroy = hdmi_phy_8960_destroy,
|
||||
.reset = hdmi_phy_8960_reset,
|
||||
.powerup = hdmi_phy_8960_powerup,
|
||||
.powerdown = hdmi_phy_8960_powerdown,
|
||||
};
|
||||
|
||||
struct hdmi_phy *hdmi_phy_8960_init(struct hdmi *hdmi)
|
||||
{
|
||||
struct hdmi_phy_8960 *phy_8960;
|
||||
struct hdmi_phy *phy = NULL;
|
||||
int ret;
|
||||
#ifdef CONFIG_COMMON_CLK
|
||||
int i;
|
||||
|
||||
/* sanity check: */
|
||||
for (i = 0; i < (ARRAY_SIZE(freqtbl) - 1); i++)
|
||||
if (WARN_ON(freqtbl[i].rate < freqtbl[i+1].rate))
|
||||
return ERR_PTR(-EINVAL);
|
||||
#endif
|
||||
|
||||
phy_8960 = kzalloc(sizeof(*phy_8960), GFP_KERNEL);
|
||||
if (!phy_8960) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
phy = &phy_8960->base;
|
||||
|
||||
phy->funcs = &hdmi_phy_8960_funcs;
|
||||
|
||||
phy_8960->hdmi = hdmi;
|
||||
|
||||
#ifdef CONFIG_COMMON_CLK
|
||||
phy_8960->pll_hw.init = &pll_init;
|
||||
phy_8960->pll = devm_clk_register(hdmi->dev->dev, &phy_8960->pll_hw);
|
||||
if (IS_ERR(phy_8960->pll)) {
|
||||
ret = PTR_ERR(phy_8960->pll);
|
||||
phy_8960->pll = NULL;
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
return phy;
|
||||
|
||||
fail:
|
||||
if (phy)
|
||||
hdmi_phy_8960_destroy(phy);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
214
drivers/gpu/drm/msm/hdmi/hdmi_phy_8x60.c
Normal file
214
drivers/gpu/drm/msm/hdmi/hdmi_phy_8x60.c
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hdmi.h"
|
||||
|
||||
struct hdmi_phy_8x60 {
|
||||
struct hdmi_phy base;
|
||||
struct hdmi *hdmi;
|
||||
};
|
||||
#define to_hdmi_phy_8x60(x) container_of(x, struct hdmi_phy_8x60, base)
|
||||
|
||||
static void hdmi_phy_8x60_destroy(struct hdmi_phy *phy)
|
||||
{
|
||||
struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy);
|
||||
kfree(phy_8x60);
|
||||
}
|
||||
|
||||
static void hdmi_phy_8x60_reset(struct hdmi_phy *phy)
|
||||
{
|
||||
struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy);
|
||||
struct hdmi *hdmi = phy_8x60->hdmi;
|
||||
unsigned int val;
|
||||
|
||||
val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL);
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET);
|
||||
} else {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET);
|
||||
}
|
||||
|
||||
msleep(100);
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET);
|
||||
} else {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET);
|
||||
}
|
||||
}
|
||||
|
||||
static void hdmi_phy_8x60_powerup(struct hdmi_phy *phy,
|
||||
unsigned long int pixclock)
|
||||
{
|
||||
struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy);
|
||||
struct hdmi *hdmi = phy_8x60->hdmi;
|
||||
|
||||
/* De-serializer delay D/C for non-lbk mode: */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG0,
|
||||
HDMI_8x60_PHY_REG0_DESER_DEL_CTRL(3));
|
||||
|
||||
if (pixclock == 27000000) {
|
||||
/* video_format == HDMI_VFRMT_720x480p60_16_9 */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG1,
|
||||
HDMI_8x60_PHY_REG1_DTEST_MUX_SEL(5) |
|
||||
HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL(3));
|
||||
} else {
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG1,
|
||||
HDMI_8x60_PHY_REG1_DTEST_MUX_SEL(5) |
|
||||
HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL(4));
|
||||
}
|
||||
|
||||
/* No matter what, start from the power down mode: */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
|
||||
HDMI_8x60_PHY_REG2_PD_PWRGEN |
|
||||
HDMI_8x60_PHY_REG2_PD_PLL |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
|
||||
HDMI_8x60_PHY_REG2_PD_DESER);
|
||||
|
||||
/* Turn PowerGen on: */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
|
||||
HDMI_8x60_PHY_REG2_PD_PLL |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
|
||||
HDMI_8x60_PHY_REG2_PD_DESER);
|
||||
|
||||
/* Turn PLL power on: */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
|
||||
HDMI_8x60_PHY_REG2_PD_DESER);
|
||||
|
||||
/* Write to HIGH after PLL power down de-assert: */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG3,
|
||||
HDMI_8x60_PHY_REG3_PLL_ENABLE);
|
||||
|
||||
/* ASIC power on; PHY REG9 = 0 */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG9, 0);
|
||||
|
||||
/* Enable PLL lock detect, PLL lock det will go high after lock
|
||||
* Enable the re-time logic
|
||||
*/
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG12,
|
||||
HDMI_8x60_PHY_REG12_RETIMING_EN |
|
||||
HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN);
|
||||
|
||||
/* Drivers are on: */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
|
||||
HDMI_8x60_PHY_REG2_PD_DESER);
|
||||
|
||||
/* If the RX detector is needed: */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
|
||||
HDMI_8x60_PHY_REG2_RCV_SENSE_EN |
|
||||
HDMI_8x60_PHY_REG2_PD_DESER);
|
||||
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG4, 0);
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG5, 0);
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG6, 0);
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG7, 0);
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG8, 0);
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG9, 0);
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG10, 0);
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG11, 0);
|
||||
|
||||
/* If we want to use lock enable based on counting: */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG12,
|
||||
HDMI_8x60_PHY_REG12_RETIMING_EN |
|
||||
HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN |
|
||||
HDMI_8x60_PHY_REG12_FORCE_LOCK);
|
||||
}
|
||||
|
||||
static void hdmi_phy_8x60_powerdown(struct hdmi_phy *phy)
|
||||
{
|
||||
struct hdmi_phy_8x60 *phy_8x60 = to_hdmi_phy_8x60(phy);
|
||||
struct hdmi *hdmi = phy_8x60->hdmi;
|
||||
|
||||
/* Assert RESET PHY from controller */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
HDMI_PHY_CTRL_SW_RESET);
|
||||
udelay(10);
|
||||
/* De-assert RESET PHY from controller */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL, 0);
|
||||
/* Turn off Driver */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
|
||||
HDMI_8x60_PHY_REG2_PD_DESER);
|
||||
udelay(10);
|
||||
/* Disable PLL */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG3, 0);
|
||||
/* Power down PHY, but keep RX-sense: */
|
||||
hdmi_write(hdmi, REG_HDMI_8x60_PHY_REG2,
|
||||
HDMI_8x60_PHY_REG2_RCV_SENSE_EN |
|
||||
HDMI_8x60_PHY_REG2_PD_PWRGEN |
|
||||
HDMI_8x60_PHY_REG2_PD_PLL |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
|
||||
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
|
||||
HDMI_8x60_PHY_REG2_PD_DESER);
|
||||
}
|
||||
|
||||
static const struct hdmi_phy_funcs hdmi_phy_8x60_funcs = {
|
||||
.destroy = hdmi_phy_8x60_destroy,
|
||||
.reset = hdmi_phy_8x60_reset,
|
||||
.powerup = hdmi_phy_8x60_powerup,
|
||||
.powerdown = hdmi_phy_8x60_powerdown,
|
||||
};
|
||||
|
||||
struct hdmi_phy *hdmi_phy_8x60_init(struct hdmi *hdmi)
|
||||
{
|
||||
struct hdmi_phy_8x60 *phy_8x60;
|
||||
struct hdmi_phy *phy = NULL;
|
||||
int ret;
|
||||
|
||||
phy_8x60 = kzalloc(sizeof(*phy_8x60), GFP_KERNEL);
|
||||
if (!phy_8x60) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
phy = &phy_8x60->base;
|
||||
|
||||
phy->funcs = &hdmi_phy_8x60_funcs;
|
||||
|
||||
phy_8x60->hdmi = hdmi;
|
||||
|
||||
return phy;
|
||||
|
||||
fail:
|
||||
if (phy)
|
||||
hdmi_phy_8x60_destroy(phy);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
157
drivers/gpu/drm/msm/hdmi/hdmi_phy_8x74.c
Normal file
157
drivers/gpu/drm/msm/hdmi/hdmi_phy_8x74.c
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hdmi.h"
|
||||
|
||||
struct hdmi_phy_8x74 {
|
||||
struct hdmi_phy base;
|
||||
struct hdmi *hdmi;
|
||||
void __iomem *mmio;
|
||||
};
|
||||
#define to_hdmi_phy_8x74(x) container_of(x, struct hdmi_phy_8x74, base)
|
||||
|
||||
|
||||
static void phy_write(struct hdmi_phy_8x74 *phy, u32 reg, u32 data)
|
||||
{
|
||||
msm_writel(data, phy->mmio + reg);
|
||||
}
|
||||
|
||||
//static u32 phy_read(struct hdmi_phy_8x74 *phy, u32 reg)
|
||||
//{
|
||||
// return msm_readl(phy->mmio + reg);
|
||||
//}
|
||||
|
||||
static void hdmi_phy_8x74_destroy(struct hdmi_phy *phy)
|
||||
{
|
||||
struct hdmi_phy_8x74 *phy_8x74 = to_hdmi_phy_8x74(phy);
|
||||
kfree(phy_8x74);
|
||||
}
|
||||
|
||||
static void hdmi_phy_8x74_reset(struct hdmi_phy *phy)
|
||||
{
|
||||
struct hdmi_phy_8x74 *phy_8x74 = to_hdmi_phy_8x74(phy);
|
||||
struct hdmi *hdmi = phy_8x74->hdmi;
|
||||
unsigned int val;
|
||||
|
||||
/* NOTE that HDMI_PHY_CTL is in core mmio, not phy mmio: */
|
||||
|
||||
val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL);
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET);
|
||||
} else {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET);
|
||||
}
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
|
||||
} else {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET_PLL);
|
||||
}
|
||||
|
||||
msleep(100);
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET);
|
||||
} else {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET);
|
||||
}
|
||||
|
||||
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
|
||||
/* pull high */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val | HDMI_PHY_CTRL_SW_RESET_PLL);
|
||||
} else {
|
||||
/* pull low */
|
||||
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
|
||||
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
|
||||
}
|
||||
}
|
||||
|
||||
static void hdmi_phy_8x74_powerup(struct hdmi_phy *phy,
|
||||
unsigned long int pixclock)
|
||||
{
|
||||
struct hdmi_phy_8x74 *phy_8x74 = to_hdmi_phy_8x74(phy);
|
||||
|
||||
phy_write(phy_8x74, REG_HDMI_8x74_ANA_CFG0, 0x1b);
|
||||
phy_write(phy_8x74, REG_HDMI_8x74_ANA_CFG1, 0xf2);
|
||||
phy_write(phy_8x74, REG_HDMI_8x74_BIST_CFG0, 0x0);
|
||||
phy_write(phy_8x74, REG_HDMI_8x74_BIST_PATN0, 0x0);
|
||||
phy_write(phy_8x74, REG_HDMI_8x74_BIST_PATN1, 0x0);
|
||||
phy_write(phy_8x74, REG_HDMI_8x74_BIST_PATN2, 0x0);
|
||||
phy_write(phy_8x74, REG_HDMI_8x74_BIST_PATN3, 0x0);
|
||||
phy_write(phy_8x74, REG_HDMI_8x74_PD_CTRL1, 0x20);
|
||||
}
|
||||
|
||||
static void hdmi_phy_8x74_powerdown(struct hdmi_phy *phy)
|
||||
{
|
||||
struct hdmi_phy_8x74 *phy_8x74 = to_hdmi_phy_8x74(phy);
|
||||
phy_write(phy_8x74, REG_HDMI_8x74_PD_CTRL0, 0x7f);
|
||||
}
|
||||
|
||||
static const struct hdmi_phy_funcs hdmi_phy_8x74_funcs = {
|
||||
.destroy = hdmi_phy_8x74_destroy,
|
||||
.reset = hdmi_phy_8x74_reset,
|
||||
.powerup = hdmi_phy_8x74_powerup,
|
||||
.powerdown = hdmi_phy_8x74_powerdown,
|
||||
};
|
||||
|
||||
struct hdmi_phy *hdmi_phy_8x74_init(struct hdmi *hdmi)
|
||||
{
|
||||
struct hdmi_phy_8x74 *phy_8x74;
|
||||
struct hdmi_phy *phy = NULL;
|
||||
int ret;
|
||||
|
||||
phy_8x74 = kzalloc(sizeof(*phy_8x74), GFP_KERNEL);
|
||||
if (!phy_8x74) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
phy = &phy_8x74->base;
|
||||
|
||||
phy->funcs = &hdmi_phy_8x74_funcs;
|
||||
|
||||
phy_8x74->hdmi = hdmi;
|
||||
|
||||
/* for 8x74, the phy mmio is mapped separately: */
|
||||
phy_8x74->mmio = msm_ioremap(hdmi->pdev,
|
||||
"phy_physical", "HDMI_8x74");
|
||||
if (IS_ERR(phy_8x74->mmio)) {
|
||||
ret = PTR_ERR(phy_8x74->mmio);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return phy;
|
||||
|
||||
fail:
|
||||
if (phy)
|
||||
hdmi_phy_8x74_destroy(phy);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
52
drivers/gpu/drm/msm/hdmi/qfprom.xml.h
Normal file
52
drivers/gpu/drm/msm/hdmi/qfprom.xml.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef QFPROM_XML
|
||||
#define QFPROM_XML
|
||||
|
||||
/* Autogenerated file, DO NOT EDIT manually!
|
||||
|
||||
This file was generated by the rules-ng-ng headergen tool in this git repository:
|
||||
http://github.com/freedreno/envytools/
|
||||
git clone https://github.com/freedreno/envytools.git
|
||||
|
||||
The rules-ng-ng source files this header was generated from are:
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/msm.xml ( 647 bytes, from 2013-11-30 14:45:35)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/freedreno_copyright.xml ( 1453 bytes, from 2013-03-31 16:51:27)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp4.xml ( 20457 bytes, from 2014-08-01 12:22:48)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp_common.xml ( 1615 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp5.xml ( 22517 bytes, from 2014-07-17 15:34:33)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/dsi.xml ( 11712 bytes, from 2013-08-17 17:13:43)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/sfpb.xml ( 344 bytes, from 2013-08-11 19:26:32)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/mmss_cc.xml ( 1686 bytes, from 2014-08-01 12:23:53)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2013-07-05 19:21:12)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/hdmi.xml ( 23613 bytes, from 2014-07-17 15:33:30)
|
||||
|
||||
Copyright (C) 2013 by the following authors:
|
||||
- Rob Clark <robdclark@gmail.com> (robclark)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the
|
||||
next paragraph) shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#define REG_QFPROM_CONFIG_ROW0_LSB 0x00000238
|
||||
#define QFPROM_CONFIG_ROW0_LSB_HDMI_DISABLE 0x00200000
|
||||
#define QFPROM_CONFIG_ROW0_LSB_HDCP_DISABLE 0x00400000
|
||||
|
||||
|
||||
#endif /* QFPROM_XML */
|
||||
1128
drivers/gpu/drm/msm/mdp/mdp4/mdp4.xml.h
Normal file
1128
drivers/gpu/drm/msm/mdp/mdp4/mdp4.xml.h
Normal file
File diff suppressed because it is too large
Load diff
809
drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
Normal file
809
drivers/gpu/drm/msm/mdp/mdp4/mdp4_crtc.c
Normal file
|
|
@ -0,0 +1,809 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mdp4_kms.h"
|
||||
|
||||
#include <drm/drm_mode.h>
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_crtc_helper.h"
|
||||
#include "drm_flip_work.h"
|
||||
|
||||
struct mdp4_crtc {
|
||||
struct drm_crtc base;
|
||||
char name[8];
|
||||
struct drm_plane *plane;
|
||||
struct drm_plane *planes[8];
|
||||
int id;
|
||||
int ovlp;
|
||||
enum mdp4_dma dma;
|
||||
bool enabled;
|
||||
|
||||
/* which mixer/encoder we route output to: */
|
||||
int mixer;
|
||||
|
||||
struct {
|
||||
spinlock_t lock;
|
||||
bool stale;
|
||||
uint32_t width, height;
|
||||
uint32_t x, y;
|
||||
|
||||
/* next cursor to scan-out: */
|
||||
uint32_t next_iova;
|
||||
struct drm_gem_object *next_bo;
|
||||
|
||||
/* current cursor being scanned out: */
|
||||
struct drm_gem_object *scanout_bo;
|
||||
} cursor;
|
||||
|
||||
|
||||
/* if there is a pending flip, these will be non-null: */
|
||||
struct drm_pending_vblank_event *event;
|
||||
struct msm_fence_cb pageflip_cb;
|
||||
|
||||
#define PENDING_CURSOR 0x1
|
||||
#define PENDING_FLIP 0x2
|
||||
atomic_t pending;
|
||||
|
||||
/* the fb that we logically (from PoV of KMS API) hold a ref
|
||||
* to. Which we may not yet be scanning out (we may still
|
||||
* be scanning out previous in case of page_flip while waiting
|
||||
* for gpu rendering to complete:
|
||||
*/
|
||||
struct drm_framebuffer *fb;
|
||||
|
||||
/* the fb that we currently hold a scanout ref to: */
|
||||
struct drm_framebuffer *scanout_fb;
|
||||
|
||||
/* for unref'ing framebuffers after scanout completes: */
|
||||
struct drm_flip_work unref_fb_work;
|
||||
|
||||
/* for unref'ing cursor bo's after scanout completes: */
|
||||
struct drm_flip_work unref_cursor_work;
|
||||
|
||||
struct mdp_irq vblank;
|
||||
struct mdp_irq err;
|
||||
};
|
||||
#define to_mdp4_crtc(x) container_of(x, struct mdp4_crtc, base)
|
||||
|
||||
static struct mdp4_kms *get_kms(struct drm_crtc *crtc)
|
||||
{
|
||||
struct msm_drm_private *priv = crtc->dev->dev_private;
|
||||
return to_mdp4_kms(to_mdp_kms(priv->kms));
|
||||
}
|
||||
|
||||
static void request_pending(struct drm_crtc *crtc, uint32_t pending)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
|
||||
atomic_or(pending, &mdp4_crtc->pending);
|
||||
mdp_irq_register(&get_kms(crtc)->base, &mdp4_crtc->vblank);
|
||||
}
|
||||
|
||||
static void crtc_flush(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(crtc);
|
||||
uint32_t i, flush = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mdp4_crtc->planes); i++) {
|
||||
struct drm_plane *plane = mdp4_crtc->planes[i];
|
||||
if (plane) {
|
||||
enum mdp4_pipe pipe_id = mdp4_plane_pipe(plane);
|
||||
flush |= pipe2flush(pipe_id);
|
||||
}
|
||||
}
|
||||
flush |= ovlp2flush(mdp4_crtc->ovlp);
|
||||
|
||||
DBG("%s: flush=%08x", mdp4_crtc->name, flush);
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVERLAY_FLUSH, flush);
|
||||
}
|
||||
|
||||
static void update_fb(struct drm_crtc *crtc, struct drm_framebuffer *new_fb)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct drm_framebuffer *old_fb = mdp4_crtc->fb;
|
||||
|
||||
/* grab reference to incoming scanout fb: */
|
||||
drm_framebuffer_reference(new_fb);
|
||||
mdp4_crtc->base.primary->fb = new_fb;
|
||||
mdp4_crtc->fb = new_fb;
|
||||
|
||||
if (old_fb)
|
||||
drm_flip_work_queue(&mdp4_crtc->unref_fb_work, old_fb);
|
||||
}
|
||||
|
||||
/* unlike update_fb(), take a ref to the new scanout fb *before* updating
|
||||
* plane, then call this. Needed to ensure we don't unref the buffer that
|
||||
* is actually still being scanned out.
|
||||
*
|
||||
* Note that this whole thing goes away with atomic.. since we can defer
|
||||
* calling into driver until rendering is done.
|
||||
*/
|
||||
static void update_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
|
||||
/* flush updates, to make sure hw is updated to new scanout fb,
|
||||
* so that we can safely queue unref to current fb (ie. next
|
||||
* vblank we know hw is done w/ previous scanout_fb).
|
||||
*/
|
||||
crtc_flush(crtc);
|
||||
|
||||
if (mdp4_crtc->scanout_fb)
|
||||
drm_flip_work_queue(&mdp4_crtc->unref_fb_work,
|
||||
mdp4_crtc->scanout_fb);
|
||||
|
||||
mdp4_crtc->scanout_fb = fb;
|
||||
|
||||
/* enable vblank to complete flip: */
|
||||
request_pending(crtc, PENDING_FLIP);
|
||||
}
|
||||
|
||||
/* if file!=NULL, this is preclose potential cancel-flip path */
|
||||
static void complete_flip(struct drm_crtc *crtc, struct drm_file *file)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_pending_vblank_event *event;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
event = mdp4_crtc->event;
|
||||
if (event) {
|
||||
/* if regular vblank case (!file) or if cancel-flip from
|
||||
* preclose on file that requested flip, then send the
|
||||
* event:
|
||||
*/
|
||||
if (!file || (event->base.file_priv == file)) {
|
||||
mdp4_crtc->event = NULL;
|
||||
drm_send_vblank_event(dev, mdp4_crtc->id, event);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
}
|
||||
|
||||
static void pageflip_cb(struct msm_fence_cb *cb)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc =
|
||||
container_of(cb, struct mdp4_crtc, pageflip_cb);
|
||||
struct drm_crtc *crtc = &mdp4_crtc->base;
|
||||
struct drm_framebuffer *fb = crtc->primary->fb;
|
||||
|
||||
if (!fb)
|
||||
return;
|
||||
|
||||
drm_framebuffer_reference(fb);
|
||||
mdp4_plane_set_scanout(mdp4_crtc->plane, fb);
|
||||
update_scanout(crtc, fb);
|
||||
}
|
||||
|
||||
static void unref_fb_worker(struct drm_flip_work *work, void *val)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc =
|
||||
container_of(work, struct mdp4_crtc, unref_fb_work);
|
||||
struct drm_device *dev = mdp4_crtc->base.dev;
|
||||
|
||||
mutex_lock(&dev->mode_config.mutex);
|
||||
drm_framebuffer_unreference(val);
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
}
|
||||
|
||||
static void unref_cursor_worker(struct drm_flip_work *work, void *val)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc =
|
||||
container_of(work, struct mdp4_crtc, unref_cursor_work);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(&mdp4_crtc->base);
|
||||
|
||||
msm_gem_put_iova(val, mdp4_kms->id);
|
||||
drm_gem_object_unreference_unlocked(val);
|
||||
}
|
||||
|
||||
static void mdp4_crtc_destroy(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
|
||||
drm_crtc_cleanup(crtc);
|
||||
drm_flip_work_cleanup(&mdp4_crtc->unref_fb_work);
|
||||
drm_flip_work_cleanup(&mdp4_crtc->unref_cursor_work);
|
||||
|
||||
kfree(mdp4_crtc);
|
||||
}
|
||||
|
||||
static void mdp4_crtc_dpms(struct drm_crtc *crtc, int mode)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(crtc);
|
||||
bool enabled = (mode == DRM_MODE_DPMS_ON);
|
||||
|
||||
DBG("%s: mode=%d", mdp4_crtc->name, mode);
|
||||
|
||||
if (enabled != mdp4_crtc->enabled) {
|
||||
if (enabled) {
|
||||
mdp4_enable(mdp4_kms);
|
||||
mdp_irq_register(&mdp4_kms->base, &mdp4_crtc->err);
|
||||
} else {
|
||||
mdp_irq_unregister(&mdp4_kms->base, &mdp4_crtc->err);
|
||||
mdp4_disable(mdp4_kms);
|
||||
}
|
||||
mdp4_crtc->enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
static bool mdp4_crtc_mode_fixup(struct drm_crtc *crtc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void blend_setup(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(crtc);
|
||||
int i, ovlp = mdp4_crtc->ovlp;
|
||||
uint32_t mixer_cfg = 0;
|
||||
static const enum mdp_mixer_stage_id stages[] = {
|
||||
STAGE_BASE, STAGE0, STAGE1, STAGE2, STAGE3,
|
||||
};
|
||||
/* statically (for now) map planes to mixer stage (z-order): */
|
||||
static const int idxs[] = {
|
||||
[VG1] = 1,
|
||||
[VG2] = 2,
|
||||
[RGB1] = 0,
|
||||
[RGB2] = 0,
|
||||
[RGB3] = 0,
|
||||
[VG3] = 3,
|
||||
[VG4] = 4,
|
||||
|
||||
};
|
||||
bool alpha[4]= { false, false, false, false };
|
||||
|
||||
/* Don't rely on value read back from hw, but instead use our
|
||||
* own shadowed value. Possibly disable/reenable looses the
|
||||
* previous value and goes back to power-on default?
|
||||
*/
|
||||
mixer_cfg = mdp4_kms->mixer_cfg;
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_LOW0(ovlp), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_LOW1(ovlp), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_HIGH0(ovlp), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_HIGH1(ovlp), 0);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mdp4_crtc->planes); i++) {
|
||||
struct drm_plane *plane = mdp4_crtc->planes[i];
|
||||
if (plane) {
|
||||
enum mdp4_pipe pipe_id = mdp4_plane_pipe(plane);
|
||||
int idx = idxs[pipe_id];
|
||||
if (idx > 0) {
|
||||
const struct mdp_format *format =
|
||||
to_mdp_format(msm_framebuffer_format(plane->fb));
|
||||
alpha[idx-1] = format->alpha_enable;
|
||||
}
|
||||
mixer_cfg = mixercfg(mixer_cfg, mdp4_crtc->mixer,
|
||||
pipe_id, stages[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
/* this shouldn't happen.. and seems to cause underflow: */
|
||||
WARN_ON(!mixer_cfg);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
uint32_t op;
|
||||
|
||||
if (alpha[i]) {
|
||||
op = MDP4_OVLP_STAGE_OP_FG_ALPHA(FG_PIXEL) |
|
||||
MDP4_OVLP_STAGE_OP_BG_ALPHA(FG_PIXEL) |
|
||||
MDP4_OVLP_STAGE_OP_BG_INV_ALPHA;
|
||||
} else {
|
||||
op = MDP4_OVLP_STAGE_OP_FG_ALPHA(FG_CONST) |
|
||||
MDP4_OVLP_STAGE_OP_BG_ALPHA(BG_CONST);
|
||||
}
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_FG_ALPHA(ovlp, i), 0xff);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_BG_ALPHA(ovlp, i), 0x00);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_OP(ovlp, i), op);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_CO3(ovlp, i), 1);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_LOW0(ovlp, i), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_LOW1(ovlp, i), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_HIGH0(ovlp, i), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_HIGH1(ovlp, i), 0);
|
||||
}
|
||||
|
||||
mdp4_kms->mixer_cfg = mixer_cfg;
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG, mixer_cfg);
|
||||
}
|
||||
|
||||
static int mdp4_crtc_mode_set(struct drm_crtc *crtc,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode,
|
||||
int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(crtc);
|
||||
enum mdp4_dma dma = mdp4_crtc->dma;
|
||||
int ret, ovlp = mdp4_crtc->ovlp;
|
||||
|
||||
mode = adjusted_mode;
|
||||
|
||||
DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
|
||||
mdp4_crtc->name, mode->base.id, mode->name,
|
||||
mode->vrefresh, mode->clock,
|
||||
mode->hdisplay, mode->hsync_start,
|
||||
mode->hsync_end, mode->htotal,
|
||||
mode->vdisplay, mode->vsync_start,
|
||||
mode->vsync_end, mode->vtotal,
|
||||
mode->type, mode->flags);
|
||||
|
||||
/* grab extra ref for update_scanout() */
|
||||
drm_framebuffer_reference(crtc->primary->fb);
|
||||
|
||||
ret = mdp4_plane_mode_set(mdp4_crtc->plane, crtc, crtc->primary->fb,
|
||||
0, 0, mode->hdisplay, mode->vdisplay,
|
||||
x << 16, y << 16,
|
||||
mode->hdisplay << 16, mode->vdisplay << 16);
|
||||
if (ret) {
|
||||
drm_framebuffer_unreference(crtc->primary->fb);
|
||||
dev_err(crtc->dev->dev, "%s: failed to set mode on plane: %d\n",
|
||||
mdp4_crtc->name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_SIZE(dma),
|
||||
MDP4_DMA_SRC_SIZE_WIDTH(mode->hdisplay) |
|
||||
MDP4_DMA_SRC_SIZE_HEIGHT(mode->vdisplay));
|
||||
|
||||
/* take data from pipe: */
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_BASE(dma), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_STRIDE(dma),
|
||||
crtc->primary->fb->pitches[0]);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_DST_SIZE(dma),
|
||||
MDP4_DMA_DST_SIZE_WIDTH(0) |
|
||||
MDP4_DMA_DST_SIZE_HEIGHT(0));
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_BASE(ovlp), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_SIZE(ovlp),
|
||||
MDP4_OVLP_SIZE_WIDTH(mode->hdisplay) |
|
||||
MDP4_OVLP_SIZE_HEIGHT(mode->vdisplay));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_STRIDE(ovlp),
|
||||
crtc->primary->fb->pitches[0]);
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_CFG(ovlp), 1);
|
||||
|
||||
if (dma == DMA_E) {
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(0), 0x00ff0000);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(1), 0x00ff0000);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(2), 0x00ff0000);
|
||||
}
|
||||
|
||||
update_fb(crtc, crtc->primary->fb);
|
||||
update_scanout(crtc, crtc->primary->fb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mdp4_crtc_prepare(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
DBG("%s", mdp4_crtc->name);
|
||||
/* make sure we hold a ref to mdp clks while setting up mode: */
|
||||
drm_crtc_vblank_get(crtc);
|
||||
mdp4_enable(get_kms(crtc));
|
||||
mdp4_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
}
|
||||
|
||||
static void mdp4_crtc_commit(struct drm_crtc *crtc)
|
||||
{
|
||||
mdp4_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
|
||||
crtc_flush(crtc);
|
||||
/* drop the ref to mdp clk's that we got in prepare: */
|
||||
mdp4_disable(get_kms(crtc));
|
||||
drm_crtc_vblank_put(crtc);
|
||||
}
|
||||
|
||||
static int mdp4_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct drm_plane *plane = mdp4_crtc->plane;
|
||||
struct drm_display_mode *mode = &crtc->mode;
|
||||
int ret;
|
||||
|
||||
/* grab extra ref for update_scanout() */
|
||||
drm_framebuffer_reference(crtc->primary->fb);
|
||||
|
||||
ret = mdp4_plane_mode_set(plane, crtc, crtc->primary->fb,
|
||||
0, 0, mode->hdisplay, mode->vdisplay,
|
||||
x << 16, y << 16,
|
||||
mode->hdisplay << 16, mode->vdisplay << 16);
|
||||
if (ret) {
|
||||
drm_framebuffer_unreference(crtc->primary->fb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
update_fb(crtc, crtc->primary->fb);
|
||||
update_scanout(crtc, crtc->primary->fb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mdp4_crtc_load_lut(struct drm_crtc *crtc)
|
||||
{
|
||||
}
|
||||
|
||||
static int mdp4_crtc_page_flip(struct drm_crtc *crtc,
|
||||
struct drm_framebuffer *new_fb,
|
||||
struct drm_pending_vblank_event *event,
|
||||
uint32_t page_flip_flags)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_gem_object *obj;
|
||||
unsigned long flags;
|
||||
|
||||
if (mdp4_crtc->event) {
|
||||
dev_err(dev->dev, "already pending flip!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
obj = msm_framebuffer_bo(new_fb, 0);
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
mdp4_crtc->event = event;
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
|
||||
update_fb(crtc, new_fb);
|
||||
|
||||
return msm_gem_queue_inactive_cb(obj, &mdp4_crtc->pageflip_cb);
|
||||
}
|
||||
|
||||
static int mdp4_crtc_set_property(struct drm_crtc *crtc,
|
||||
struct drm_property *property, uint64_t val)
|
||||
{
|
||||
// XXX
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#define CURSOR_WIDTH 64
|
||||
#define CURSOR_HEIGHT 64
|
||||
|
||||
/* called from IRQ to update cursor related registers (if needed). The
|
||||
* cursor registers, other than x/y position, appear not to be double
|
||||
* buffered, and changing them other than from vblank seems to trigger
|
||||
* underflow.
|
||||
*/
|
||||
static void update_cursor(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(crtc);
|
||||
enum mdp4_dma dma = mdp4_crtc->dma;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags);
|
||||
if (mdp4_crtc->cursor.stale) {
|
||||
struct drm_gem_object *next_bo = mdp4_crtc->cursor.next_bo;
|
||||
struct drm_gem_object *prev_bo = mdp4_crtc->cursor.scanout_bo;
|
||||
uint32_t iova = mdp4_crtc->cursor.next_iova;
|
||||
|
||||
if (next_bo) {
|
||||
/* take a obj ref + iova ref when we start scanning out: */
|
||||
drm_gem_object_reference(next_bo);
|
||||
msm_gem_get_iova_locked(next_bo, mdp4_kms->id, &iova);
|
||||
|
||||
/* enable cursor: */
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_SIZE(dma),
|
||||
MDP4_DMA_CURSOR_SIZE_WIDTH(mdp4_crtc->cursor.width) |
|
||||
MDP4_DMA_CURSOR_SIZE_HEIGHT(mdp4_crtc->cursor.height));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BASE(dma), iova);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BLEND_CONFIG(dma),
|
||||
MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT(CURSOR_ARGB) |
|
||||
MDP4_DMA_CURSOR_BLEND_CONFIG_CURSOR_EN);
|
||||
} else {
|
||||
/* disable cursor: */
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BASE(dma),
|
||||
mdp4_kms->blank_cursor_iova);
|
||||
}
|
||||
|
||||
/* and drop the iova ref + obj rev when done scanning out: */
|
||||
if (prev_bo)
|
||||
drm_flip_work_queue(&mdp4_crtc->unref_cursor_work, prev_bo);
|
||||
|
||||
mdp4_crtc->cursor.scanout_bo = next_bo;
|
||||
mdp4_crtc->cursor.stale = false;
|
||||
}
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_POS(dma),
|
||||
MDP4_DMA_CURSOR_POS_X(mdp4_crtc->cursor.x) |
|
||||
MDP4_DMA_CURSOR_POS_Y(mdp4_crtc->cursor.y));
|
||||
|
||||
spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags);
|
||||
}
|
||||
|
||||
static int mdp4_crtc_cursor_set(struct drm_crtc *crtc,
|
||||
struct drm_file *file_priv, uint32_t handle,
|
||||
uint32_t width, uint32_t height)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_gem_object *cursor_bo, *old_bo;
|
||||
unsigned long flags;
|
||||
uint32_t iova;
|
||||
int ret;
|
||||
|
||||
if ((width > CURSOR_WIDTH) || (height > CURSOR_HEIGHT)) {
|
||||
dev_err(dev->dev, "bad cursor size: %dx%d\n", width, height);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (handle) {
|
||||
cursor_bo = drm_gem_object_lookup(dev, file_priv, handle);
|
||||
if (!cursor_bo)
|
||||
return -ENOENT;
|
||||
} else {
|
||||
cursor_bo = NULL;
|
||||
}
|
||||
|
||||
if (cursor_bo) {
|
||||
ret = msm_gem_get_iova(cursor_bo, mdp4_kms->id, &iova);
|
||||
if (ret)
|
||||
goto fail;
|
||||
} else {
|
||||
iova = 0;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags);
|
||||
old_bo = mdp4_crtc->cursor.next_bo;
|
||||
mdp4_crtc->cursor.next_bo = cursor_bo;
|
||||
mdp4_crtc->cursor.next_iova = iova;
|
||||
mdp4_crtc->cursor.width = width;
|
||||
mdp4_crtc->cursor.height = height;
|
||||
mdp4_crtc->cursor.stale = true;
|
||||
spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags);
|
||||
|
||||
if (old_bo) {
|
||||
/* drop our previous reference: */
|
||||
drm_flip_work_queue(&mdp4_crtc->unref_cursor_work, old_bo);
|
||||
}
|
||||
|
||||
request_pending(crtc, PENDING_CURSOR);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
drm_gem_object_unreference_unlocked(cursor_bo);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mdp4_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags);
|
||||
mdp4_crtc->cursor.x = x;
|
||||
mdp4_crtc->cursor.y = y;
|
||||
spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags);
|
||||
|
||||
crtc_flush(crtc);
|
||||
request_pending(crtc, PENDING_CURSOR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_crtc_funcs mdp4_crtc_funcs = {
|
||||
.set_config = drm_crtc_helper_set_config,
|
||||
.destroy = mdp4_crtc_destroy,
|
||||
.page_flip = mdp4_crtc_page_flip,
|
||||
.set_property = mdp4_crtc_set_property,
|
||||
.cursor_set = mdp4_crtc_cursor_set,
|
||||
.cursor_move = mdp4_crtc_cursor_move,
|
||||
};
|
||||
|
||||
static const struct drm_crtc_helper_funcs mdp4_crtc_helper_funcs = {
|
||||
.dpms = mdp4_crtc_dpms,
|
||||
.mode_fixup = mdp4_crtc_mode_fixup,
|
||||
.mode_set = mdp4_crtc_mode_set,
|
||||
.prepare = mdp4_crtc_prepare,
|
||||
.commit = mdp4_crtc_commit,
|
||||
.mode_set_base = mdp4_crtc_mode_set_base,
|
||||
.load_lut = mdp4_crtc_load_lut,
|
||||
};
|
||||
|
||||
static void mdp4_crtc_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = container_of(irq, struct mdp4_crtc, vblank);
|
||||
struct drm_crtc *crtc = &mdp4_crtc->base;
|
||||
struct msm_drm_private *priv = crtc->dev->dev_private;
|
||||
unsigned pending;
|
||||
|
||||
mdp_irq_unregister(&get_kms(crtc)->base, &mdp4_crtc->vblank);
|
||||
|
||||
pending = atomic_xchg(&mdp4_crtc->pending, 0);
|
||||
|
||||
if (pending & PENDING_FLIP) {
|
||||
complete_flip(crtc, NULL);
|
||||
drm_flip_work_commit(&mdp4_crtc->unref_fb_work, priv->wq);
|
||||
}
|
||||
|
||||
if (pending & PENDING_CURSOR) {
|
||||
update_cursor(crtc);
|
||||
drm_flip_work_commit(&mdp4_crtc->unref_cursor_work, priv->wq);
|
||||
}
|
||||
}
|
||||
|
||||
static void mdp4_crtc_err_irq(struct mdp_irq *irq, uint32_t irqstatus)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = container_of(irq, struct mdp4_crtc, err);
|
||||
struct drm_crtc *crtc = &mdp4_crtc->base;
|
||||
DBG("%s: error: %08x", mdp4_crtc->name, irqstatus);
|
||||
crtc_flush(crtc);
|
||||
}
|
||||
|
||||
uint32_t mdp4_crtc_vblank(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
return mdp4_crtc->vblank.irqmask;
|
||||
}
|
||||
|
||||
void mdp4_crtc_cancel_pending_flip(struct drm_crtc *crtc, struct drm_file *file)
|
||||
{
|
||||
DBG("cancel: %p", file);
|
||||
complete_flip(crtc, file);
|
||||
}
|
||||
|
||||
/* set dma config, ie. the format the encoder wants. */
|
||||
void mdp4_crtc_set_config(struct drm_crtc *crtc, uint32_t config)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(crtc);
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_CONFIG(mdp4_crtc->dma), config);
|
||||
}
|
||||
|
||||
/* set interface for routing crtc->encoder: */
|
||||
void mdp4_crtc_set_intf(struct drm_crtc *crtc, enum mdp4_intf intf, int mixer)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(crtc);
|
||||
uint32_t intf_sel;
|
||||
|
||||
intf_sel = mdp4_read(mdp4_kms, REG_MDP4_DISP_INTF_SEL);
|
||||
|
||||
switch (mdp4_crtc->dma) {
|
||||
case DMA_P:
|
||||
intf_sel &= ~MDP4_DISP_INTF_SEL_PRIM__MASK;
|
||||
intf_sel |= MDP4_DISP_INTF_SEL_PRIM(intf);
|
||||
break;
|
||||
case DMA_S:
|
||||
intf_sel &= ~MDP4_DISP_INTF_SEL_SEC__MASK;
|
||||
intf_sel |= MDP4_DISP_INTF_SEL_SEC(intf);
|
||||
break;
|
||||
case DMA_E:
|
||||
intf_sel &= ~MDP4_DISP_INTF_SEL_EXT__MASK;
|
||||
intf_sel |= MDP4_DISP_INTF_SEL_EXT(intf);
|
||||
break;
|
||||
}
|
||||
|
||||
if (intf == INTF_DSI_VIDEO) {
|
||||
intf_sel &= ~MDP4_DISP_INTF_SEL_DSI_CMD;
|
||||
intf_sel |= MDP4_DISP_INTF_SEL_DSI_VIDEO;
|
||||
} else if (intf == INTF_DSI_CMD) {
|
||||
intf_sel &= ~MDP4_DISP_INTF_SEL_DSI_VIDEO;
|
||||
intf_sel |= MDP4_DISP_INTF_SEL_DSI_CMD;
|
||||
}
|
||||
|
||||
mdp4_crtc->mixer = mixer;
|
||||
|
||||
blend_setup(crtc);
|
||||
|
||||
DBG("%s: intf_sel=%08x", mdp4_crtc->name, intf_sel);
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DISP_INTF_SEL, intf_sel);
|
||||
}
|
||||
|
||||
static void set_attach(struct drm_crtc *crtc, enum mdp4_pipe pipe_id,
|
||||
struct drm_plane *plane)
|
||||
{
|
||||
struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc);
|
||||
|
||||
BUG_ON(pipe_id >= ARRAY_SIZE(mdp4_crtc->planes));
|
||||
|
||||
if (mdp4_crtc->planes[pipe_id] == plane)
|
||||
return;
|
||||
|
||||
mdp4_crtc->planes[pipe_id] = plane;
|
||||
blend_setup(crtc);
|
||||
if (mdp4_crtc->enabled && (plane != mdp4_crtc->plane))
|
||||
crtc_flush(crtc);
|
||||
}
|
||||
|
||||
void mdp4_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane)
|
||||
{
|
||||
set_attach(crtc, mdp4_plane_pipe(plane), plane);
|
||||
}
|
||||
|
||||
void mdp4_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane)
|
||||
{
|
||||
/* don't actually detatch our primary plane: */
|
||||
if (to_mdp4_crtc(crtc)->plane == plane)
|
||||
return;
|
||||
set_attach(crtc, mdp4_plane_pipe(plane), NULL);
|
||||
}
|
||||
|
||||
static const char *dma_names[] = {
|
||||
"DMA_P", "DMA_S", "DMA_E",
|
||||
};
|
||||
|
||||
/* initialize crtc */
|
||||
struct drm_crtc *mdp4_crtc_init(struct drm_device *dev,
|
||||
struct drm_plane *plane, int id, int ovlp_id,
|
||||
enum mdp4_dma dma_id)
|
||||
{
|
||||
struct drm_crtc *crtc = NULL;
|
||||
struct mdp4_crtc *mdp4_crtc;
|
||||
int ret;
|
||||
|
||||
mdp4_crtc = kzalloc(sizeof(*mdp4_crtc), GFP_KERNEL);
|
||||
if (!mdp4_crtc) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
crtc = &mdp4_crtc->base;
|
||||
|
||||
mdp4_crtc->plane = plane;
|
||||
mdp4_crtc->id = id;
|
||||
|
||||
mdp4_crtc->ovlp = ovlp_id;
|
||||
mdp4_crtc->dma = dma_id;
|
||||
|
||||
mdp4_crtc->vblank.irqmask = dma2irq(mdp4_crtc->dma);
|
||||
mdp4_crtc->vblank.irq = mdp4_crtc_vblank_irq;
|
||||
|
||||
mdp4_crtc->err.irqmask = dma2err(mdp4_crtc->dma);
|
||||
mdp4_crtc->err.irq = mdp4_crtc_err_irq;
|
||||
|
||||
snprintf(mdp4_crtc->name, sizeof(mdp4_crtc->name), "%s:%d",
|
||||
dma_names[dma_id], ovlp_id);
|
||||
|
||||
spin_lock_init(&mdp4_crtc->cursor.lock);
|
||||
|
||||
ret = drm_flip_work_init(&mdp4_crtc->unref_fb_work, 16,
|
||||
"unref fb", unref_fb_worker);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
ret = drm_flip_work_init(&mdp4_crtc->unref_cursor_work, 64,
|
||||
"unref cursor", unref_cursor_worker);
|
||||
|
||||
INIT_FENCE_CB(&mdp4_crtc->pageflip_cb, pageflip_cb);
|
||||
|
||||
drm_crtc_init_with_planes(dev, crtc, plane, NULL, &mdp4_crtc_funcs);
|
||||
drm_crtc_helper_add(crtc, &mdp4_crtc_helper_funcs);
|
||||
|
||||
mdp4_plane_install_properties(mdp4_crtc->plane, &crtc->base);
|
||||
|
||||
return crtc;
|
||||
|
||||
fail:
|
||||
if (crtc)
|
||||
mdp4_crtc_destroy(crtc);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
303
drivers/gpu/drm/msm/mdp/mdp4/mdp4_dtv_encoder.c
Normal file
303
drivers/gpu/drm/msm/mdp/mdp4/mdp4_dtv_encoder.c
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mdp4_kms.h"
|
||||
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_crtc_helper.h"
|
||||
|
||||
|
||||
struct mdp4_dtv_encoder {
|
||||
struct drm_encoder base;
|
||||
struct clk *src_clk;
|
||||
struct clk *hdmi_clk;
|
||||
struct clk *mdp_clk;
|
||||
unsigned long int pixclock;
|
||||
bool enabled;
|
||||
uint32_t bsc;
|
||||
};
|
||||
#define to_mdp4_dtv_encoder(x) container_of(x, struct mdp4_dtv_encoder, base)
|
||||
|
||||
static struct mdp4_kms *get_kms(struct drm_encoder *encoder)
|
||||
{
|
||||
struct msm_drm_private *priv = encoder->dev->dev_private;
|
||||
return to_mdp4_kms(to_mdp_kms(priv->kms));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MSM_BUS_SCALING
|
||||
#include <mach/board.h>
|
||||
/* not ironically named at all.. no, really.. */
|
||||
static void bs_init(struct mdp4_dtv_encoder *mdp4_dtv_encoder)
|
||||
{
|
||||
struct drm_device *dev = mdp4_dtv_encoder->base.dev;
|
||||
struct lcdc_platform_data *dtv_pdata = mdp4_find_pdata("dtv.0");
|
||||
|
||||
if (!dtv_pdata) {
|
||||
dev_err(dev->dev, "could not find dtv pdata\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (dtv_pdata->bus_scale_table) {
|
||||
mdp4_dtv_encoder->bsc = msm_bus_scale_register_client(
|
||||
dtv_pdata->bus_scale_table);
|
||||
DBG("bus scale client: %08x", mdp4_dtv_encoder->bsc);
|
||||
DBG("lcdc_power_save: %p", dtv_pdata->lcdc_power_save);
|
||||
if (dtv_pdata->lcdc_power_save)
|
||||
dtv_pdata->lcdc_power_save(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void bs_fini(struct mdp4_dtv_encoder *mdp4_dtv_encoder)
|
||||
{
|
||||
if (mdp4_dtv_encoder->bsc) {
|
||||
msm_bus_scale_unregister_client(mdp4_dtv_encoder->bsc);
|
||||
mdp4_dtv_encoder->bsc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void bs_set(struct mdp4_dtv_encoder *mdp4_dtv_encoder, int idx)
|
||||
{
|
||||
if (mdp4_dtv_encoder->bsc) {
|
||||
DBG("set bus scaling: %d", idx);
|
||||
msm_bus_scale_client_update_request(mdp4_dtv_encoder->bsc, idx);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void bs_init(struct mdp4_dtv_encoder *mdp4_dtv_encoder) {}
|
||||
static void bs_fini(struct mdp4_dtv_encoder *mdp4_dtv_encoder) {}
|
||||
static void bs_set(struct mdp4_dtv_encoder *mdp4_dtv_encoder, int idx) {}
|
||||
#endif
|
||||
|
||||
static void mdp4_dtv_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder);
|
||||
bs_fini(mdp4_dtv_encoder);
|
||||
drm_encoder_cleanup(encoder);
|
||||
kfree(mdp4_dtv_encoder);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs mdp4_dtv_encoder_funcs = {
|
||||
.destroy = mdp4_dtv_encoder_destroy,
|
||||
};
|
||||
|
||||
static void mdp4_dtv_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
struct drm_device *dev = encoder->dev;
|
||||
struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(encoder);
|
||||
bool enabled = (mode == DRM_MODE_DPMS_ON);
|
||||
|
||||
DBG("mode=%d", mode);
|
||||
|
||||
if (enabled == mdp4_dtv_encoder->enabled)
|
||||
return;
|
||||
|
||||
if (enabled) {
|
||||
unsigned long pc = mdp4_dtv_encoder->pixclock;
|
||||
int ret;
|
||||
|
||||
bs_set(mdp4_dtv_encoder, 1);
|
||||
|
||||
DBG("setting src_clk=%lu", pc);
|
||||
|
||||
ret = clk_set_rate(mdp4_dtv_encoder->src_clk, pc);
|
||||
if (ret)
|
||||
dev_err(dev->dev, "failed to set src_clk to %lu: %d\n", pc, ret);
|
||||
clk_prepare_enable(mdp4_dtv_encoder->src_clk);
|
||||
ret = clk_prepare_enable(mdp4_dtv_encoder->hdmi_clk);
|
||||
if (ret)
|
||||
dev_err(dev->dev, "failed to enable hdmi_clk: %d\n", ret);
|
||||
ret = clk_prepare_enable(mdp4_dtv_encoder->mdp_clk);
|
||||
if (ret)
|
||||
dev_err(dev->dev, "failed to enabled mdp_clk: %d\n", ret);
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 1);
|
||||
} else {
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 0);
|
||||
|
||||
/*
|
||||
* Wait for a vsync so we know the ENABLE=0 latched before
|
||||
* the (connector) source of the vsync's gets disabled,
|
||||
* otherwise we end up in a funny state if we re-enable
|
||||
* before the disable latches, which results that some of
|
||||
* the settings changes for the new modeset (like new
|
||||
* scanout buffer) don't latch properly..
|
||||
*/
|
||||
mdp_irq_wait(&mdp4_kms->base, MDP4_IRQ_EXTERNAL_VSYNC);
|
||||
|
||||
clk_disable_unprepare(mdp4_dtv_encoder->src_clk);
|
||||
clk_disable_unprepare(mdp4_dtv_encoder->hdmi_clk);
|
||||
clk_disable_unprepare(mdp4_dtv_encoder->mdp_clk);
|
||||
|
||||
bs_set(mdp4_dtv_encoder, 0);
|
||||
}
|
||||
|
||||
mdp4_dtv_encoder->enabled = enabled;
|
||||
}
|
||||
|
||||
static bool mdp4_dtv_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mdp4_dtv_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(encoder);
|
||||
uint32_t dtv_hsync_skew, vsync_period, vsync_len, ctrl_pol;
|
||||
uint32_t display_v_start, display_v_end;
|
||||
uint32_t hsync_start_x, hsync_end_x;
|
||||
|
||||
mode = adjusted_mode;
|
||||
|
||||
DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
|
||||
mode->base.id, mode->name,
|
||||
mode->vrefresh, mode->clock,
|
||||
mode->hdisplay, mode->hsync_start,
|
||||
mode->hsync_end, mode->htotal,
|
||||
mode->vdisplay, mode->vsync_start,
|
||||
mode->vsync_end, mode->vtotal,
|
||||
mode->type, mode->flags);
|
||||
|
||||
mdp4_dtv_encoder->pixclock = mode->clock * 1000;
|
||||
|
||||
DBG("pixclock=%lu", mdp4_dtv_encoder->pixclock);
|
||||
|
||||
ctrl_pol = 0;
|
||||
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
||||
ctrl_pol |= MDP4_DTV_CTRL_POLARITY_HSYNC_LOW;
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
ctrl_pol |= MDP4_DTV_CTRL_POLARITY_VSYNC_LOW;
|
||||
/* probably need to get DATA_EN polarity from panel.. */
|
||||
|
||||
dtv_hsync_skew = 0; /* get this from panel? */
|
||||
|
||||
hsync_start_x = (mode->htotal - mode->hsync_start);
|
||||
hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1;
|
||||
|
||||
vsync_period = mode->vtotal * mode->htotal;
|
||||
vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal;
|
||||
display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + dtv_hsync_skew;
|
||||
display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + dtv_hsync_skew - 1;
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_HSYNC_CTRL,
|
||||
MDP4_DTV_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) |
|
||||
MDP4_DTV_HSYNC_CTRL_PERIOD(mode->htotal));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_VSYNC_PERIOD, vsync_period);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_VSYNC_LEN, vsync_len);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_HCTRL,
|
||||
MDP4_DTV_DISPLAY_HCTRL_START(hsync_start_x) |
|
||||
MDP4_DTV_DISPLAY_HCTRL_END(hsync_end_x));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_VSTART, display_v_start);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_VEND, display_v_end);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_BORDER_CLR, 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_UNDERFLOW_CLR,
|
||||
MDP4_DTV_UNDERFLOW_CLR_ENABLE_RECOVERY |
|
||||
MDP4_DTV_UNDERFLOW_CLR_COLOR(0xff));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_HSYNC_SKEW, dtv_hsync_skew);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_CTRL_POLARITY, ctrl_pol);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_HCTL,
|
||||
MDP4_DTV_ACTIVE_HCTL_START(0) |
|
||||
MDP4_DTV_ACTIVE_HCTL_END(0));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_VSTART, 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_VEND, 0);
|
||||
}
|
||||
|
||||
static void mdp4_dtv_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
mdp4_dtv_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
||||
}
|
||||
|
||||
static void mdp4_dtv_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
mdp4_crtc_set_config(encoder->crtc,
|
||||
MDP4_DMA_CONFIG_R_BPC(BPC8) |
|
||||
MDP4_DMA_CONFIG_G_BPC(BPC8) |
|
||||
MDP4_DMA_CONFIG_B_BPC(BPC8) |
|
||||
MDP4_DMA_CONFIG_PACK(0x21));
|
||||
mdp4_crtc_set_intf(encoder->crtc, INTF_LCDC_DTV, 1);
|
||||
mdp4_dtv_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_helper_funcs mdp4_dtv_encoder_helper_funcs = {
|
||||
.dpms = mdp4_dtv_encoder_dpms,
|
||||
.mode_fixup = mdp4_dtv_encoder_mode_fixup,
|
||||
.mode_set = mdp4_dtv_encoder_mode_set,
|
||||
.prepare = mdp4_dtv_encoder_prepare,
|
||||
.commit = mdp4_dtv_encoder_commit,
|
||||
};
|
||||
|
||||
long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate)
|
||||
{
|
||||
struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder);
|
||||
return clk_round_rate(mdp4_dtv_encoder->src_clk, rate);
|
||||
}
|
||||
|
||||
/* initialize encoder */
|
||||
struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev)
|
||||
{
|
||||
struct drm_encoder *encoder = NULL;
|
||||
struct mdp4_dtv_encoder *mdp4_dtv_encoder;
|
||||
int ret;
|
||||
|
||||
mdp4_dtv_encoder = kzalloc(sizeof(*mdp4_dtv_encoder), GFP_KERNEL);
|
||||
if (!mdp4_dtv_encoder) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
encoder = &mdp4_dtv_encoder->base;
|
||||
|
||||
drm_encoder_init(dev, encoder, &mdp4_dtv_encoder_funcs,
|
||||
DRM_MODE_ENCODER_TMDS);
|
||||
drm_encoder_helper_add(encoder, &mdp4_dtv_encoder_helper_funcs);
|
||||
|
||||
mdp4_dtv_encoder->src_clk = devm_clk_get(dev->dev, "src_clk");
|
||||
if (IS_ERR(mdp4_dtv_encoder->src_clk)) {
|
||||
dev_err(dev->dev, "failed to get src_clk\n");
|
||||
ret = PTR_ERR(mdp4_dtv_encoder->src_clk);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp4_dtv_encoder->hdmi_clk = devm_clk_get(dev->dev, "hdmi_clk");
|
||||
if (IS_ERR(mdp4_dtv_encoder->hdmi_clk)) {
|
||||
dev_err(dev->dev, "failed to get hdmi_clk\n");
|
||||
ret = PTR_ERR(mdp4_dtv_encoder->hdmi_clk);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp4_dtv_encoder->mdp_clk = devm_clk_get(dev->dev, "mdp_clk");
|
||||
if (IS_ERR(mdp4_dtv_encoder->mdp_clk)) {
|
||||
dev_err(dev->dev, "failed to get mdp_clk\n");
|
||||
ret = PTR_ERR(mdp4_dtv_encoder->mdp_clk);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bs_init(mdp4_dtv_encoder);
|
||||
|
||||
return encoder;
|
||||
|
||||
fail:
|
||||
if (encoder)
|
||||
mdp4_dtv_encoder_destroy(encoder);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
93
drivers/gpu/drm/msm/mdp/mdp4/mdp4_irq.c
Normal file
93
drivers/gpu/drm/msm/mdp/mdp4/mdp4_irq.c
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "mdp4_kms.h"
|
||||
|
||||
void mdp4_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask)
|
||||
{
|
||||
mdp4_write(to_mdp4_kms(mdp_kms), REG_MDP4_INTR_ENABLE, irqmask);
|
||||
}
|
||||
|
||||
static void mdp4_irq_error_handler(struct mdp_irq *irq, uint32_t irqstatus)
|
||||
{
|
||||
DRM_ERROR("errors: %08x\n", irqstatus);
|
||||
}
|
||||
|
||||
void mdp4_irq_preinstall(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, 0xffffffff);
|
||||
}
|
||||
|
||||
int mdp4_irq_postinstall(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
|
||||
struct mdp4_kms *mdp4_kms = to_mdp4_kms(mdp_kms);
|
||||
struct mdp_irq *error_handler = &mdp4_kms->error_handler;
|
||||
|
||||
error_handler->irq = mdp4_irq_error_handler;
|
||||
error_handler->irqmask = MDP4_IRQ_PRIMARY_INTF_UDERRUN |
|
||||
MDP4_IRQ_EXTERNAL_INTF_UDERRUN;
|
||||
|
||||
mdp_irq_register(mdp_kms, error_handler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdp4_irq_uninstall(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_INTR_ENABLE, 0x00000000);
|
||||
}
|
||||
|
||||
irqreturn_t mdp4_irq(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
|
||||
struct mdp4_kms *mdp4_kms = to_mdp4_kms(mdp_kms);
|
||||
struct drm_device *dev = mdp4_kms->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
unsigned int id;
|
||||
uint32_t status;
|
||||
|
||||
status = mdp4_read(mdp4_kms, REG_MDP4_INTR_STATUS);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, status);
|
||||
|
||||
VERB("status=%08x", status);
|
||||
|
||||
mdp_dispatch_irqs(mdp_kms, status);
|
||||
|
||||
for (id = 0; id < priv->num_crtcs; id++)
|
||||
if (status & mdp4_crtc_vblank(priv->crtcs[id]))
|
||||
drm_handle_vblank(dev, id);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
|
||||
{
|
||||
mdp_update_vblank_mask(to_mdp_kms(kms),
|
||||
mdp4_crtc_vblank(crtc), true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
|
||||
{
|
||||
mdp_update_vblank_mask(to_mdp_kms(kms),
|
||||
mdp4_crtc_vblank(crtc), false);
|
||||
}
|
||||
506
drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c
Normal file
506
drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_mmu.h"
|
||||
#include "mdp4_kms.h"
|
||||
|
||||
static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev);
|
||||
|
||||
static int mdp4_hw_init(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
|
||||
struct drm_device *dev = mdp4_kms->dev;
|
||||
uint32_t version, major, minor, dmap_cfg, vg_cfg;
|
||||
unsigned long clk;
|
||||
int ret = 0;
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
|
||||
mdp4_enable(mdp4_kms);
|
||||
version = mdp4_read(mdp4_kms, REG_MDP4_VERSION);
|
||||
mdp4_disable(mdp4_kms);
|
||||
|
||||
major = FIELD(version, MDP4_VERSION_MAJOR);
|
||||
minor = FIELD(version, MDP4_VERSION_MINOR);
|
||||
|
||||
DBG("found MDP4 version v%d.%d", major, minor);
|
||||
|
||||
if (major != 4) {
|
||||
dev_err(dev->dev, "unexpected MDP version: v%d.%d\n",
|
||||
major, minor);
|
||||
ret = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mdp4_kms->rev = minor;
|
||||
|
||||
if (mdp4_kms->dsi_pll_vdda) {
|
||||
if ((mdp4_kms->rev == 2) || (mdp4_kms->rev == 4)) {
|
||||
ret = regulator_set_voltage(mdp4_kms->dsi_pll_vdda,
|
||||
1200000, 1200000);
|
||||
if (ret) {
|
||||
dev_err(dev->dev,
|
||||
"failed to set dsi_pll_vdda voltage: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mdp4_kms->dsi_pll_vddio) {
|
||||
if (mdp4_kms->rev == 2) {
|
||||
ret = regulator_set_voltage(mdp4_kms->dsi_pll_vddio,
|
||||
1800000, 1800000);
|
||||
if (ret) {
|
||||
dev_err(dev->dev,
|
||||
"failed to set dsi_pll_vddio voltage: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mdp4_kms->rev > 1) {
|
||||
mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER0, 0x0707ffff);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER1, 0x03073f3f);
|
||||
}
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PORTMAP_MODE, 0x3);
|
||||
|
||||
/* max read pending cmd config, 3 pending requests: */
|
||||
mdp4_write(mdp4_kms, REG_MDP4_READ_CNFG, 0x02222);
|
||||
|
||||
clk = clk_get_rate(mdp4_kms->clk);
|
||||
|
||||
if ((mdp4_kms->rev >= 1) || (clk >= 90000000)) {
|
||||
dmap_cfg = 0x47; /* 16 bytes-burst x 8 req */
|
||||
vg_cfg = 0x47; /* 16 bytes-burs x 8 req */
|
||||
} else {
|
||||
dmap_cfg = 0x27; /* 8 bytes-burst x 8 req */
|
||||
vg_cfg = 0x43; /* 16 bytes-burst x 4 req */
|
||||
}
|
||||
|
||||
DBG("fetch config: dmap=%02x, vg=%02x", dmap_cfg, vg_cfg);
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_P), dmap_cfg);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_E), dmap_cfg);
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG1), vg_cfg);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG2), vg_cfg);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB1), vg_cfg);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB2), vg_cfg);
|
||||
|
||||
if (mdp4_kms->rev >= 2)
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG_UPDATE_METHOD, 1);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG, 0);
|
||||
|
||||
/* disable CSC matrix / YUV by default: */
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG1), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG2), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_P_OP_MODE, 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DMA_S_OP_MODE, 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(1), 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(2), 0);
|
||||
|
||||
if (mdp4_kms->rev > 1)
|
||||
mdp4_write(mdp4_kms, REG_MDP4_RESET_STATUS, 1);
|
||||
|
||||
out:
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long mdp4_round_pixclk(struct msm_kms *kms, unsigned long rate,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
/* if we had >1 encoder, we'd need something more clever: */
|
||||
return mdp4_dtv_round_pixclk(encoder, rate);
|
||||
}
|
||||
|
||||
static void mdp4_preclose(struct msm_kms *kms, struct drm_file *file)
|
||||
{
|
||||
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
|
||||
struct msm_drm_private *priv = mdp4_kms->dev->dev_private;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < priv->num_crtcs; i++)
|
||||
mdp4_crtc_cancel_pending_flip(priv->crtcs[i], file);
|
||||
}
|
||||
|
||||
static void mdp4_destroy(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms));
|
||||
if (mdp4_kms->blank_cursor_iova)
|
||||
msm_gem_put_iova(mdp4_kms->blank_cursor_bo, mdp4_kms->id);
|
||||
if (mdp4_kms->blank_cursor_bo)
|
||||
drm_gem_object_unreference_unlocked(mdp4_kms->blank_cursor_bo);
|
||||
kfree(mdp4_kms);
|
||||
}
|
||||
|
||||
static const struct mdp_kms_funcs kms_funcs = {
|
||||
.base = {
|
||||
.hw_init = mdp4_hw_init,
|
||||
.irq_preinstall = mdp4_irq_preinstall,
|
||||
.irq_postinstall = mdp4_irq_postinstall,
|
||||
.irq_uninstall = mdp4_irq_uninstall,
|
||||
.irq = mdp4_irq,
|
||||
.enable_vblank = mdp4_enable_vblank,
|
||||
.disable_vblank = mdp4_disable_vblank,
|
||||
.get_format = mdp_get_format,
|
||||
.round_pixclk = mdp4_round_pixclk,
|
||||
.preclose = mdp4_preclose,
|
||||
.destroy = mdp4_destroy,
|
||||
},
|
||||
.set_irqmask = mdp4_set_irqmask,
|
||||
};
|
||||
|
||||
int mdp4_disable(struct mdp4_kms *mdp4_kms)
|
||||
{
|
||||
DBG("");
|
||||
|
||||
clk_disable_unprepare(mdp4_kms->clk);
|
||||
if (mdp4_kms->pclk)
|
||||
clk_disable_unprepare(mdp4_kms->pclk);
|
||||
clk_disable_unprepare(mdp4_kms->lut_clk);
|
||||
if (mdp4_kms->axi_clk)
|
||||
clk_disable_unprepare(mdp4_kms->axi_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdp4_enable(struct mdp4_kms *mdp4_kms)
|
||||
{
|
||||
DBG("");
|
||||
|
||||
clk_prepare_enable(mdp4_kms->clk);
|
||||
if (mdp4_kms->pclk)
|
||||
clk_prepare_enable(mdp4_kms->pclk);
|
||||
clk_prepare_enable(mdp4_kms->lut_clk);
|
||||
if (mdp4_kms->axi_clk)
|
||||
clk_prepare_enable(mdp4_kms->axi_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static struct drm_panel *detect_panel(struct drm_device *dev, const char *name)
|
||||
{
|
||||
struct device_node *n;
|
||||
struct drm_panel *panel = NULL;
|
||||
|
||||
n = of_parse_phandle(dev->dev->of_node, name, 0);
|
||||
if (n) {
|
||||
panel = of_drm_find_panel(n);
|
||||
if (!panel)
|
||||
panel = ERR_PTR(-EPROBE_DEFER);
|
||||
}
|
||||
|
||||
return panel;
|
||||
}
|
||||
#else
|
||||
static struct drm_panel *detect_panel(struct drm_device *dev, const char *name)
|
||||
{
|
||||
// ??? maybe use a module param to specify which panel is attached?
|
||||
}
|
||||
#endif
|
||||
|
||||
static int modeset_init(struct mdp4_kms *mdp4_kms)
|
||||
{
|
||||
struct drm_device *dev = mdp4_kms->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct drm_plane *plane;
|
||||
struct drm_crtc *crtc;
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector *connector;
|
||||
struct drm_panel *panel;
|
||||
struct hdmi *hdmi;
|
||||
int ret;
|
||||
|
||||
/* construct non-private planes: */
|
||||
plane = mdp4_plane_init(dev, VG1, false);
|
||||
if (IS_ERR(plane)) {
|
||||
dev_err(dev->dev, "failed to construct plane for VG1\n");
|
||||
ret = PTR_ERR(plane);
|
||||
goto fail;
|
||||
}
|
||||
priv->planes[priv->num_planes++] = plane;
|
||||
|
||||
plane = mdp4_plane_init(dev, VG2, false);
|
||||
if (IS_ERR(plane)) {
|
||||
dev_err(dev->dev, "failed to construct plane for VG2\n");
|
||||
ret = PTR_ERR(plane);
|
||||
goto fail;
|
||||
}
|
||||
priv->planes[priv->num_planes++] = plane;
|
||||
|
||||
/*
|
||||
* Setup the LCDC/LVDS path: RGB2 -> DMA_P -> LCDC -> LVDS:
|
||||
*/
|
||||
|
||||
panel = detect_panel(dev, "qcom,lvds-panel");
|
||||
if (IS_ERR(panel)) {
|
||||
ret = PTR_ERR(panel);
|
||||
dev_err(dev->dev, "failed to detect LVDS panel: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
plane = mdp4_plane_init(dev, RGB2, true);
|
||||
if (IS_ERR(plane)) {
|
||||
dev_err(dev->dev, "failed to construct plane for RGB2\n");
|
||||
ret = PTR_ERR(plane);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
crtc = mdp4_crtc_init(dev, plane, priv->num_crtcs, 0, DMA_P);
|
||||
if (IS_ERR(crtc)) {
|
||||
dev_err(dev->dev, "failed to construct crtc for DMA_P\n");
|
||||
ret = PTR_ERR(crtc);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
encoder = mdp4_lcdc_encoder_init(dev, panel);
|
||||
if (IS_ERR(encoder)) {
|
||||
dev_err(dev->dev, "failed to construct LCDC encoder\n");
|
||||
ret = PTR_ERR(encoder);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* LCDC can be hooked to DMA_P: */
|
||||
encoder->possible_crtcs = 1 << priv->num_crtcs;
|
||||
|
||||
priv->crtcs[priv->num_crtcs++] = crtc;
|
||||
priv->encoders[priv->num_encoders++] = encoder;
|
||||
|
||||
connector = mdp4_lvds_connector_init(dev, panel, encoder);
|
||||
if (IS_ERR(connector)) {
|
||||
ret = PTR_ERR(connector);
|
||||
dev_err(dev->dev, "failed to initialize LVDS connector: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->connectors[priv->num_connectors++] = connector;
|
||||
|
||||
/*
|
||||
* Setup DTV/HDMI path: RGB1 -> DMA_E -> DTV -> HDMI:
|
||||
*/
|
||||
|
||||
plane = mdp4_plane_init(dev, RGB1, true);
|
||||
if (IS_ERR(plane)) {
|
||||
dev_err(dev->dev, "failed to construct plane for RGB1\n");
|
||||
ret = PTR_ERR(plane);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
crtc = mdp4_crtc_init(dev, plane, priv->num_crtcs, 1, DMA_E);
|
||||
if (IS_ERR(crtc)) {
|
||||
dev_err(dev->dev, "failed to construct crtc for DMA_E\n");
|
||||
ret = PTR_ERR(crtc);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
encoder = mdp4_dtv_encoder_init(dev);
|
||||
if (IS_ERR(encoder)) {
|
||||
dev_err(dev->dev, "failed to construct DTV encoder\n");
|
||||
ret = PTR_ERR(encoder);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* DTV can be hooked to DMA_E: */
|
||||
encoder->possible_crtcs = 1 << priv->num_crtcs;
|
||||
|
||||
priv->crtcs[priv->num_crtcs++] = crtc;
|
||||
priv->encoders[priv->num_encoders++] = encoder;
|
||||
|
||||
hdmi = hdmi_init(dev, encoder);
|
||||
if (IS_ERR(hdmi)) {
|
||||
ret = PTR_ERR(hdmi);
|
||||
dev_err(dev->dev, "failed to initialize HDMI: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char *iommu_ports[] = {
|
||||
"mdp_port0_cb0", "mdp_port1_cb0",
|
||||
};
|
||||
|
||||
struct msm_kms *mdp4_kms_init(struct drm_device *dev)
|
||||
{
|
||||
struct platform_device *pdev = dev->platformdev;
|
||||
struct mdp4_platform_config *config = mdp4_get_config(pdev);
|
||||
struct mdp4_kms *mdp4_kms;
|
||||
struct msm_kms *kms = NULL;
|
||||
struct msm_mmu *mmu;
|
||||
int ret;
|
||||
|
||||
mdp4_kms = kzalloc(sizeof(*mdp4_kms), GFP_KERNEL);
|
||||
if (!mdp4_kms) {
|
||||
dev_err(dev->dev, "failed to allocate kms\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp_kms_init(&mdp4_kms->base, &kms_funcs);
|
||||
|
||||
kms = &mdp4_kms->base.base;
|
||||
|
||||
mdp4_kms->dev = dev;
|
||||
|
||||
mdp4_kms->mmio = msm_ioremap(pdev, NULL, "MDP4");
|
||||
if (IS_ERR(mdp4_kms->mmio)) {
|
||||
ret = PTR_ERR(mdp4_kms->mmio);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp4_kms->dsi_pll_vdda =
|
||||
devm_regulator_get_optional(&pdev->dev, "dsi_pll_vdda");
|
||||
if (IS_ERR(mdp4_kms->dsi_pll_vdda))
|
||||
mdp4_kms->dsi_pll_vdda = NULL;
|
||||
|
||||
mdp4_kms->dsi_pll_vddio =
|
||||
devm_regulator_get_optional(&pdev->dev, "dsi_pll_vddio");
|
||||
if (IS_ERR(mdp4_kms->dsi_pll_vddio))
|
||||
mdp4_kms->dsi_pll_vddio = NULL;
|
||||
|
||||
mdp4_kms->vdd = devm_regulator_get_exclusive(&pdev->dev, "vdd");
|
||||
if (IS_ERR(mdp4_kms->vdd))
|
||||
mdp4_kms->vdd = NULL;
|
||||
|
||||
if (mdp4_kms->vdd) {
|
||||
ret = regulator_enable(mdp4_kms->vdd);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to enable regulator vdd: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
mdp4_kms->clk = devm_clk_get(&pdev->dev, "core_clk");
|
||||
if (IS_ERR(mdp4_kms->clk)) {
|
||||
dev_err(dev->dev, "failed to get core_clk\n");
|
||||
ret = PTR_ERR(mdp4_kms->clk);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp4_kms->pclk = devm_clk_get(&pdev->dev, "iface_clk");
|
||||
if (IS_ERR(mdp4_kms->pclk))
|
||||
mdp4_kms->pclk = NULL;
|
||||
|
||||
// XXX if (rev >= MDP_REV_42) { ???
|
||||
mdp4_kms->lut_clk = devm_clk_get(&pdev->dev, "lut_clk");
|
||||
if (IS_ERR(mdp4_kms->lut_clk)) {
|
||||
dev_err(dev->dev, "failed to get lut_clk\n");
|
||||
ret = PTR_ERR(mdp4_kms->lut_clk);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp4_kms->axi_clk = devm_clk_get(&pdev->dev, "mdp_axi_clk");
|
||||
if (IS_ERR(mdp4_kms->axi_clk)) {
|
||||
dev_err(dev->dev, "failed to get axi_clk\n");
|
||||
ret = PTR_ERR(mdp4_kms->axi_clk);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
clk_set_rate(mdp4_kms->clk, config->max_clk);
|
||||
clk_set_rate(mdp4_kms->lut_clk, config->max_clk);
|
||||
|
||||
/* make sure things are off before attaching iommu (bootloader could
|
||||
* have left things on, in which case we'll start getting faults if
|
||||
* we don't disable):
|
||||
*/
|
||||
mdp4_enable(mdp4_kms);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_DSI_ENABLE, 0);
|
||||
mdp4_disable(mdp4_kms);
|
||||
mdelay(16);
|
||||
|
||||
if (config->iommu) {
|
||||
mmu = msm_iommu_new(&pdev->dev, config->iommu);
|
||||
if (IS_ERR(mmu)) {
|
||||
ret = PTR_ERR(mmu);
|
||||
goto fail;
|
||||
}
|
||||
ret = mmu->funcs->attach(mmu, iommu_ports,
|
||||
ARRAY_SIZE(iommu_ports));
|
||||
if (ret)
|
||||
goto fail;
|
||||
} else {
|
||||
dev_info(dev->dev, "no iommu, fallback to phys "
|
||||
"contig buffers for scanout\n");
|
||||
mmu = NULL;
|
||||
}
|
||||
|
||||
mdp4_kms->id = msm_register_mmu(dev, mmu);
|
||||
if (mdp4_kms->id < 0) {
|
||||
ret = mdp4_kms->id;
|
||||
dev_err(dev->dev, "failed to register mdp4 iommu: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = modeset_init(mdp4_kms);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "modeset_init failed: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
mdp4_kms->blank_cursor_bo = msm_gem_new(dev, SZ_16K, MSM_BO_WC);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
if (IS_ERR(mdp4_kms->blank_cursor_bo)) {
|
||||
ret = PTR_ERR(mdp4_kms->blank_cursor_bo);
|
||||
dev_err(dev->dev, "could not allocate blank-cursor bo: %d\n", ret);
|
||||
mdp4_kms->blank_cursor_bo = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = msm_gem_get_iova(mdp4_kms->blank_cursor_bo, mdp4_kms->id,
|
||||
&mdp4_kms->blank_cursor_iova);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "could not pin blank-cursor bo: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return kms;
|
||||
|
||||
fail:
|
||||
if (kms)
|
||||
mdp4_destroy(kms);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev)
|
||||
{
|
||||
static struct mdp4_platform_config config = {};
|
||||
#ifdef CONFIG_OF
|
||||
/* TODO */
|
||||
config.max_clk = 266667000;
|
||||
config.iommu = iommu_domain_alloc(&platform_bus_type);
|
||||
#else
|
||||
if (cpu_is_apq8064())
|
||||
config.max_clk = 266667000;
|
||||
else
|
||||
config.max_clk = 200000000;
|
||||
|
||||
config.iommu = msm_get_iommu_domain(DISPLAY_READ_DOMAIN);
|
||||
#endif
|
||||
return &config;
|
||||
}
|
||||
256
drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h
Normal file
256
drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MDP4_KMS_H__
|
||||
#define __MDP4_KMS_H__
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_kms.h"
|
||||
#include "mdp/mdp_kms.h"
|
||||
#include "mdp4.xml.h"
|
||||
|
||||
#include "drm_panel.h"
|
||||
|
||||
struct mdp4_kms {
|
||||
struct mdp_kms base;
|
||||
|
||||
struct drm_device *dev;
|
||||
|
||||
int rev;
|
||||
|
||||
/* Shadow value for MDP4_LAYERMIXER_IN_CFG.. since setup for all
|
||||
* crtcs/encoders is in one shared register, we need to update it
|
||||
* via read/modify/write. But to avoid getting confused by power-
|
||||
* on-default values after resume, use this shadow value instead:
|
||||
*/
|
||||
uint32_t mixer_cfg;
|
||||
|
||||
/* mapper-id used to request GEM buffer mapped for scanout: */
|
||||
int id;
|
||||
|
||||
void __iomem *mmio;
|
||||
|
||||
struct regulator *dsi_pll_vdda;
|
||||
struct regulator *dsi_pll_vddio;
|
||||
struct regulator *vdd;
|
||||
|
||||
struct clk *clk;
|
||||
struct clk *pclk;
|
||||
struct clk *lut_clk;
|
||||
struct clk *axi_clk;
|
||||
|
||||
struct mdp_irq error_handler;
|
||||
|
||||
/* empty/blank cursor bo to use when cursor is "disabled" */
|
||||
struct drm_gem_object *blank_cursor_bo;
|
||||
uint32_t blank_cursor_iova;
|
||||
};
|
||||
#define to_mdp4_kms(x) container_of(x, struct mdp4_kms, base)
|
||||
|
||||
/* platform config data (ie. from DT, or pdata) */
|
||||
struct mdp4_platform_config {
|
||||
struct iommu_domain *iommu;
|
||||
uint32_t max_clk;
|
||||
};
|
||||
|
||||
static inline void mdp4_write(struct mdp4_kms *mdp4_kms, u32 reg, u32 data)
|
||||
{
|
||||
msm_writel(data, mdp4_kms->mmio + reg);
|
||||
}
|
||||
|
||||
static inline u32 mdp4_read(struct mdp4_kms *mdp4_kms, u32 reg)
|
||||
{
|
||||
return msm_readl(mdp4_kms->mmio + reg);
|
||||
}
|
||||
|
||||
static inline uint32_t pipe2flush(enum mdp4_pipe pipe)
|
||||
{
|
||||
switch (pipe) {
|
||||
case VG1: return MDP4_OVERLAY_FLUSH_VG1;
|
||||
case VG2: return MDP4_OVERLAY_FLUSH_VG2;
|
||||
case RGB1: return MDP4_OVERLAY_FLUSH_RGB1;
|
||||
case RGB2: return MDP4_OVERLAY_FLUSH_RGB2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t ovlp2flush(int ovlp)
|
||||
{
|
||||
switch (ovlp) {
|
||||
case 0: return MDP4_OVERLAY_FLUSH_OVLP0;
|
||||
case 1: return MDP4_OVERLAY_FLUSH_OVLP1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t dma2irq(enum mdp4_dma dma)
|
||||
{
|
||||
switch (dma) {
|
||||
case DMA_P: return MDP4_IRQ_DMA_P_DONE;
|
||||
case DMA_S: return MDP4_IRQ_DMA_S_DONE;
|
||||
case DMA_E: return MDP4_IRQ_DMA_E_DONE;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t dma2err(enum mdp4_dma dma)
|
||||
{
|
||||
switch (dma) {
|
||||
case DMA_P: return MDP4_IRQ_PRIMARY_INTF_UDERRUN;
|
||||
case DMA_S: return 0; // ???
|
||||
case DMA_E: return MDP4_IRQ_EXTERNAL_INTF_UDERRUN;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t mixercfg(uint32_t mixer_cfg, int mixer,
|
||||
enum mdp4_pipe pipe, enum mdp_mixer_stage_id stage)
|
||||
{
|
||||
switch (pipe) {
|
||||
case VG1:
|
||||
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE0__MASK |
|
||||
MDP4_LAYERMIXER_IN_CFG_PIPE0_MIXER1);
|
||||
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE0(stage) |
|
||||
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE0_MIXER1);
|
||||
break;
|
||||
case VG2:
|
||||
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE1__MASK |
|
||||
MDP4_LAYERMIXER_IN_CFG_PIPE1_MIXER1);
|
||||
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE1(stage) |
|
||||
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE1_MIXER1);
|
||||
break;
|
||||
case RGB1:
|
||||
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE2__MASK |
|
||||
MDP4_LAYERMIXER_IN_CFG_PIPE2_MIXER1);
|
||||
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE2(stage) |
|
||||
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE2_MIXER1);
|
||||
break;
|
||||
case RGB2:
|
||||
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE3__MASK |
|
||||
MDP4_LAYERMIXER_IN_CFG_PIPE3_MIXER1);
|
||||
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE3(stage) |
|
||||
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE3_MIXER1);
|
||||
break;
|
||||
case RGB3:
|
||||
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE4__MASK |
|
||||
MDP4_LAYERMIXER_IN_CFG_PIPE4_MIXER1);
|
||||
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE4(stage) |
|
||||
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE4_MIXER1);
|
||||
break;
|
||||
case VG3:
|
||||
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE5__MASK |
|
||||
MDP4_LAYERMIXER_IN_CFG_PIPE5_MIXER1);
|
||||
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE5(stage) |
|
||||
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE5_MIXER1);
|
||||
break;
|
||||
case VG4:
|
||||
mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE6__MASK |
|
||||
MDP4_LAYERMIXER_IN_CFG_PIPE6_MIXER1);
|
||||
mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE6(stage) |
|
||||
COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE6_MIXER1);
|
||||
break;
|
||||
default:
|
||||
WARN_ON("invalid pipe");
|
||||
break;
|
||||
}
|
||||
|
||||
return mixer_cfg;
|
||||
}
|
||||
|
||||
int mdp4_disable(struct mdp4_kms *mdp4_kms);
|
||||
int mdp4_enable(struct mdp4_kms *mdp4_kms);
|
||||
|
||||
void mdp4_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask);
|
||||
void mdp4_irq_preinstall(struct msm_kms *kms);
|
||||
int mdp4_irq_postinstall(struct msm_kms *kms);
|
||||
void mdp4_irq_uninstall(struct msm_kms *kms);
|
||||
irqreturn_t mdp4_irq(struct msm_kms *kms);
|
||||
int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
|
||||
void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
|
||||
|
||||
static inline
|
||||
uint32_t mdp4_get_formats(enum mdp4_pipe pipe_id, uint32_t *pixel_formats,
|
||||
uint32_t max_formats)
|
||||
{
|
||||
/* TODO when we have YUV, we need to filter supported formats
|
||||
* based on pipe_id..
|
||||
*/
|
||||
return mdp_get_formats(pixel_formats, max_formats);
|
||||
}
|
||||
|
||||
void mdp4_plane_install_properties(struct drm_plane *plane,
|
||||
struct drm_mode_object *obj);
|
||||
void mdp4_plane_set_scanout(struct drm_plane *plane,
|
||||
struct drm_framebuffer *fb);
|
||||
int mdp4_plane_mode_set(struct drm_plane *plane,
|
||||
struct drm_crtc *crtc, struct drm_framebuffer *fb,
|
||||
int crtc_x, int crtc_y,
|
||||
unsigned int crtc_w, unsigned int crtc_h,
|
||||
uint32_t src_x, uint32_t src_y,
|
||||
uint32_t src_w, uint32_t src_h);
|
||||
enum mdp4_pipe mdp4_plane_pipe(struct drm_plane *plane);
|
||||
struct drm_plane *mdp4_plane_init(struct drm_device *dev,
|
||||
enum mdp4_pipe pipe_id, bool private_plane);
|
||||
|
||||
uint32_t mdp4_crtc_vblank(struct drm_crtc *crtc);
|
||||
void mdp4_crtc_cancel_pending_flip(struct drm_crtc *crtc, struct drm_file *file);
|
||||
void mdp4_crtc_set_config(struct drm_crtc *crtc, uint32_t config);
|
||||
void mdp4_crtc_set_intf(struct drm_crtc *crtc, enum mdp4_intf intf, int mixer);
|
||||
void mdp4_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane);
|
||||
void mdp4_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane);
|
||||
struct drm_crtc *mdp4_crtc_init(struct drm_device *dev,
|
||||
struct drm_plane *plane, int id, int ovlp_id,
|
||||
enum mdp4_dma dma_id);
|
||||
|
||||
long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate);
|
||||
struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev);
|
||||
|
||||
long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate);
|
||||
struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev,
|
||||
struct drm_panel *panel);
|
||||
|
||||
struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev,
|
||||
struct drm_panel *panel, struct drm_encoder *encoder);
|
||||
|
||||
#ifdef CONFIG_COMMON_CLK
|
||||
struct clk *mpd4_lvds_pll_init(struct drm_device *dev);
|
||||
#else
|
||||
static inline struct clk *mpd4_lvds_pll_init(struct drm_device *dev)
|
||||
{
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MSM_BUS_SCALING
|
||||
static inline int match_dev_name(struct device *dev, void *data)
|
||||
{
|
||||
return !strcmp(dev_name(dev), data);
|
||||
}
|
||||
/* bus scaling data is associated with extra pointless platform devices,
|
||||
* "dtv", etc.. this is a bit of a hack, but we need a way for encoders
|
||||
* to find their pdata to make the bus-scaling stuff work.
|
||||
*/
|
||||
static inline void *mdp4_find_pdata(const char *devname)
|
||||
{
|
||||
struct device *dev;
|
||||
dev = bus_find_device(&platform_bus_type, NULL,
|
||||
(void *)devname, match_dev_name);
|
||||
return dev ? dev->platform_data : NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __MDP4_KMS_H__ */
|
||||
506
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c
Normal file
506
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
* Author: Vinay Simha <vinaysimha@inforcecomputing.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mdp4_kms.h"
|
||||
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_crtc_helper.h"
|
||||
|
||||
struct mdp4_lcdc_encoder {
|
||||
struct drm_encoder base;
|
||||
struct drm_panel *panel;
|
||||
struct clk *lcdc_clk;
|
||||
unsigned long int pixclock;
|
||||
struct regulator *regs[3];
|
||||
bool enabled;
|
||||
uint32_t bsc;
|
||||
};
|
||||
#define to_mdp4_lcdc_encoder(x) container_of(x, struct mdp4_lcdc_encoder, base)
|
||||
|
||||
static struct mdp4_kms *get_kms(struct drm_encoder *encoder)
|
||||
{
|
||||
struct msm_drm_private *priv = encoder->dev->dev_private;
|
||||
return to_mdp4_kms(to_mdp_kms(priv->kms));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MSM_BUS_SCALING
|
||||
#include <mach/board.h>
|
||||
static void bs_init(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder)
|
||||
{
|
||||
struct drm_device *dev = mdp4_lcdc_encoder->base.dev;
|
||||
struct lcdc_platform_data *lcdc_pdata = mdp4_find_pdata("lvds.0");
|
||||
|
||||
if (!lcdc_pdata) {
|
||||
dev_err(dev->dev, "could not find lvds pdata\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (lcdc_pdata->bus_scale_table) {
|
||||
mdp4_lcdc_encoder->bsc = msm_bus_scale_register_client(
|
||||
lcdc_pdata->bus_scale_table);
|
||||
DBG("lvds : bus scale client: %08x", mdp4_lcdc_encoder->bsc);
|
||||
}
|
||||
}
|
||||
|
||||
static void bs_fini(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder)
|
||||
{
|
||||
if (mdp4_lcdc_encoder->bsc) {
|
||||
msm_bus_scale_unregister_client(mdp4_lcdc_encoder->bsc);
|
||||
mdp4_lcdc_encoder->bsc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void bs_set(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder, int idx)
|
||||
{
|
||||
if (mdp4_lcdc_encoder->bsc) {
|
||||
DBG("set bus scaling: %d", idx);
|
||||
msm_bus_scale_client_update_request(mdp4_lcdc_encoder->bsc, idx);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void bs_init(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) {}
|
||||
static void bs_fini(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) {}
|
||||
static void bs_set(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder, int idx) {}
|
||||
#endif
|
||||
|
||||
static void mdp4_lcdc_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder =
|
||||
to_mdp4_lcdc_encoder(encoder);
|
||||
bs_fini(mdp4_lcdc_encoder);
|
||||
drm_encoder_cleanup(encoder);
|
||||
kfree(mdp4_lcdc_encoder);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs mdp4_lcdc_encoder_funcs = {
|
||||
.destroy = mdp4_lcdc_encoder_destroy,
|
||||
};
|
||||
|
||||
/* this should probably be a helper: */
|
||||
struct drm_connector *get_connector(struct drm_encoder *encoder)
|
||||
{
|
||||
struct drm_device *dev = encoder->dev;
|
||||
struct drm_connector *connector;
|
||||
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head)
|
||||
if (connector->encoder == encoder)
|
||||
return connector;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void setup_phy(struct drm_encoder *encoder)
|
||||
{
|
||||
struct drm_device *dev = encoder->dev;
|
||||
struct drm_connector *connector = get_connector(encoder);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(encoder);
|
||||
uint32_t lvds_intf = 0, lvds_phy_cfg0 = 0;
|
||||
int bpp, nchan, swap;
|
||||
|
||||
if (!connector)
|
||||
return;
|
||||
|
||||
bpp = 3 * connector->display_info.bpc;
|
||||
|
||||
if (!bpp)
|
||||
bpp = 18;
|
||||
|
||||
/* TODO, these should come from panel somehow: */
|
||||
nchan = 1;
|
||||
swap = 0;
|
||||
|
||||
switch (bpp) {
|
||||
case 24:
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x08) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x05) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x04) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x03));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x02) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x01) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x00));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x11) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x10) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0d) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0c));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0b) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0a) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x09));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x15));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x14) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x13) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x12));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(3),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1b) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x17) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x16) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0f));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(3),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0e) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x07) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x06));
|
||||
if (nchan == 2) {
|
||||
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE3_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN;
|
||||
} else {
|
||||
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN;
|
||||
}
|
||||
break;
|
||||
|
||||
case 18:
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x0a) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x07) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x06) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x05));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x04) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x03) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x02));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x13) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x12) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0f) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0e));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0d) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0c) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x0b));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x17));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2),
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x16) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x15) |
|
||||
MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x14));
|
||||
if (nchan == 2) {
|
||||
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN;
|
||||
} else {
|
||||
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN;
|
||||
}
|
||||
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_RGB_OUT;
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(dev->dev, "unknown bpp: %d\n", bpp);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (nchan) {
|
||||
case 1:
|
||||
lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0;
|
||||
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_MODE_SEL;
|
||||
break;
|
||||
case 2:
|
||||
lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0 |
|
||||
MDP4_LVDS_PHY_CFG0_CHANNEL1;
|
||||
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_CLK_LANE_EN |
|
||||
MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev->dev, "unknown # of channels: %d\n", nchan);
|
||||
return;
|
||||
}
|
||||
|
||||
if (swap)
|
||||
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH_SWAP;
|
||||
|
||||
lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_ENABLE;
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_INTF_CTL, lvds_intf);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG2, 0x30);
|
||||
|
||||
mb();
|
||||
udelay(1);
|
||||
lvds_phy_cfg0 |= MDP4_LVDS_PHY_CFG0_SERIALIZATION_ENBLE;
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0);
|
||||
}
|
||||
|
||||
static void mdp4_lcdc_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
struct drm_device *dev = encoder->dev;
|
||||
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder =
|
||||
to_mdp4_lcdc_encoder(encoder);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(encoder);
|
||||
struct drm_panel *panel = mdp4_lcdc_encoder->panel;
|
||||
bool enabled = (mode == DRM_MODE_DPMS_ON);
|
||||
int i, ret;
|
||||
|
||||
DBG("mode=%d", mode);
|
||||
|
||||
if (enabled == mdp4_lcdc_encoder->enabled)
|
||||
return;
|
||||
|
||||
if (enabled) {
|
||||
unsigned long pc = mdp4_lcdc_encoder->pixclock;
|
||||
int ret;
|
||||
|
||||
bs_set(mdp4_lcdc_encoder, 1);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) {
|
||||
ret = regulator_enable(mdp4_lcdc_encoder->regs[i]);
|
||||
if (ret)
|
||||
dev_err(dev->dev, "failed to enable regulator: %d\n", ret);
|
||||
}
|
||||
|
||||
DBG("setting lcdc_clk=%lu", pc);
|
||||
ret = clk_set_rate(mdp4_lcdc_encoder->lcdc_clk, pc);
|
||||
if (ret)
|
||||
dev_err(dev->dev, "failed to configure lcdc_clk: %d\n", ret);
|
||||
ret = clk_prepare_enable(mdp4_lcdc_encoder->lcdc_clk);
|
||||
if (ret)
|
||||
dev_err(dev->dev, "failed to enable lcdc_clk: %d\n", ret);
|
||||
|
||||
if (panel)
|
||||
drm_panel_enable(panel);
|
||||
|
||||
setup_phy(encoder);
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 1);
|
||||
} else {
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0);
|
||||
|
||||
if (panel)
|
||||
drm_panel_disable(panel);
|
||||
|
||||
/*
|
||||
* Wait for a vsync so we know the ENABLE=0 latched before
|
||||
* the (connector) source of the vsync's gets disabled,
|
||||
* otherwise we end up in a funny state if we re-enable
|
||||
* before the disable latches, which results that some of
|
||||
* the settings changes for the new modeset (like new
|
||||
* scanout buffer) don't latch properly..
|
||||
*/
|
||||
mdp_irq_wait(&mdp4_kms->base, MDP4_IRQ_PRIMARY_VSYNC);
|
||||
|
||||
clk_disable_unprepare(mdp4_lcdc_encoder->lcdc_clk);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) {
|
||||
ret = regulator_disable(mdp4_lcdc_encoder->regs[i]);
|
||||
if (ret)
|
||||
dev_err(dev->dev, "failed to disable regulator: %d\n", ret);
|
||||
}
|
||||
|
||||
bs_set(mdp4_lcdc_encoder, 0);
|
||||
}
|
||||
|
||||
mdp4_lcdc_encoder->enabled = enabled;
|
||||
}
|
||||
|
||||
static bool mdp4_lcdc_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mdp4_lcdc_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder =
|
||||
to_mdp4_lcdc_encoder(encoder);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(encoder);
|
||||
uint32_t lcdc_hsync_skew, vsync_period, vsync_len, ctrl_pol;
|
||||
uint32_t display_v_start, display_v_end;
|
||||
uint32_t hsync_start_x, hsync_end_x;
|
||||
|
||||
mode = adjusted_mode;
|
||||
|
||||
DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
|
||||
mode->base.id, mode->name,
|
||||
mode->vrefresh, mode->clock,
|
||||
mode->hdisplay, mode->hsync_start,
|
||||
mode->hsync_end, mode->htotal,
|
||||
mode->vdisplay, mode->vsync_start,
|
||||
mode->vsync_end, mode->vtotal,
|
||||
mode->type, mode->flags);
|
||||
|
||||
mdp4_lcdc_encoder->pixclock = mode->clock * 1000;
|
||||
|
||||
DBG("pixclock=%lu", mdp4_lcdc_encoder->pixclock);
|
||||
|
||||
ctrl_pol = 0;
|
||||
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
||||
ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_HSYNC_LOW;
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_VSYNC_LOW;
|
||||
/* probably need to get DATA_EN polarity from panel.. */
|
||||
|
||||
lcdc_hsync_skew = 0; /* get this from panel? */
|
||||
|
||||
hsync_start_x = (mode->htotal - mode->hsync_start);
|
||||
hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1;
|
||||
|
||||
vsync_period = mode->vtotal * mode->htotal;
|
||||
vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal;
|
||||
display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + lcdc_hsync_skew;
|
||||
display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + lcdc_hsync_skew - 1;
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_CTRL,
|
||||
MDP4_LCDC_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) |
|
||||
MDP4_LCDC_HSYNC_CTRL_PERIOD(mode->htotal));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_PERIOD, vsync_period);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_LEN, vsync_len);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_HCTRL,
|
||||
MDP4_LCDC_DISPLAY_HCTRL_START(hsync_start_x) |
|
||||
MDP4_LCDC_DISPLAY_HCTRL_END(hsync_end_x));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VSTART, display_v_start);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VEND, display_v_end);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_BORDER_CLR, 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_UNDERFLOW_CLR,
|
||||
MDP4_LCDC_UNDERFLOW_CLR_ENABLE_RECOVERY |
|
||||
MDP4_LCDC_UNDERFLOW_CLR_COLOR(0xff));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_SKEW, lcdc_hsync_skew);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_CTRL_POLARITY, ctrl_pol);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_HCTL,
|
||||
MDP4_LCDC_ACTIVE_HCTL_START(0) |
|
||||
MDP4_LCDC_ACTIVE_HCTL_END(0));
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VSTART, 0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VEND, 0);
|
||||
}
|
||||
|
||||
static void mdp4_lcdc_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
mdp4_lcdc_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
||||
}
|
||||
|
||||
static void mdp4_lcdc_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
/* TODO: hard-coded for 18bpp: */
|
||||
mdp4_crtc_set_config(encoder->crtc,
|
||||
MDP4_DMA_CONFIG_R_BPC(BPC6) |
|
||||
MDP4_DMA_CONFIG_G_BPC(BPC6) |
|
||||
MDP4_DMA_CONFIG_B_BPC(BPC6) |
|
||||
MDP4_DMA_CONFIG_PACK_ALIGN_MSB |
|
||||
MDP4_DMA_CONFIG_PACK(0x21) |
|
||||
MDP4_DMA_CONFIG_DEFLKR_EN |
|
||||
MDP4_DMA_CONFIG_DITHER_EN);
|
||||
mdp4_crtc_set_intf(encoder->crtc, INTF_LCDC_DTV, 0);
|
||||
mdp4_lcdc_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_helper_funcs mdp4_lcdc_encoder_helper_funcs = {
|
||||
.dpms = mdp4_lcdc_encoder_dpms,
|
||||
.mode_fixup = mdp4_lcdc_encoder_mode_fixup,
|
||||
.mode_set = mdp4_lcdc_encoder_mode_set,
|
||||
.prepare = mdp4_lcdc_encoder_prepare,
|
||||
.commit = mdp4_lcdc_encoder_commit,
|
||||
};
|
||||
|
||||
long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate)
|
||||
{
|
||||
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder =
|
||||
to_mdp4_lcdc_encoder(encoder);
|
||||
return clk_round_rate(mdp4_lcdc_encoder->lcdc_clk, rate);
|
||||
}
|
||||
|
||||
/* initialize encoder */
|
||||
struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev,
|
||||
struct drm_panel *panel)
|
||||
{
|
||||
struct drm_encoder *encoder = NULL;
|
||||
struct mdp4_lcdc_encoder *mdp4_lcdc_encoder;
|
||||
struct regulator *reg;
|
||||
int ret;
|
||||
|
||||
mdp4_lcdc_encoder = kzalloc(sizeof(*mdp4_lcdc_encoder), GFP_KERNEL);
|
||||
if (!mdp4_lcdc_encoder) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp4_lcdc_encoder->panel = panel;
|
||||
|
||||
encoder = &mdp4_lcdc_encoder->base;
|
||||
|
||||
drm_encoder_init(dev, encoder, &mdp4_lcdc_encoder_funcs,
|
||||
DRM_MODE_ENCODER_LVDS);
|
||||
drm_encoder_helper_add(encoder, &mdp4_lcdc_encoder_helper_funcs);
|
||||
|
||||
/* TODO: do we need different pll in other cases? */
|
||||
mdp4_lcdc_encoder->lcdc_clk = mpd4_lvds_pll_init(dev);
|
||||
if (IS_ERR(mdp4_lcdc_encoder->lcdc_clk)) {
|
||||
dev_err(dev->dev, "failed to get lvds_clk\n");
|
||||
ret = PTR_ERR(mdp4_lcdc_encoder->lcdc_clk);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* TODO: different regulators in other cases? */
|
||||
reg = devm_regulator_get(dev->dev, "lvds-vccs-3p3v");
|
||||
if (IS_ERR(reg)) {
|
||||
ret = PTR_ERR(reg);
|
||||
dev_err(dev->dev, "failed to get lvds-vccs-3p3v: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
mdp4_lcdc_encoder->regs[0] = reg;
|
||||
|
||||
reg = devm_regulator_get(dev->dev, "lvds-pll-vdda");
|
||||
if (IS_ERR(reg)) {
|
||||
ret = PTR_ERR(reg);
|
||||
dev_err(dev->dev, "failed to get lvds-pll-vdda: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
mdp4_lcdc_encoder->regs[1] = reg;
|
||||
|
||||
reg = devm_regulator_get(dev->dev, "lvds-vdda");
|
||||
if (IS_ERR(reg)) {
|
||||
ret = PTR_ERR(reg);
|
||||
dev_err(dev->dev, "failed to get lvds-vdda: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
mdp4_lcdc_encoder->regs[2] = reg;
|
||||
|
||||
bs_init(mdp4_lcdc_encoder);
|
||||
|
||||
return encoder;
|
||||
|
||||
fail:
|
||||
if (encoder)
|
||||
mdp4_lcdc_encoder_destroy(encoder);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
151
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c
Normal file
151
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
* Author: Vinay Simha <vinaysimha@inforcecomputing.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include "mdp4_kms.h"
|
||||
|
||||
struct mdp4_lvds_connector {
|
||||
struct drm_connector base;
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_panel *panel;
|
||||
};
|
||||
#define to_mdp4_lvds_connector(x) container_of(x, struct mdp4_lvds_connector, base)
|
||||
|
||||
static enum drm_connector_status mdp4_lvds_connector_detect(
|
||||
struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct mdp4_lvds_connector *mdp4_lvds_connector =
|
||||
to_mdp4_lvds_connector(connector);
|
||||
|
||||
return mdp4_lvds_connector->panel ?
|
||||
connector_status_connected :
|
||||
connector_status_disconnected;
|
||||
}
|
||||
|
||||
static void mdp4_lvds_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct mdp4_lvds_connector *mdp4_lvds_connector =
|
||||
to_mdp4_lvds_connector(connector);
|
||||
struct drm_panel *panel = mdp4_lvds_connector->panel;
|
||||
|
||||
if (panel)
|
||||
drm_panel_detach(panel);
|
||||
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
|
||||
kfree(mdp4_lvds_connector);
|
||||
}
|
||||
|
||||
static int mdp4_lvds_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct mdp4_lvds_connector *mdp4_lvds_connector =
|
||||
to_mdp4_lvds_connector(connector);
|
||||
struct drm_panel *panel = mdp4_lvds_connector->panel;
|
||||
int ret = 0;
|
||||
|
||||
if (panel)
|
||||
ret = panel->funcs->get_modes(panel);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mdp4_lvds_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct mdp4_lvds_connector *mdp4_lvds_connector =
|
||||
to_mdp4_lvds_connector(connector);
|
||||
struct drm_encoder *encoder = mdp4_lvds_connector->encoder;
|
||||
long actual, requested;
|
||||
|
||||
requested = 1000 * mode->clock;
|
||||
actual = mdp4_lcdc_round_pixclk(encoder, requested);
|
||||
|
||||
DBG("requested=%ld, actual=%ld", requested, actual);
|
||||
|
||||
if (actual != requested)
|
||||
return MODE_CLOCK_RANGE;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static struct drm_encoder *
|
||||
mdp4_lvds_connector_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct mdp4_lvds_connector *mdp4_lvds_connector =
|
||||
to_mdp4_lvds_connector(connector);
|
||||
return mdp4_lvds_connector->encoder;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs mdp4_lvds_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.detect = mdp4_lvds_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = mdp4_lvds_connector_destroy,
|
||||
};
|
||||
|
||||
static const struct drm_connector_helper_funcs mdp4_lvds_connector_helper_funcs = {
|
||||
.get_modes = mdp4_lvds_connector_get_modes,
|
||||
.mode_valid = mdp4_lvds_connector_mode_valid,
|
||||
.best_encoder = mdp4_lvds_connector_best_encoder,
|
||||
};
|
||||
|
||||
/* initialize connector */
|
||||
struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev,
|
||||
struct drm_panel *panel, struct drm_encoder *encoder)
|
||||
{
|
||||
struct drm_connector *connector = NULL;
|
||||
struct mdp4_lvds_connector *mdp4_lvds_connector;
|
||||
int ret;
|
||||
|
||||
mdp4_lvds_connector = kzalloc(sizeof(*mdp4_lvds_connector), GFP_KERNEL);
|
||||
if (!mdp4_lvds_connector) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp4_lvds_connector->encoder = encoder;
|
||||
mdp4_lvds_connector->panel = panel;
|
||||
|
||||
connector = &mdp4_lvds_connector->base;
|
||||
|
||||
drm_connector_init(dev, connector, &mdp4_lvds_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
drm_connector_helper_add(connector, &mdp4_lvds_connector_helper_funcs);
|
||||
|
||||
connector->polled = 0;
|
||||
|
||||
connector->interlace_allowed = 0;
|
||||
connector->doublescan_allowed = 0;
|
||||
|
||||
drm_connector_register(connector);
|
||||
|
||||
drm_mode_connector_attach_encoder(connector, encoder);
|
||||
|
||||
if (panel)
|
||||
drm_panel_attach(panel, connector);
|
||||
|
||||
return connector;
|
||||
|
||||
fail:
|
||||
if (connector)
|
||||
mdp4_lvds_connector_destroy(connector);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
172
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c
Normal file
172
drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
#include "mdp4_kms.h"
|
||||
|
||||
struct mdp4_lvds_pll {
|
||||
struct clk_hw pll_hw;
|
||||
struct drm_device *dev;
|
||||
unsigned long pixclk;
|
||||
};
|
||||
#define to_mdp4_lvds_pll(x) container_of(x, struct mdp4_lvds_pll, pll_hw)
|
||||
|
||||
static struct mdp4_kms *get_kms(struct mdp4_lvds_pll *lvds_pll)
|
||||
{
|
||||
struct msm_drm_private *priv = lvds_pll->dev->dev_private;
|
||||
return to_mdp4_kms(to_mdp_kms(priv->kms));
|
||||
}
|
||||
|
||||
struct pll_rate {
|
||||
unsigned long rate;
|
||||
struct {
|
||||
uint32_t val;
|
||||
uint32_t reg;
|
||||
} conf[32];
|
||||
};
|
||||
|
||||
/* NOTE: keep sorted highest freq to lowest: */
|
||||
static const struct pll_rate freqtbl[] = {
|
||||
{ 72000000, {
|
||||
{ 0x8f, REG_MDP4_LVDS_PHY_PLL_CTRL_1 },
|
||||
{ 0x30, REG_MDP4_LVDS_PHY_PLL_CTRL_2 },
|
||||
{ 0xc6, REG_MDP4_LVDS_PHY_PLL_CTRL_3 },
|
||||
{ 0x10, REG_MDP4_LVDS_PHY_PLL_CTRL_5 },
|
||||
{ 0x07, REG_MDP4_LVDS_PHY_PLL_CTRL_6 },
|
||||
{ 0x62, REG_MDP4_LVDS_PHY_PLL_CTRL_7 },
|
||||
{ 0x41, REG_MDP4_LVDS_PHY_PLL_CTRL_8 },
|
||||
{ 0x0d, REG_MDP4_LVDS_PHY_PLL_CTRL_9 },
|
||||
{ 0, 0 } }
|
||||
},
|
||||
};
|
||||
|
||||
static const struct pll_rate *find_rate(unsigned long rate)
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i < ARRAY_SIZE(freqtbl); i++)
|
||||
if (rate > freqtbl[i].rate)
|
||||
return &freqtbl[i-1];
|
||||
return &freqtbl[i-1];
|
||||
}
|
||||
|
||||
static int mpd4_lvds_pll_enable(struct clk_hw *hw)
|
||||
{
|
||||
struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(lvds_pll);
|
||||
const struct pll_rate *pll_rate = find_rate(lvds_pll->pixclk);
|
||||
int i;
|
||||
|
||||
DBG("pixclk=%lu (%lu)", lvds_pll->pixclk, pll_rate->rate);
|
||||
|
||||
if (WARN_ON(!pll_rate))
|
||||
return -EINVAL;
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_PHY_RESET, 0x33);
|
||||
|
||||
for (i = 0; pll_rate->conf[i].reg; i++)
|
||||
mdp4_write(mdp4_kms, pll_rate->conf[i].reg, pll_rate->conf[i].val);
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x01);
|
||||
|
||||
/* Wait until LVDS PLL is locked and ready */
|
||||
while (!mdp4_read(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_LOCKED))
|
||||
cpu_relax();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mpd4_lvds_pll_disable(struct clk_hw *hw)
|
||||
{
|
||||
struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(lvds_pll);
|
||||
|
||||
DBG("");
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, 0x0);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x0);
|
||||
}
|
||||
|
||||
static unsigned long mpd4_lvds_pll_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw);
|
||||
return lvds_pll->pixclk;
|
||||
}
|
||||
|
||||
static long mpd4_lvds_pll_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long *parent_rate)
|
||||
{
|
||||
const struct pll_rate *pll_rate = find_rate(rate);
|
||||
return pll_rate->rate;
|
||||
}
|
||||
|
||||
static int mpd4_lvds_pll_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw);
|
||||
lvds_pll->pixclk = rate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct clk_ops mpd4_lvds_pll_ops = {
|
||||
.enable = mpd4_lvds_pll_enable,
|
||||
.disable = mpd4_lvds_pll_disable,
|
||||
.recalc_rate = mpd4_lvds_pll_recalc_rate,
|
||||
.round_rate = mpd4_lvds_pll_round_rate,
|
||||
.set_rate = mpd4_lvds_pll_set_rate,
|
||||
};
|
||||
|
||||
static const char *mpd4_lvds_pll_parents[] = {
|
||||
"pxo",
|
||||
};
|
||||
|
||||
static struct clk_init_data pll_init = {
|
||||
.name = "mpd4_lvds_pll",
|
||||
.ops = &mpd4_lvds_pll_ops,
|
||||
.parent_names = mpd4_lvds_pll_parents,
|
||||
.num_parents = ARRAY_SIZE(mpd4_lvds_pll_parents),
|
||||
};
|
||||
|
||||
struct clk *mpd4_lvds_pll_init(struct drm_device *dev)
|
||||
{
|
||||
struct mdp4_lvds_pll *lvds_pll;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
lvds_pll = devm_kzalloc(dev->dev, sizeof(*lvds_pll), GFP_KERNEL);
|
||||
if (!lvds_pll) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
lvds_pll->dev = dev;
|
||||
|
||||
lvds_pll->pll_hw.init = &pll_init;
|
||||
clk = devm_clk_register(dev->dev, &lvds_pll->pll_hw);
|
||||
if (IS_ERR(clk)) {
|
||||
ret = PTR_ERR(clk);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return clk;
|
||||
|
||||
fail:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
255
drivers/gpu/drm/msm/mdp/mdp4/mdp4_plane.c
Normal file
255
drivers/gpu/drm/msm/mdp/mdp4/mdp4_plane.c
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mdp4_kms.h"
|
||||
|
||||
|
||||
struct mdp4_plane {
|
||||
struct drm_plane base;
|
||||
const char *name;
|
||||
|
||||
enum mdp4_pipe pipe;
|
||||
|
||||
uint32_t nformats;
|
||||
uint32_t formats[32];
|
||||
|
||||
bool enabled;
|
||||
};
|
||||
#define to_mdp4_plane(x) container_of(x, struct mdp4_plane, base)
|
||||
|
||||
static struct mdp4_kms *get_kms(struct drm_plane *plane)
|
||||
{
|
||||
struct msm_drm_private *priv = plane->dev->dev_private;
|
||||
return to_mdp4_kms(to_mdp_kms(priv->kms));
|
||||
}
|
||||
|
||||
static int mdp4_plane_update(struct drm_plane *plane,
|
||||
struct drm_crtc *crtc, struct drm_framebuffer *fb,
|
||||
int crtc_x, int crtc_y,
|
||||
unsigned int crtc_w, unsigned int crtc_h,
|
||||
uint32_t src_x, uint32_t src_y,
|
||||
uint32_t src_w, uint32_t src_h)
|
||||
{
|
||||
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
|
||||
|
||||
mdp4_plane->enabled = true;
|
||||
|
||||
if (plane->fb)
|
||||
drm_framebuffer_unreference(plane->fb);
|
||||
|
||||
drm_framebuffer_reference(fb);
|
||||
|
||||
return mdp4_plane_mode_set(plane, crtc, fb,
|
||||
crtc_x, crtc_y, crtc_w, crtc_h,
|
||||
src_x, src_y, src_w, src_h);
|
||||
}
|
||||
|
||||
static int mdp4_plane_disable(struct drm_plane *plane)
|
||||
{
|
||||
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
|
||||
DBG("%s: disable", mdp4_plane->name);
|
||||
if (plane->crtc)
|
||||
mdp4_crtc_detach(plane->crtc, plane);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mdp4_plane_destroy(struct drm_plane *plane)
|
||||
{
|
||||
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
|
||||
|
||||
mdp4_plane_disable(plane);
|
||||
drm_plane_cleanup(plane);
|
||||
|
||||
kfree(mdp4_plane);
|
||||
}
|
||||
|
||||
/* helper to install properties which are common to planes and crtcs */
|
||||
void mdp4_plane_install_properties(struct drm_plane *plane,
|
||||
struct drm_mode_object *obj)
|
||||
{
|
||||
// XXX
|
||||
}
|
||||
|
||||
int mdp4_plane_set_property(struct drm_plane *plane,
|
||||
struct drm_property *property, uint64_t val)
|
||||
{
|
||||
// XXX
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct drm_plane_funcs mdp4_plane_funcs = {
|
||||
.update_plane = mdp4_plane_update,
|
||||
.disable_plane = mdp4_plane_disable,
|
||||
.destroy = mdp4_plane_destroy,
|
||||
.set_property = mdp4_plane_set_property,
|
||||
};
|
||||
|
||||
void mdp4_plane_set_scanout(struct drm_plane *plane,
|
||||
struct drm_framebuffer *fb)
|
||||
{
|
||||
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(plane);
|
||||
enum mdp4_pipe pipe = mdp4_plane->pipe;
|
||||
uint32_t iova;
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_A(pipe),
|
||||
MDP4_PIPE_SRC_STRIDE_A_P0(fb->pitches[0]) |
|
||||
MDP4_PIPE_SRC_STRIDE_A_P1(fb->pitches[1]));
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_B(pipe),
|
||||
MDP4_PIPE_SRC_STRIDE_B_P2(fb->pitches[2]) |
|
||||
MDP4_PIPE_SRC_STRIDE_B_P3(fb->pitches[3]));
|
||||
|
||||
msm_gem_get_iova(msm_framebuffer_bo(fb, 0), mdp4_kms->id, &iova);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRCP0_BASE(pipe), iova);
|
||||
|
||||
plane->fb = fb;
|
||||
}
|
||||
|
||||
#define MDP4_VG_PHASE_STEP_DEFAULT 0x20000000
|
||||
|
||||
int mdp4_plane_mode_set(struct drm_plane *plane,
|
||||
struct drm_crtc *crtc, struct drm_framebuffer *fb,
|
||||
int crtc_x, int crtc_y,
|
||||
unsigned int crtc_w, unsigned int crtc_h,
|
||||
uint32_t src_x, uint32_t src_y,
|
||||
uint32_t src_w, uint32_t src_h)
|
||||
{
|
||||
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
|
||||
struct mdp4_kms *mdp4_kms = get_kms(plane);
|
||||
enum mdp4_pipe pipe = mdp4_plane->pipe;
|
||||
const struct mdp_format *format;
|
||||
uint32_t op_mode = 0;
|
||||
uint32_t phasex_step = MDP4_VG_PHASE_STEP_DEFAULT;
|
||||
uint32_t phasey_step = MDP4_VG_PHASE_STEP_DEFAULT;
|
||||
|
||||
/* src values are in Q16 fixed point, convert to integer: */
|
||||
src_x = src_x >> 16;
|
||||
src_y = src_y >> 16;
|
||||
src_w = src_w >> 16;
|
||||
src_h = src_h >> 16;
|
||||
|
||||
DBG("%s: FB[%u] %u,%u,%u,%u -> CRTC[%u] %d,%d,%u,%u", mdp4_plane->name,
|
||||
fb->base.id, src_x, src_y, src_w, src_h,
|
||||
crtc->base.id, crtc_x, crtc_y, crtc_w, crtc_h);
|
||||
|
||||
if (src_w != crtc_w) {
|
||||
op_mode |= MDP4_PIPE_OP_MODE_SCALEX_EN;
|
||||
/* TODO calc phasex_step */
|
||||
}
|
||||
|
||||
if (src_h != crtc_h) {
|
||||
op_mode |= MDP4_PIPE_OP_MODE_SCALEY_EN;
|
||||
/* TODO calc phasey_step */
|
||||
}
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_SIZE(pipe),
|
||||
MDP4_PIPE_SRC_SIZE_WIDTH(src_w) |
|
||||
MDP4_PIPE_SRC_SIZE_HEIGHT(src_h));
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_XY(pipe),
|
||||
MDP4_PIPE_SRC_XY_X(src_x) |
|
||||
MDP4_PIPE_SRC_XY_Y(src_y));
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_SIZE(pipe),
|
||||
MDP4_PIPE_DST_SIZE_WIDTH(crtc_w) |
|
||||
MDP4_PIPE_DST_SIZE_HEIGHT(crtc_h));
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_XY(pipe),
|
||||
MDP4_PIPE_DST_XY_X(crtc_x) |
|
||||
MDP4_PIPE_DST_XY_Y(crtc_y));
|
||||
|
||||
mdp4_plane_set_scanout(plane, fb);
|
||||
|
||||
format = to_mdp_format(msm_framebuffer_format(fb));
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_FORMAT(pipe),
|
||||
MDP4_PIPE_SRC_FORMAT_A_BPC(format->bpc_a) |
|
||||
MDP4_PIPE_SRC_FORMAT_R_BPC(format->bpc_r) |
|
||||
MDP4_PIPE_SRC_FORMAT_G_BPC(format->bpc_g) |
|
||||
MDP4_PIPE_SRC_FORMAT_B_BPC(format->bpc_b) |
|
||||
COND(format->alpha_enable, MDP4_PIPE_SRC_FORMAT_ALPHA_ENABLE) |
|
||||
MDP4_PIPE_SRC_FORMAT_CPP(format->cpp - 1) |
|
||||
MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT(format->unpack_count - 1) |
|
||||
COND(format->unpack_tight, MDP4_PIPE_SRC_FORMAT_UNPACK_TIGHT));
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_UNPACK(pipe),
|
||||
MDP4_PIPE_SRC_UNPACK_ELEM0(format->unpack[0]) |
|
||||
MDP4_PIPE_SRC_UNPACK_ELEM1(format->unpack[1]) |
|
||||
MDP4_PIPE_SRC_UNPACK_ELEM2(format->unpack[2]) |
|
||||
MDP4_PIPE_SRC_UNPACK_ELEM3(format->unpack[3]));
|
||||
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(pipe), op_mode);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEX_STEP(pipe), phasex_step);
|
||||
mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEY_STEP(pipe), phasey_step);
|
||||
|
||||
/* TODO detach from old crtc (if we had more than one) */
|
||||
mdp4_crtc_attach(crtc, plane);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *pipe_names[] = {
|
||||
"VG1", "VG2",
|
||||
"RGB1", "RGB2", "RGB3",
|
||||
"VG3", "VG4",
|
||||
};
|
||||
|
||||
enum mdp4_pipe mdp4_plane_pipe(struct drm_plane *plane)
|
||||
{
|
||||
struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane);
|
||||
return mdp4_plane->pipe;
|
||||
}
|
||||
|
||||
/* initialize plane */
|
||||
struct drm_plane *mdp4_plane_init(struct drm_device *dev,
|
||||
enum mdp4_pipe pipe_id, bool private_plane)
|
||||
{
|
||||
struct drm_plane *plane = NULL;
|
||||
struct mdp4_plane *mdp4_plane;
|
||||
int ret;
|
||||
enum drm_plane_type type;
|
||||
|
||||
mdp4_plane = kzalloc(sizeof(*mdp4_plane), GFP_KERNEL);
|
||||
if (!mdp4_plane) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
plane = &mdp4_plane->base;
|
||||
|
||||
mdp4_plane->pipe = pipe_id;
|
||||
mdp4_plane->name = pipe_names[pipe_id];
|
||||
|
||||
mdp4_plane->nformats = mdp4_get_formats(pipe_id, mdp4_plane->formats,
|
||||
ARRAY_SIZE(mdp4_plane->formats));
|
||||
|
||||
type = private_plane ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY;
|
||||
drm_universal_plane_init(dev, plane, 0xff, &mdp4_plane_funcs,
|
||||
mdp4_plane->formats, mdp4_plane->nformats,
|
||||
type);
|
||||
|
||||
mdp4_plane_install_properties(plane, &plane->base);
|
||||
|
||||
return plane;
|
||||
|
||||
fail:
|
||||
if (plane)
|
||||
mdp4_plane_destroy(plane);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
1139
drivers/gpu/drm/msm/mdp/mdp5/mdp5.xml.h
Normal file
1139
drivers/gpu/drm/msm/mdp/mdp5/mdp5.xml.h
Normal file
File diff suppressed because it is too large
Load diff
575
drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
Normal file
575
drivers/gpu/drm/msm/mdp/mdp5/mdp5_crtc.c
Normal file
|
|
@ -0,0 +1,575 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mdp5_kms.h"
|
||||
|
||||
#include <drm/drm_mode.h>
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_crtc_helper.h"
|
||||
#include "drm_flip_work.h"
|
||||
|
||||
struct mdp5_crtc {
|
||||
struct drm_crtc base;
|
||||
char name[8];
|
||||
struct drm_plane *plane;
|
||||
struct drm_plane *planes[8];
|
||||
int id;
|
||||
bool enabled;
|
||||
|
||||
/* which mixer/encoder we route output to: */
|
||||
int mixer;
|
||||
|
||||
/* if there is a pending flip, these will be non-null: */
|
||||
struct drm_pending_vblank_event *event;
|
||||
struct msm_fence_cb pageflip_cb;
|
||||
|
||||
#define PENDING_CURSOR 0x1
|
||||
#define PENDING_FLIP 0x2
|
||||
atomic_t pending;
|
||||
|
||||
/* the fb that we logically (from PoV of KMS API) hold a ref
|
||||
* to. Which we may not yet be scanning out (we may still
|
||||
* be scanning out previous in case of page_flip while waiting
|
||||
* for gpu rendering to complete:
|
||||
*/
|
||||
struct drm_framebuffer *fb;
|
||||
|
||||
/* the fb that we currently hold a scanout ref to: */
|
||||
struct drm_framebuffer *scanout_fb;
|
||||
|
||||
/* for unref'ing framebuffers after scanout completes: */
|
||||
struct drm_flip_work unref_fb_work;
|
||||
|
||||
struct mdp_irq vblank;
|
||||
struct mdp_irq err;
|
||||
};
|
||||
#define to_mdp5_crtc(x) container_of(x, struct mdp5_crtc, base)
|
||||
|
||||
static struct mdp5_kms *get_kms(struct drm_crtc *crtc)
|
||||
{
|
||||
struct msm_drm_private *priv = crtc->dev->dev_private;
|
||||
return to_mdp5_kms(to_mdp_kms(priv->kms));
|
||||
}
|
||||
|
||||
static void request_pending(struct drm_crtc *crtc, uint32_t pending)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
|
||||
atomic_or(pending, &mdp5_crtc->pending);
|
||||
mdp_irq_register(&get_kms(crtc)->base, &mdp5_crtc->vblank);
|
||||
}
|
||||
|
||||
static void crtc_flush(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(crtc);
|
||||
int id = mdp5_crtc->id;
|
||||
uint32_t i, flush = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mdp5_crtc->planes); i++) {
|
||||
struct drm_plane *plane = mdp5_crtc->planes[i];
|
||||
if (plane) {
|
||||
enum mdp5_pipe pipe = mdp5_plane_pipe(plane);
|
||||
flush |= pipe2flush(pipe);
|
||||
}
|
||||
}
|
||||
flush |= mixer2flush(mdp5_crtc->id);
|
||||
flush |= MDP5_CTL_FLUSH_CTL;
|
||||
|
||||
DBG("%s: flush=%08x", mdp5_crtc->name, flush);
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_CTL_FLUSH(id), flush);
|
||||
}
|
||||
|
||||
static void update_fb(struct drm_crtc *crtc, struct drm_framebuffer *new_fb)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
struct drm_framebuffer *old_fb = mdp5_crtc->fb;
|
||||
|
||||
/* grab reference to incoming scanout fb: */
|
||||
drm_framebuffer_reference(new_fb);
|
||||
mdp5_crtc->base.primary->fb = new_fb;
|
||||
mdp5_crtc->fb = new_fb;
|
||||
|
||||
if (old_fb)
|
||||
drm_flip_work_queue(&mdp5_crtc->unref_fb_work, old_fb);
|
||||
}
|
||||
|
||||
/* unlike update_fb(), take a ref to the new scanout fb *before* updating
|
||||
* plane, then call this. Needed to ensure we don't unref the buffer that
|
||||
* is actually still being scanned out.
|
||||
*
|
||||
* Note that this whole thing goes away with atomic.. since we can defer
|
||||
* calling into driver until rendering is done.
|
||||
*/
|
||||
static void update_scanout(struct drm_crtc *crtc, struct drm_framebuffer *fb)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
|
||||
/* flush updates, to make sure hw is updated to new scanout fb,
|
||||
* so that we can safely queue unref to current fb (ie. next
|
||||
* vblank we know hw is done w/ previous scanout_fb).
|
||||
*/
|
||||
crtc_flush(crtc);
|
||||
|
||||
if (mdp5_crtc->scanout_fb)
|
||||
drm_flip_work_queue(&mdp5_crtc->unref_fb_work,
|
||||
mdp5_crtc->scanout_fb);
|
||||
|
||||
mdp5_crtc->scanout_fb = fb;
|
||||
|
||||
/* enable vblank to complete flip: */
|
||||
request_pending(crtc, PENDING_FLIP);
|
||||
}
|
||||
|
||||
/* if file!=NULL, this is preclose potential cancel-flip path */
|
||||
static void complete_flip(struct drm_crtc *crtc, struct drm_file *file)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_pending_vblank_event *event;
|
||||
unsigned long flags, i;
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
event = mdp5_crtc->event;
|
||||
if (event) {
|
||||
/* if regular vblank case (!file) or if cancel-flip from
|
||||
* preclose on file that requested flip, then send the
|
||||
* event:
|
||||
*/
|
||||
if (!file || (event->base.file_priv == file)) {
|
||||
mdp5_crtc->event = NULL;
|
||||
drm_send_vblank_event(dev, mdp5_crtc->id, event);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mdp5_crtc->planes); i++) {
|
||||
struct drm_plane *plane = mdp5_crtc->planes[i];
|
||||
if (plane)
|
||||
mdp5_plane_complete_flip(plane);
|
||||
}
|
||||
}
|
||||
|
||||
static void pageflip_cb(struct msm_fence_cb *cb)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc =
|
||||
container_of(cb, struct mdp5_crtc, pageflip_cb);
|
||||
struct drm_crtc *crtc = &mdp5_crtc->base;
|
||||
struct drm_framebuffer *fb = mdp5_crtc->fb;
|
||||
|
||||
if (!fb)
|
||||
return;
|
||||
|
||||
drm_framebuffer_reference(fb);
|
||||
mdp5_plane_set_scanout(mdp5_crtc->plane, fb);
|
||||
update_scanout(crtc, fb);
|
||||
}
|
||||
|
||||
static void unref_fb_worker(struct drm_flip_work *work, void *val)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc =
|
||||
container_of(work, struct mdp5_crtc, unref_fb_work);
|
||||
struct drm_device *dev = mdp5_crtc->base.dev;
|
||||
|
||||
mutex_lock(&dev->mode_config.mutex);
|
||||
drm_framebuffer_unreference(val);
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
}
|
||||
|
||||
static void mdp5_crtc_destroy(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
|
||||
drm_crtc_cleanup(crtc);
|
||||
drm_flip_work_cleanup(&mdp5_crtc->unref_fb_work);
|
||||
|
||||
kfree(mdp5_crtc);
|
||||
}
|
||||
|
||||
static void mdp5_crtc_dpms(struct drm_crtc *crtc, int mode)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(crtc);
|
||||
bool enabled = (mode == DRM_MODE_DPMS_ON);
|
||||
|
||||
DBG("%s: mode=%d", mdp5_crtc->name, mode);
|
||||
|
||||
if (enabled != mdp5_crtc->enabled) {
|
||||
if (enabled) {
|
||||
mdp5_enable(mdp5_kms);
|
||||
mdp_irq_register(&mdp5_kms->base, &mdp5_crtc->err);
|
||||
} else {
|
||||
mdp_irq_unregister(&mdp5_kms->base, &mdp5_crtc->err);
|
||||
mdp5_disable(mdp5_kms);
|
||||
}
|
||||
mdp5_crtc->enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
static bool mdp5_crtc_mode_fixup(struct drm_crtc *crtc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void blend_setup(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(crtc);
|
||||
int id = mdp5_crtc->id;
|
||||
|
||||
/*
|
||||
* Hard-coded setup for now until I figure out how the
|
||||
* layer-mixer works
|
||||
*/
|
||||
|
||||
/* LM[id]: */
|
||||
mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(id),
|
||||
MDP5_LM_BLEND_COLOR_OUT_STAGE0_FG_ALPHA);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_OP_MODE(id, 0),
|
||||
MDP5_LM_BLEND_OP_MODE_FG_ALPHA(FG_CONST) |
|
||||
MDP5_LM_BLEND_OP_MODE_BG_ALPHA(FG_PIXEL) |
|
||||
MDP5_LM_BLEND_OP_MODE_BG_INV_ALPHA);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_FG_ALPHA(id, 0), 0xff);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_BG_ALPHA(id, 0), 0x00);
|
||||
|
||||
/* NOTE: seems that LM[n] and CTL[m], we do not need n==m.. but
|
||||
* we want to be setting CTL[m].LAYER[n]. Not sure what the
|
||||
* point of having CTL[m].LAYER[o] (for o!=n).. maybe that is
|
||||
* used when chaining up mixers for high resolution displays?
|
||||
*/
|
||||
|
||||
/* CTL[id]: */
|
||||
mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 0),
|
||||
MDP5_CTL_LAYER_REG_RGB0(STAGE0) |
|
||||
MDP5_CTL_LAYER_REG_BORDER_COLOR);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 1), 0);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 2), 0);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 3), 0);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_CTL_LAYER_REG(id, 4), 0);
|
||||
}
|
||||
|
||||
static int mdp5_crtc_mode_set(struct drm_crtc *crtc,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode,
|
||||
int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(crtc);
|
||||
int ret;
|
||||
|
||||
mode = adjusted_mode;
|
||||
|
||||
DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
|
||||
mdp5_crtc->name, mode->base.id, mode->name,
|
||||
mode->vrefresh, mode->clock,
|
||||
mode->hdisplay, mode->hsync_start,
|
||||
mode->hsync_end, mode->htotal,
|
||||
mode->vdisplay, mode->vsync_start,
|
||||
mode->vsync_end, mode->vtotal,
|
||||
mode->type, mode->flags);
|
||||
|
||||
/* grab extra ref for update_scanout() */
|
||||
drm_framebuffer_reference(crtc->primary->fb);
|
||||
|
||||
ret = mdp5_plane_mode_set(mdp5_crtc->plane, crtc, crtc->primary->fb,
|
||||
0, 0, mode->hdisplay, mode->vdisplay,
|
||||
x << 16, y << 16,
|
||||
mode->hdisplay << 16, mode->vdisplay << 16);
|
||||
if (ret) {
|
||||
drm_framebuffer_unreference(crtc->primary->fb);
|
||||
dev_err(crtc->dev->dev, "%s: failed to set mode on plane: %d\n",
|
||||
mdp5_crtc->name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_LM_OUT_SIZE(mdp5_crtc->id),
|
||||
MDP5_LM_OUT_SIZE_WIDTH(mode->hdisplay) |
|
||||
MDP5_LM_OUT_SIZE_HEIGHT(mode->vdisplay));
|
||||
|
||||
update_fb(crtc, crtc->primary->fb);
|
||||
update_scanout(crtc, crtc->primary->fb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mdp5_crtc_prepare(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
DBG("%s", mdp5_crtc->name);
|
||||
/* make sure we hold a ref to mdp clks while setting up mode: */
|
||||
mdp5_enable(get_kms(crtc));
|
||||
mdp5_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
}
|
||||
|
||||
static void mdp5_crtc_commit(struct drm_crtc *crtc)
|
||||
{
|
||||
mdp5_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
|
||||
crtc_flush(crtc);
|
||||
/* drop the ref to mdp clk's that we got in prepare: */
|
||||
mdp5_disable(get_kms(crtc));
|
||||
}
|
||||
|
||||
static int mdp5_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
struct drm_plane *plane = mdp5_crtc->plane;
|
||||
struct drm_display_mode *mode = &crtc->mode;
|
||||
int ret;
|
||||
|
||||
/* grab extra ref for update_scanout() */
|
||||
drm_framebuffer_reference(crtc->primary->fb);
|
||||
|
||||
ret = mdp5_plane_mode_set(plane, crtc, crtc->primary->fb,
|
||||
0, 0, mode->hdisplay, mode->vdisplay,
|
||||
x << 16, y << 16,
|
||||
mode->hdisplay << 16, mode->vdisplay << 16);
|
||||
if (ret) {
|
||||
drm_framebuffer_unreference(crtc->primary->fb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
update_fb(crtc, crtc->primary->fb);
|
||||
update_scanout(crtc, crtc->primary->fb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mdp5_crtc_load_lut(struct drm_crtc *crtc)
|
||||
{
|
||||
}
|
||||
|
||||
static int mdp5_crtc_page_flip(struct drm_crtc *crtc,
|
||||
struct drm_framebuffer *new_fb,
|
||||
struct drm_pending_vblank_event *event,
|
||||
uint32_t page_flip_flags)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_gem_object *obj;
|
||||
unsigned long flags;
|
||||
|
||||
if (mdp5_crtc->event) {
|
||||
dev_err(dev->dev, "already pending flip!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
obj = msm_framebuffer_bo(new_fb, 0);
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
mdp5_crtc->event = event;
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
|
||||
update_fb(crtc, new_fb);
|
||||
|
||||
return msm_gem_queue_inactive_cb(obj, &mdp5_crtc->pageflip_cb);
|
||||
}
|
||||
|
||||
static int mdp5_crtc_set_property(struct drm_crtc *crtc,
|
||||
struct drm_property *property, uint64_t val)
|
||||
{
|
||||
// XXX
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct drm_crtc_funcs mdp5_crtc_funcs = {
|
||||
.set_config = drm_crtc_helper_set_config,
|
||||
.destroy = mdp5_crtc_destroy,
|
||||
.page_flip = mdp5_crtc_page_flip,
|
||||
.set_property = mdp5_crtc_set_property,
|
||||
};
|
||||
|
||||
static const struct drm_crtc_helper_funcs mdp5_crtc_helper_funcs = {
|
||||
.dpms = mdp5_crtc_dpms,
|
||||
.mode_fixup = mdp5_crtc_mode_fixup,
|
||||
.mode_set = mdp5_crtc_mode_set,
|
||||
.prepare = mdp5_crtc_prepare,
|
||||
.commit = mdp5_crtc_commit,
|
||||
.mode_set_base = mdp5_crtc_mode_set_base,
|
||||
.load_lut = mdp5_crtc_load_lut,
|
||||
};
|
||||
|
||||
static void mdp5_crtc_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = container_of(irq, struct mdp5_crtc, vblank);
|
||||
struct drm_crtc *crtc = &mdp5_crtc->base;
|
||||
struct msm_drm_private *priv = crtc->dev->dev_private;
|
||||
unsigned pending;
|
||||
|
||||
mdp_irq_unregister(&get_kms(crtc)->base, &mdp5_crtc->vblank);
|
||||
|
||||
pending = atomic_xchg(&mdp5_crtc->pending, 0);
|
||||
|
||||
if (pending & PENDING_FLIP) {
|
||||
complete_flip(crtc, NULL);
|
||||
drm_flip_work_commit(&mdp5_crtc->unref_fb_work, priv->wq);
|
||||
}
|
||||
}
|
||||
|
||||
static void mdp5_crtc_err_irq(struct mdp_irq *irq, uint32_t irqstatus)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = container_of(irq, struct mdp5_crtc, err);
|
||||
struct drm_crtc *crtc = &mdp5_crtc->base;
|
||||
DBG("%s: error: %08x", mdp5_crtc->name, irqstatus);
|
||||
crtc_flush(crtc);
|
||||
}
|
||||
|
||||
uint32_t mdp5_crtc_vblank(struct drm_crtc *crtc)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
return mdp5_crtc->vblank.irqmask;
|
||||
}
|
||||
|
||||
void mdp5_crtc_cancel_pending_flip(struct drm_crtc *crtc, struct drm_file *file)
|
||||
{
|
||||
DBG("cancel: %p", file);
|
||||
complete_flip(crtc, file);
|
||||
}
|
||||
|
||||
/* set interface for routing crtc->encoder: */
|
||||
void mdp5_crtc_set_intf(struct drm_crtc *crtc, int intf,
|
||||
enum mdp5_intf intf_id)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(crtc);
|
||||
static const enum mdp5_intfnum intfnum[] = {
|
||||
INTF0, INTF1, INTF2, INTF3,
|
||||
};
|
||||
uint32_t intf_sel;
|
||||
|
||||
/* now that we know what irq's we want: */
|
||||
mdp5_crtc->err.irqmask = intf2err(intf);
|
||||
mdp5_crtc->vblank.irqmask = intf2vblank(intf);
|
||||
|
||||
/* when called from modeset_init(), skip the rest until later: */
|
||||
if (!mdp5_kms)
|
||||
return;
|
||||
|
||||
intf_sel = mdp5_read(mdp5_kms, REG_MDP5_DISP_INTF_SEL);
|
||||
|
||||
switch (intf) {
|
||||
case 0:
|
||||
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF0__MASK;
|
||||
intf_sel |= MDP5_DISP_INTF_SEL_INTF0(intf_id);
|
||||
break;
|
||||
case 1:
|
||||
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF1__MASK;
|
||||
intf_sel |= MDP5_DISP_INTF_SEL_INTF1(intf_id);
|
||||
break;
|
||||
case 2:
|
||||
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF2__MASK;
|
||||
intf_sel |= MDP5_DISP_INTF_SEL_INTF2(intf_id);
|
||||
break;
|
||||
case 3:
|
||||
intf_sel &= ~MDP5_DISP_INTF_SEL_INTF3__MASK;
|
||||
intf_sel |= MDP5_DISP_INTF_SEL_INTF3(intf_id);
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
|
||||
blend_setup(crtc);
|
||||
|
||||
DBG("%s: intf_sel=%08x", mdp5_crtc->name, intf_sel);
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_DISP_INTF_SEL, intf_sel);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_CTL_OP(mdp5_crtc->id),
|
||||
MDP5_CTL_OP_MODE(MODE_NONE) |
|
||||
MDP5_CTL_OP_INTF_NUM(intfnum[intf]));
|
||||
|
||||
crtc_flush(crtc);
|
||||
}
|
||||
|
||||
static void set_attach(struct drm_crtc *crtc, enum mdp5_pipe pipe_id,
|
||||
struct drm_plane *plane)
|
||||
{
|
||||
struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc);
|
||||
|
||||
BUG_ON(pipe_id >= ARRAY_SIZE(mdp5_crtc->planes));
|
||||
|
||||
if (mdp5_crtc->planes[pipe_id] == plane)
|
||||
return;
|
||||
|
||||
mdp5_crtc->planes[pipe_id] = plane;
|
||||
blend_setup(crtc);
|
||||
if (mdp5_crtc->enabled && (plane != mdp5_crtc->plane))
|
||||
crtc_flush(crtc);
|
||||
}
|
||||
|
||||
void mdp5_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane)
|
||||
{
|
||||
set_attach(crtc, mdp5_plane_pipe(plane), plane);
|
||||
}
|
||||
|
||||
void mdp5_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane)
|
||||
{
|
||||
/* don't actually detatch our primary plane: */
|
||||
if (to_mdp5_crtc(crtc)->plane == plane)
|
||||
return;
|
||||
set_attach(crtc, mdp5_plane_pipe(plane), NULL);
|
||||
}
|
||||
|
||||
/* initialize crtc */
|
||||
struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,
|
||||
struct drm_plane *plane, int id)
|
||||
{
|
||||
struct drm_crtc *crtc = NULL;
|
||||
struct mdp5_crtc *mdp5_crtc;
|
||||
int ret;
|
||||
|
||||
mdp5_crtc = kzalloc(sizeof(*mdp5_crtc), GFP_KERNEL);
|
||||
if (!mdp5_crtc) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
crtc = &mdp5_crtc->base;
|
||||
|
||||
mdp5_crtc->plane = plane;
|
||||
mdp5_crtc->id = id;
|
||||
|
||||
mdp5_crtc->vblank.irq = mdp5_crtc_vblank_irq;
|
||||
mdp5_crtc->err.irq = mdp5_crtc_err_irq;
|
||||
|
||||
snprintf(mdp5_crtc->name, sizeof(mdp5_crtc->name), "%s:%d",
|
||||
pipe2name(mdp5_plane_pipe(plane)), id);
|
||||
|
||||
ret = drm_flip_work_init(&mdp5_crtc->unref_fb_work, 16,
|
||||
"unref fb", unref_fb_worker);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
INIT_FENCE_CB(&mdp5_crtc->pageflip_cb, pageflip_cb);
|
||||
|
||||
drm_crtc_init_with_planes(dev, crtc, plane, NULL, &mdp5_crtc_funcs);
|
||||
drm_crtc_helper_add(crtc, &mdp5_crtc_helper_funcs);
|
||||
|
||||
mdp5_plane_install_properties(mdp5_crtc->plane, &crtc->base);
|
||||
|
||||
return crtc;
|
||||
|
||||
fail:
|
||||
if (crtc)
|
||||
mdp5_crtc_destroy(crtc);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
258
drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c
Normal file
258
drivers/gpu/drm/msm/mdp/mdp5/mdp5_encoder.c
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mdp5_kms.h"
|
||||
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_crtc_helper.h"
|
||||
|
||||
struct mdp5_encoder {
|
||||
struct drm_encoder base;
|
||||
int intf;
|
||||
enum mdp5_intf intf_id;
|
||||
bool enabled;
|
||||
uint32_t bsc;
|
||||
};
|
||||
#define to_mdp5_encoder(x) container_of(x, struct mdp5_encoder, base)
|
||||
|
||||
static struct mdp5_kms *get_kms(struct drm_encoder *encoder)
|
||||
{
|
||||
struct msm_drm_private *priv = encoder->dev->dev_private;
|
||||
return to_mdp5_kms(to_mdp_kms(priv->kms));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MSM_BUS_SCALING
|
||||
#include <mach/board.h>
|
||||
#include <mach/msm_bus.h>
|
||||
#include <mach/msm_bus_board.h>
|
||||
#define MDP_BUS_VECTOR_ENTRY(ab_val, ib_val) \
|
||||
{ \
|
||||
.src = MSM_BUS_MASTER_MDP_PORT0, \
|
||||
.dst = MSM_BUS_SLAVE_EBI_CH0, \
|
||||
.ab = (ab_val), \
|
||||
.ib = (ib_val), \
|
||||
}
|
||||
|
||||
static struct msm_bus_vectors mdp_bus_vectors[] = {
|
||||
MDP_BUS_VECTOR_ENTRY(0, 0),
|
||||
MDP_BUS_VECTOR_ENTRY(2000000000, 2000000000),
|
||||
};
|
||||
static struct msm_bus_paths mdp_bus_usecases[] = { {
|
||||
.num_paths = 1,
|
||||
.vectors = &mdp_bus_vectors[0],
|
||||
}, {
|
||||
.num_paths = 1,
|
||||
.vectors = &mdp_bus_vectors[1],
|
||||
} };
|
||||
static struct msm_bus_scale_pdata mdp_bus_scale_table = {
|
||||
.usecase = mdp_bus_usecases,
|
||||
.num_usecases = ARRAY_SIZE(mdp_bus_usecases),
|
||||
.name = "mdss_mdp",
|
||||
};
|
||||
|
||||
static void bs_init(struct mdp5_encoder *mdp5_encoder)
|
||||
{
|
||||
mdp5_encoder->bsc = msm_bus_scale_register_client(
|
||||
&mdp_bus_scale_table);
|
||||
DBG("bus scale client: %08x", mdp5_encoder->bsc);
|
||||
}
|
||||
|
||||
static void bs_fini(struct mdp5_encoder *mdp5_encoder)
|
||||
{
|
||||
if (mdp5_encoder->bsc) {
|
||||
msm_bus_scale_unregister_client(mdp5_encoder->bsc);
|
||||
mdp5_encoder->bsc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void bs_set(struct mdp5_encoder *mdp5_encoder, int idx)
|
||||
{
|
||||
if (mdp5_encoder->bsc) {
|
||||
DBG("set bus scaling: %d", idx);
|
||||
/* HACK: scaling down, and then immediately back up
|
||||
* seems to leave things broken (underflow).. so
|
||||
* never disable:
|
||||
*/
|
||||
idx = 1;
|
||||
msm_bus_scale_client_update_request(mdp5_encoder->bsc, idx);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void bs_init(struct mdp5_encoder *mdp5_encoder) {}
|
||||
static void bs_fini(struct mdp5_encoder *mdp5_encoder) {}
|
||||
static void bs_set(struct mdp5_encoder *mdp5_encoder, int idx) {}
|
||||
#endif
|
||||
|
||||
static void mdp5_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
|
||||
bs_fini(mdp5_encoder);
|
||||
drm_encoder_cleanup(encoder);
|
||||
kfree(mdp5_encoder);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs mdp5_encoder_funcs = {
|
||||
.destroy = mdp5_encoder_destroy,
|
||||
};
|
||||
|
||||
static void mdp5_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(encoder);
|
||||
int intf = mdp5_encoder->intf;
|
||||
bool enabled = (mode == DRM_MODE_DPMS_ON);
|
||||
|
||||
DBG("mode=%d", mode);
|
||||
|
||||
if (enabled == mdp5_encoder->enabled)
|
||||
return;
|
||||
|
||||
if (enabled) {
|
||||
bs_set(mdp5_encoder, 1);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intf), 1);
|
||||
} else {
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intf), 0);
|
||||
bs_set(mdp5_encoder, 0);
|
||||
}
|
||||
|
||||
mdp5_encoder->enabled = enabled;
|
||||
}
|
||||
|
||||
static bool mdp5_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mdp5_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(encoder);
|
||||
int intf = mdp5_encoder->intf;
|
||||
uint32_t dtv_hsync_skew, vsync_period, vsync_len, ctrl_pol;
|
||||
uint32_t display_v_start, display_v_end;
|
||||
uint32_t hsync_start_x, hsync_end_x;
|
||||
uint32_t format;
|
||||
|
||||
mode = adjusted_mode;
|
||||
|
||||
DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
|
||||
mode->base.id, mode->name,
|
||||
mode->vrefresh, mode->clock,
|
||||
mode->hdisplay, mode->hsync_start,
|
||||
mode->hsync_end, mode->htotal,
|
||||
mode->vdisplay, mode->vsync_start,
|
||||
mode->vsync_end, mode->vtotal,
|
||||
mode->type, mode->flags);
|
||||
|
||||
ctrl_pol = 0;
|
||||
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
||||
ctrl_pol |= MDP5_INTF_POLARITY_CTL_HSYNC_LOW;
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
ctrl_pol |= MDP5_INTF_POLARITY_CTL_VSYNC_LOW;
|
||||
/* probably need to get DATA_EN polarity from panel.. */
|
||||
|
||||
dtv_hsync_skew = 0; /* get this from panel? */
|
||||
format = 0x213f; /* get this from panel? */
|
||||
|
||||
hsync_start_x = (mode->htotal - mode->hsync_start);
|
||||
hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1;
|
||||
|
||||
vsync_period = mode->vtotal * mode->htotal;
|
||||
vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal;
|
||||
display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + dtv_hsync_skew;
|
||||
display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + dtv_hsync_skew - 1;
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_HSYNC_CTL(intf),
|
||||
MDP5_INTF_HSYNC_CTL_PULSEW(mode->hsync_end - mode->hsync_start) |
|
||||
MDP5_INTF_HSYNC_CTL_PERIOD(mode->htotal));
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_VSYNC_PERIOD_F0(intf), vsync_period);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_VSYNC_LEN_F0(intf), vsync_len);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_HCTL(intf),
|
||||
MDP5_INTF_DISPLAY_HCTL_START(hsync_start_x) |
|
||||
MDP5_INTF_DISPLAY_HCTL_END(hsync_end_x));
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_VSTART_F0(intf), display_v_start);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_VEND_F0(intf), display_v_end);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_BORDER_COLOR(intf), 0);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_UNDERFLOW_COLOR(intf), 0xff);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_HSYNC_SKEW(intf), dtv_hsync_skew);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_POLARITY_CTL(intf), ctrl_pol);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_HCTL(intf),
|
||||
MDP5_INTF_ACTIVE_HCTL_START(0) |
|
||||
MDP5_INTF_ACTIVE_HCTL_END(0));
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_VSTART_F0(intf), 0);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_VEND_F0(intf), 0);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_PANEL_FORMAT(intf), format);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_FRAME_LINE_COUNT_EN(intf), 0x3); /* frame+line? */
|
||||
}
|
||||
|
||||
static void mdp5_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
mdp5_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
||||
}
|
||||
|
||||
static void mdp5_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder);
|
||||
mdp5_crtc_set_intf(encoder->crtc, mdp5_encoder->intf,
|
||||
mdp5_encoder->intf_id);
|
||||
mdp5_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_helper_funcs mdp5_encoder_helper_funcs = {
|
||||
.dpms = mdp5_encoder_dpms,
|
||||
.mode_fixup = mdp5_encoder_mode_fixup,
|
||||
.mode_set = mdp5_encoder_mode_set,
|
||||
.prepare = mdp5_encoder_prepare,
|
||||
.commit = mdp5_encoder_commit,
|
||||
};
|
||||
|
||||
/* initialize encoder */
|
||||
struct drm_encoder *mdp5_encoder_init(struct drm_device *dev, int intf,
|
||||
enum mdp5_intf intf_id)
|
||||
{
|
||||
struct drm_encoder *encoder = NULL;
|
||||
struct mdp5_encoder *mdp5_encoder;
|
||||
int ret;
|
||||
|
||||
mdp5_encoder = kzalloc(sizeof(*mdp5_encoder), GFP_KERNEL);
|
||||
if (!mdp5_encoder) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp5_encoder->intf = intf;
|
||||
mdp5_encoder->intf_id = intf_id;
|
||||
encoder = &mdp5_encoder->base;
|
||||
|
||||
drm_encoder_init(dev, encoder, &mdp5_encoder_funcs,
|
||||
DRM_MODE_ENCODER_TMDS);
|
||||
drm_encoder_helper_add(encoder, &mdp5_encoder_helper_funcs);
|
||||
|
||||
bs_init(mdp5_encoder);
|
||||
|
||||
return encoder;
|
||||
|
||||
fail:
|
||||
if (encoder)
|
||||
mdp5_encoder_destroy(encoder);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
111
drivers/gpu/drm/msm/mdp/mdp5/mdp5_irq.c
Normal file
111
drivers/gpu/drm/msm/mdp/mdp5/mdp5_irq.c
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "mdp5_kms.h"
|
||||
|
||||
void mdp5_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask)
|
||||
{
|
||||
mdp5_write(to_mdp5_kms(mdp_kms), REG_MDP5_INTR_EN, irqmask);
|
||||
}
|
||||
|
||||
static void mdp5_irq_error_handler(struct mdp_irq *irq, uint32_t irqstatus)
|
||||
{
|
||||
DRM_ERROR("errors: %08x\n", irqstatus);
|
||||
}
|
||||
|
||||
void mdp5_irq_preinstall(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTR_CLEAR, 0xffffffff);
|
||||
}
|
||||
|
||||
int mdp5_irq_postinstall(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
|
||||
struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms);
|
||||
struct mdp_irq *error_handler = &mdp5_kms->error_handler;
|
||||
|
||||
error_handler->irq = mdp5_irq_error_handler;
|
||||
error_handler->irqmask = MDP5_IRQ_INTF0_UNDER_RUN |
|
||||
MDP5_IRQ_INTF1_UNDER_RUN |
|
||||
MDP5_IRQ_INTF2_UNDER_RUN |
|
||||
MDP5_IRQ_INTF3_UNDER_RUN;
|
||||
|
||||
mdp_irq_register(mdp_kms, error_handler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdp5_irq_uninstall(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTR_EN, 0x00000000);
|
||||
}
|
||||
|
||||
static void mdp5_irq_mdp(struct mdp_kms *mdp_kms)
|
||||
{
|
||||
struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms);
|
||||
struct drm_device *dev = mdp5_kms->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
unsigned int id;
|
||||
uint32_t status;
|
||||
|
||||
status = mdp5_read(mdp5_kms, REG_MDP5_INTR_STATUS);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTR_CLEAR, status);
|
||||
|
||||
VERB("status=%08x", status);
|
||||
|
||||
mdp_dispatch_irqs(mdp_kms, status);
|
||||
|
||||
for (id = 0; id < priv->num_crtcs; id++)
|
||||
if (status & mdp5_crtc_vblank(priv->crtcs[id]))
|
||||
drm_handle_vblank(dev, id);
|
||||
}
|
||||
|
||||
irqreturn_t mdp5_irq(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp_kms *mdp_kms = to_mdp_kms(kms);
|
||||
struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms);
|
||||
uint32_t intr;
|
||||
|
||||
intr = mdp5_read(mdp5_kms, REG_MDP5_HW_INTR_STATUS);
|
||||
|
||||
VERB("intr=%08x", intr);
|
||||
|
||||
if (intr & MDP5_HW_INTR_STATUS_INTR_MDP)
|
||||
mdp5_irq_mdp(mdp_kms);
|
||||
|
||||
if (intr & MDP5_HW_INTR_STATUS_INTR_HDMI)
|
||||
hdmi_irq(0, mdp5_kms->hdmi);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int mdp5_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
|
||||
{
|
||||
mdp_update_vblank_mask(to_mdp_kms(kms),
|
||||
mdp5_crtc_vblank(crtc), true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdp5_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
|
||||
{
|
||||
mdp_update_vblank_mask(to_mdp_kms(kms),
|
||||
mdp5_crtc_vblank(crtc), false);
|
||||
}
|
||||
491
drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
Normal file
491
drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_mmu.h"
|
||||
#include "mdp5_kms.h"
|
||||
|
||||
static const char *iommu_ports[] = {
|
||||
"mdp_0",
|
||||
};
|
||||
|
||||
static struct mdp5_platform_config *mdp5_get_config(struct platform_device *dev);
|
||||
|
||||
const struct mdp5_config *mdp5_cfg;
|
||||
|
||||
static const struct mdp5_config msm8x74_config = {
|
||||
.name = "msm8x74",
|
||||
.ctl = {
|
||||
.count = 5,
|
||||
.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
|
||||
},
|
||||
.pipe_vig = {
|
||||
.count = 3,
|
||||
.base = { 0x01200, 0x01600, 0x01a00 },
|
||||
},
|
||||
.pipe_rgb = {
|
||||
.count = 3,
|
||||
.base = { 0x01e00, 0x02200, 0x02600 },
|
||||
},
|
||||
.pipe_dma = {
|
||||
.count = 2,
|
||||
.base = { 0x02a00, 0x02e00 },
|
||||
},
|
||||
.lm = {
|
||||
.count = 5,
|
||||
.base = { 0x03200, 0x03600, 0x03a00, 0x03e00, 0x04200 },
|
||||
},
|
||||
.dspp = {
|
||||
.count = 3,
|
||||
.base = { 0x04600, 0x04a00, 0x04e00 },
|
||||
},
|
||||
.ad = {
|
||||
.count = 2,
|
||||
.base = { 0x13100, 0x13300 }, /* NOTE: no ad in v1.0 */
|
||||
},
|
||||
.intf = {
|
||||
.count = 4,
|
||||
.base = { 0x12500, 0x12700, 0x12900, 0x12b00 },
|
||||
},
|
||||
};
|
||||
|
||||
static const struct mdp5_config apq8084_config = {
|
||||
.name = "apq8084",
|
||||
.ctl = {
|
||||
.count = 5,
|
||||
.base = { 0x00600, 0x00700, 0x00800, 0x00900, 0x00a00 },
|
||||
},
|
||||
.pipe_vig = {
|
||||
.count = 4,
|
||||
.base = { 0x01200, 0x01600, 0x01a00, 0x01e00 },
|
||||
},
|
||||
.pipe_rgb = {
|
||||
.count = 4,
|
||||
.base = { 0x02200, 0x02600, 0x02a00, 0x02e00 },
|
||||
},
|
||||
.pipe_dma = {
|
||||
.count = 2,
|
||||
.base = { 0x03200, 0x03600 },
|
||||
},
|
||||
.lm = {
|
||||
.count = 6,
|
||||
.base = { 0x03a00, 0x03e00, 0x04200, 0x04600, 0x04a00, 0x04e00 },
|
||||
},
|
||||
.dspp = {
|
||||
.count = 4,
|
||||
.base = { 0x05200, 0x05600, 0x05a00, 0x05e00 },
|
||||
|
||||
},
|
||||
.ad = {
|
||||
.count = 3,
|
||||
.base = { 0x13500, 0x13700, 0x13900 },
|
||||
},
|
||||
.intf = {
|
||||
.count = 5,
|
||||
.base = { 0x12500, 0x12700, 0x12900, 0x12b00, 0x12d00 },
|
||||
},
|
||||
};
|
||||
|
||||
struct mdp5_config_entry {
|
||||
int revision;
|
||||
const struct mdp5_config *config;
|
||||
};
|
||||
|
||||
static const struct mdp5_config_entry mdp5_configs[] = {
|
||||
{ .revision = 0, .config = &msm8x74_config },
|
||||
{ .revision = 2, .config = &msm8x74_config },
|
||||
{ .revision = 3, .config = &apq8084_config },
|
||||
};
|
||||
|
||||
static int mdp5_select_hw_cfg(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
|
||||
struct drm_device *dev = mdp5_kms->dev;
|
||||
uint32_t version, major, minor;
|
||||
int i, ret = 0;
|
||||
|
||||
mdp5_enable(mdp5_kms);
|
||||
version = mdp5_read(mdp5_kms, REG_MDP5_MDP_VERSION);
|
||||
mdp5_disable(mdp5_kms);
|
||||
|
||||
major = FIELD(version, MDP5_MDP_VERSION_MAJOR);
|
||||
minor = FIELD(version, MDP5_MDP_VERSION_MINOR);
|
||||
|
||||
DBG("found MDP5 version v%d.%d", major, minor);
|
||||
|
||||
if (major != 1) {
|
||||
dev_err(dev->dev, "unexpected MDP major version: v%d.%d\n",
|
||||
major, minor);
|
||||
ret = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mdp5_kms->rev = minor;
|
||||
|
||||
/* only after mdp5_cfg global pointer's init can we access the hw */
|
||||
for (i = 0; i < ARRAY_SIZE(mdp5_configs); i++) {
|
||||
if (mdp5_configs[i].revision != minor)
|
||||
continue;
|
||||
mdp5_kms->hw_cfg = mdp5_cfg = mdp5_configs[i].config;
|
||||
break;
|
||||
}
|
||||
if (unlikely(!mdp5_kms->hw_cfg)) {
|
||||
dev_err(dev->dev, "unexpected MDP minor revision: v%d.%d\n",
|
||||
major, minor);
|
||||
ret = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
DBG("MDP5: %s config selected", mdp5_kms->hw_cfg->name);
|
||||
|
||||
return 0;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mdp5_hw_init(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
|
||||
struct drm_device *dev = mdp5_kms->dev;
|
||||
int i;
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
|
||||
/* Magic unknown register writes:
|
||||
*
|
||||
* W VBIF:0x004 00000001 (mdss_mdp.c:839)
|
||||
* W MDP5:0x2e0 0xe9 (mdss_mdp.c:839)
|
||||
* W MDP5:0x2e4 0x55 (mdss_mdp.c:839)
|
||||
* W MDP5:0x3ac 0xc0000ccc (mdss_mdp.c:839)
|
||||
* W MDP5:0x3b4 0xc0000ccc (mdss_mdp.c:839)
|
||||
* W MDP5:0x3bc 0xcccccc (mdss_mdp.c:839)
|
||||
* W MDP5:0x4a8 0xcccc0c0 (mdss_mdp.c:839)
|
||||
* W MDP5:0x4b0 0xccccc0c0 (mdss_mdp.c:839)
|
||||
* W MDP5:0x4b8 0xccccc000 (mdss_mdp.c:839)
|
||||
*
|
||||
* Downstream fbdev driver gets these register offsets/values
|
||||
* from DT.. not really sure what these registers are or if
|
||||
* different values for different boards/SoC's, etc. I guess
|
||||
* they are the golden registers.
|
||||
*
|
||||
* Not setting these does not seem to cause any problem. But
|
||||
* we may be getting lucky with the bootloader initializing
|
||||
* them for us. OTOH, if we can always count on the bootloader
|
||||
* setting the golden registers, then perhaps we don't need to
|
||||
* care.
|
||||
*/
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_DISP_INTF_SEL, 0);
|
||||
|
||||
for (i = 0; i < mdp5_kms->hw_cfg->ctl.count; i++)
|
||||
mdp5_write(mdp5_kms, REG_MDP5_CTL_OP(i), 0);
|
||||
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long mdp5_round_pixclk(struct msm_kms *kms, unsigned long rate,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
return rate;
|
||||
}
|
||||
|
||||
static void mdp5_preclose(struct msm_kms *kms, struct drm_file *file)
|
||||
{
|
||||
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
|
||||
struct msm_drm_private *priv = mdp5_kms->dev->dev_private;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < priv->num_crtcs; i++)
|
||||
mdp5_crtc_cancel_pending_flip(priv->crtcs[i], file);
|
||||
}
|
||||
|
||||
static void mdp5_destroy(struct msm_kms *kms)
|
||||
{
|
||||
struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms));
|
||||
struct msm_mmu *mmu = mdp5_kms->mmu;
|
||||
|
||||
if (mmu) {
|
||||
mmu->funcs->detach(mmu, iommu_ports, ARRAY_SIZE(iommu_ports));
|
||||
mmu->funcs->destroy(mmu);
|
||||
}
|
||||
kfree(mdp5_kms);
|
||||
}
|
||||
|
||||
static const struct mdp_kms_funcs kms_funcs = {
|
||||
.base = {
|
||||
.hw_init = mdp5_hw_init,
|
||||
.irq_preinstall = mdp5_irq_preinstall,
|
||||
.irq_postinstall = mdp5_irq_postinstall,
|
||||
.irq_uninstall = mdp5_irq_uninstall,
|
||||
.irq = mdp5_irq,
|
||||
.enable_vblank = mdp5_enable_vblank,
|
||||
.disable_vblank = mdp5_disable_vblank,
|
||||
.get_format = mdp_get_format,
|
||||
.round_pixclk = mdp5_round_pixclk,
|
||||
.preclose = mdp5_preclose,
|
||||
.destroy = mdp5_destroy,
|
||||
},
|
||||
.set_irqmask = mdp5_set_irqmask,
|
||||
};
|
||||
|
||||
int mdp5_disable(struct mdp5_kms *mdp5_kms)
|
||||
{
|
||||
DBG("");
|
||||
|
||||
clk_disable_unprepare(mdp5_kms->ahb_clk);
|
||||
clk_disable_unprepare(mdp5_kms->axi_clk);
|
||||
clk_disable_unprepare(mdp5_kms->core_clk);
|
||||
clk_disable_unprepare(mdp5_kms->lut_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdp5_enable(struct mdp5_kms *mdp5_kms)
|
||||
{
|
||||
DBG("");
|
||||
|
||||
clk_prepare_enable(mdp5_kms->ahb_clk);
|
||||
clk_prepare_enable(mdp5_kms->axi_clk);
|
||||
clk_prepare_enable(mdp5_kms->core_clk);
|
||||
clk_prepare_enable(mdp5_kms->lut_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int modeset_init(struct mdp5_kms *mdp5_kms)
|
||||
{
|
||||
static const enum mdp5_pipe crtcs[] = {
|
||||
SSPP_RGB0, SSPP_RGB1, SSPP_RGB2, SSPP_RGB3,
|
||||
};
|
||||
struct drm_device *dev = mdp5_kms->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct drm_encoder *encoder;
|
||||
int i, ret;
|
||||
|
||||
/* construct CRTCs: */
|
||||
for (i = 0; i < mdp5_kms->hw_cfg->pipe_rgb.count; i++) {
|
||||
struct drm_plane *plane;
|
||||
struct drm_crtc *crtc;
|
||||
|
||||
plane = mdp5_plane_init(dev, crtcs[i], true);
|
||||
if (IS_ERR(plane)) {
|
||||
ret = PTR_ERR(plane);
|
||||
dev_err(dev->dev, "failed to construct plane for %s (%d)\n",
|
||||
pipe2name(crtcs[i]), ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
crtc = mdp5_crtc_init(dev, plane, i);
|
||||
if (IS_ERR(crtc)) {
|
||||
ret = PTR_ERR(crtc);
|
||||
dev_err(dev->dev, "failed to construct crtc for %s (%d)\n",
|
||||
pipe2name(crtcs[i]), ret);
|
||||
goto fail;
|
||||
}
|
||||
priv->crtcs[priv->num_crtcs++] = crtc;
|
||||
}
|
||||
|
||||
/* Construct encoder for HDMI: */
|
||||
encoder = mdp5_encoder_init(dev, 3, INTF_HDMI);
|
||||
if (IS_ERR(encoder)) {
|
||||
dev_err(dev->dev, "failed to construct encoder\n");
|
||||
ret = PTR_ERR(encoder);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* NOTE: the vsync and error irq's are actually associated with
|
||||
* the INTF/encoder.. the easiest way to deal with this (ie. what
|
||||
* we do now) is assume a fixed relationship between crtc's and
|
||||
* encoders. I'm not sure if there is ever a need to more freely
|
||||
* assign crtcs to encoders, but if there is then we need to take
|
||||
* care of error and vblank irq's that the crtc has registered,
|
||||
* and also update user-requested vblank_mask.
|
||||
*/
|
||||
encoder->possible_crtcs = BIT(0);
|
||||
mdp5_crtc_set_intf(priv->crtcs[0], 3, INTF_HDMI);
|
||||
|
||||
priv->encoders[priv->num_encoders++] = encoder;
|
||||
|
||||
/* Construct bridge/connector for HDMI: */
|
||||
mdp5_kms->hdmi = hdmi_init(dev, encoder);
|
||||
if (IS_ERR(mdp5_kms->hdmi)) {
|
||||
ret = PTR_ERR(mdp5_kms->hdmi);
|
||||
dev_err(dev->dev, "failed to initialize HDMI: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_clk(struct platform_device *pdev, struct clk **clkp,
|
||||
const char *name)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct clk *clk = devm_clk_get(dev, name);
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(dev, "failed to get %s (%ld)\n", name, PTR_ERR(clk));
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
*clkp = clk;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct msm_kms *mdp5_kms_init(struct drm_device *dev)
|
||||
{
|
||||
struct platform_device *pdev = dev->platformdev;
|
||||
struct mdp5_platform_config *config = mdp5_get_config(pdev);
|
||||
struct mdp5_kms *mdp5_kms;
|
||||
struct msm_kms *kms = NULL;
|
||||
struct msm_mmu *mmu;
|
||||
int i, ret;
|
||||
|
||||
mdp5_kms = kzalloc(sizeof(*mdp5_kms), GFP_KERNEL);
|
||||
if (!mdp5_kms) {
|
||||
dev_err(dev->dev, "failed to allocate kms\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp_kms_init(&mdp5_kms->base, &kms_funcs);
|
||||
|
||||
kms = &mdp5_kms->base.base;
|
||||
|
||||
mdp5_kms->dev = dev;
|
||||
mdp5_kms->smp_blk_cnt = config->smp_blk_cnt;
|
||||
|
||||
mdp5_kms->mmio = msm_ioremap(pdev, "mdp_phys", "MDP5");
|
||||
if (IS_ERR(mdp5_kms->mmio)) {
|
||||
ret = PTR_ERR(mdp5_kms->mmio);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp5_kms->vbif = msm_ioremap(pdev, "vbif_phys", "VBIF");
|
||||
if (IS_ERR(mdp5_kms->vbif)) {
|
||||
ret = PTR_ERR(mdp5_kms->vbif);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mdp5_kms->vdd = devm_regulator_get(&pdev->dev, "vdd");
|
||||
if (IS_ERR(mdp5_kms->vdd)) {
|
||||
ret = PTR_ERR(mdp5_kms->vdd);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = regulator_enable(mdp5_kms->vdd);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to enable regulator vdd: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = get_clk(pdev, &mdp5_kms->axi_clk, "bus_clk");
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = get_clk(pdev, &mdp5_kms->ahb_clk, "iface_clk");
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = get_clk(pdev, &mdp5_kms->src_clk, "core_clk_src");
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = get_clk(pdev, &mdp5_kms->core_clk, "core_clk");
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = get_clk(pdev, &mdp5_kms->lut_clk, "lut_clk");
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = get_clk(pdev, &mdp5_kms->vsync_clk, "vsync_clk");
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
ret = clk_set_rate(mdp5_kms->src_clk, config->max_clk);
|
||||
|
||||
ret = mdp5_select_hw_cfg(kms);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/* make sure things are off before attaching iommu (bootloader could
|
||||
* have left things on, in which case we'll start getting faults if
|
||||
* we don't disable):
|
||||
*/
|
||||
mdp5_enable(mdp5_kms);
|
||||
for (i = 0; i < mdp5_kms->hw_cfg->intf.count; i++)
|
||||
mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(i), 0);
|
||||
mdp5_disable(mdp5_kms);
|
||||
mdelay(16);
|
||||
|
||||
if (config->iommu) {
|
||||
mmu = msm_iommu_new(&pdev->dev, config->iommu);
|
||||
if (IS_ERR(mmu)) {
|
||||
ret = PTR_ERR(mmu);
|
||||
dev_err(dev->dev, "failed to init iommu: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = mmu->funcs->attach(mmu, iommu_ports,
|
||||
ARRAY_SIZE(iommu_ports));
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to attach iommu: %d\n", ret);
|
||||
mmu->funcs->destroy(mmu);
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
dev_info(dev->dev, "no iommu, fallback to phys "
|
||||
"contig buffers for scanout\n");
|
||||
mmu = NULL;
|
||||
}
|
||||
mdp5_kms->mmu = mmu;
|
||||
|
||||
mdp5_kms->id = msm_register_mmu(dev, mmu);
|
||||
if (mdp5_kms->id < 0) {
|
||||
ret = mdp5_kms->id;
|
||||
dev_err(dev->dev, "failed to register mdp5 iommu: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = modeset_init(mdp5_kms);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "modeset_init failed: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return kms;
|
||||
|
||||
fail:
|
||||
if (kms)
|
||||
mdp5_destroy(kms);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static struct mdp5_platform_config *mdp5_get_config(struct platform_device *dev)
|
||||
{
|
||||
static struct mdp5_platform_config config = {};
|
||||
#ifdef CONFIG_OF
|
||||
/* TODO */
|
||||
#endif
|
||||
config.iommu = iommu_domain_alloc(&platform_bus_type);
|
||||
/* TODO hard-coded in downstream mdss, but should it be? */
|
||||
config.max_clk = 200000000;
|
||||
/* TODO get from DT: */
|
||||
config.smp_blk_cnt = 22;
|
||||
|
||||
return &config;
|
||||
}
|
||||
239
drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
Normal file
239
drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MDP5_KMS_H__
|
||||
#define __MDP5_KMS_H__
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_kms.h"
|
||||
#include "mdp/mdp_kms.h"
|
||||
/* dynamic offsets used by mdp5.xml.h (initialized in mdp5_kms.c) */
|
||||
#define MDP5_MAX_BASES 8
|
||||
struct mdp5_sub_block {
|
||||
int count;
|
||||
uint32_t base[MDP5_MAX_BASES];
|
||||
};
|
||||
struct mdp5_config {
|
||||
char *name;
|
||||
struct mdp5_sub_block ctl;
|
||||
struct mdp5_sub_block pipe_vig;
|
||||
struct mdp5_sub_block pipe_rgb;
|
||||
struct mdp5_sub_block pipe_dma;
|
||||
struct mdp5_sub_block lm;
|
||||
struct mdp5_sub_block dspp;
|
||||
struct mdp5_sub_block ad;
|
||||
struct mdp5_sub_block intf;
|
||||
};
|
||||
extern const struct mdp5_config *mdp5_cfg;
|
||||
#include "mdp5.xml.h"
|
||||
#include "mdp5_smp.h"
|
||||
|
||||
struct mdp5_kms {
|
||||
struct mdp_kms base;
|
||||
|
||||
struct drm_device *dev;
|
||||
|
||||
int rev;
|
||||
const struct mdp5_config *hw_cfg;
|
||||
|
||||
/* mapper-id used to request GEM buffer mapped for scanout: */
|
||||
int id;
|
||||
struct msm_mmu *mmu;
|
||||
|
||||
/* for tracking smp allocation amongst pipes: */
|
||||
mdp5_smp_state_t smp_state;
|
||||
struct mdp5_client_smp_state smp_client_state[CID_MAX];
|
||||
int smp_blk_cnt;
|
||||
|
||||
/* io/register spaces: */
|
||||
void __iomem *mmio, *vbif;
|
||||
|
||||
struct regulator *vdd;
|
||||
|
||||
struct clk *axi_clk;
|
||||
struct clk *ahb_clk;
|
||||
struct clk *src_clk;
|
||||
struct clk *core_clk;
|
||||
struct clk *lut_clk;
|
||||
struct clk *vsync_clk;
|
||||
|
||||
struct hdmi *hdmi;
|
||||
|
||||
struct mdp_irq error_handler;
|
||||
};
|
||||
#define to_mdp5_kms(x) container_of(x, struct mdp5_kms, base)
|
||||
|
||||
/* platform config data (ie. from DT, or pdata) */
|
||||
struct mdp5_platform_config {
|
||||
struct iommu_domain *iommu;
|
||||
uint32_t max_clk;
|
||||
int smp_blk_cnt;
|
||||
};
|
||||
|
||||
static inline void mdp5_write(struct mdp5_kms *mdp5_kms, u32 reg, u32 data)
|
||||
{
|
||||
msm_writel(data, mdp5_kms->mmio + reg);
|
||||
}
|
||||
|
||||
static inline u32 mdp5_read(struct mdp5_kms *mdp5_kms, u32 reg)
|
||||
{
|
||||
return msm_readl(mdp5_kms->mmio + reg);
|
||||
}
|
||||
|
||||
static inline const char *pipe2name(enum mdp5_pipe pipe)
|
||||
{
|
||||
static const char *names[] = {
|
||||
#define NAME(n) [SSPP_ ## n] = #n
|
||||
NAME(VIG0), NAME(VIG1), NAME(VIG2),
|
||||
NAME(RGB0), NAME(RGB1), NAME(RGB2),
|
||||
NAME(DMA0), NAME(DMA1),
|
||||
NAME(VIG3), NAME(RGB3),
|
||||
#undef NAME
|
||||
};
|
||||
return names[pipe];
|
||||
}
|
||||
|
||||
static inline uint32_t pipe2flush(enum mdp5_pipe pipe)
|
||||
{
|
||||
switch (pipe) {
|
||||
case SSPP_VIG0: return MDP5_CTL_FLUSH_VIG0;
|
||||
case SSPP_VIG1: return MDP5_CTL_FLUSH_VIG1;
|
||||
case SSPP_VIG2: return MDP5_CTL_FLUSH_VIG2;
|
||||
case SSPP_RGB0: return MDP5_CTL_FLUSH_RGB0;
|
||||
case SSPP_RGB1: return MDP5_CTL_FLUSH_RGB1;
|
||||
case SSPP_RGB2: return MDP5_CTL_FLUSH_RGB2;
|
||||
case SSPP_DMA0: return MDP5_CTL_FLUSH_DMA0;
|
||||
case SSPP_DMA1: return MDP5_CTL_FLUSH_DMA1;
|
||||
case SSPP_VIG3: return MDP5_CTL_FLUSH_VIG3;
|
||||
case SSPP_RGB3: return MDP5_CTL_FLUSH_RGB3;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int pipe2nclients(enum mdp5_pipe pipe)
|
||||
{
|
||||
switch (pipe) {
|
||||
case SSPP_RGB0:
|
||||
case SSPP_RGB1:
|
||||
case SSPP_RGB2:
|
||||
case SSPP_RGB3:
|
||||
return 1;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
static inline enum mdp5_client_id pipe2client(enum mdp5_pipe pipe, int plane)
|
||||
{
|
||||
WARN_ON(plane >= pipe2nclients(pipe));
|
||||
switch (pipe) {
|
||||
case SSPP_VIG0: return CID_VIG0_Y + plane;
|
||||
case SSPP_VIG1: return CID_VIG1_Y + plane;
|
||||
case SSPP_VIG2: return CID_VIG2_Y + plane;
|
||||
case SSPP_RGB0: return CID_RGB0;
|
||||
case SSPP_RGB1: return CID_RGB1;
|
||||
case SSPP_RGB2: return CID_RGB2;
|
||||
case SSPP_DMA0: return CID_DMA0_Y + plane;
|
||||
case SSPP_DMA1: return CID_DMA1_Y + plane;
|
||||
case SSPP_VIG3: return CID_VIG3_Y + plane;
|
||||
case SSPP_RGB3: return CID_RGB3;
|
||||
default: return CID_UNUSED;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t mixer2flush(int lm)
|
||||
{
|
||||
switch (lm) {
|
||||
case 0: return MDP5_CTL_FLUSH_LM0;
|
||||
case 1: return MDP5_CTL_FLUSH_LM1;
|
||||
case 2: return MDP5_CTL_FLUSH_LM2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t intf2err(int intf)
|
||||
{
|
||||
switch (intf) {
|
||||
case 0: return MDP5_IRQ_INTF0_UNDER_RUN;
|
||||
case 1: return MDP5_IRQ_INTF1_UNDER_RUN;
|
||||
case 2: return MDP5_IRQ_INTF2_UNDER_RUN;
|
||||
case 3: return MDP5_IRQ_INTF3_UNDER_RUN;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t intf2vblank(int intf)
|
||||
{
|
||||
switch (intf) {
|
||||
case 0: return MDP5_IRQ_INTF0_VSYNC;
|
||||
case 1: return MDP5_IRQ_INTF1_VSYNC;
|
||||
case 2: return MDP5_IRQ_INTF2_VSYNC;
|
||||
case 3: return MDP5_IRQ_INTF3_VSYNC;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int mdp5_disable(struct mdp5_kms *mdp5_kms);
|
||||
int mdp5_enable(struct mdp5_kms *mdp5_kms);
|
||||
|
||||
void mdp5_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask);
|
||||
void mdp5_irq_preinstall(struct msm_kms *kms);
|
||||
int mdp5_irq_postinstall(struct msm_kms *kms);
|
||||
void mdp5_irq_uninstall(struct msm_kms *kms);
|
||||
irqreturn_t mdp5_irq(struct msm_kms *kms);
|
||||
int mdp5_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
|
||||
void mdp5_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc);
|
||||
|
||||
static inline
|
||||
uint32_t mdp5_get_formats(enum mdp5_pipe pipe, uint32_t *pixel_formats,
|
||||
uint32_t max_formats)
|
||||
{
|
||||
/* TODO when we have YUV, we need to filter supported formats
|
||||
* based on pipe id..
|
||||
*/
|
||||
return mdp_get_formats(pixel_formats, max_formats);
|
||||
}
|
||||
|
||||
void mdp5_plane_install_properties(struct drm_plane *plane,
|
||||
struct drm_mode_object *obj);
|
||||
void mdp5_plane_set_scanout(struct drm_plane *plane,
|
||||
struct drm_framebuffer *fb);
|
||||
int mdp5_plane_mode_set(struct drm_plane *plane,
|
||||
struct drm_crtc *crtc, struct drm_framebuffer *fb,
|
||||
int crtc_x, int crtc_y,
|
||||
unsigned int crtc_w, unsigned int crtc_h,
|
||||
uint32_t src_x, uint32_t src_y,
|
||||
uint32_t src_w, uint32_t src_h);
|
||||
void mdp5_plane_complete_flip(struct drm_plane *plane);
|
||||
enum mdp5_pipe mdp5_plane_pipe(struct drm_plane *plane);
|
||||
struct drm_plane *mdp5_plane_init(struct drm_device *dev,
|
||||
enum mdp5_pipe pipe, bool private_plane);
|
||||
|
||||
uint32_t mdp5_crtc_vblank(struct drm_crtc *crtc);
|
||||
|
||||
void mdp5_crtc_cancel_pending_flip(struct drm_crtc *crtc, struct drm_file *file);
|
||||
void mdp5_crtc_set_intf(struct drm_crtc *crtc, int intf,
|
||||
enum mdp5_intf intf_id);
|
||||
void mdp5_crtc_attach(struct drm_crtc *crtc, struct drm_plane *plane);
|
||||
void mdp5_crtc_detach(struct drm_crtc *crtc, struct drm_plane *plane);
|
||||
struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,
|
||||
struct drm_plane *plane, int id);
|
||||
|
||||
struct drm_encoder *mdp5_encoder_init(struct drm_device *dev, int intf,
|
||||
enum mdp5_intf intf_id);
|
||||
|
||||
#endif /* __MDP5_KMS_H__ */
|
||||
394
drivers/gpu/drm/msm/mdp/mdp5/mdp5_plane.c
Normal file
394
drivers/gpu/drm/msm/mdp/mdp5/mdp5_plane.c
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "mdp5_kms.h"
|
||||
|
||||
|
||||
struct mdp5_plane {
|
||||
struct drm_plane base;
|
||||
const char *name;
|
||||
|
||||
enum mdp5_pipe pipe;
|
||||
|
||||
uint32_t nformats;
|
||||
uint32_t formats[32];
|
||||
|
||||
bool enabled;
|
||||
};
|
||||
#define to_mdp5_plane(x) container_of(x, struct mdp5_plane, base)
|
||||
|
||||
static struct mdp5_kms *get_kms(struct drm_plane *plane)
|
||||
{
|
||||
struct msm_drm_private *priv = plane->dev->dev_private;
|
||||
return to_mdp5_kms(to_mdp_kms(priv->kms));
|
||||
}
|
||||
|
||||
static int mdp5_plane_update(struct drm_plane *plane,
|
||||
struct drm_crtc *crtc, struct drm_framebuffer *fb,
|
||||
int crtc_x, int crtc_y,
|
||||
unsigned int crtc_w, unsigned int crtc_h,
|
||||
uint32_t src_x, uint32_t src_y,
|
||||
uint32_t src_w, uint32_t src_h)
|
||||
{
|
||||
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
|
||||
|
||||
mdp5_plane->enabled = true;
|
||||
|
||||
if (plane->fb)
|
||||
drm_framebuffer_unreference(plane->fb);
|
||||
|
||||
drm_framebuffer_reference(fb);
|
||||
|
||||
return mdp5_plane_mode_set(plane, crtc, fb,
|
||||
crtc_x, crtc_y, crtc_w, crtc_h,
|
||||
src_x, src_y, src_w, src_h);
|
||||
}
|
||||
|
||||
static int mdp5_plane_disable(struct drm_plane *plane)
|
||||
{
|
||||
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(plane);
|
||||
enum mdp5_pipe pipe = mdp5_plane->pipe;
|
||||
int i;
|
||||
|
||||
DBG("%s: disable", mdp5_plane->name);
|
||||
|
||||
/* update our SMP request to zero (release all our blks): */
|
||||
for (i = 0; i < pipe2nclients(pipe); i++)
|
||||
mdp5_smp_request(mdp5_kms, pipe2client(pipe, i), 0);
|
||||
|
||||
/* TODO detaching now will cause us not to get the last
|
||||
* vblank and mdp5_smp_commit().. so other planes will
|
||||
* still see smp blocks previously allocated to us as
|
||||
* in-use..
|
||||
*/
|
||||
if (plane->crtc)
|
||||
mdp5_crtc_detach(plane->crtc, plane);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mdp5_plane_destroy(struct drm_plane *plane)
|
||||
{
|
||||
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
|
||||
struct msm_drm_private *priv = plane->dev->dev_private;
|
||||
|
||||
if (priv->kms)
|
||||
mdp5_plane_disable(plane);
|
||||
|
||||
drm_plane_cleanup(plane);
|
||||
|
||||
kfree(mdp5_plane);
|
||||
}
|
||||
|
||||
/* helper to install properties which are common to planes and crtcs */
|
||||
void mdp5_plane_install_properties(struct drm_plane *plane,
|
||||
struct drm_mode_object *obj)
|
||||
{
|
||||
// XXX
|
||||
}
|
||||
|
||||
int mdp5_plane_set_property(struct drm_plane *plane,
|
||||
struct drm_property *property, uint64_t val)
|
||||
{
|
||||
// XXX
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct drm_plane_funcs mdp5_plane_funcs = {
|
||||
.update_plane = mdp5_plane_update,
|
||||
.disable_plane = mdp5_plane_disable,
|
||||
.destroy = mdp5_plane_destroy,
|
||||
.set_property = mdp5_plane_set_property,
|
||||
};
|
||||
|
||||
void mdp5_plane_set_scanout(struct drm_plane *plane,
|
||||
struct drm_framebuffer *fb)
|
||||
{
|
||||
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(plane);
|
||||
enum mdp5_pipe pipe = mdp5_plane->pipe;
|
||||
uint32_t nplanes = drm_format_num_planes(fb->pixel_format);
|
||||
uint32_t iova[4];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nplanes; i++) {
|
||||
struct drm_gem_object *bo = msm_framebuffer_bo(fb, i);
|
||||
msm_gem_get_iova(bo, mdp5_kms->id, &iova[i]);
|
||||
}
|
||||
for (; i < 4; i++)
|
||||
iova[i] = 0;
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_STRIDE_A(pipe),
|
||||
MDP5_PIPE_SRC_STRIDE_A_P0(fb->pitches[0]) |
|
||||
MDP5_PIPE_SRC_STRIDE_A_P1(fb->pitches[1]));
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_STRIDE_B(pipe),
|
||||
MDP5_PIPE_SRC_STRIDE_B_P2(fb->pitches[2]) |
|
||||
MDP5_PIPE_SRC_STRIDE_B_P3(fb->pitches[3]));
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC0_ADDR(pipe), iova[0]);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC1_ADDR(pipe), iova[1]);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC2_ADDR(pipe), iova[2]);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC3_ADDR(pipe), iova[3]);
|
||||
|
||||
plane->fb = fb;
|
||||
}
|
||||
|
||||
/* NOTE: looks like if horizontal decimation is used (if we supported that)
|
||||
* then the width used to calculate SMP block requirements is the post-
|
||||
* decimated width. Ie. SMP buffering sits downstream of decimation (which
|
||||
* presumably happens during the dma from scanout buffer).
|
||||
*/
|
||||
static int request_smp_blocks(struct drm_plane *plane, uint32_t format,
|
||||
uint32_t nplanes, uint32_t width)
|
||||
{
|
||||
struct drm_device *dev = plane->dev;
|
||||
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(plane);
|
||||
enum mdp5_pipe pipe = mdp5_plane->pipe;
|
||||
int i, hsub, nlines, nblks, ret;
|
||||
|
||||
hsub = drm_format_horz_chroma_subsampling(format);
|
||||
|
||||
/* different if BWC (compressed framebuffer?) enabled: */
|
||||
nlines = 2;
|
||||
|
||||
for (i = 0, nblks = 0; i < nplanes; i++) {
|
||||
int n, fetch_stride, cpp;
|
||||
|
||||
cpp = drm_format_plane_cpp(format, i);
|
||||
fetch_stride = width * cpp / (i ? hsub : 1);
|
||||
|
||||
n = DIV_ROUND_UP(fetch_stride * nlines, SMP_BLK_SIZE);
|
||||
|
||||
/* for hw rev v1.00 */
|
||||
if (mdp5_kms->rev == 0)
|
||||
n = roundup_pow_of_two(n);
|
||||
|
||||
DBG("%s[%d]: request %d SMP blocks", mdp5_plane->name, i, n);
|
||||
ret = mdp5_smp_request(mdp5_kms, pipe2client(pipe, i), n);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "Could not allocate %d SMP blocks: %d\n",
|
||||
n, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
nblks += n;
|
||||
}
|
||||
|
||||
/* in success case, return total # of blocks allocated: */
|
||||
return nblks;
|
||||
}
|
||||
|
||||
static void set_fifo_thresholds(struct drm_plane *plane, int nblks)
|
||||
{
|
||||
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(plane);
|
||||
enum mdp5_pipe pipe = mdp5_plane->pipe;
|
||||
uint32_t val;
|
||||
|
||||
/* 1/4 of SMP pool that is being fetched */
|
||||
val = (nblks * SMP_ENTRIES_PER_BLK) / 4;
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_0(pipe), val * 1);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_1(pipe), val * 2);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_2(pipe), val * 3);
|
||||
|
||||
}
|
||||
|
||||
int mdp5_plane_mode_set(struct drm_plane *plane,
|
||||
struct drm_crtc *crtc, struct drm_framebuffer *fb,
|
||||
int crtc_x, int crtc_y,
|
||||
unsigned int crtc_w, unsigned int crtc_h,
|
||||
uint32_t src_x, uint32_t src_y,
|
||||
uint32_t src_w, uint32_t src_h)
|
||||
{
|
||||
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
|
||||
struct mdp5_kms *mdp5_kms = get_kms(plane);
|
||||
enum mdp5_pipe pipe = mdp5_plane->pipe;
|
||||
const struct mdp_format *format;
|
||||
uint32_t nplanes, config = 0;
|
||||
uint32_t phasex_step = 0, phasey_step = 0;
|
||||
uint32_t hdecm = 0, vdecm = 0;
|
||||
int i, nblks;
|
||||
|
||||
nplanes = drm_format_num_planes(fb->pixel_format);
|
||||
|
||||
/* bad formats should already be rejected: */
|
||||
if (WARN_ON(nplanes > pipe2nclients(pipe)))
|
||||
return -EINVAL;
|
||||
|
||||
/* src values are in Q16 fixed point, convert to integer: */
|
||||
src_x = src_x >> 16;
|
||||
src_y = src_y >> 16;
|
||||
src_w = src_w >> 16;
|
||||
src_h = src_h >> 16;
|
||||
|
||||
DBG("%s: FB[%u] %u,%u,%u,%u -> CRTC[%u] %d,%d,%u,%u", mdp5_plane->name,
|
||||
fb->base.id, src_x, src_y, src_w, src_h,
|
||||
crtc->base.id, crtc_x, crtc_y, crtc_w, crtc_h);
|
||||
|
||||
/*
|
||||
* Calculate and request required # of smp blocks:
|
||||
*/
|
||||
nblks = request_smp_blocks(plane, fb->pixel_format, nplanes, src_w);
|
||||
if (nblks < 0)
|
||||
return nblks;
|
||||
|
||||
/*
|
||||
* Currently we update the hw for allocations/requests immediately,
|
||||
* but once atomic modeset/pageflip is in place, the allocation
|
||||
* would move into atomic->check_plane_state(), while updating the
|
||||
* hw would remain here:
|
||||
*/
|
||||
for (i = 0; i < pipe2nclients(pipe); i++)
|
||||
mdp5_smp_configure(mdp5_kms, pipe2client(pipe, i));
|
||||
|
||||
if (src_w != crtc_w) {
|
||||
config |= MDP5_PIPE_SCALE_CONFIG_SCALEX_EN;
|
||||
/* TODO calc phasex_step, hdecm */
|
||||
}
|
||||
|
||||
if (src_h != crtc_h) {
|
||||
config |= MDP5_PIPE_SCALE_CONFIG_SCALEY_EN;
|
||||
/* TODO calc phasey_step, vdecm */
|
||||
}
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_IMG_SIZE(pipe),
|
||||
MDP5_PIPE_SRC_IMG_SIZE_WIDTH(src_w) |
|
||||
MDP5_PIPE_SRC_IMG_SIZE_HEIGHT(src_h));
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_SIZE(pipe),
|
||||
MDP5_PIPE_SRC_SIZE_WIDTH(src_w) |
|
||||
MDP5_PIPE_SRC_SIZE_HEIGHT(src_h));
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_XY(pipe),
|
||||
MDP5_PIPE_SRC_XY_X(src_x) |
|
||||
MDP5_PIPE_SRC_XY_Y(src_y));
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_OUT_SIZE(pipe),
|
||||
MDP5_PIPE_OUT_SIZE_WIDTH(crtc_w) |
|
||||
MDP5_PIPE_OUT_SIZE_HEIGHT(crtc_h));
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_OUT_XY(pipe),
|
||||
MDP5_PIPE_OUT_XY_X(crtc_x) |
|
||||
MDP5_PIPE_OUT_XY_Y(crtc_y));
|
||||
|
||||
mdp5_plane_set_scanout(plane, fb);
|
||||
|
||||
format = to_mdp_format(msm_framebuffer_format(fb));
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_FORMAT(pipe),
|
||||
MDP5_PIPE_SRC_FORMAT_A_BPC(format->bpc_a) |
|
||||
MDP5_PIPE_SRC_FORMAT_R_BPC(format->bpc_r) |
|
||||
MDP5_PIPE_SRC_FORMAT_G_BPC(format->bpc_g) |
|
||||
MDP5_PIPE_SRC_FORMAT_B_BPC(format->bpc_b) |
|
||||
COND(format->alpha_enable, MDP5_PIPE_SRC_FORMAT_ALPHA_ENABLE) |
|
||||
MDP5_PIPE_SRC_FORMAT_CPP(format->cpp - 1) |
|
||||
MDP5_PIPE_SRC_FORMAT_UNPACK_COUNT(format->unpack_count - 1) |
|
||||
COND(format->unpack_tight, MDP5_PIPE_SRC_FORMAT_UNPACK_TIGHT) |
|
||||
MDP5_PIPE_SRC_FORMAT_NUM_PLANES(nplanes - 1) |
|
||||
MDP5_PIPE_SRC_FORMAT_CHROMA_SAMP(CHROMA_RGB));
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_UNPACK(pipe),
|
||||
MDP5_PIPE_SRC_UNPACK_ELEM0(format->unpack[0]) |
|
||||
MDP5_PIPE_SRC_UNPACK_ELEM1(format->unpack[1]) |
|
||||
MDP5_PIPE_SRC_UNPACK_ELEM2(format->unpack[2]) |
|
||||
MDP5_PIPE_SRC_UNPACK_ELEM3(format->unpack[3]));
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_OP_MODE(pipe),
|
||||
MDP5_PIPE_SRC_OP_MODE_BWC(BWC_LOSSLESS));
|
||||
|
||||
/* not using secure mode: */
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_ADDR_SW_STATUS(pipe), 0);
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_PHASE_STEP_X(pipe), phasex_step);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_PHASE_STEP_Y(pipe), phasey_step);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_DECIMATION(pipe),
|
||||
MDP5_PIPE_DECIMATION_VERT(vdecm) |
|
||||
MDP5_PIPE_DECIMATION_HORZ(hdecm));
|
||||
mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_CONFIG(pipe),
|
||||
MDP5_PIPE_SCALE_CONFIG_SCALEX_MIN_FILTER(SCALE_FILTER_NEAREST) |
|
||||
MDP5_PIPE_SCALE_CONFIG_SCALEY_MIN_FILTER(SCALE_FILTER_NEAREST) |
|
||||
MDP5_PIPE_SCALE_CONFIG_SCALEX_CR_FILTER(SCALE_FILTER_NEAREST) |
|
||||
MDP5_PIPE_SCALE_CONFIG_SCALEY_CR_FILTER(SCALE_FILTER_NEAREST) |
|
||||
MDP5_PIPE_SCALE_CONFIG_SCALEX_MAX_FILTER(SCALE_FILTER_NEAREST) |
|
||||
MDP5_PIPE_SCALE_CONFIG_SCALEY_MAX_FILTER(SCALE_FILTER_NEAREST));
|
||||
|
||||
set_fifo_thresholds(plane, nblks);
|
||||
|
||||
/* TODO detach from old crtc (if we had more than one) */
|
||||
mdp5_crtc_attach(crtc, plane);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdp5_plane_complete_flip(struct drm_plane *plane)
|
||||
{
|
||||
struct mdp5_kms *mdp5_kms = get_kms(plane);
|
||||
enum mdp5_pipe pipe = to_mdp5_plane(plane)->pipe;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pipe2nclients(pipe); i++)
|
||||
mdp5_smp_commit(mdp5_kms, pipe2client(pipe, i));
|
||||
}
|
||||
|
||||
enum mdp5_pipe mdp5_plane_pipe(struct drm_plane *plane)
|
||||
{
|
||||
struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane);
|
||||
return mdp5_plane->pipe;
|
||||
}
|
||||
|
||||
/* initialize plane */
|
||||
struct drm_plane *mdp5_plane_init(struct drm_device *dev,
|
||||
enum mdp5_pipe pipe, bool private_plane)
|
||||
{
|
||||
struct drm_plane *plane = NULL;
|
||||
struct mdp5_plane *mdp5_plane;
|
||||
int ret;
|
||||
enum drm_plane_type type;
|
||||
|
||||
mdp5_plane = kzalloc(sizeof(*mdp5_plane), GFP_KERNEL);
|
||||
if (!mdp5_plane) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
plane = &mdp5_plane->base;
|
||||
|
||||
mdp5_plane->pipe = pipe;
|
||||
mdp5_plane->name = pipe2name(pipe);
|
||||
|
||||
mdp5_plane->nformats = mdp5_get_formats(pipe, mdp5_plane->formats,
|
||||
ARRAY_SIZE(mdp5_plane->formats));
|
||||
|
||||
type = private_plane ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY;
|
||||
drm_universal_plane_init(dev, plane, 0xff, &mdp5_plane_funcs,
|
||||
mdp5_plane->formats, mdp5_plane->nformats,
|
||||
type);
|
||||
|
||||
mdp5_plane_install_properties(plane, &plane->base);
|
||||
|
||||
return plane;
|
||||
|
||||
fail:
|
||||
if (plane)
|
||||
mdp5_plane_destroy(plane);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
173
drivers/gpu/drm/msm/mdp/mdp5/mdp5_smp.c
Normal file
173
drivers/gpu/drm/msm/mdp/mdp5/mdp5_smp.c
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "mdp5_kms.h"
|
||||
#include "mdp5_smp.h"
|
||||
|
||||
|
||||
/* SMP - Shared Memory Pool
|
||||
*
|
||||
* These are shared between all the clients, where each plane in a
|
||||
* scanout buffer is a SMP client. Ie. scanout of 3 plane I420 on
|
||||
* pipe VIG0 => 3 clients: VIG0_Y, VIG0_CB, VIG0_CR.
|
||||
*
|
||||
* Based on the size of the attached scanout buffer, a certain # of
|
||||
* blocks must be allocated to that client out of the shared pool.
|
||||
*
|
||||
* For each block, it can be either free, or pending/in-use by a
|
||||
* client. The updates happen in three steps:
|
||||
*
|
||||
* 1) mdp5_smp_request():
|
||||
* When plane scanout is setup, calculate required number of
|
||||
* blocks needed per client, and request. Blocks not inuse or
|
||||
* pending by any other client are added to client's pending
|
||||
* set.
|
||||
*
|
||||
* 2) mdp5_smp_configure():
|
||||
* As hw is programmed, before FLUSH, MDP5_SMP_ALLOC registers
|
||||
* are configured for the union(pending, inuse)
|
||||
*
|
||||
* 3) mdp5_smp_commit():
|
||||
* After next vblank, copy pending -> inuse. Optionally update
|
||||
* MDP5_SMP_ALLOC registers if there are newly unused blocks
|
||||
*
|
||||
* On the next vblank after changes have been committed to hw, the
|
||||
* client's pending blocks become it's in-use blocks (and no-longer
|
||||
* in-use blocks become available to other clients).
|
||||
*
|
||||
* btw, hurray for confusing overloaded acronyms! :-/
|
||||
*
|
||||
* NOTE: for atomic modeset/pageflip NONBLOCK operations, step #1
|
||||
* should happen at (or before)? atomic->check(). And we'd need
|
||||
* an API to discard previous requests if update is aborted or
|
||||
* (test-only).
|
||||
*
|
||||
* TODO would perhaps be nice to have debugfs to dump out kernel
|
||||
* inuse and pending state of all clients..
|
||||
*/
|
||||
|
||||
static DEFINE_SPINLOCK(smp_lock);
|
||||
|
||||
|
||||
/* step #1: update # of blocks pending for the client: */
|
||||
int mdp5_smp_request(struct mdp5_kms *mdp5_kms,
|
||||
enum mdp5_client_id cid, int nblks)
|
||||
{
|
||||
struct mdp5_client_smp_state *ps = &mdp5_kms->smp_client_state[cid];
|
||||
int i, ret, avail, cur_nblks, cnt = mdp5_kms->smp_blk_cnt;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&smp_lock, flags);
|
||||
|
||||
avail = cnt - bitmap_weight(mdp5_kms->smp_state, cnt);
|
||||
if (nblks > avail) {
|
||||
ret = -ENOSPC;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cur_nblks = bitmap_weight(ps->pending, cnt);
|
||||
if (nblks > cur_nblks) {
|
||||
/* grow the existing pending reservation: */
|
||||
for (i = cur_nblks; i < nblks; i++) {
|
||||
int blk = find_first_zero_bit(mdp5_kms->smp_state, cnt);
|
||||
set_bit(blk, ps->pending);
|
||||
set_bit(blk, mdp5_kms->smp_state);
|
||||
}
|
||||
} else {
|
||||
/* shrink the existing pending reservation: */
|
||||
for (i = cur_nblks; i > nblks; i--) {
|
||||
int blk = find_first_bit(ps->pending, cnt);
|
||||
clear_bit(blk, ps->pending);
|
||||
/* don't clear in global smp_state until _commit() */
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
spin_unlock_irqrestore(&smp_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void update_smp_state(struct mdp5_kms *mdp5_kms,
|
||||
enum mdp5_client_id cid, mdp5_smp_state_t *assigned)
|
||||
{
|
||||
int cnt = mdp5_kms->smp_blk_cnt;
|
||||
uint32_t blk, val;
|
||||
|
||||
for_each_set_bit(blk, *assigned, cnt) {
|
||||
int idx = blk / 3;
|
||||
int fld = blk % 3;
|
||||
|
||||
val = mdp5_read(mdp5_kms, REG_MDP5_SMP_ALLOC_W_REG(idx));
|
||||
|
||||
switch (fld) {
|
||||
case 0:
|
||||
val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT0__MASK;
|
||||
val |= MDP5_SMP_ALLOC_W_REG_CLIENT0(cid);
|
||||
break;
|
||||
case 1:
|
||||
val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT1__MASK;
|
||||
val |= MDP5_SMP_ALLOC_W_REG_CLIENT1(cid);
|
||||
break;
|
||||
case 2:
|
||||
val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT2__MASK;
|
||||
val |= MDP5_SMP_ALLOC_W_REG_CLIENT2(cid);
|
||||
break;
|
||||
}
|
||||
|
||||
mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_W_REG(idx), val);
|
||||
mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_R_REG(idx), val);
|
||||
}
|
||||
}
|
||||
|
||||
/* step #2: configure hw for union(pending, inuse): */
|
||||
void mdp5_smp_configure(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid)
|
||||
{
|
||||
struct mdp5_client_smp_state *ps = &mdp5_kms->smp_client_state[cid];
|
||||
int cnt = mdp5_kms->smp_blk_cnt;
|
||||
mdp5_smp_state_t assigned;
|
||||
|
||||
bitmap_or(assigned, ps->inuse, ps->pending, cnt);
|
||||
update_smp_state(mdp5_kms, cid, &assigned);
|
||||
}
|
||||
|
||||
/* step #3: after vblank, copy pending -> inuse: */
|
||||
void mdp5_smp_commit(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid)
|
||||
{
|
||||
struct mdp5_client_smp_state *ps = &mdp5_kms->smp_client_state[cid];
|
||||
int cnt = mdp5_kms->smp_blk_cnt;
|
||||
mdp5_smp_state_t released;
|
||||
|
||||
/*
|
||||
* Figure out if there are any blocks we where previously
|
||||
* using, which can be released and made available to other
|
||||
* clients:
|
||||
*/
|
||||
if (bitmap_andnot(released, ps->inuse, ps->pending, cnt)) {
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&smp_lock, flags);
|
||||
/* clear released blocks: */
|
||||
bitmap_andnot(mdp5_kms->smp_state, mdp5_kms->smp_state,
|
||||
released, cnt);
|
||||
spin_unlock_irqrestore(&smp_lock, flags);
|
||||
|
||||
update_smp_state(mdp5_kms, CID_UNUSED, &released);
|
||||
}
|
||||
|
||||
bitmap_copy(ps->inuse, ps->pending, cnt);
|
||||
}
|
||||
41
drivers/gpu/drm/msm/mdp/mdp5/mdp5_smp.h
Normal file
41
drivers/gpu/drm/msm/mdp/mdp5/mdp5_smp.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MDP5_SMP_H__
|
||||
#define __MDP5_SMP_H__
|
||||
|
||||
#include "msm_drv.h"
|
||||
|
||||
#define MAX_SMP_BLOCKS 22
|
||||
#define SMP_BLK_SIZE 4096
|
||||
#define SMP_ENTRIES_PER_BLK (SMP_BLK_SIZE / 16)
|
||||
|
||||
typedef DECLARE_BITMAP(mdp5_smp_state_t, MAX_SMP_BLOCKS);
|
||||
|
||||
struct mdp5_client_smp_state {
|
||||
mdp5_smp_state_t inuse;
|
||||
mdp5_smp_state_t pending;
|
||||
};
|
||||
|
||||
struct mdp5_kms;
|
||||
|
||||
int mdp5_smp_request(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid, int nblks);
|
||||
void mdp5_smp_configure(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid);
|
||||
void mdp5_smp_commit(struct mdp5_kms *mdp5_kms, enum mdp5_client_id cid);
|
||||
|
||||
|
||||
#endif /* __MDP5_SMP_H__ */
|
||||
78
drivers/gpu/drm/msm/mdp/mdp_common.xml.h
Normal file
78
drivers/gpu/drm/msm/mdp/mdp_common.xml.h
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#ifndef MDP_COMMON_XML
|
||||
#define MDP_COMMON_XML
|
||||
|
||||
/* Autogenerated file, DO NOT EDIT manually!
|
||||
|
||||
This file was generated by the rules-ng-ng headergen tool in this git repository:
|
||||
http://github.com/freedreno/envytools/
|
||||
git clone https://github.com/freedreno/envytools.git
|
||||
|
||||
The rules-ng-ng source files this header was generated from are:
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/msm.xml ( 647 bytes, from 2013-11-30 14:45:35)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/freedreno_copyright.xml ( 1453 bytes, from 2013-03-31 16:51:27)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp4.xml ( 17996 bytes, from 2013-12-01 19:10:31)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp_common.xml ( 1615 bytes, from 2013-11-30 15:00:52)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/mdp/mdp5.xml ( 22517 bytes, from 2014-06-25 12:55:02)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/dsi.xml ( 11712 bytes, from 2013-08-17 17:13:43)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/sfpb.xml ( 344 bytes, from 2013-08-11 19:26:32)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/dsi/mmss_cc.xml ( 1544 bytes, from 2013-08-16 19:17:05)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2013-07-05 19:21:12)
|
||||
- /home/robclark/src/freedreno/envytools/rnndb/hdmi/hdmi.xml ( 23613 bytes, from 2014-06-25 12:53:44)
|
||||
|
||||
Copyright (C) 2013 by the following authors:
|
||||
- Rob Clark <robdclark@gmail.com> (robclark)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the
|
||||
next paragraph) shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
enum mdp_mixer_stage_id {
|
||||
STAGE_UNUSED = 0,
|
||||
STAGE_BASE = 1,
|
||||
STAGE0 = 2,
|
||||
STAGE1 = 3,
|
||||
STAGE2 = 4,
|
||||
STAGE3 = 5,
|
||||
};
|
||||
|
||||
enum mdp_alpha_type {
|
||||
FG_CONST = 0,
|
||||
BG_CONST = 1,
|
||||
FG_PIXEL = 2,
|
||||
BG_PIXEL = 3,
|
||||
};
|
||||
|
||||
enum mdp_bpc {
|
||||
BPC1 = 0,
|
||||
BPC5 = 1,
|
||||
BPC6 = 2,
|
||||
BPC8 = 3,
|
||||
};
|
||||
|
||||
enum mdp_bpc_alpha {
|
||||
BPC1A = 0,
|
||||
BPC4A = 1,
|
||||
BPC6A = 2,
|
||||
BPC8A = 3,
|
||||
};
|
||||
|
||||
|
||||
#endif /* MDP_COMMON_XML */
|
||||
71
drivers/gpu/drm/msm/mdp/mdp_format.c
Normal file
71
drivers/gpu/drm/msm/mdp/mdp_format.c
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "mdp_kms.h"
|
||||
|
||||
#define FMT(name, a, r, g, b, e0, e1, e2, e3, alpha, tight, c, cnt) { \
|
||||
.base = { .pixel_format = DRM_FORMAT_ ## name }, \
|
||||
.bpc_a = BPC ## a ## A, \
|
||||
.bpc_r = BPC ## r, \
|
||||
.bpc_g = BPC ## g, \
|
||||
.bpc_b = BPC ## b, \
|
||||
.unpack = { e0, e1, e2, e3 }, \
|
||||
.alpha_enable = alpha, \
|
||||
.unpack_tight = tight, \
|
||||
.cpp = c, \
|
||||
.unpack_count = cnt, \
|
||||
}
|
||||
|
||||
#define BPC0A 0
|
||||
|
||||
static const struct mdp_format formats[] = {
|
||||
/* name a r g b e0 e1 e2 e3 alpha tight cpp cnt */
|
||||
FMT(ARGB8888, 8, 8, 8, 8, 1, 0, 2, 3, true, true, 4, 4),
|
||||
FMT(XRGB8888, 8, 8, 8, 8, 1, 0, 2, 3, false, true, 4, 4),
|
||||
FMT(RGB888, 0, 8, 8, 8, 1, 0, 2, 0, false, true, 3, 3),
|
||||
FMT(BGR888, 0, 8, 8, 8, 2, 0, 1, 0, false, true, 3, 3),
|
||||
FMT(RGB565, 0, 5, 6, 5, 1, 0, 2, 0, false, true, 2, 3),
|
||||
FMT(BGR565, 0, 5, 6, 5, 2, 0, 1, 0, false, true, 2, 3),
|
||||
};
|
||||
|
||||
uint32_t mdp_get_formats(uint32_t *pixel_formats, uint32_t max_formats)
|
||||
{
|
||||
uint32_t i;
|
||||
for (i = 0; i < ARRAY_SIZE(formats); i++) {
|
||||
const struct mdp_format *f = &formats[i];
|
||||
|
||||
if (i == max_formats)
|
||||
break;
|
||||
|
||||
pixel_formats[i] = f->base.pixel_format;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
const struct msm_format *mdp_get_format(struct msm_kms *kms, uint32_t format)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(formats); i++) {
|
||||
const struct mdp_format *f = &formats[i];
|
||||
if (f->base.pixel_format == format)
|
||||
return &f->base;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
145
drivers/gpu/drm/msm/mdp/mdp_kms.c
Normal file
145
drivers/gpu/drm/msm/mdp/mdp_kms.c
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "mdp_kms.h"
|
||||
|
||||
|
||||
struct mdp_irq_wait {
|
||||
struct mdp_irq irq;
|
||||
int count;
|
||||
};
|
||||
|
||||
static DECLARE_WAIT_QUEUE_HEAD(wait_event);
|
||||
|
||||
static DEFINE_SPINLOCK(list_lock);
|
||||
|
||||
static void update_irq(struct mdp_kms *mdp_kms)
|
||||
{
|
||||
struct mdp_irq *irq;
|
||||
uint32_t irqmask = mdp_kms->vblank_mask;
|
||||
|
||||
BUG_ON(!spin_is_locked(&list_lock));
|
||||
|
||||
list_for_each_entry(irq, &mdp_kms->irq_list, node)
|
||||
irqmask |= irq->irqmask;
|
||||
|
||||
mdp_kms->funcs->set_irqmask(mdp_kms, irqmask);
|
||||
}
|
||||
|
||||
static void update_irq_unlocked(struct mdp_kms *mdp_kms)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&list_lock, flags);
|
||||
update_irq(mdp_kms);
|
||||
spin_unlock_irqrestore(&list_lock, flags);
|
||||
}
|
||||
|
||||
void mdp_dispatch_irqs(struct mdp_kms *mdp_kms, uint32_t status)
|
||||
{
|
||||
struct mdp_irq *handler, *n;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&list_lock, flags);
|
||||
mdp_kms->in_irq = true;
|
||||
list_for_each_entry_safe(handler, n, &mdp_kms->irq_list, node) {
|
||||
if (handler->irqmask & status) {
|
||||
spin_unlock_irqrestore(&list_lock, flags);
|
||||
handler->irq(handler, handler->irqmask & status);
|
||||
spin_lock_irqsave(&list_lock, flags);
|
||||
}
|
||||
}
|
||||
mdp_kms->in_irq = false;
|
||||
update_irq(mdp_kms);
|
||||
spin_unlock_irqrestore(&list_lock, flags);
|
||||
|
||||
}
|
||||
|
||||
void mdp_update_vblank_mask(struct mdp_kms *mdp_kms, uint32_t mask, bool enable)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&list_lock, flags);
|
||||
if (enable)
|
||||
mdp_kms->vblank_mask |= mask;
|
||||
else
|
||||
mdp_kms->vblank_mask &= ~mask;
|
||||
update_irq(mdp_kms);
|
||||
spin_unlock_irqrestore(&list_lock, flags);
|
||||
}
|
||||
|
||||
static void wait_irq(struct mdp_irq *irq, uint32_t irqstatus)
|
||||
{
|
||||
struct mdp_irq_wait *wait =
|
||||
container_of(irq, struct mdp_irq_wait, irq);
|
||||
wait->count--;
|
||||
wake_up_all(&wait_event);
|
||||
}
|
||||
|
||||
void mdp_irq_wait(struct mdp_kms *mdp_kms, uint32_t irqmask)
|
||||
{
|
||||
struct mdp_irq_wait wait = {
|
||||
.irq = {
|
||||
.irq = wait_irq,
|
||||
.irqmask = irqmask,
|
||||
},
|
||||
.count = 1,
|
||||
};
|
||||
mdp_irq_register(mdp_kms, &wait.irq);
|
||||
wait_event_timeout(wait_event, (wait.count <= 0),
|
||||
msecs_to_jiffies(100));
|
||||
mdp_irq_unregister(mdp_kms, &wait.irq);
|
||||
}
|
||||
|
||||
void mdp_irq_register(struct mdp_kms *mdp_kms, struct mdp_irq *irq)
|
||||
{
|
||||
unsigned long flags;
|
||||
bool needs_update = false;
|
||||
|
||||
spin_lock_irqsave(&list_lock, flags);
|
||||
|
||||
if (!irq->registered) {
|
||||
irq->registered = true;
|
||||
list_add(&irq->node, &mdp_kms->irq_list);
|
||||
needs_update = !mdp_kms->in_irq;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&list_lock, flags);
|
||||
|
||||
if (needs_update)
|
||||
update_irq_unlocked(mdp_kms);
|
||||
}
|
||||
|
||||
void mdp_irq_unregister(struct mdp_kms *mdp_kms, struct mdp_irq *irq)
|
||||
{
|
||||
unsigned long flags;
|
||||
bool needs_update = false;
|
||||
|
||||
spin_lock_irqsave(&list_lock, flags);
|
||||
|
||||
if (irq->registered) {
|
||||
irq->registered = false;
|
||||
list_del(&irq->node);
|
||||
needs_update = !mdp_kms->in_irq;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&list_lock, flags);
|
||||
|
||||
if (needs_update)
|
||||
update_irq_unlocked(mdp_kms);
|
||||
}
|
||||
97
drivers/gpu/drm/msm/mdp/mdp_kms.h
Normal file
97
drivers/gpu/drm/msm/mdp/mdp_kms.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MDP_KMS_H__
|
||||
#define __MDP_KMS_H__
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_kms.h"
|
||||
#include "mdp_common.xml.h"
|
||||
|
||||
struct mdp_kms;
|
||||
|
||||
struct mdp_kms_funcs {
|
||||
struct msm_kms_funcs base;
|
||||
void (*set_irqmask)(struct mdp_kms *mdp_kms, uint32_t irqmask);
|
||||
};
|
||||
|
||||
struct mdp_kms {
|
||||
struct msm_kms base;
|
||||
|
||||
const struct mdp_kms_funcs *funcs;
|
||||
|
||||
/* irq handling: */
|
||||
bool in_irq;
|
||||
struct list_head irq_list; /* list of mdp4_irq */
|
||||
uint32_t vblank_mask; /* irq bits set for userspace vblank */
|
||||
};
|
||||
#define to_mdp_kms(x) container_of(x, struct mdp_kms, base)
|
||||
|
||||
static inline void mdp_kms_init(struct mdp_kms *mdp_kms,
|
||||
const struct mdp_kms_funcs *funcs)
|
||||
{
|
||||
mdp_kms->funcs = funcs;
|
||||
INIT_LIST_HEAD(&mdp_kms->irq_list);
|
||||
msm_kms_init(&mdp_kms->base, &funcs->base);
|
||||
}
|
||||
|
||||
/*
|
||||
* irq helpers:
|
||||
*/
|
||||
|
||||
/* For transiently registering for different MDP irqs that various parts
|
||||
* of the KMS code need during setup/configuration. These are not
|
||||
* necessarily the same as what drm_vblank_get/put() are requesting, and
|
||||
* the hysteresis in drm_vblank_put() is not necessarily desirable for
|
||||
* internal housekeeping related irq usage.
|
||||
*/
|
||||
struct mdp_irq {
|
||||
struct list_head node;
|
||||
uint32_t irqmask;
|
||||
bool registered;
|
||||
void (*irq)(struct mdp_irq *irq, uint32_t irqstatus);
|
||||
};
|
||||
|
||||
void mdp_dispatch_irqs(struct mdp_kms *mdp_kms, uint32_t status);
|
||||
void mdp_update_vblank_mask(struct mdp_kms *mdp_kms, uint32_t mask, bool enable);
|
||||
void mdp_irq_wait(struct mdp_kms *mdp_kms, uint32_t irqmask);
|
||||
void mdp_irq_register(struct mdp_kms *mdp_kms, struct mdp_irq *irq);
|
||||
void mdp_irq_unregister(struct mdp_kms *mdp_kms, struct mdp_irq *irq);
|
||||
|
||||
|
||||
/*
|
||||
* pixel format helpers:
|
||||
*/
|
||||
|
||||
struct mdp_format {
|
||||
struct msm_format base;
|
||||
enum mdp_bpc bpc_r, bpc_g, bpc_b;
|
||||
enum mdp_bpc_alpha bpc_a;
|
||||
uint8_t unpack[4];
|
||||
bool alpha_enable, unpack_tight;
|
||||
uint8_t cpp, unpack_count;
|
||||
};
|
||||
#define to_mdp_format(x) container_of(x, struct mdp_format, base)
|
||||
|
||||
uint32_t mdp_get_formats(uint32_t *formats, uint32_t max_formats);
|
||||
const struct msm_format *mdp_get_format(struct msm_kms *kms, uint32_t format);
|
||||
|
||||
#endif /* __MDP_KMS_H__ */
|
||||
1019
drivers/gpu/drm/msm/msm_drv.c
Normal file
1019
drivers/gpu/drm/msm/msm_drv.c
Normal file
File diff suppressed because it is too large
Load diff
258
drivers/gpu/drm/msm/msm_drv.h
Normal file
258
drivers/gpu/drm/msm/msm_drv.h
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MSM_DRV_H__
|
||||
#define __MSM_DRV_H__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/types.h>
|
||||
#include <asm/sizes.h>
|
||||
|
||||
|
||||
#if defined(CONFIG_COMPILE_TEST) && !defined(CONFIG_ARCH_QCOM)
|
||||
/* stubs we need for compile-test: */
|
||||
static inline struct device *msm_iommu_get_ctx(const char *ctx_name)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_OF
|
||||
#include <mach/board.h>
|
||||
#include <mach/socinfo.h>
|
||||
#include <mach/iommu_domains.h>
|
||||
#endif
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_fb_helper.h>
|
||||
#include <drm/msm_drm.h>
|
||||
#include <drm/drm_gem.h>
|
||||
|
||||
struct msm_kms;
|
||||
struct msm_gpu;
|
||||
struct msm_mmu;
|
||||
struct msm_rd_state;
|
||||
struct msm_perf_state;
|
||||
struct msm_gem_submit;
|
||||
|
||||
#define NUM_DOMAINS 2 /* one for KMS, then one per gpu core (?) */
|
||||
|
||||
struct msm_file_private {
|
||||
/* currently we don't do anything useful with this.. but when
|
||||
* per-context address spaces are supported we'd keep track of
|
||||
* the context's page-tables here.
|
||||
*/
|
||||
int dummy;
|
||||
};
|
||||
|
||||
struct msm_drm_private {
|
||||
|
||||
struct msm_kms *kms;
|
||||
|
||||
/* subordinate devices, if present: */
|
||||
struct platform_device *hdmi_pdev, *gpu_pdev;
|
||||
|
||||
/* when we have more than one 'msm_gpu' these need to be an array: */
|
||||
struct msm_gpu *gpu;
|
||||
struct msm_file_private *lastctx;
|
||||
|
||||
struct drm_fb_helper *fbdev;
|
||||
|
||||
uint32_t next_fence, completed_fence;
|
||||
wait_queue_head_t fence_event;
|
||||
|
||||
struct msm_rd_state *rd;
|
||||
struct msm_perf_state *perf;
|
||||
|
||||
/* list of GEM objects: */
|
||||
struct list_head inactive_list;
|
||||
|
||||
struct workqueue_struct *wq;
|
||||
|
||||
/* callbacks deferred until bo is inactive: */
|
||||
struct list_head fence_cbs;
|
||||
|
||||
/* registered MMUs: */
|
||||
unsigned int num_mmus;
|
||||
struct msm_mmu *mmus[NUM_DOMAINS];
|
||||
|
||||
unsigned int num_planes;
|
||||
struct drm_plane *planes[8];
|
||||
|
||||
unsigned int num_crtcs;
|
||||
struct drm_crtc *crtcs[8];
|
||||
|
||||
unsigned int num_encoders;
|
||||
struct drm_encoder *encoders[8];
|
||||
|
||||
unsigned int num_bridges;
|
||||
struct drm_bridge *bridges[8];
|
||||
|
||||
unsigned int num_connectors;
|
||||
struct drm_connector *connectors[8];
|
||||
|
||||
/* VRAM carveout, used when no IOMMU: */
|
||||
struct {
|
||||
unsigned long size;
|
||||
dma_addr_t paddr;
|
||||
/* NOTE: mm managed at the page level, size is in # of pages
|
||||
* and position mm_node->start is in # of pages:
|
||||
*/
|
||||
struct drm_mm mm;
|
||||
} vram;
|
||||
};
|
||||
|
||||
struct msm_format {
|
||||
uint32_t pixel_format;
|
||||
};
|
||||
|
||||
/* callback from wq once fence has passed: */
|
||||
struct msm_fence_cb {
|
||||
struct work_struct work;
|
||||
uint32_t fence;
|
||||
void (*func)(struct msm_fence_cb *cb);
|
||||
};
|
||||
|
||||
void __msm_fence_worker(struct work_struct *work);
|
||||
|
||||
#define INIT_FENCE_CB(_cb, _func) do { \
|
||||
INIT_WORK(&(_cb)->work, __msm_fence_worker); \
|
||||
(_cb)->func = _func; \
|
||||
} while (0)
|
||||
|
||||
int msm_register_mmu(struct drm_device *dev, struct msm_mmu *mmu);
|
||||
|
||||
int msm_wait_fence_interruptable(struct drm_device *dev, uint32_t fence,
|
||||
struct timespec *timeout);
|
||||
void msm_update_fence(struct drm_device *dev, uint32_t fence);
|
||||
|
||||
int msm_ioctl_gem_submit(struct drm_device *dev, void *data,
|
||||
struct drm_file *file);
|
||||
|
||||
int msm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
|
||||
int msm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
|
||||
uint64_t msm_gem_mmap_offset(struct drm_gem_object *obj);
|
||||
int msm_gem_get_iova_locked(struct drm_gem_object *obj, int id,
|
||||
uint32_t *iova);
|
||||
int msm_gem_get_iova(struct drm_gem_object *obj, int id, uint32_t *iova);
|
||||
struct page **msm_gem_get_pages(struct drm_gem_object *obj);
|
||||
void msm_gem_put_pages(struct drm_gem_object *obj);
|
||||
void msm_gem_put_iova(struct drm_gem_object *obj, int id);
|
||||
int msm_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
|
||||
struct drm_mode_create_dumb *args);
|
||||
int msm_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
|
||||
uint32_t handle, uint64_t *offset);
|
||||
struct sg_table *msm_gem_prime_get_sg_table(struct drm_gem_object *obj);
|
||||
void *msm_gem_prime_vmap(struct drm_gem_object *obj);
|
||||
void msm_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr);
|
||||
struct drm_gem_object *msm_gem_prime_import_sg_table(struct drm_device *dev,
|
||||
struct dma_buf_attachment *attach, struct sg_table *sg);
|
||||
int msm_gem_prime_pin(struct drm_gem_object *obj);
|
||||
void msm_gem_prime_unpin(struct drm_gem_object *obj);
|
||||
void *msm_gem_vaddr_locked(struct drm_gem_object *obj);
|
||||
void *msm_gem_vaddr(struct drm_gem_object *obj);
|
||||
int msm_gem_queue_inactive_cb(struct drm_gem_object *obj,
|
||||
struct msm_fence_cb *cb);
|
||||
void msm_gem_move_to_active(struct drm_gem_object *obj,
|
||||
struct msm_gpu *gpu, bool write, uint32_t fence);
|
||||
void msm_gem_move_to_inactive(struct drm_gem_object *obj);
|
||||
int msm_gem_cpu_prep(struct drm_gem_object *obj, uint32_t op,
|
||||
struct timespec *timeout);
|
||||
int msm_gem_cpu_fini(struct drm_gem_object *obj);
|
||||
void msm_gem_free_object(struct drm_gem_object *obj);
|
||||
int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file,
|
||||
uint32_t size, uint32_t flags, uint32_t *handle);
|
||||
struct drm_gem_object *msm_gem_new(struct drm_device *dev,
|
||||
uint32_t size, uint32_t flags);
|
||||
struct drm_gem_object *msm_gem_import(struct drm_device *dev,
|
||||
uint32_t size, struct sg_table *sgt);
|
||||
|
||||
struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane);
|
||||
const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb);
|
||||
struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev,
|
||||
struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos);
|
||||
struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev,
|
||||
struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd);
|
||||
|
||||
struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev);
|
||||
|
||||
struct hdmi;
|
||||
struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder);
|
||||
irqreturn_t hdmi_irq(int irq, void *dev_id);
|
||||
void __init hdmi_register(void);
|
||||
void __exit hdmi_unregister(void);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);
|
||||
void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);
|
||||
void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m);
|
||||
int msm_debugfs_late_init(struct drm_device *dev);
|
||||
int msm_rd_debugfs_init(struct drm_minor *minor);
|
||||
void msm_rd_debugfs_cleanup(struct drm_minor *minor);
|
||||
void msm_rd_dump_submit(struct msm_gem_submit *submit);
|
||||
int msm_perf_debugfs_init(struct drm_minor *minor);
|
||||
void msm_perf_debugfs_cleanup(struct drm_minor *minor);
|
||||
#else
|
||||
static inline int msm_debugfs_late_init(struct drm_device *dev) { return 0; }
|
||||
static inline void msm_rd_dump_submit(struct msm_gem_submit *submit) {}
|
||||
#endif
|
||||
|
||||
void __iomem *msm_ioremap(struct platform_device *pdev, const char *name,
|
||||
const char *dbgname);
|
||||
void msm_writel(u32 data, void __iomem *addr);
|
||||
u32 msm_readl(const void __iomem *addr);
|
||||
|
||||
#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
|
||||
#define VERB(fmt, ...) if (0) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
|
||||
|
||||
static inline bool fence_completed(struct drm_device *dev, uint32_t fence)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
return priv->completed_fence >= fence;
|
||||
}
|
||||
|
||||
static inline int align_pitch(int width, int bpp)
|
||||
{
|
||||
int bytespp = (bpp + 7) / 8;
|
||||
/* adreno needs pitch aligned to 32 pixels: */
|
||||
return bytespp * ALIGN(width, 32);
|
||||
}
|
||||
|
||||
/* for the generated headers: */
|
||||
#define INVALID_IDX(idx) ({BUG(); 0;})
|
||||
#define fui(x) ({BUG(); 0;})
|
||||
#define util_float_to_half(x) ({BUG(); 0;})
|
||||
|
||||
|
||||
#define FIELD(val, name) (((val) & name ## __MASK) >> name ## __SHIFT)
|
||||
|
||||
/* for conditionally setting boolean flag(s): */
|
||||
#define COND(bool, val) ((bool) ? (val) : 0)
|
||||
|
||||
|
||||
#endif /* __MSM_DRV_H__ */
|
||||
203
drivers/gpu/drm/msm/msm_fb.c
Normal file
203
drivers/gpu/drm/msm/msm_fb.c
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_kms.h"
|
||||
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_crtc_helper.h"
|
||||
|
||||
struct msm_framebuffer {
|
||||
struct drm_framebuffer base;
|
||||
const struct msm_format *format;
|
||||
struct drm_gem_object *planes[2];
|
||||
};
|
||||
#define to_msm_framebuffer(x) container_of(x, struct msm_framebuffer, base)
|
||||
|
||||
|
||||
static int msm_framebuffer_create_handle(struct drm_framebuffer *fb,
|
||||
struct drm_file *file_priv,
|
||||
unsigned int *handle)
|
||||
{
|
||||
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
|
||||
return drm_gem_handle_create(file_priv,
|
||||
msm_fb->planes[0], handle);
|
||||
}
|
||||
|
||||
static void msm_framebuffer_destroy(struct drm_framebuffer *fb)
|
||||
{
|
||||
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
|
||||
int i, n = drm_format_num_planes(fb->pixel_format);
|
||||
|
||||
DBG("destroy: FB ID: %d (%p)", fb->base.id, fb);
|
||||
|
||||
drm_framebuffer_cleanup(fb);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
struct drm_gem_object *bo = msm_fb->planes[i];
|
||||
if (bo)
|
||||
drm_gem_object_unreference_unlocked(bo);
|
||||
}
|
||||
|
||||
kfree(msm_fb);
|
||||
}
|
||||
|
||||
static int msm_framebuffer_dirty(struct drm_framebuffer *fb,
|
||||
struct drm_file *file_priv, unsigned flags, unsigned color,
|
||||
struct drm_clip_rect *clips, unsigned num_clips)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_framebuffer_funcs msm_framebuffer_funcs = {
|
||||
.create_handle = msm_framebuffer_create_handle,
|
||||
.destroy = msm_framebuffer_destroy,
|
||||
.dirty = msm_framebuffer_dirty,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m)
|
||||
{
|
||||
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
|
||||
int i, n = drm_format_num_planes(fb->pixel_format);
|
||||
|
||||
seq_printf(m, "fb: %dx%d@%4.4s (%2d, ID:%d)\n",
|
||||
fb->width, fb->height, (char *)&fb->pixel_format,
|
||||
fb->refcount.refcount.counter, fb->base.id);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
seq_printf(m, " %d: offset=%d pitch=%d, obj: ",
|
||||
i, fb->offsets[i], fb->pitches[i]);
|
||||
msm_gem_describe(msm_fb->planes[i], m);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane)
|
||||
{
|
||||
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
|
||||
return msm_fb->planes[plane];
|
||||
}
|
||||
|
||||
const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb)
|
||||
{
|
||||
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
|
||||
return msm_fb->format;
|
||||
}
|
||||
|
||||
struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev,
|
||||
struct drm_file *file, struct drm_mode_fb_cmd2 *mode_cmd)
|
||||
{
|
||||
struct drm_gem_object *bos[4] = {0};
|
||||
struct drm_framebuffer *fb;
|
||||
int ret, i, n = drm_format_num_planes(mode_cmd->pixel_format);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
bos[i] = drm_gem_object_lookup(dev, file,
|
||||
mode_cmd->handles[i]);
|
||||
if (!bos[i]) {
|
||||
ret = -ENXIO;
|
||||
goto out_unref;
|
||||
}
|
||||
}
|
||||
|
||||
fb = msm_framebuffer_init(dev, mode_cmd, bos);
|
||||
if (IS_ERR(fb)) {
|
||||
ret = PTR_ERR(fb);
|
||||
goto out_unref;
|
||||
}
|
||||
|
||||
return fb;
|
||||
|
||||
out_unref:
|
||||
for (i = 0; i < n; i++)
|
||||
drm_gem_object_unreference_unlocked(bos[i]);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
struct drm_framebuffer *msm_framebuffer_init(struct drm_device *dev,
|
||||
struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_kms *kms = priv->kms;
|
||||
struct msm_framebuffer *msm_fb;
|
||||
struct drm_framebuffer *fb = NULL;
|
||||
const struct msm_format *format;
|
||||
int ret, i, n;
|
||||
unsigned int hsub, vsub;
|
||||
|
||||
DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%4.4s)",
|
||||
dev, mode_cmd, mode_cmd->width, mode_cmd->height,
|
||||
(char *)&mode_cmd->pixel_format);
|
||||
|
||||
n = drm_format_num_planes(mode_cmd->pixel_format);
|
||||
hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format);
|
||||
vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format);
|
||||
|
||||
format = kms->funcs->get_format(kms, mode_cmd->pixel_format);
|
||||
if (!format) {
|
||||
dev_err(dev->dev, "unsupported pixel format: %4.4s\n",
|
||||
(char *)&mode_cmd->pixel_format);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
msm_fb = kzalloc(sizeof(*msm_fb), GFP_KERNEL);
|
||||
if (!msm_fb) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fb = &msm_fb->base;
|
||||
|
||||
msm_fb->format = format;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
unsigned int width = mode_cmd->width / (i ? hsub : 1);
|
||||
unsigned int height = mode_cmd->height / (i ? vsub : 1);
|
||||
unsigned int min_size;
|
||||
|
||||
min_size = (height - 1) * mode_cmd->pitches[i]
|
||||
+ width * drm_format_plane_cpp(mode_cmd->pixel_format, i)
|
||||
+ mode_cmd->offsets[i];
|
||||
|
||||
if (bos[i]->size < min_size) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
msm_fb->planes[i] = bos[i];
|
||||
}
|
||||
|
||||
drm_helper_mode_fill_fb_struct(fb, mode_cmd);
|
||||
|
||||
ret = drm_framebuffer_init(dev, fb, &msm_framebuffer_funcs);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "framebuffer init failed: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
DBG("create: FB ID: %d (%p)", fb->base.id, fb);
|
||||
|
||||
return fb;
|
||||
|
||||
fail:
|
||||
if (fb)
|
||||
msm_framebuffer_destroy(fb);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
294
drivers/gpu/drm/msm/msm_fbdev.c
Normal file
294
drivers/gpu/drm/msm/msm_fbdev.c
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "msm_drv.h"
|
||||
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_fb_helper.h"
|
||||
#include "msm_gem.h"
|
||||
|
||||
extern int msm_gem_mmap_obj(struct drm_gem_object *obj,
|
||||
struct vm_area_struct *vma);
|
||||
static int msm_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma);
|
||||
|
||||
/*
|
||||
* fbdev funcs, to implement legacy fbdev interface on top of drm driver
|
||||
*/
|
||||
|
||||
#define to_msm_fbdev(x) container_of(x, struct msm_fbdev, base)
|
||||
|
||||
struct msm_fbdev {
|
||||
struct drm_fb_helper base;
|
||||
struct drm_framebuffer *fb;
|
||||
struct drm_gem_object *bo;
|
||||
};
|
||||
|
||||
static struct fb_ops msm_fb_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
|
||||
/* Note: to properly handle manual update displays, we wrap the
|
||||
* basic fbdev ops which write to the framebuffer
|
||||
*/
|
||||
.fb_read = fb_sys_read,
|
||||
.fb_write = fb_sys_write,
|
||||
.fb_fillrect = sys_fillrect,
|
||||
.fb_copyarea = sys_copyarea,
|
||||
.fb_imageblit = sys_imageblit,
|
||||
.fb_mmap = msm_fbdev_mmap,
|
||||
|
||||
.fb_check_var = drm_fb_helper_check_var,
|
||||
.fb_set_par = drm_fb_helper_set_par,
|
||||
.fb_pan_display = drm_fb_helper_pan_display,
|
||||
.fb_blank = drm_fb_helper_blank,
|
||||
.fb_setcmap = drm_fb_helper_setcmap,
|
||||
};
|
||||
|
||||
static int msm_fbdev_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
||||
{
|
||||
struct drm_fb_helper *helper = (struct drm_fb_helper *)info->par;
|
||||
struct msm_fbdev *fbdev = to_msm_fbdev(helper);
|
||||
struct drm_gem_object *drm_obj = fbdev->bo;
|
||||
struct drm_device *dev = helper->dev;
|
||||
int ret = 0;
|
||||
|
||||
if (drm_device_is_unplugged(dev))
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
|
||||
ret = drm_gem_mmap_obj(drm_obj, drm_obj->size, vma);
|
||||
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
if (ret) {
|
||||
pr_err("%s:drm_gem_mmap_obj fail\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return msm_gem_mmap_obj(drm_obj, vma);
|
||||
}
|
||||
|
||||
static int msm_fbdev_create(struct drm_fb_helper *helper,
|
||||
struct drm_fb_helper_surface_size *sizes)
|
||||
{
|
||||
struct msm_fbdev *fbdev = to_msm_fbdev(helper);
|
||||
struct drm_device *dev = helper->dev;
|
||||
struct drm_framebuffer *fb = NULL;
|
||||
struct fb_info *fbi = NULL;
|
||||
struct drm_mode_fb_cmd2 mode_cmd = {0};
|
||||
uint32_t paddr;
|
||||
int ret, size;
|
||||
|
||||
sizes->surface_bpp = 32;
|
||||
sizes->surface_depth = 24;
|
||||
|
||||
DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width,
|
||||
sizes->surface_height, sizes->surface_bpp,
|
||||
sizes->fb_width, sizes->fb_height);
|
||||
|
||||
mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
|
||||
sizes->surface_depth);
|
||||
|
||||
mode_cmd.width = sizes->surface_width;
|
||||
mode_cmd.height = sizes->surface_height;
|
||||
|
||||
mode_cmd.pitches[0] = align_pitch(
|
||||
mode_cmd.width, sizes->surface_bpp);
|
||||
|
||||
/* allocate backing bo */
|
||||
size = mode_cmd.pitches[0] * mode_cmd.height;
|
||||
DBG("allocating %d bytes for fb %d", size, dev->primary->index);
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
fbdev->bo = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
if (IS_ERR(fbdev->bo)) {
|
||||
ret = PTR_ERR(fbdev->bo);
|
||||
fbdev->bo = NULL;
|
||||
dev_err(dev->dev, "failed to allocate buffer object: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fb = msm_framebuffer_init(dev, &mode_cmd, &fbdev->bo);
|
||||
if (IS_ERR(fb)) {
|
||||
dev_err(dev->dev, "failed to allocate fb\n");
|
||||
/* note: if fb creation failed, we can't rely on fb destroy
|
||||
* to unref the bo:
|
||||
*/
|
||||
drm_gem_object_unreference(fbdev->bo);
|
||||
ret = PTR_ERR(fb);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
|
||||
/*
|
||||
* NOTE: if we can be guaranteed to be able to map buffer
|
||||
* in panic (ie. lock-safe, etc) we could avoid pinning the
|
||||
* buffer now:
|
||||
*/
|
||||
ret = msm_gem_get_iova_locked(fbdev->bo, 0, &paddr);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to get buffer obj iova: %d\n", ret);
|
||||
goto fail_unlock;
|
||||
}
|
||||
|
||||
fbi = framebuffer_alloc(0, dev->dev);
|
||||
if (!fbi) {
|
||||
dev_err(dev->dev, "failed to allocate fb info\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail_unlock;
|
||||
}
|
||||
|
||||
DBG("fbi=%p, dev=%p", fbi, dev);
|
||||
|
||||
fbdev->fb = fb;
|
||||
helper->fb = fb;
|
||||
helper->fbdev = fbi;
|
||||
|
||||
fbi->par = helper;
|
||||
fbi->flags = FBINFO_DEFAULT;
|
||||
fbi->fbops = &msm_fb_ops;
|
||||
|
||||
strcpy(fbi->fix.id, "msm");
|
||||
|
||||
ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
|
||||
if (ret) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_unlock;
|
||||
}
|
||||
|
||||
drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
|
||||
drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
|
||||
|
||||
dev->mode_config.fb_base = paddr;
|
||||
|
||||
fbi->screen_base = msm_gem_vaddr_locked(fbdev->bo);
|
||||
fbi->screen_size = fbdev->bo->size;
|
||||
fbi->fix.smem_start = paddr;
|
||||
fbi->fix.smem_len = fbdev->bo->size;
|
||||
|
||||
DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres);
|
||||
DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height);
|
||||
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_unlock:
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
fail:
|
||||
|
||||
if (ret) {
|
||||
if (fbi)
|
||||
framebuffer_release(fbi);
|
||||
if (fb) {
|
||||
drm_framebuffer_unregister_private(fb);
|
||||
drm_framebuffer_remove(fb);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void msm_crtc_fb_gamma_set(struct drm_crtc *crtc,
|
||||
u16 red, u16 green, u16 blue, int regno)
|
||||
{
|
||||
DBG("fbdev: set gamma");
|
||||
}
|
||||
|
||||
static void msm_crtc_fb_gamma_get(struct drm_crtc *crtc,
|
||||
u16 *red, u16 *green, u16 *blue, int regno)
|
||||
{
|
||||
DBG("fbdev: get gamma");
|
||||
}
|
||||
|
||||
static const struct drm_fb_helper_funcs msm_fb_helper_funcs = {
|
||||
.gamma_set = msm_crtc_fb_gamma_set,
|
||||
.gamma_get = msm_crtc_fb_gamma_get,
|
||||
.fb_probe = msm_fbdev_create,
|
||||
};
|
||||
|
||||
/* initialize fbdev helper */
|
||||
struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_fbdev *fbdev = NULL;
|
||||
struct drm_fb_helper *helper;
|
||||
int ret;
|
||||
|
||||
fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
|
||||
if (!fbdev)
|
||||
goto fail;
|
||||
|
||||
helper = &fbdev->base;
|
||||
|
||||
drm_fb_helper_prepare(dev, helper, &msm_fb_helper_funcs);
|
||||
|
||||
ret = drm_fb_helper_init(dev, helper,
|
||||
priv->num_crtcs, priv->num_connectors);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
drm_fb_helper_single_add_all_connectors(helper);
|
||||
|
||||
/* disable all the possible outputs/crtcs before entering KMS mode */
|
||||
drm_helper_disable_unused_functions(dev);
|
||||
|
||||
drm_fb_helper_initial_config(helper, 32);
|
||||
|
||||
priv->fbdev = helper;
|
||||
|
||||
return helper;
|
||||
|
||||
fail:
|
||||
kfree(fbdev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void msm_fbdev_free(struct drm_device *dev)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct drm_fb_helper *helper = priv->fbdev;
|
||||
struct msm_fbdev *fbdev;
|
||||
struct fb_info *fbi;
|
||||
|
||||
DBG();
|
||||
|
||||
fbi = helper->fbdev;
|
||||
|
||||
/* only cleanup framebuffer if it is present */
|
||||
if (fbi) {
|
||||
unregister_framebuffer(fbi);
|
||||
framebuffer_release(fbi);
|
||||
}
|
||||
|
||||
drm_fb_helper_fini(helper);
|
||||
|
||||
fbdev = to_msm_fbdev(priv->fbdev);
|
||||
|
||||
/* this will free the backing object */
|
||||
if (fbdev->fb) {
|
||||
drm_framebuffer_unregister_private(fbdev->fb);
|
||||
drm_framebuffer_remove(fbdev->fb);
|
||||
}
|
||||
|
||||
kfree(fbdev);
|
||||
|
||||
priv->fbdev = NULL;
|
||||
}
|
||||
701
drivers/gpu/drm/msm/msm_gem.c
Normal file
701
drivers/gpu/drm/msm/msm_gem.c
Normal file
|
|
@ -0,0 +1,701 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/shmem_fs.h>
|
||||
#include <linux/dma-buf.h>
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_gem.h"
|
||||
#include "msm_gpu.h"
|
||||
#include "msm_mmu.h"
|
||||
|
||||
static dma_addr_t physaddr(struct drm_gem_object *obj)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
struct msm_drm_private *priv = obj->dev->dev_private;
|
||||
return (((dma_addr_t)msm_obj->vram_node->start) << PAGE_SHIFT) +
|
||||
priv->vram.paddr;
|
||||
}
|
||||
|
||||
/* allocate pages from VRAM carveout, used when no IOMMU: */
|
||||
static struct page **get_pages_vram(struct drm_gem_object *obj,
|
||||
int npages)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
struct msm_drm_private *priv = obj->dev->dev_private;
|
||||
dma_addr_t paddr;
|
||||
struct page **p;
|
||||
int ret, i;
|
||||
|
||||
p = drm_malloc_ab(npages, sizeof(struct page *));
|
||||
if (!p)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = drm_mm_insert_node(&priv->vram.mm, msm_obj->vram_node,
|
||||
npages, 0, DRM_MM_SEARCH_DEFAULT);
|
||||
if (ret) {
|
||||
drm_free_large(p);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
paddr = physaddr(obj);
|
||||
for (i = 0; i < npages; i++) {
|
||||
p[i] = phys_to_page(paddr);
|
||||
paddr += PAGE_SIZE;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/* called with dev->struct_mutex held */
|
||||
static struct page **get_pages(struct drm_gem_object *obj)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
if (!msm_obj->pages) {
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct page **p;
|
||||
int npages = obj->size >> PAGE_SHIFT;
|
||||
|
||||
if (iommu_present(&platform_bus_type))
|
||||
p = drm_gem_get_pages(obj);
|
||||
else
|
||||
p = get_pages_vram(obj, npages);
|
||||
|
||||
if (IS_ERR(p)) {
|
||||
dev_err(dev->dev, "could not get pages: %ld\n",
|
||||
PTR_ERR(p));
|
||||
return p;
|
||||
}
|
||||
|
||||
msm_obj->sgt = drm_prime_pages_to_sg(p, npages);
|
||||
if (IS_ERR(msm_obj->sgt)) {
|
||||
dev_err(dev->dev, "failed to allocate sgt\n");
|
||||
return ERR_CAST(msm_obj->sgt);
|
||||
}
|
||||
|
||||
msm_obj->pages = p;
|
||||
|
||||
/* For non-cached buffers, ensure the new pages are clean
|
||||
* because display controller, GPU, etc. are not coherent:
|
||||
*/
|
||||
if (msm_obj->flags & (MSM_BO_WC|MSM_BO_UNCACHED))
|
||||
dma_map_sg(dev->dev, msm_obj->sgt->sgl,
|
||||
msm_obj->sgt->nents, DMA_BIDIRECTIONAL);
|
||||
}
|
||||
|
||||
return msm_obj->pages;
|
||||
}
|
||||
|
||||
static void put_pages(struct drm_gem_object *obj)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
if (msm_obj->pages) {
|
||||
/* For non-cached buffers, ensure the new pages are clean
|
||||
* because display controller, GPU, etc. are not coherent:
|
||||
*/
|
||||
if (msm_obj->flags & (MSM_BO_WC|MSM_BO_UNCACHED))
|
||||
dma_unmap_sg(obj->dev->dev, msm_obj->sgt->sgl,
|
||||
msm_obj->sgt->nents, DMA_BIDIRECTIONAL);
|
||||
sg_free_table(msm_obj->sgt);
|
||||
kfree(msm_obj->sgt);
|
||||
|
||||
if (iommu_present(&platform_bus_type))
|
||||
drm_gem_put_pages(obj, msm_obj->pages, true, false);
|
||||
else {
|
||||
drm_mm_remove_node(msm_obj->vram_node);
|
||||
drm_free_large(msm_obj->pages);
|
||||
}
|
||||
|
||||
msm_obj->pages = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
struct page **msm_gem_get_pages(struct drm_gem_object *obj)
|
||||
{
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct page **p;
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
p = get_pages(obj);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
return p;
|
||||
}
|
||||
|
||||
void msm_gem_put_pages(struct drm_gem_object *obj)
|
||||
{
|
||||
/* when we start tracking the pin count, then do something here */
|
||||
}
|
||||
|
||||
int msm_gem_mmap_obj(struct drm_gem_object *obj,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
vma->vm_flags &= ~VM_PFNMAP;
|
||||
vma->vm_flags |= VM_MIXEDMAP;
|
||||
|
||||
if (msm_obj->flags & MSM_BO_WC) {
|
||||
vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
|
||||
} else if (msm_obj->flags & MSM_BO_UNCACHED) {
|
||||
vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags));
|
||||
} else {
|
||||
/*
|
||||
* Shunt off cached objs to shmem file so they have their own
|
||||
* address_space (so unmap_mapping_range does what we want,
|
||||
* in particular in the case of mmap'd dmabufs)
|
||||
*/
|
||||
fput(vma->vm_file);
|
||||
get_file(obj->filp);
|
||||
vma->vm_pgoff = 0;
|
||||
vma->vm_file = obj->filp;
|
||||
|
||||
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = drm_gem_mmap(filp, vma);
|
||||
if (ret) {
|
||||
DBG("mmap failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return msm_gem_mmap_obj(vma->vm_private_data, vma);
|
||||
}
|
||||
|
||||
int msm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
|
||||
{
|
||||
struct drm_gem_object *obj = vma->vm_private_data;
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct page **pages;
|
||||
unsigned long pfn;
|
||||
pgoff_t pgoff;
|
||||
int ret;
|
||||
|
||||
/* Make sure we don't parallel update on a fault, nor move or remove
|
||||
* something from beneath our feet
|
||||
*/
|
||||
ret = mutex_lock_interruptible(&dev->struct_mutex);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* make sure we have pages attached now */
|
||||
pages = get_pages(obj);
|
||||
if (IS_ERR(pages)) {
|
||||
ret = PTR_ERR(pages);
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
/* We don't use vmf->pgoff since that has the fake offset: */
|
||||
pgoff = ((unsigned long)vmf->virtual_address -
|
||||
vma->vm_start) >> PAGE_SHIFT;
|
||||
|
||||
pfn = page_to_pfn(pages[pgoff]);
|
||||
|
||||
VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address,
|
||||
pfn, pfn << PAGE_SHIFT);
|
||||
|
||||
ret = vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
out:
|
||||
switch (ret) {
|
||||
case -EAGAIN:
|
||||
case 0:
|
||||
case -ERESTARTSYS:
|
||||
case -EINTR:
|
||||
case -EBUSY:
|
||||
/*
|
||||
* EBUSY is ok: this just means that another thread
|
||||
* already did the job.
|
||||
*/
|
||||
return VM_FAULT_NOPAGE;
|
||||
case -ENOMEM:
|
||||
return VM_FAULT_OOM;
|
||||
default:
|
||||
return VM_FAULT_SIGBUS;
|
||||
}
|
||||
}
|
||||
|
||||
/** get mmap offset */
|
||||
static uint64_t mmap_offset(struct drm_gem_object *obj)
|
||||
{
|
||||
struct drm_device *dev = obj->dev;
|
||||
int ret;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->struct_mutex));
|
||||
|
||||
/* Make it mmapable */
|
||||
ret = drm_gem_create_mmap_offset(obj);
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "could not allocate mmap offset\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return drm_vma_node_offset_addr(&obj->vma_node);
|
||||
}
|
||||
|
||||
uint64_t msm_gem_mmap_offset(struct drm_gem_object *obj)
|
||||
{
|
||||
uint64_t offset;
|
||||
mutex_lock(&obj->dev->struct_mutex);
|
||||
offset = mmap_offset(obj);
|
||||
mutex_unlock(&obj->dev->struct_mutex);
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* should be called under struct_mutex.. although it can be called
|
||||
* from atomic context without struct_mutex to acquire an extra
|
||||
* iova ref if you know one is already held.
|
||||
*
|
||||
* That means when I do eventually need to add support for unpinning
|
||||
* the refcnt counter needs to be atomic_t.
|
||||
*/
|
||||
int msm_gem_get_iova_locked(struct drm_gem_object *obj, int id,
|
||||
uint32_t *iova)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
int ret = 0;
|
||||
|
||||
if (!msm_obj->domain[id].iova) {
|
||||
struct msm_drm_private *priv = obj->dev->dev_private;
|
||||
struct page **pages = get_pages(obj);
|
||||
|
||||
if (IS_ERR(pages))
|
||||
return PTR_ERR(pages);
|
||||
|
||||
if (iommu_present(&platform_bus_type)) {
|
||||
struct msm_mmu *mmu = priv->mmus[id];
|
||||
uint32_t offset;
|
||||
|
||||
if (WARN_ON(!mmu))
|
||||
return -EINVAL;
|
||||
|
||||
offset = (uint32_t)mmap_offset(obj);
|
||||
ret = mmu->funcs->map(mmu, offset, msm_obj->sgt,
|
||||
obj->size, IOMMU_READ | IOMMU_WRITE);
|
||||
msm_obj->domain[id].iova = offset;
|
||||
} else {
|
||||
msm_obj->domain[id].iova = physaddr(obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
*iova = msm_obj->domain[id].iova;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int msm_gem_get_iova(struct drm_gem_object *obj, int id, uint32_t *iova)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
int ret;
|
||||
|
||||
/* this is safe right now because we don't unmap until the
|
||||
* bo is deleted:
|
||||
*/
|
||||
if (msm_obj->domain[id].iova) {
|
||||
*iova = msm_obj->domain[id].iova;
|
||||
return 0;
|
||||
}
|
||||
|
||||
mutex_lock(&obj->dev->struct_mutex);
|
||||
ret = msm_gem_get_iova_locked(obj, id, iova);
|
||||
mutex_unlock(&obj->dev->struct_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void msm_gem_put_iova(struct drm_gem_object *obj, int id)
|
||||
{
|
||||
// XXX TODO ..
|
||||
// NOTE: probably don't need a _locked() version.. we wouldn't
|
||||
// normally unmap here, but instead just mark that it could be
|
||||
// unmapped (if the iova refcnt drops to zero), but then later
|
||||
// if another _get_iova_locked() fails we can start unmapping
|
||||
// things that are no longer needed..
|
||||
}
|
||||
|
||||
int msm_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
|
||||
struct drm_mode_create_dumb *args)
|
||||
{
|
||||
args->pitch = align_pitch(args->width, args->bpp);
|
||||
args->size = PAGE_ALIGN(args->pitch * args->height);
|
||||
return msm_gem_new_handle(dev, file, args->size,
|
||||
MSM_BO_SCANOUT | MSM_BO_WC, &args->handle);
|
||||
}
|
||||
|
||||
int msm_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
|
||||
uint32_t handle, uint64_t *offset)
|
||||
{
|
||||
struct drm_gem_object *obj;
|
||||
int ret = 0;
|
||||
|
||||
/* GEM does all our handle to object mapping */
|
||||
obj = drm_gem_object_lookup(dev, file, handle);
|
||||
if (obj == NULL) {
|
||||
ret = -ENOENT;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
*offset = msm_gem_mmap_offset(obj);
|
||||
|
||||
drm_gem_object_unreference_unlocked(obj);
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *msm_gem_vaddr_locked(struct drm_gem_object *obj)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
WARN_ON(!mutex_is_locked(&obj->dev->struct_mutex));
|
||||
if (!msm_obj->vaddr) {
|
||||
struct page **pages = get_pages(obj);
|
||||
if (IS_ERR(pages))
|
||||
return ERR_CAST(pages);
|
||||
msm_obj->vaddr = vmap(pages, obj->size >> PAGE_SHIFT,
|
||||
VM_MAP, pgprot_writecombine(PAGE_KERNEL));
|
||||
}
|
||||
return msm_obj->vaddr;
|
||||
}
|
||||
|
||||
void *msm_gem_vaddr(struct drm_gem_object *obj)
|
||||
{
|
||||
void *ret;
|
||||
mutex_lock(&obj->dev->struct_mutex);
|
||||
ret = msm_gem_vaddr_locked(obj);
|
||||
mutex_unlock(&obj->dev->struct_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* setup callback for when bo is no longer busy..
|
||||
* TODO probably want to differentiate read vs write..
|
||||
*/
|
||||
int msm_gem_queue_inactive_cb(struct drm_gem_object *obj,
|
||||
struct msm_fence_cb *cb)
|
||||
{
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
if (!list_empty(&cb->work.entry)) {
|
||||
ret = -EINVAL;
|
||||
} else if (is_active(msm_obj)) {
|
||||
cb->fence = max(msm_obj->read_fence, msm_obj->write_fence);
|
||||
list_add_tail(&cb->work.entry, &priv->fence_cbs);
|
||||
} else {
|
||||
queue_work(priv->wq, &cb->work);
|
||||
}
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void msm_gem_move_to_active(struct drm_gem_object *obj,
|
||||
struct msm_gpu *gpu, bool write, uint32_t fence)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
msm_obj->gpu = gpu;
|
||||
if (write)
|
||||
msm_obj->write_fence = fence;
|
||||
else
|
||||
msm_obj->read_fence = fence;
|
||||
list_del_init(&msm_obj->mm_list);
|
||||
list_add_tail(&msm_obj->mm_list, &gpu->active_list);
|
||||
}
|
||||
|
||||
void msm_gem_move_to_inactive(struct drm_gem_object *obj)
|
||||
{
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->struct_mutex));
|
||||
|
||||
msm_obj->gpu = NULL;
|
||||
msm_obj->read_fence = 0;
|
||||
msm_obj->write_fence = 0;
|
||||
list_del_init(&msm_obj->mm_list);
|
||||
list_add_tail(&msm_obj->mm_list, &priv->inactive_list);
|
||||
}
|
||||
|
||||
int msm_gem_cpu_prep(struct drm_gem_object *obj, uint32_t op,
|
||||
struct timespec *timeout)
|
||||
{
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
int ret = 0;
|
||||
|
||||
if (is_active(msm_obj)) {
|
||||
uint32_t fence = 0;
|
||||
|
||||
if (op & MSM_PREP_READ)
|
||||
fence = msm_obj->write_fence;
|
||||
if (op & MSM_PREP_WRITE)
|
||||
fence = max(fence, msm_obj->read_fence);
|
||||
if (op & MSM_PREP_NOSYNC)
|
||||
timeout = NULL;
|
||||
|
||||
ret = msm_wait_fence_interruptable(dev, fence, timeout);
|
||||
}
|
||||
|
||||
/* TODO cache maintenance */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int msm_gem_cpu_fini(struct drm_gem_object *obj)
|
||||
{
|
||||
/* TODO cache maintenance */
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m)
|
||||
{
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
uint64_t off = drm_vma_node_start(&obj->vma_node);
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->struct_mutex));
|
||||
seq_printf(m, "%08x: %c(r=%u,w=%u) %2d (%2d) %08llx %p %d\n",
|
||||
msm_obj->flags, is_active(msm_obj) ? 'A' : 'I',
|
||||
msm_obj->read_fence, msm_obj->write_fence,
|
||||
obj->name, obj->refcount.refcount.counter,
|
||||
off, msm_obj->vaddr, obj->size);
|
||||
}
|
||||
|
||||
void msm_gem_describe_objects(struct list_head *list, struct seq_file *m)
|
||||
{
|
||||
struct msm_gem_object *msm_obj;
|
||||
int count = 0;
|
||||
size_t size = 0;
|
||||
|
||||
list_for_each_entry(msm_obj, list, mm_list) {
|
||||
struct drm_gem_object *obj = &msm_obj->base;
|
||||
seq_printf(m, " ");
|
||||
msm_gem_describe(obj, m);
|
||||
count++;
|
||||
size += obj->size;
|
||||
}
|
||||
|
||||
seq_printf(m, "Total %d objects, %zu bytes\n", count, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
void msm_gem_free_object(struct drm_gem_object *obj)
|
||||
{
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct msm_drm_private *priv = obj->dev->dev_private;
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
int id;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->struct_mutex));
|
||||
|
||||
/* object should not be on active list: */
|
||||
WARN_ON(is_active(msm_obj));
|
||||
|
||||
list_del(&msm_obj->mm_list);
|
||||
|
||||
for (id = 0; id < ARRAY_SIZE(msm_obj->domain); id++) {
|
||||
struct msm_mmu *mmu = priv->mmus[id];
|
||||
if (mmu && msm_obj->domain[id].iova) {
|
||||
uint32_t offset = (uint32_t)mmap_offset(obj);
|
||||
mmu->funcs->unmap(mmu, offset, msm_obj->sgt, obj->size);
|
||||
}
|
||||
}
|
||||
|
||||
drm_gem_free_mmap_offset(obj);
|
||||
|
||||
if (obj->import_attach) {
|
||||
if (msm_obj->vaddr)
|
||||
dma_buf_vunmap(obj->import_attach->dmabuf, msm_obj->vaddr);
|
||||
|
||||
/* Don't drop the pages for imported dmabuf, as they are not
|
||||
* ours, just free the array we allocated:
|
||||
*/
|
||||
if (msm_obj->pages)
|
||||
drm_free_large(msm_obj->pages);
|
||||
|
||||
} else {
|
||||
if (msm_obj->vaddr)
|
||||
vunmap(msm_obj->vaddr);
|
||||
put_pages(obj);
|
||||
}
|
||||
|
||||
if (msm_obj->resv == &msm_obj->_resv)
|
||||
reservation_object_fini(msm_obj->resv);
|
||||
|
||||
drm_gem_object_release(obj);
|
||||
|
||||
kfree(msm_obj);
|
||||
}
|
||||
|
||||
/* convenience method to construct a GEM buffer object, and userspace handle */
|
||||
int msm_gem_new_handle(struct drm_device *dev, struct drm_file *file,
|
||||
uint32_t size, uint32_t flags, uint32_t *handle)
|
||||
{
|
||||
struct drm_gem_object *obj;
|
||||
int ret;
|
||||
|
||||
ret = mutex_lock_interruptible(&dev->struct_mutex);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
obj = msm_gem_new(dev, size, flags);
|
||||
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
if (IS_ERR(obj))
|
||||
return PTR_ERR(obj);
|
||||
|
||||
ret = drm_gem_handle_create(file, obj, handle);
|
||||
|
||||
/* drop reference from allocate - handle holds it now */
|
||||
drm_gem_object_unreference_unlocked(obj);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int msm_gem_new_impl(struct drm_device *dev,
|
||||
uint32_t size, uint32_t flags,
|
||||
struct drm_gem_object **obj)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_gem_object *msm_obj;
|
||||
unsigned sz;
|
||||
|
||||
switch (flags & MSM_BO_CACHE_MASK) {
|
||||
case MSM_BO_UNCACHED:
|
||||
case MSM_BO_CACHED:
|
||||
case MSM_BO_WC:
|
||||
break;
|
||||
default:
|
||||
dev_err(dev->dev, "invalid cache flag: %x\n",
|
||||
(flags & MSM_BO_CACHE_MASK));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sz = sizeof(*msm_obj);
|
||||
if (!iommu_present(&platform_bus_type))
|
||||
sz += sizeof(struct drm_mm_node);
|
||||
|
||||
msm_obj = kzalloc(sz, GFP_KERNEL);
|
||||
if (!msm_obj)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!iommu_present(&platform_bus_type))
|
||||
msm_obj->vram_node = (void *)&msm_obj[1];
|
||||
|
||||
msm_obj->flags = flags;
|
||||
|
||||
msm_obj->resv = &msm_obj->_resv;
|
||||
reservation_object_init(msm_obj->resv);
|
||||
|
||||
INIT_LIST_HEAD(&msm_obj->submit_entry);
|
||||
list_add_tail(&msm_obj->mm_list, &priv->inactive_list);
|
||||
|
||||
*obj = &msm_obj->base;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct drm_gem_object *msm_gem_new(struct drm_device *dev,
|
||||
uint32_t size, uint32_t flags)
|
||||
{
|
||||
struct drm_gem_object *obj = NULL;
|
||||
int ret;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->struct_mutex));
|
||||
|
||||
size = PAGE_ALIGN(size);
|
||||
|
||||
ret = msm_gem_new_impl(dev, size, flags, &obj);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
if (iommu_present(&platform_bus_type)) {
|
||||
ret = drm_gem_object_init(dev, obj, size);
|
||||
if (ret)
|
||||
goto fail;
|
||||
} else {
|
||||
drm_gem_private_object_init(dev, obj, size);
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
fail:
|
||||
if (obj)
|
||||
drm_gem_object_unreference(obj);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
struct drm_gem_object *msm_gem_import(struct drm_device *dev,
|
||||
uint32_t size, struct sg_table *sgt)
|
||||
{
|
||||
struct msm_gem_object *msm_obj;
|
||||
struct drm_gem_object *obj;
|
||||
int ret, npages;
|
||||
|
||||
/* if we don't have IOMMU, don't bother pretending we can import: */
|
||||
if (!iommu_present(&platform_bus_type)) {
|
||||
dev_err(dev->dev, "cannot import without IOMMU\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
size = PAGE_ALIGN(size);
|
||||
|
||||
ret = msm_gem_new_impl(dev, size, MSM_BO_WC, &obj);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
drm_gem_private_object_init(dev, obj, size);
|
||||
|
||||
npages = size / PAGE_SIZE;
|
||||
|
||||
msm_obj = to_msm_bo(obj);
|
||||
msm_obj->sgt = sgt;
|
||||
msm_obj->pages = drm_malloc_ab(npages, sizeof(struct page *));
|
||||
if (!msm_obj->pages) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = drm_prime_sg_to_page_addr_arrays(sgt, msm_obj->pages, NULL, npages);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
return obj;
|
||||
|
||||
fail:
|
||||
if (obj)
|
||||
drm_gem_object_unreference_unlocked(obj);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
102
drivers/gpu/drm/msm/msm_gem.h
Normal file
102
drivers/gpu/drm/msm/msm_gem.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MSM_GEM_H__
|
||||
#define __MSM_GEM_H__
|
||||
|
||||
#include <linux/reservation.h>
|
||||
#include "msm_drv.h"
|
||||
|
||||
struct msm_gem_object {
|
||||
struct drm_gem_object base;
|
||||
|
||||
uint32_t flags;
|
||||
|
||||
/* And object is either:
|
||||
* inactive - on priv->inactive_list
|
||||
* active - on one one of the gpu's active_list.. well, at
|
||||
* least for now we don't have (I don't think) hw sync between
|
||||
* 2d and 3d one devices which have both, meaning we need to
|
||||
* block on submit if a bo is already on other ring
|
||||
*
|
||||
*/
|
||||
struct list_head mm_list;
|
||||
struct msm_gpu *gpu; /* non-null if active */
|
||||
uint32_t read_fence, write_fence;
|
||||
|
||||
/* Transiently in the process of submit ioctl, objects associated
|
||||
* with the submit are on submit->bo_list.. this only lasts for
|
||||
* the duration of the ioctl, so one bo can never be on multiple
|
||||
* submit lists.
|
||||
*/
|
||||
struct list_head submit_entry;
|
||||
|
||||
struct page **pages;
|
||||
struct sg_table *sgt;
|
||||
void *vaddr;
|
||||
|
||||
struct {
|
||||
// XXX
|
||||
uint32_t iova;
|
||||
} domain[NUM_DOMAINS];
|
||||
|
||||
/* normally (resv == &_resv) except for imported bo's */
|
||||
struct reservation_object *resv;
|
||||
struct reservation_object _resv;
|
||||
|
||||
/* For physically contiguous buffers. Used when we don't have
|
||||
* an IOMMU.
|
||||
*/
|
||||
struct drm_mm_node *vram_node;
|
||||
};
|
||||
#define to_msm_bo(x) container_of(x, struct msm_gem_object, base)
|
||||
|
||||
static inline bool is_active(struct msm_gem_object *msm_obj)
|
||||
{
|
||||
return msm_obj->gpu != NULL;
|
||||
}
|
||||
|
||||
#define MAX_CMDS 4
|
||||
|
||||
/* Created per submit-ioctl, to track bo's and cmdstream bufs, etc,
|
||||
* associated with the cmdstream submission for synchronization (and
|
||||
* make it easier to unwind when things go wrong, etc). This only
|
||||
* lasts for the duration of the submit-ioctl.
|
||||
*/
|
||||
struct msm_gem_submit {
|
||||
struct drm_device *dev;
|
||||
struct msm_gpu *gpu;
|
||||
struct list_head bo_list;
|
||||
struct ww_acquire_ctx ticket;
|
||||
uint32_t fence;
|
||||
bool valid;
|
||||
unsigned int nr_cmds;
|
||||
unsigned int nr_bos;
|
||||
struct {
|
||||
uint32_t type;
|
||||
uint32_t size; /* in dwords */
|
||||
uint32_t iova;
|
||||
uint32_t idx; /* cmdstream buffer idx in bos[] */
|
||||
} cmd[MAX_CMDS];
|
||||
struct {
|
||||
uint32_t flags;
|
||||
struct msm_gem_object *obj;
|
||||
uint32_t iova;
|
||||
} bos[0];
|
||||
};
|
||||
|
||||
#endif /* __MSM_GEM_H__ */
|
||||
57
drivers/gpu/drm/msm/msm_gem_prime.c
Normal file
57
drivers/gpu/drm/msm/msm_gem_prime.c
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_gem.h"
|
||||
|
||||
#include <linux/dma-buf.h>
|
||||
|
||||
struct sg_table *msm_gem_prime_get_sg_table(struct drm_gem_object *obj)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = to_msm_bo(obj);
|
||||
BUG_ON(!msm_obj->sgt); /* should have already pinned! */
|
||||
return msm_obj->sgt;
|
||||
}
|
||||
|
||||
void *msm_gem_prime_vmap(struct drm_gem_object *obj)
|
||||
{
|
||||
return msm_gem_vaddr(obj);
|
||||
}
|
||||
|
||||
void msm_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr)
|
||||
{
|
||||
/* TODO msm_gem_vunmap() */
|
||||
}
|
||||
|
||||
struct drm_gem_object *msm_gem_prime_import_sg_table(struct drm_device *dev,
|
||||
struct dma_buf_attachment *attach, struct sg_table *sg)
|
||||
{
|
||||
return msm_gem_import(dev, attach->dmabuf->size, sg);
|
||||
}
|
||||
|
||||
int msm_gem_prime_pin(struct drm_gem_object *obj)
|
||||
{
|
||||
if (!obj->import_attach)
|
||||
msm_gem_get_pages(obj);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msm_gem_prime_unpin(struct drm_gem_object *obj)
|
||||
{
|
||||
if (!obj->import_attach)
|
||||
msm_gem_put_pages(obj);
|
||||
}
|
||||
427
drivers/gpu/drm/msm/msm_gem_submit.c
Normal file
427
drivers/gpu/drm/msm/msm_gem_submit.c
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_gpu.h"
|
||||
#include "msm_gem.h"
|
||||
|
||||
/*
|
||||
* Cmdstream submission:
|
||||
*/
|
||||
|
||||
/* make sure these don't conflict w/ MSM_SUBMIT_BO_x */
|
||||
#define BO_VALID 0x8000
|
||||
#define BO_LOCKED 0x4000
|
||||
#define BO_PINNED 0x2000
|
||||
|
||||
static inline void __user *to_user_ptr(u64 address)
|
||||
{
|
||||
return (void __user *)(uintptr_t)address;
|
||||
}
|
||||
|
||||
static struct msm_gem_submit *submit_create(struct drm_device *dev,
|
||||
struct msm_gpu *gpu, int nr)
|
||||
{
|
||||
struct msm_gem_submit *submit;
|
||||
int sz = sizeof(*submit) + (nr * sizeof(submit->bos[0]));
|
||||
|
||||
submit = kmalloc(sz, GFP_TEMPORARY | __GFP_NOWARN | __GFP_NORETRY);
|
||||
if (submit) {
|
||||
submit->dev = dev;
|
||||
submit->gpu = gpu;
|
||||
|
||||
/* initially, until copy_from_user() and bo lookup succeeds: */
|
||||
submit->nr_bos = 0;
|
||||
submit->nr_cmds = 0;
|
||||
|
||||
INIT_LIST_HEAD(&submit->bo_list);
|
||||
ww_acquire_init(&submit->ticket, &reservation_ww_class);
|
||||
}
|
||||
|
||||
return submit;
|
||||
}
|
||||
|
||||
static int submit_lookup_objects(struct msm_gem_submit *submit,
|
||||
struct drm_msm_gem_submit *args, struct drm_file *file)
|
||||
{
|
||||
unsigned i;
|
||||
int ret = 0;
|
||||
|
||||
spin_lock(&file->table_lock);
|
||||
|
||||
for (i = 0; i < args->nr_bos; i++) {
|
||||
struct drm_msm_gem_submit_bo submit_bo;
|
||||
struct drm_gem_object *obj;
|
||||
struct msm_gem_object *msm_obj;
|
||||
void __user *userptr =
|
||||
to_user_ptr(args->bos + (i * sizeof(submit_bo)));
|
||||
|
||||
ret = copy_from_user(&submit_bo, userptr, sizeof(submit_bo));
|
||||
if (ret) {
|
||||
ret = -EFAULT;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (submit_bo.flags & ~MSM_SUBMIT_BO_FLAGS) {
|
||||
DRM_ERROR("invalid flags: %x\n", submit_bo.flags);
|
||||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
submit->bos[i].flags = submit_bo.flags;
|
||||
/* in validate_objects() we figure out if this is true: */
|
||||
submit->bos[i].iova = submit_bo.presumed;
|
||||
|
||||
/* normally use drm_gem_object_lookup(), but for bulk lookup
|
||||
* all under single table_lock just hit object_idr directly:
|
||||
*/
|
||||
obj = idr_find(&file->object_idr, submit_bo.handle);
|
||||
if (!obj) {
|
||||
DRM_ERROR("invalid handle %u at index %u\n", submit_bo.handle, i);
|
||||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
msm_obj = to_msm_bo(obj);
|
||||
|
||||
if (!list_empty(&msm_obj->submit_entry)) {
|
||||
DRM_ERROR("handle %u at index %u already on submit list\n",
|
||||
submit_bo.handle, i);
|
||||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
drm_gem_object_reference(obj);
|
||||
|
||||
submit->bos[i].obj = msm_obj;
|
||||
|
||||
list_add_tail(&msm_obj->submit_entry, &submit->bo_list);
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
submit->nr_bos = i;
|
||||
spin_unlock(&file->table_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void submit_unlock_unpin_bo(struct msm_gem_submit *submit, int i)
|
||||
{
|
||||
struct msm_gem_object *msm_obj = submit->bos[i].obj;
|
||||
|
||||
if (submit->bos[i].flags & BO_PINNED)
|
||||
msm_gem_put_iova(&msm_obj->base, submit->gpu->id);
|
||||
|
||||
if (submit->bos[i].flags & BO_LOCKED)
|
||||
ww_mutex_unlock(&msm_obj->resv->lock);
|
||||
|
||||
if (!(submit->bos[i].flags & BO_VALID))
|
||||
submit->bos[i].iova = 0;
|
||||
|
||||
submit->bos[i].flags &= ~(BO_LOCKED | BO_PINNED);
|
||||
}
|
||||
|
||||
/* This is where we make sure all the bo's are reserved and pin'd: */
|
||||
static int submit_validate_objects(struct msm_gem_submit *submit)
|
||||
{
|
||||
int contended, slow_locked = -1, i, ret = 0;
|
||||
|
||||
retry:
|
||||
submit->valid = true;
|
||||
|
||||
for (i = 0; i < submit->nr_bos; i++) {
|
||||
struct msm_gem_object *msm_obj = submit->bos[i].obj;
|
||||
uint32_t iova;
|
||||
|
||||
if (slow_locked == i)
|
||||
slow_locked = -1;
|
||||
|
||||
contended = i;
|
||||
|
||||
if (!(submit->bos[i].flags & BO_LOCKED)) {
|
||||
ret = ww_mutex_lock_interruptible(&msm_obj->resv->lock,
|
||||
&submit->ticket);
|
||||
if (ret)
|
||||
goto fail;
|
||||
submit->bos[i].flags |= BO_LOCKED;
|
||||
}
|
||||
|
||||
|
||||
/* if locking succeeded, pin bo: */
|
||||
ret = msm_gem_get_iova_locked(&msm_obj->base,
|
||||
submit->gpu->id, &iova);
|
||||
|
||||
/* this would break the logic in the fail path.. there is no
|
||||
* reason for this to happen, but just to be on the safe side
|
||||
* let's notice if this starts happening in the future:
|
||||
*/
|
||||
WARN_ON(ret == -EDEADLK);
|
||||
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
submit->bos[i].flags |= BO_PINNED;
|
||||
|
||||
if (iova == submit->bos[i].iova) {
|
||||
submit->bos[i].flags |= BO_VALID;
|
||||
} else {
|
||||
submit->bos[i].iova = iova;
|
||||
submit->bos[i].flags &= ~BO_VALID;
|
||||
submit->valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
ww_acquire_done(&submit->ticket);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
for (; i >= 0; i--)
|
||||
submit_unlock_unpin_bo(submit, i);
|
||||
|
||||
if (slow_locked > 0)
|
||||
submit_unlock_unpin_bo(submit, slow_locked);
|
||||
|
||||
if (ret == -EDEADLK) {
|
||||
struct msm_gem_object *msm_obj = submit->bos[contended].obj;
|
||||
/* we lost out in a seqno race, lock and retry.. */
|
||||
ret = ww_mutex_lock_slow_interruptible(&msm_obj->resv->lock,
|
||||
&submit->ticket);
|
||||
if (!ret) {
|
||||
submit->bos[contended].flags |= BO_LOCKED;
|
||||
slow_locked = contended;
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int submit_bo(struct msm_gem_submit *submit, uint32_t idx,
|
||||
struct msm_gem_object **obj, uint32_t *iova, bool *valid)
|
||||
{
|
||||
if (idx >= submit->nr_bos) {
|
||||
DRM_ERROR("invalid buffer index: %u (out of %u)\n",
|
||||
idx, submit->nr_bos);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (obj)
|
||||
*obj = submit->bos[idx].obj;
|
||||
if (iova)
|
||||
*iova = submit->bos[idx].iova;
|
||||
if (valid)
|
||||
*valid = !!(submit->bos[idx].flags & BO_VALID);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* process the reloc's and patch up the cmdstream as needed: */
|
||||
static int submit_reloc(struct msm_gem_submit *submit, struct msm_gem_object *obj,
|
||||
uint32_t offset, uint32_t nr_relocs, uint64_t relocs)
|
||||
{
|
||||
uint32_t i, last_offset = 0;
|
||||
uint32_t *ptr;
|
||||
int ret;
|
||||
|
||||
if (offset % 4) {
|
||||
DRM_ERROR("non-aligned cmdstream buffer: %u\n", offset);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* For now, just map the entire thing. Eventually we probably
|
||||
* to do it page-by-page, w/ kmap() if not vmap()d..
|
||||
*/
|
||||
ptr = msm_gem_vaddr_locked(&obj->base);
|
||||
|
||||
if (IS_ERR(ptr)) {
|
||||
ret = PTR_ERR(ptr);
|
||||
DBG("failed to map: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_relocs; i++) {
|
||||
struct drm_msm_gem_submit_reloc submit_reloc;
|
||||
void __user *userptr =
|
||||
to_user_ptr(relocs + (i * sizeof(submit_reloc)));
|
||||
uint32_t iova, off;
|
||||
bool valid;
|
||||
|
||||
ret = copy_from_user(&submit_reloc, userptr, sizeof(submit_reloc));
|
||||
if (ret)
|
||||
return -EFAULT;
|
||||
|
||||
if (submit_reloc.submit_offset % 4) {
|
||||
DRM_ERROR("non-aligned reloc offset: %u\n",
|
||||
submit_reloc.submit_offset);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* offset in dwords: */
|
||||
off = submit_reloc.submit_offset / 4;
|
||||
|
||||
if ((off >= (obj->base.size / 4)) ||
|
||||
(off < last_offset)) {
|
||||
DRM_ERROR("invalid offset %u at reloc %u\n", off, i);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = submit_bo(submit, submit_reloc.reloc_idx, NULL, &iova, &valid);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (valid)
|
||||
continue;
|
||||
|
||||
iova += submit_reloc.reloc_offset;
|
||||
|
||||
if (submit_reloc.shift < 0)
|
||||
iova >>= -submit_reloc.shift;
|
||||
else
|
||||
iova <<= submit_reloc.shift;
|
||||
|
||||
ptr[off] = iova | submit_reloc.or;
|
||||
|
||||
last_offset = off;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void submit_cleanup(struct msm_gem_submit *submit, bool fail)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < submit->nr_bos; i++) {
|
||||
struct msm_gem_object *msm_obj = submit->bos[i].obj;
|
||||
submit_unlock_unpin_bo(submit, i);
|
||||
list_del_init(&msm_obj->submit_entry);
|
||||
drm_gem_object_unreference(&msm_obj->base);
|
||||
}
|
||||
|
||||
ww_acquire_fini(&submit->ticket);
|
||||
kfree(submit);
|
||||
}
|
||||
|
||||
int msm_ioctl_gem_submit(struct drm_device *dev, void *data,
|
||||
struct drm_file *file)
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct drm_msm_gem_submit *args = data;
|
||||
struct msm_file_private *ctx = file->driver_priv;
|
||||
struct msm_gem_submit *submit;
|
||||
struct msm_gpu *gpu;
|
||||
unsigned i;
|
||||
int ret;
|
||||
|
||||
/* for now, we just have 3d pipe.. eventually this would need to
|
||||
* be more clever to dispatch to appropriate gpu module:
|
||||
*/
|
||||
if (args->pipe != MSM_PIPE_3D0)
|
||||
return -EINVAL;
|
||||
|
||||
gpu = priv->gpu;
|
||||
|
||||
if (args->nr_cmds > MAX_CMDS)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
|
||||
submit = submit_create(dev, gpu, args->nr_bos);
|
||||
if (!submit) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = submit_lookup_objects(submit, args, file);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = submit_validate_objects(submit);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < args->nr_cmds; i++) {
|
||||
struct drm_msm_gem_submit_cmd submit_cmd;
|
||||
void __user *userptr =
|
||||
to_user_ptr(args->cmds + (i * sizeof(submit_cmd)));
|
||||
struct msm_gem_object *msm_obj;
|
||||
uint32_t iova;
|
||||
|
||||
ret = copy_from_user(&submit_cmd, userptr, sizeof(submit_cmd));
|
||||
if (ret) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* validate input from userspace: */
|
||||
switch (submit_cmd.type) {
|
||||
case MSM_SUBMIT_CMD_BUF:
|
||||
case MSM_SUBMIT_CMD_IB_TARGET_BUF:
|
||||
case MSM_SUBMIT_CMD_CTX_RESTORE_BUF:
|
||||
break;
|
||||
default:
|
||||
DRM_ERROR("invalid type: %08x\n", submit_cmd.type);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = submit_bo(submit, submit_cmd.submit_idx,
|
||||
&msm_obj, &iova, NULL);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (submit_cmd.size % 4) {
|
||||
DRM_ERROR("non-aligned cmdstream buffer size: %u\n",
|
||||
submit_cmd.size);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((submit_cmd.size + submit_cmd.submit_offset) >=
|
||||
msm_obj->base.size) {
|
||||
DRM_ERROR("invalid cmdstream size: %u\n", submit_cmd.size);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
submit->cmd[i].type = submit_cmd.type;
|
||||
submit->cmd[i].size = submit_cmd.size / 4;
|
||||
submit->cmd[i].iova = iova + submit_cmd.submit_offset;
|
||||
submit->cmd[i].idx = submit_cmd.submit_idx;
|
||||
|
||||
if (submit->valid)
|
||||
continue;
|
||||
|
||||
ret = submit_reloc(submit, msm_obj, submit_cmd.submit_offset,
|
||||
submit_cmd.nr_relocs, submit_cmd.relocs);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
submit->nr_cmds = i;
|
||||
|
||||
ret = msm_gpu_submit(gpu, submit, ctx);
|
||||
|
||||
args->fence = submit->fence;
|
||||
|
||||
out:
|
||||
if (submit)
|
||||
submit_cleanup(submit, !!ret);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
return ret;
|
||||
}
|
||||
651
drivers/gpu/drm/msm/msm_gpu.c
Normal file
651
drivers/gpu/drm/msm/msm_gpu.c
Normal file
|
|
@ -0,0 +1,651 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "msm_gpu.h"
|
||||
#include "msm_gem.h"
|
||||
#include "msm_mmu.h"
|
||||
|
||||
|
||||
/*
|
||||
* Power Management:
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_MSM_BUS_SCALING
|
||||
#include <mach/board.h>
|
||||
static void bs_init(struct msm_gpu *gpu)
|
||||
{
|
||||
if (gpu->bus_scale_table) {
|
||||
gpu->bsc = msm_bus_scale_register_client(gpu->bus_scale_table);
|
||||
DBG("bus scale client: %08x", gpu->bsc);
|
||||
}
|
||||
}
|
||||
|
||||
static void bs_fini(struct msm_gpu *gpu)
|
||||
{
|
||||
if (gpu->bsc) {
|
||||
msm_bus_scale_unregister_client(gpu->bsc);
|
||||
gpu->bsc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void bs_set(struct msm_gpu *gpu, int idx)
|
||||
{
|
||||
if (gpu->bsc) {
|
||||
DBG("set bus scaling: %d", idx);
|
||||
msm_bus_scale_client_update_request(gpu->bsc, idx);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void bs_init(struct msm_gpu *gpu) {}
|
||||
static void bs_fini(struct msm_gpu *gpu) {}
|
||||
static void bs_set(struct msm_gpu *gpu, int idx) {}
|
||||
#endif
|
||||
|
||||
static int enable_pwrrail(struct msm_gpu *gpu)
|
||||
{
|
||||
struct drm_device *dev = gpu->dev;
|
||||
int ret = 0;
|
||||
|
||||
if (gpu->gpu_reg) {
|
||||
ret = regulator_enable(gpu->gpu_reg);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to enable 'gpu_reg': %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (gpu->gpu_cx) {
|
||||
ret = regulator_enable(gpu->gpu_cx);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to enable 'gpu_cx': %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int disable_pwrrail(struct msm_gpu *gpu)
|
||||
{
|
||||
if (gpu->gpu_cx)
|
||||
regulator_disable(gpu->gpu_cx);
|
||||
if (gpu->gpu_reg)
|
||||
regulator_disable(gpu->gpu_reg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enable_clk(struct msm_gpu *gpu)
|
||||
{
|
||||
struct clk *rate_clk = NULL;
|
||||
int i;
|
||||
|
||||
/* NOTE: kgsl_pwrctrl_clk() ignores grp_clks[0].. */
|
||||
for (i = ARRAY_SIZE(gpu->grp_clks) - 1; i > 0; i--) {
|
||||
if (gpu->grp_clks[i]) {
|
||||
clk_prepare(gpu->grp_clks[i]);
|
||||
rate_clk = gpu->grp_clks[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (rate_clk && gpu->fast_rate)
|
||||
clk_set_rate(rate_clk, gpu->fast_rate);
|
||||
|
||||
for (i = ARRAY_SIZE(gpu->grp_clks) - 1; i > 0; i--)
|
||||
if (gpu->grp_clks[i])
|
||||
clk_enable(gpu->grp_clks[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int disable_clk(struct msm_gpu *gpu)
|
||||
{
|
||||
struct clk *rate_clk = NULL;
|
||||
int i;
|
||||
|
||||
/* NOTE: kgsl_pwrctrl_clk() ignores grp_clks[0].. */
|
||||
for (i = ARRAY_SIZE(gpu->grp_clks) - 1; i > 0; i--) {
|
||||
if (gpu->grp_clks[i]) {
|
||||
clk_disable(gpu->grp_clks[i]);
|
||||
rate_clk = gpu->grp_clks[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (rate_clk && gpu->slow_rate)
|
||||
clk_set_rate(rate_clk, gpu->slow_rate);
|
||||
|
||||
for (i = ARRAY_SIZE(gpu->grp_clks) - 1; i > 0; i--)
|
||||
if (gpu->grp_clks[i])
|
||||
clk_unprepare(gpu->grp_clks[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int enable_axi(struct msm_gpu *gpu)
|
||||
{
|
||||
if (gpu->ebi1_clk)
|
||||
clk_prepare_enable(gpu->ebi1_clk);
|
||||
if (gpu->bus_freq)
|
||||
bs_set(gpu, gpu->bus_freq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int disable_axi(struct msm_gpu *gpu)
|
||||
{
|
||||
if (gpu->ebi1_clk)
|
||||
clk_disable_unprepare(gpu->ebi1_clk);
|
||||
if (gpu->bus_freq)
|
||||
bs_set(gpu, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msm_gpu_pm_resume(struct msm_gpu *gpu)
|
||||
{
|
||||
struct drm_device *dev = gpu->dev;
|
||||
int ret;
|
||||
|
||||
DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt);
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->struct_mutex));
|
||||
|
||||
if (gpu->active_cnt++ > 0)
|
||||
return 0;
|
||||
|
||||
if (WARN_ON(gpu->active_cnt <= 0))
|
||||
return -EINVAL;
|
||||
|
||||
ret = enable_pwrrail(gpu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = enable_clk(gpu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = enable_axi(gpu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msm_gpu_pm_suspend(struct msm_gpu *gpu)
|
||||
{
|
||||
struct drm_device *dev = gpu->dev;
|
||||
int ret;
|
||||
|
||||
DBG("%s: active_cnt=%d", gpu->name, gpu->active_cnt);
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->struct_mutex));
|
||||
|
||||
if (--gpu->active_cnt > 0)
|
||||
return 0;
|
||||
|
||||
if (WARN_ON(gpu->active_cnt < 0))
|
||||
return -EINVAL;
|
||||
|
||||
ret = disable_axi(gpu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = disable_clk(gpu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = disable_pwrrail(gpu);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inactivity detection (for suspend):
|
||||
*/
|
||||
|
||||
static void inactive_worker(struct work_struct *work)
|
||||
{
|
||||
struct msm_gpu *gpu = container_of(work, struct msm_gpu, inactive_work);
|
||||
struct drm_device *dev = gpu->dev;
|
||||
|
||||
if (gpu->inactive)
|
||||
return;
|
||||
|
||||
DBG("%s: inactive!\n", gpu->name);
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
if (!(msm_gpu_active(gpu) || gpu->inactive)) {
|
||||
disable_axi(gpu);
|
||||
disable_clk(gpu);
|
||||
gpu->inactive = true;
|
||||
}
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
}
|
||||
|
||||
static void inactive_handler(unsigned long data)
|
||||
{
|
||||
struct msm_gpu *gpu = (struct msm_gpu *)data;
|
||||
struct msm_drm_private *priv = gpu->dev->dev_private;
|
||||
|
||||
queue_work(priv->wq, &gpu->inactive_work);
|
||||
}
|
||||
|
||||
/* cancel inactive timer and make sure we are awake: */
|
||||
static void inactive_cancel(struct msm_gpu *gpu)
|
||||
{
|
||||
DBG("%s", gpu->name);
|
||||
del_timer(&gpu->inactive_timer);
|
||||
if (gpu->inactive) {
|
||||
enable_clk(gpu);
|
||||
enable_axi(gpu);
|
||||
gpu->inactive = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void inactive_start(struct msm_gpu *gpu)
|
||||
{
|
||||
DBG("%s", gpu->name);
|
||||
mod_timer(&gpu->inactive_timer,
|
||||
round_jiffies_up(jiffies + DRM_MSM_INACTIVE_JIFFIES));
|
||||
}
|
||||
|
||||
/*
|
||||
* Hangcheck detection for locked gpu:
|
||||
*/
|
||||
|
||||
static void recover_worker(struct work_struct *work)
|
||||
{
|
||||
struct msm_gpu *gpu = container_of(work, struct msm_gpu, recover_work);
|
||||
struct drm_device *dev = gpu->dev;
|
||||
|
||||
dev_err(dev->dev, "%s: hangcheck recover!\n", gpu->name);
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
if (msm_gpu_active(gpu)) {
|
||||
inactive_cancel(gpu);
|
||||
gpu->funcs->recover(gpu);
|
||||
}
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
msm_gpu_retire(gpu);
|
||||
}
|
||||
|
||||
static void hangcheck_timer_reset(struct msm_gpu *gpu)
|
||||
{
|
||||
DBG("%s", gpu->name);
|
||||
mod_timer(&gpu->hangcheck_timer,
|
||||
round_jiffies_up(jiffies + DRM_MSM_HANGCHECK_JIFFIES));
|
||||
}
|
||||
|
||||
static void hangcheck_handler(unsigned long data)
|
||||
{
|
||||
struct msm_gpu *gpu = (struct msm_gpu *)data;
|
||||
struct drm_device *dev = gpu->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
uint32_t fence = gpu->funcs->last_fence(gpu);
|
||||
|
||||
if (fence != gpu->hangcheck_fence) {
|
||||
/* some progress has been made.. ya! */
|
||||
gpu->hangcheck_fence = fence;
|
||||
} else if (fence < gpu->submitted_fence) {
|
||||
/* no progress and not done.. hung! */
|
||||
gpu->hangcheck_fence = fence;
|
||||
dev_err(dev->dev, "%s: hangcheck detected gpu lockup!\n",
|
||||
gpu->name);
|
||||
dev_err(dev->dev, "%s: completed fence: %u\n",
|
||||
gpu->name, fence);
|
||||
dev_err(dev->dev, "%s: submitted fence: %u\n",
|
||||
gpu->name, gpu->submitted_fence);
|
||||
queue_work(priv->wq, &gpu->recover_work);
|
||||
}
|
||||
|
||||
/* if still more pending work, reset the hangcheck timer: */
|
||||
if (gpu->submitted_fence > gpu->hangcheck_fence)
|
||||
hangcheck_timer_reset(gpu);
|
||||
|
||||
/* workaround for missing irq: */
|
||||
queue_work(priv->wq, &gpu->retire_work);
|
||||
}
|
||||
|
||||
/*
|
||||
* Performance Counters:
|
||||
*/
|
||||
|
||||
/* called under perf_lock */
|
||||
static int update_hw_cntrs(struct msm_gpu *gpu, uint32_t ncntrs, uint32_t *cntrs)
|
||||
{
|
||||
uint32_t current_cntrs[ARRAY_SIZE(gpu->last_cntrs)];
|
||||
int i, n = min(ncntrs, gpu->num_perfcntrs);
|
||||
|
||||
/* read current values: */
|
||||
for (i = 0; i < gpu->num_perfcntrs; i++)
|
||||
current_cntrs[i] = gpu_read(gpu, gpu->perfcntrs[i].sample_reg);
|
||||
|
||||
/* update cntrs: */
|
||||
for (i = 0; i < n; i++)
|
||||
cntrs[i] = current_cntrs[i] - gpu->last_cntrs[i];
|
||||
|
||||
/* save current values: */
|
||||
for (i = 0; i < gpu->num_perfcntrs; i++)
|
||||
gpu->last_cntrs[i] = current_cntrs[i];
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static void update_sw_cntrs(struct msm_gpu *gpu)
|
||||
{
|
||||
ktime_t time;
|
||||
uint32_t elapsed;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&gpu->perf_lock, flags);
|
||||
if (!gpu->perfcntr_active)
|
||||
goto out;
|
||||
|
||||
time = ktime_get();
|
||||
elapsed = ktime_to_us(ktime_sub(time, gpu->last_sample.time));
|
||||
|
||||
gpu->totaltime += elapsed;
|
||||
if (gpu->last_sample.active)
|
||||
gpu->activetime += elapsed;
|
||||
|
||||
gpu->last_sample.active = msm_gpu_active(gpu);
|
||||
gpu->last_sample.time = time;
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&gpu->perf_lock, flags);
|
||||
}
|
||||
|
||||
void msm_gpu_perfcntr_start(struct msm_gpu *gpu)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&gpu->perf_lock, flags);
|
||||
/* we could dynamically enable/disable perfcntr registers too.. */
|
||||
gpu->last_sample.active = msm_gpu_active(gpu);
|
||||
gpu->last_sample.time = ktime_get();
|
||||
gpu->activetime = gpu->totaltime = 0;
|
||||
gpu->perfcntr_active = true;
|
||||
update_hw_cntrs(gpu, 0, NULL);
|
||||
spin_unlock_irqrestore(&gpu->perf_lock, flags);
|
||||
}
|
||||
|
||||
void msm_gpu_perfcntr_stop(struct msm_gpu *gpu)
|
||||
{
|
||||
gpu->perfcntr_active = false;
|
||||
}
|
||||
|
||||
/* returns -errno or # of cntrs sampled */
|
||||
int msm_gpu_perfcntr_sample(struct msm_gpu *gpu, uint32_t *activetime,
|
||||
uint32_t *totaltime, uint32_t ncntrs, uint32_t *cntrs)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&gpu->perf_lock, flags);
|
||||
|
||||
if (!gpu->perfcntr_active) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*activetime = gpu->activetime;
|
||||
*totaltime = gpu->totaltime;
|
||||
|
||||
gpu->activetime = gpu->totaltime = 0;
|
||||
|
||||
ret = update_hw_cntrs(gpu, ncntrs, cntrs);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&gpu->perf_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cmdstream submission/retirement:
|
||||
*/
|
||||
|
||||
static void retire_worker(struct work_struct *work)
|
||||
{
|
||||
struct msm_gpu *gpu = container_of(work, struct msm_gpu, retire_work);
|
||||
struct drm_device *dev = gpu->dev;
|
||||
uint32_t fence = gpu->funcs->last_fence(gpu);
|
||||
|
||||
msm_update_fence(gpu->dev, fence);
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
|
||||
while (!list_empty(&gpu->active_list)) {
|
||||
struct msm_gem_object *obj;
|
||||
|
||||
obj = list_first_entry(&gpu->active_list,
|
||||
struct msm_gem_object, mm_list);
|
||||
|
||||
if ((obj->read_fence <= fence) &&
|
||||
(obj->write_fence <= fence)) {
|
||||
/* move to inactive: */
|
||||
msm_gem_move_to_inactive(&obj->base);
|
||||
msm_gem_put_iova(&obj->base, gpu->id);
|
||||
drm_gem_object_unreference(&obj->base);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
if (!msm_gpu_active(gpu))
|
||||
inactive_start(gpu);
|
||||
}
|
||||
|
||||
/* call from irq handler to schedule work to retire bo's */
|
||||
void msm_gpu_retire(struct msm_gpu *gpu)
|
||||
{
|
||||
struct msm_drm_private *priv = gpu->dev->dev_private;
|
||||
queue_work(priv->wq, &gpu->retire_work);
|
||||
update_sw_cntrs(gpu);
|
||||
}
|
||||
|
||||
/* add bo's to gpu's ring, and kick gpu: */
|
||||
int msm_gpu_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit,
|
||||
struct msm_file_private *ctx)
|
||||
{
|
||||
struct drm_device *dev = gpu->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
int i, ret;
|
||||
|
||||
submit->fence = ++priv->next_fence;
|
||||
|
||||
gpu->submitted_fence = submit->fence;
|
||||
|
||||
inactive_cancel(gpu);
|
||||
|
||||
msm_rd_dump_submit(submit);
|
||||
|
||||
gpu->submitted_fence = submit->fence;
|
||||
|
||||
update_sw_cntrs(gpu);
|
||||
|
||||
ret = gpu->funcs->submit(gpu, submit, ctx);
|
||||
priv->lastctx = ctx;
|
||||
|
||||
for (i = 0; i < submit->nr_bos; i++) {
|
||||
struct msm_gem_object *msm_obj = submit->bos[i].obj;
|
||||
|
||||
/* can't happen yet.. but when we add 2d support we'll have
|
||||
* to deal w/ cross-ring synchronization:
|
||||
*/
|
||||
WARN_ON(is_active(msm_obj) && (msm_obj->gpu != gpu));
|
||||
|
||||
if (!is_active(msm_obj)) {
|
||||
uint32_t iova;
|
||||
|
||||
/* ring takes a reference to the bo and iova: */
|
||||
drm_gem_object_reference(&msm_obj->base);
|
||||
msm_gem_get_iova_locked(&msm_obj->base,
|
||||
submit->gpu->id, &iova);
|
||||
}
|
||||
|
||||
if (submit->bos[i].flags & MSM_SUBMIT_BO_READ)
|
||||
msm_gem_move_to_active(&msm_obj->base, gpu, false, submit->fence);
|
||||
|
||||
if (submit->bos[i].flags & MSM_SUBMIT_BO_WRITE)
|
||||
msm_gem_move_to_active(&msm_obj->base, gpu, true, submit->fence);
|
||||
}
|
||||
hangcheck_timer_reset(gpu);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init/Cleanup:
|
||||
*/
|
||||
|
||||
static irqreturn_t irq_handler(int irq, void *data)
|
||||
{
|
||||
struct msm_gpu *gpu = data;
|
||||
return gpu->funcs->irq(gpu);
|
||||
}
|
||||
|
||||
static const char *clk_names[] = {
|
||||
"src_clk", "core_clk", "iface_clk", "mem_clk", "mem_iface_clk",
|
||||
};
|
||||
|
||||
int msm_gpu_init(struct drm_device *drm, struct platform_device *pdev,
|
||||
struct msm_gpu *gpu, const struct msm_gpu_funcs *funcs,
|
||||
const char *name, const char *ioname, const char *irqname, int ringsz)
|
||||
{
|
||||
struct iommu_domain *iommu;
|
||||
int i, ret;
|
||||
|
||||
if (WARN_ON(gpu->num_perfcntrs > ARRAY_SIZE(gpu->last_cntrs)))
|
||||
gpu->num_perfcntrs = ARRAY_SIZE(gpu->last_cntrs);
|
||||
|
||||
gpu->dev = drm;
|
||||
gpu->funcs = funcs;
|
||||
gpu->name = name;
|
||||
gpu->inactive = true;
|
||||
|
||||
INIT_LIST_HEAD(&gpu->active_list);
|
||||
INIT_WORK(&gpu->retire_work, retire_worker);
|
||||
INIT_WORK(&gpu->inactive_work, inactive_worker);
|
||||
INIT_WORK(&gpu->recover_work, recover_worker);
|
||||
|
||||
setup_timer(&gpu->inactive_timer, inactive_handler,
|
||||
(unsigned long)gpu);
|
||||
setup_timer(&gpu->hangcheck_timer, hangcheck_handler,
|
||||
(unsigned long)gpu);
|
||||
|
||||
spin_lock_init(&gpu->perf_lock);
|
||||
|
||||
BUG_ON(ARRAY_SIZE(clk_names) != ARRAY_SIZE(gpu->grp_clks));
|
||||
|
||||
/* Map registers: */
|
||||
gpu->mmio = msm_ioremap(pdev, ioname, name);
|
||||
if (IS_ERR(gpu->mmio)) {
|
||||
ret = PTR_ERR(gpu->mmio);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Get Interrupt: */
|
||||
gpu->irq = platform_get_irq_byname(pdev, irqname);
|
||||
if (gpu->irq < 0) {
|
||||
ret = gpu->irq;
|
||||
dev_err(drm->dev, "failed to get irq: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, gpu->irq, irq_handler,
|
||||
IRQF_TRIGGER_HIGH, gpu->name, gpu);
|
||||
if (ret) {
|
||||
dev_err(drm->dev, "failed to request IRQ%u: %d\n", gpu->irq, ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Acquire clocks: */
|
||||
for (i = 0; i < ARRAY_SIZE(clk_names); i++) {
|
||||
gpu->grp_clks[i] = devm_clk_get(&pdev->dev, clk_names[i]);
|
||||
DBG("grp_clks[%s]: %p", clk_names[i], gpu->grp_clks[i]);
|
||||
if (IS_ERR(gpu->grp_clks[i]))
|
||||
gpu->grp_clks[i] = NULL;
|
||||
}
|
||||
|
||||
gpu->ebi1_clk = devm_clk_get(&pdev->dev, "bus_clk");
|
||||
DBG("ebi1_clk: %p", gpu->ebi1_clk);
|
||||
if (IS_ERR(gpu->ebi1_clk))
|
||||
gpu->ebi1_clk = NULL;
|
||||
|
||||
/* Acquire regulators: */
|
||||
gpu->gpu_reg = devm_regulator_get(&pdev->dev, "vdd");
|
||||
DBG("gpu_reg: %p", gpu->gpu_reg);
|
||||
if (IS_ERR(gpu->gpu_reg))
|
||||
gpu->gpu_reg = NULL;
|
||||
|
||||
gpu->gpu_cx = devm_regulator_get(&pdev->dev, "vddcx");
|
||||
DBG("gpu_cx: %p", gpu->gpu_cx);
|
||||
if (IS_ERR(gpu->gpu_cx))
|
||||
gpu->gpu_cx = NULL;
|
||||
|
||||
/* Setup IOMMU.. eventually we will (I think) do this once per context
|
||||
* and have separate page tables per context. For now, to keep things
|
||||
* simple and to get something working, just use a single address space:
|
||||
*/
|
||||
iommu = iommu_domain_alloc(&platform_bus_type);
|
||||
if (iommu) {
|
||||
dev_info(drm->dev, "%s: using IOMMU\n", name);
|
||||
gpu->mmu = msm_iommu_new(&pdev->dev, iommu);
|
||||
} else {
|
||||
dev_info(drm->dev, "%s: no IOMMU, fallback to VRAM carveout!\n", name);
|
||||
}
|
||||
gpu->id = msm_register_mmu(drm, gpu->mmu);
|
||||
|
||||
|
||||
/* Create ringbuffer: */
|
||||
mutex_lock(&drm->struct_mutex);
|
||||
gpu->rb = msm_ringbuffer_new(gpu, ringsz);
|
||||
mutex_unlock(&drm->struct_mutex);
|
||||
if (IS_ERR(gpu->rb)) {
|
||||
ret = PTR_ERR(gpu->rb);
|
||||
gpu->rb = NULL;
|
||||
dev_err(drm->dev, "could not create ringbuffer: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bs_init(gpu);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void msm_gpu_cleanup(struct msm_gpu *gpu)
|
||||
{
|
||||
DBG("%s", gpu->name);
|
||||
|
||||
WARN_ON(!list_empty(&gpu->active_list));
|
||||
|
||||
bs_fini(gpu);
|
||||
|
||||
if (gpu->rb) {
|
||||
if (gpu->rb_iova)
|
||||
msm_gem_put_iova(gpu->rb->bo, gpu->id);
|
||||
msm_ringbuffer_destroy(gpu->rb);
|
||||
}
|
||||
|
||||
if (gpu->mmu)
|
||||
gpu->mmu->funcs->destroy(gpu->mmu);
|
||||
}
|
||||
173
drivers/gpu/drm/msm/msm_gpu.h
Normal file
173
drivers/gpu/drm/msm/msm_gpu.h
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MSM_GPU_H__
|
||||
#define __MSM_GPU_H__
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_ringbuffer.h"
|
||||
|
||||
struct msm_gem_submit;
|
||||
struct msm_gpu_perfcntr;
|
||||
|
||||
/* So far, with hardware that I've seen to date, we can have:
|
||||
* + zero, one, or two z180 2d cores
|
||||
* + a3xx or a2xx 3d core, which share a common CP (the firmware
|
||||
* for the CP seems to implement some different PM4 packet types
|
||||
* but the basics of cmdstream submission are the same)
|
||||
*
|
||||
* Which means that the eventual complete "class" hierarchy, once
|
||||
* support for all past and present hw is in place, becomes:
|
||||
* + msm_gpu
|
||||
* + adreno_gpu
|
||||
* + a3xx_gpu
|
||||
* + a2xx_gpu
|
||||
* + z180_gpu
|
||||
*/
|
||||
struct msm_gpu_funcs {
|
||||
int (*get_param)(struct msm_gpu *gpu, uint32_t param, uint64_t *value);
|
||||
int (*hw_init)(struct msm_gpu *gpu);
|
||||
int (*pm_suspend)(struct msm_gpu *gpu);
|
||||
int (*pm_resume)(struct msm_gpu *gpu);
|
||||
int (*submit)(struct msm_gpu *gpu, struct msm_gem_submit *submit,
|
||||
struct msm_file_private *ctx);
|
||||
void (*flush)(struct msm_gpu *gpu);
|
||||
void (*idle)(struct msm_gpu *gpu);
|
||||
irqreturn_t (*irq)(struct msm_gpu *irq);
|
||||
uint32_t (*last_fence)(struct msm_gpu *gpu);
|
||||
void (*recover)(struct msm_gpu *gpu);
|
||||
void (*destroy)(struct msm_gpu *gpu);
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* show GPU status in debugfs: */
|
||||
void (*show)(struct msm_gpu *gpu, struct seq_file *m);
|
||||
#endif
|
||||
};
|
||||
|
||||
struct msm_gpu {
|
||||
const char *name;
|
||||
struct drm_device *dev;
|
||||
const struct msm_gpu_funcs *funcs;
|
||||
|
||||
/* performance counters (hw & sw): */
|
||||
spinlock_t perf_lock;
|
||||
bool perfcntr_active;
|
||||
struct {
|
||||
bool active;
|
||||
ktime_t time;
|
||||
} last_sample;
|
||||
uint32_t totaltime, activetime; /* sw counters */
|
||||
uint32_t last_cntrs[5]; /* hw counters */
|
||||
const struct msm_gpu_perfcntr *perfcntrs;
|
||||
uint32_t num_perfcntrs;
|
||||
|
||||
struct msm_ringbuffer *rb;
|
||||
uint32_t rb_iova;
|
||||
|
||||
/* list of GEM active objects: */
|
||||
struct list_head active_list;
|
||||
|
||||
uint32_t submitted_fence;
|
||||
|
||||
/* is gpu powered/active? */
|
||||
int active_cnt;
|
||||
bool inactive;
|
||||
|
||||
/* worker for handling active-list retiring: */
|
||||
struct work_struct retire_work;
|
||||
|
||||
void __iomem *mmio;
|
||||
int irq;
|
||||
|
||||
struct msm_mmu *mmu;
|
||||
int id;
|
||||
|
||||
/* Power Control: */
|
||||
struct regulator *gpu_reg, *gpu_cx;
|
||||
struct clk *ebi1_clk, *grp_clks[5];
|
||||
uint32_t fast_rate, slow_rate, bus_freq;
|
||||
|
||||
#ifdef CONFIG_MSM_BUS_SCALING
|
||||
struct msm_bus_scale_pdata *bus_scale_table;
|
||||
uint32_t bsc;
|
||||
#endif
|
||||
|
||||
/* Hang and Inactivity Detection:
|
||||
*/
|
||||
#define DRM_MSM_INACTIVE_PERIOD 66 /* in ms (roughly four frames) */
|
||||
#define DRM_MSM_INACTIVE_JIFFIES msecs_to_jiffies(DRM_MSM_INACTIVE_PERIOD)
|
||||
struct timer_list inactive_timer;
|
||||
struct work_struct inactive_work;
|
||||
#define DRM_MSM_HANGCHECK_PERIOD 500 /* in ms */
|
||||
#define DRM_MSM_HANGCHECK_JIFFIES msecs_to_jiffies(DRM_MSM_HANGCHECK_PERIOD)
|
||||
struct timer_list hangcheck_timer;
|
||||
uint32_t hangcheck_fence;
|
||||
struct work_struct recover_work;
|
||||
};
|
||||
|
||||
static inline bool msm_gpu_active(struct msm_gpu *gpu)
|
||||
{
|
||||
return gpu->submitted_fence > gpu->funcs->last_fence(gpu);
|
||||
}
|
||||
|
||||
/* Perf-Counters:
|
||||
* The select_reg and select_val are just there for the benefit of the child
|
||||
* class that actually enables the perf counter.. but msm_gpu base class
|
||||
* will handle sampling/displaying the counters.
|
||||
*/
|
||||
|
||||
struct msm_gpu_perfcntr {
|
||||
uint32_t select_reg;
|
||||
uint32_t sample_reg;
|
||||
uint32_t select_val;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
static inline void gpu_write(struct msm_gpu *gpu, u32 reg, u32 data)
|
||||
{
|
||||
msm_writel(data, gpu->mmio + (reg << 2));
|
||||
}
|
||||
|
||||
static inline u32 gpu_read(struct msm_gpu *gpu, u32 reg)
|
||||
{
|
||||
return msm_readl(gpu->mmio + (reg << 2));
|
||||
}
|
||||
|
||||
int msm_gpu_pm_suspend(struct msm_gpu *gpu);
|
||||
int msm_gpu_pm_resume(struct msm_gpu *gpu);
|
||||
|
||||
void msm_gpu_perfcntr_start(struct msm_gpu *gpu);
|
||||
void msm_gpu_perfcntr_stop(struct msm_gpu *gpu);
|
||||
int msm_gpu_perfcntr_sample(struct msm_gpu *gpu, uint32_t *activetime,
|
||||
uint32_t *totaltime, uint32_t ncntrs, uint32_t *cntrs);
|
||||
|
||||
void msm_gpu_retire(struct msm_gpu *gpu);
|
||||
int msm_gpu_submit(struct msm_gpu *gpu, struct msm_gem_submit *submit,
|
||||
struct msm_file_private *ctx);
|
||||
|
||||
int msm_gpu_init(struct drm_device *drm, struct platform_device *pdev,
|
||||
struct msm_gpu *gpu, const struct msm_gpu_funcs *funcs,
|
||||
const char *name, const char *ioname, const char *irqname, int ringsz);
|
||||
void msm_gpu_cleanup(struct msm_gpu *gpu);
|
||||
|
||||
struct msm_gpu *adreno_load_gpu(struct drm_device *dev);
|
||||
void __init adreno_register(void);
|
||||
void __exit adreno_unregister(void);
|
||||
|
||||
#endif /* __MSM_GPU_H__ */
|
||||
140
drivers/gpu/drm/msm/msm_iommu.c
Normal file
140
drivers/gpu/drm/msm/msm_iommu.c
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_mmu.h"
|
||||
|
||||
struct msm_iommu {
|
||||
struct msm_mmu base;
|
||||
struct iommu_domain *domain;
|
||||
};
|
||||
#define to_msm_iommu(x) container_of(x, struct msm_iommu, base)
|
||||
|
||||
static int msm_fault_handler(struct iommu_domain *iommu, struct device *dev,
|
||||
unsigned long iova, int flags, void *arg)
|
||||
{
|
||||
pr_warn_ratelimited("*** fault: iova=%08lx, flags=%d\n", iova, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int msm_iommu_attach(struct msm_mmu *mmu, const char **names, int cnt)
|
||||
{
|
||||
struct msm_iommu *iommu = to_msm_iommu(mmu);
|
||||
return iommu_attach_device(iommu->domain, mmu->dev);
|
||||
}
|
||||
|
||||
static void msm_iommu_detach(struct msm_mmu *mmu, const char **names, int cnt)
|
||||
{
|
||||
struct msm_iommu *iommu = to_msm_iommu(mmu);
|
||||
iommu_detach_device(iommu->domain, mmu->dev);
|
||||
}
|
||||
|
||||
static int msm_iommu_map(struct msm_mmu *mmu, uint32_t iova,
|
||||
struct sg_table *sgt, unsigned len, int prot)
|
||||
{
|
||||
struct msm_iommu *iommu = to_msm_iommu(mmu);
|
||||
struct iommu_domain *domain = iommu->domain;
|
||||
struct scatterlist *sg;
|
||||
unsigned int da = iova;
|
||||
unsigned int i, j;
|
||||
int ret;
|
||||
|
||||
if (!domain || !sgt)
|
||||
return -EINVAL;
|
||||
|
||||
for_each_sg(sgt->sgl, sg, sgt->nents, i) {
|
||||
u32 pa = sg_phys(sg) - sg->offset;
|
||||
size_t bytes = sg->length + sg->offset;
|
||||
|
||||
VERB("map[%d]: %08x %08x(%x)", i, iova, pa, bytes);
|
||||
|
||||
ret = iommu_map(domain, da, pa, bytes, prot);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
da += bytes;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
da = iova;
|
||||
|
||||
for_each_sg(sgt->sgl, sg, i, j) {
|
||||
size_t bytes = sg->length + sg->offset;
|
||||
iommu_unmap(domain, da, bytes);
|
||||
da += bytes;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int msm_iommu_unmap(struct msm_mmu *mmu, uint32_t iova,
|
||||
struct sg_table *sgt, unsigned len)
|
||||
{
|
||||
struct msm_iommu *iommu = to_msm_iommu(mmu);
|
||||
struct iommu_domain *domain = iommu->domain;
|
||||
struct scatterlist *sg;
|
||||
unsigned int da = iova;
|
||||
int i;
|
||||
|
||||
for_each_sg(sgt->sgl, sg, sgt->nents, i) {
|
||||
size_t bytes = sg->length + sg->offset;
|
||||
size_t unmapped;
|
||||
|
||||
unmapped = iommu_unmap(domain, da, bytes);
|
||||
if (unmapped < bytes)
|
||||
return unmapped;
|
||||
|
||||
VERB("unmap[%d]: %08x(%x)", i, iova, bytes);
|
||||
|
||||
BUG_ON(!PAGE_ALIGNED(bytes));
|
||||
|
||||
da += bytes;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void msm_iommu_destroy(struct msm_mmu *mmu)
|
||||
{
|
||||
struct msm_iommu *iommu = to_msm_iommu(mmu);
|
||||
iommu_domain_free(iommu->domain);
|
||||
kfree(iommu);
|
||||
}
|
||||
|
||||
static const struct msm_mmu_funcs funcs = {
|
||||
.attach = msm_iommu_attach,
|
||||
.detach = msm_iommu_detach,
|
||||
.map = msm_iommu_map,
|
||||
.unmap = msm_iommu_unmap,
|
||||
.destroy = msm_iommu_destroy,
|
||||
};
|
||||
|
||||
struct msm_mmu *msm_iommu_new(struct device *dev, struct iommu_domain *domain)
|
||||
{
|
||||
struct msm_iommu *iommu;
|
||||
|
||||
iommu = kzalloc(sizeof(*iommu), GFP_KERNEL);
|
||||
if (!iommu)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
iommu->domain = domain;
|
||||
msm_mmu_init(&iommu->base, dev, &funcs);
|
||||
iommu_set_fault_handler(domain, msm_fault_handler, dev);
|
||||
|
||||
return &iommu->base;
|
||||
}
|
||||
68
drivers/gpu/drm/msm/msm_kms.h
Normal file
68
drivers/gpu/drm/msm/msm_kms.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MSM_KMS_H__
|
||||
#define __MSM_KMS_H__
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include "msm_drv.h"
|
||||
|
||||
/* As there are different display controller blocks depending on the
|
||||
* snapdragon version, the kms support is split out and the appropriate
|
||||
* implementation is loaded at runtime. The kms module is responsible
|
||||
* for constructing the appropriate planes/crtcs/encoders/connectors.
|
||||
*/
|
||||
struct msm_kms_funcs {
|
||||
/* hw initialization: */
|
||||
int (*hw_init)(struct msm_kms *kms);
|
||||
/* irq handling: */
|
||||
void (*irq_preinstall)(struct msm_kms *kms);
|
||||
int (*irq_postinstall)(struct msm_kms *kms);
|
||||
void (*irq_uninstall)(struct msm_kms *kms);
|
||||
irqreturn_t (*irq)(struct msm_kms *kms);
|
||||
int (*enable_vblank)(struct msm_kms *kms, struct drm_crtc *crtc);
|
||||
void (*disable_vblank)(struct msm_kms *kms, struct drm_crtc *crtc);
|
||||
/* misc: */
|
||||
const struct msm_format *(*get_format)(struct msm_kms *kms, uint32_t format);
|
||||
long (*round_pixclk)(struct msm_kms *kms, unsigned long rate,
|
||||
struct drm_encoder *encoder);
|
||||
/* cleanup: */
|
||||
void (*preclose)(struct msm_kms *kms, struct drm_file *file);
|
||||
void (*destroy)(struct msm_kms *kms);
|
||||
};
|
||||
|
||||
struct msm_kms {
|
||||
const struct msm_kms_funcs *funcs;
|
||||
|
||||
/* irq handling: */
|
||||
bool in_irq;
|
||||
struct list_head irq_list; /* list of mdp4_irq */
|
||||
uint32_t vblank_mask; /* irq bits set for userspace vblank */
|
||||
};
|
||||
|
||||
static inline void msm_kms_init(struct msm_kms *kms,
|
||||
const struct msm_kms_funcs *funcs)
|
||||
{
|
||||
kms->funcs = funcs;
|
||||
}
|
||||
|
||||
struct msm_kms *mdp4_kms_init(struct drm_device *dev);
|
||||
struct msm_kms *mdp5_kms_init(struct drm_device *dev);
|
||||
|
||||
#endif /* __MSM_KMS_H__ */
|
||||
48
drivers/gpu/drm/msm/msm_mmu.h
Normal file
48
drivers/gpu/drm/msm/msm_mmu.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MSM_MMU_H__
|
||||
#define __MSM_MMU_H__
|
||||
|
||||
#include <linux/iommu.h>
|
||||
|
||||
struct msm_mmu_funcs {
|
||||
int (*attach)(struct msm_mmu *mmu, const char **names, int cnt);
|
||||
void (*detach)(struct msm_mmu *mmu, const char **names, int cnt);
|
||||
int (*map)(struct msm_mmu *mmu, uint32_t iova, struct sg_table *sgt,
|
||||
unsigned len, int prot);
|
||||
int (*unmap)(struct msm_mmu *mmu, uint32_t iova, struct sg_table *sgt,
|
||||
unsigned len);
|
||||
void (*destroy)(struct msm_mmu *mmu);
|
||||
};
|
||||
|
||||
struct msm_mmu {
|
||||
const struct msm_mmu_funcs *funcs;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static inline void msm_mmu_init(struct msm_mmu *mmu, struct device *dev,
|
||||
const struct msm_mmu_funcs *funcs)
|
||||
{
|
||||
mmu->dev = dev;
|
||||
mmu->funcs = funcs;
|
||||
}
|
||||
|
||||
struct msm_mmu *msm_iommu_new(struct device *dev, struct iommu_domain *domain);
|
||||
struct msm_mmu *msm_gpummu_new(struct device *dev, struct msm_gpu *gpu);
|
||||
|
||||
#endif /* __MSM_MMU_H__ */
|
||||
275
drivers/gpu/drm/msm/msm_perf.c
Normal file
275
drivers/gpu/drm/msm/msm_perf.c
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* For profiling, userspace can:
|
||||
*
|
||||
* tail -f /sys/kernel/debug/dri/<minor>/gpu
|
||||
*
|
||||
* This will enable performance counters/profiling to track the busy time
|
||||
* and any gpu specific performance counters that are supported.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_gpu.h"
|
||||
|
||||
struct msm_perf_state {
|
||||
struct drm_device *dev;
|
||||
|
||||
bool open;
|
||||
int cnt;
|
||||
struct mutex read_lock;
|
||||
|
||||
char buf[256];
|
||||
int buftot, bufpos;
|
||||
|
||||
unsigned long next_jiffies;
|
||||
|
||||
struct dentry *ent;
|
||||
struct drm_info_node *node;
|
||||
};
|
||||
|
||||
#define SAMPLE_TIME (HZ/4)
|
||||
|
||||
/* wait for next sample time: */
|
||||
static int wait_sample(struct msm_perf_state *perf)
|
||||
{
|
||||
unsigned long start_jiffies = jiffies;
|
||||
|
||||
if (time_after(perf->next_jiffies, start_jiffies)) {
|
||||
unsigned long remaining_jiffies =
|
||||
perf->next_jiffies - start_jiffies;
|
||||
int ret = schedule_timeout_interruptible(remaining_jiffies);
|
||||
if (ret > 0) {
|
||||
/* interrupted */
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
}
|
||||
perf->next_jiffies += SAMPLE_TIME;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int refill_buf(struct msm_perf_state *perf)
|
||||
{
|
||||
struct msm_drm_private *priv = perf->dev->dev_private;
|
||||
struct msm_gpu *gpu = priv->gpu;
|
||||
char *ptr = perf->buf;
|
||||
int rem = sizeof(perf->buf);
|
||||
int i, n;
|
||||
|
||||
if ((perf->cnt++ % 32) == 0) {
|
||||
/* Header line: */
|
||||
n = snprintf(ptr, rem, "%%BUSY");
|
||||
ptr += n;
|
||||
rem -= n;
|
||||
|
||||
for (i = 0; i < gpu->num_perfcntrs; i++) {
|
||||
const struct msm_gpu_perfcntr *perfcntr = &gpu->perfcntrs[i];
|
||||
n = snprintf(ptr, rem, "\t%s", perfcntr->name);
|
||||
ptr += n;
|
||||
rem -= n;
|
||||
}
|
||||
} else {
|
||||
/* Sample line: */
|
||||
uint32_t activetime = 0, totaltime = 0;
|
||||
uint32_t cntrs[5];
|
||||
uint32_t val;
|
||||
int ret;
|
||||
|
||||
/* sleep until next sample time: */
|
||||
ret = wait_sample(perf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = msm_gpu_perfcntr_sample(gpu, &activetime, &totaltime,
|
||||
ARRAY_SIZE(cntrs), cntrs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = totaltime ? 1000 * activetime / totaltime : 0;
|
||||
n = snprintf(ptr, rem, "%3d.%d%%", val / 10, val % 10);
|
||||
ptr += n;
|
||||
rem -= n;
|
||||
|
||||
for (i = 0; i < ret; i++) {
|
||||
/* cycle counters (I think).. convert to MHz.. */
|
||||
val = cntrs[i] / 10000;
|
||||
n = snprintf(ptr, rem, "\t%5d.%02d",
|
||||
val / 100, val % 100);
|
||||
ptr += n;
|
||||
rem -= n;
|
||||
}
|
||||
}
|
||||
|
||||
n = snprintf(ptr, rem, "\n");
|
||||
ptr += n;
|
||||
rem -= n;
|
||||
|
||||
perf->bufpos = 0;
|
||||
perf->buftot = ptr - perf->buf;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t perf_read(struct file *file, char __user *buf,
|
||||
size_t sz, loff_t *ppos)
|
||||
{
|
||||
struct msm_perf_state *perf = file->private_data;
|
||||
int n = 0, ret;
|
||||
|
||||
mutex_lock(&perf->read_lock);
|
||||
|
||||
if (perf->bufpos >= perf->buftot) {
|
||||
ret = refill_buf(perf);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
n = min((int)sz, perf->buftot - perf->bufpos);
|
||||
ret = copy_to_user(buf, &perf->buf[perf->bufpos], n);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
perf->bufpos += n;
|
||||
*ppos += n;
|
||||
|
||||
out:
|
||||
mutex_unlock(&perf->read_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int perf_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct msm_perf_state *perf = inode->i_private;
|
||||
struct drm_device *dev = perf->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_gpu *gpu = priv->gpu;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
|
||||
if (perf->open || !gpu) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
file->private_data = perf;
|
||||
perf->open = true;
|
||||
perf->cnt = 0;
|
||||
perf->buftot = 0;
|
||||
perf->bufpos = 0;
|
||||
msm_gpu_perfcntr_start(gpu);
|
||||
perf->next_jiffies = jiffies + SAMPLE_TIME;
|
||||
|
||||
out:
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int perf_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct msm_perf_state *perf = inode->i_private;
|
||||
struct msm_drm_private *priv = perf->dev->dev_private;
|
||||
msm_gpu_perfcntr_stop(priv->gpu);
|
||||
perf->open = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct file_operations perf_debugfs_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = perf_open,
|
||||
.read = perf_read,
|
||||
.llseek = no_llseek,
|
||||
.release = perf_release,
|
||||
};
|
||||
|
||||
int msm_perf_debugfs_init(struct drm_minor *minor)
|
||||
{
|
||||
struct msm_drm_private *priv = minor->dev->dev_private;
|
||||
struct msm_perf_state *perf;
|
||||
|
||||
/* only create on first minor: */
|
||||
if (priv->perf)
|
||||
return 0;
|
||||
|
||||
perf = kzalloc(sizeof(*perf), GFP_KERNEL);
|
||||
if (!perf)
|
||||
return -ENOMEM;
|
||||
|
||||
perf->dev = minor->dev;
|
||||
|
||||
mutex_init(&perf->read_lock);
|
||||
priv->perf = perf;
|
||||
|
||||
perf->node = kzalloc(sizeof(*perf->node), GFP_KERNEL);
|
||||
if (!perf->node)
|
||||
goto fail;
|
||||
|
||||
perf->ent = debugfs_create_file("perf", S_IFREG | S_IRUGO,
|
||||
minor->debugfs_root, perf, &perf_debugfs_fops);
|
||||
if (!perf->ent) {
|
||||
DRM_ERROR("Cannot create /sys/kernel/debug/dri/%s/perf\n",
|
||||
minor->debugfs_root->d_name.name);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
perf->node->minor = minor;
|
||||
perf->node->dent = perf->ent;
|
||||
perf->node->info_ent = NULL;
|
||||
|
||||
mutex_lock(&minor->debugfs_lock);
|
||||
list_add(&perf->node->list, &minor->debugfs_list);
|
||||
mutex_unlock(&minor->debugfs_lock);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
msm_perf_debugfs_cleanup(minor);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void msm_perf_debugfs_cleanup(struct drm_minor *minor)
|
||||
{
|
||||
struct msm_drm_private *priv = minor->dev->dev_private;
|
||||
struct msm_perf_state *perf = priv->perf;
|
||||
|
||||
if (!perf)
|
||||
return;
|
||||
|
||||
priv->perf = NULL;
|
||||
|
||||
debugfs_remove(perf->ent);
|
||||
|
||||
if (perf->node) {
|
||||
mutex_lock(&minor->debugfs_lock);
|
||||
list_del(&perf->node->list);
|
||||
mutex_unlock(&minor->debugfs_lock);
|
||||
kfree(perf->node);
|
||||
}
|
||||
|
||||
mutex_destroy(&perf->read_lock);
|
||||
|
||||
kfree(perf);
|
||||
}
|
||||
|
||||
#endif
|
||||
337
drivers/gpu/drm/msm/msm_rd.c
Normal file
337
drivers/gpu/drm/msm/msm_rd.c
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* For debugging crashes, userspace can:
|
||||
*
|
||||
* tail -f /sys/kernel/debug/dri/<minor>/rd > logfile.rd
|
||||
*
|
||||
* To log the cmdstream in a format that is understood by freedreno/cffdump
|
||||
* utility. By comparing the last successfully completed fence #, to the
|
||||
* cmdstream for the next fence, you can narrow down which process and submit
|
||||
* caused the gpu crash/lockup.
|
||||
*
|
||||
* This bypasses drm_debugfs_create_files() mainly because we need to use
|
||||
* our own fops for a bit more control. In particular, we don't want to
|
||||
* do anything if userspace doesn't have the debugfs file open.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
|
||||
#include <linux/kfifo.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include "msm_drv.h"
|
||||
#include "msm_gpu.h"
|
||||
#include "msm_gem.h"
|
||||
|
||||
enum rd_sect_type {
|
||||
RD_NONE,
|
||||
RD_TEST, /* ascii text */
|
||||
RD_CMD, /* ascii text */
|
||||
RD_GPUADDR, /* u32 gpuaddr, u32 size */
|
||||
RD_CONTEXT, /* raw dump */
|
||||
RD_CMDSTREAM, /* raw dump */
|
||||
RD_CMDSTREAM_ADDR, /* gpu addr of cmdstream */
|
||||
RD_PARAM, /* u32 param_type, u32 param_val, u32 bitlen */
|
||||
RD_FLUSH, /* empty, clear previous params */
|
||||
RD_PROGRAM, /* shader program, raw dump */
|
||||
RD_VERT_SHADER,
|
||||
RD_FRAG_SHADER,
|
||||
RD_BUFFER_CONTENTS,
|
||||
RD_GPU_ID,
|
||||
};
|
||||
|
||||
#define BUF_SZ 512 /* should be power of 2 */
|
||||
|
||||
/* space used: */
|
||||
#define circ_count(circ) \
|
||||
(CIRC_CNT((circ)->head, (circ)->tail, BUF_SZ))
|
||||
#define circ_count_to_end(circ) \
|
||||
(CIRC_CNT_TO_END((circ)->head, (circ)->tail, BUF_SZ))
|
||||
/* space available: */
|
||||
#define circ_space(circ) \
|
||||
(CIRC_SPACE((circ)->head, (circ)->tail, BUF_SZ))
|
||||
#define circ_space_to_end(circ) \
|
||||
(CIRC_SPACE_TO_END((circ)->head, (circ)->tail, BUF_SZ))
|
||||
|
||||
struct msm_rd_state {
|
||||
struct drm_device *dev;
|
||||
|
||||
bool open;
|
||||
|
||||
struct dentry *ent;
|
||||
struct drm_info_node *node;
|
||||
|
||||
/* current submit to read out: */
|
||||
struct msm_gem_submit *submit;
|
||||
|
||||
/* fifo access is synchronized on the producer side by
|
||||
* struct_mutex held by submit code (otherwise we could
|
||||
* end up w/ cmds logged in different order than they
|
||||
* were executed). And read_lock synchronizes the reads
|
||||
*/
|
||||
struct mutex read_lock;
|
||||
|
||||
wait_queue_head_t fifo_event;
|
||||
struct circ_buf fifo;
|
||||
|
||||
char buf[BUF_SZ];
|
||||
};
|
||||
|
||||
static void rd_write(struct msm_rd_state *rd, const void *buf, int sz)
|
||||
{
|
||||
struct circ_buf *fifo = &rd->fifo;
|
||||
const char *ptr = buf;
|
||||
|
||||
while (sz > 0) {
|
||||
char *fptr = &fifo->buf[fifo->head];
|
||||
int n;
|
||||
|
||||
wait_event(rd->fifo_event, circ_space(&rd->fifo) > 0);
|
||||
|
||||
n = min(sz, circ_space_to_end(&rd->fifo));
|
||||
memcpy(fptr, ptr, n);
|
||||
|
||||
fifo->head = (fifo->head + n) & (BUF_SZ - 1);
|
||||
sz -= n;
|
||||
ptr += n;
|
||||
|
||||
wake_up_all(&rd->fifo_event);
|
||||
}
|
||||
}
|
||||
|
||||
static void rd_write_section(struct msm_rd_state *rd,
|
||||
enum rd_sect_type type, const void *buf, int sz)
|
||||
{
|
||||
rd_write(rd, &type, 4);
|
||||
rd_write(rd, &sz, 4);
|
||||
rd_write(rd, buf, sz);
|
||||
}
|
||||
|
||||
static ssize_t rd_read(struct file *file, char __user *buf,
|
||||
size_t sz, loff_t *ppos)
|
||||
{
|
||||
struct msm_rd_state *rd = file->private_data;
|
||||
struct circ_buf *fifo = &rd->fifo;
|
||||
const char *fptr = &fifo->buf[fifo->tail];
|
||||
int n = 0, ret = 0;
|
||||
|
||||
mutex_lock(&rd->read_lock);
|
||||
|
||||
ret = wait_event_interruptible(rd->fifo_event,
|
||||
circ_count(&rd->fifo) > 0);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
n = min_t(int, sz, circ_count_to_end(&rd->fifo));
|
||||
ret = copy_to_user(buf, fptr, n);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
fifo->tail = (fifo->tail + n) & (BUF_SZ - 1);
|
||||
*ppos += n;
|
||||
|
||||
wake_up_all(&rd->fifo_event);
|
||||
|
||||
out:
|
||||
mutex_unlock(&rd->read_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int rd_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct msm_rd_state *rd = inode->i_private;
|
||||
struct drm_device *dev = rd->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_gpu *gpu = priv->gpu;
|
||||
uint64_t val;
|
||||
uint32_t gpu_id;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
|
||||
if (rd->open || !gpu) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
file->private_data = rd;
|
||||
rd->open = true;
|
||||
|
||||
/* the parsing tools need to know gpu-id to know which
|
||||
* register database to load.
|
||||
*/
|
||||
gpu->funcs->get_param(gpu, MSM_PARAM_GPU_ID, &val);
|
||||
gpu_id = val;
|
||||
|
||||
rd_write_section(rd, RD_GPU_ID, &gpu_id, sizeof(gpu_id));
|
||||
|
||||
out:
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rd_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct msm_rd_state *rd = inode->i_private;
|
||||
rd->open = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct file_operations rd_debugfs_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rd_open,
|
||||
.read = rd_read,
|
||||
.llseek = no_llseek,
|
||||
.release = rd_release,
|
||||
};
|
||||
|
||||
int msm_rd_debugfs_init(struct drm_minor *minor)
|
||||
{
|
||||
struct msm_drm_private *priv = minor->dev->dev_private;
|
||||
struct msm_rd_state *rd;
|
||||
|
||||
/* only create on first minor: */
|
||||
if (priv->rd)
|
||||
return 0;
|
||||
|
||||
rd = kzalloc(sizeof(*rd), GFP_KERNEL);
|
||||
if (!rd)
|
||||
return -ENOMEM;
|
||||
|
||||
rd->dev = minor->dev;
|
||||
rd->fifo.buf = rd->buf;
|
||||
|
||||
mutex_init(&rd->read_lock);
|
||||
priv->rd = rd;
|
||||
|
||||
init_waitqueue_head(&rd->fifo_event);
|
||||
|
||||
rd->node = kzalloc(sizeof(*rd->node), GFP_KERNEL);
|
||||
if (!rd->node)
|
||||
goto fail;
|
||||
|
||||
rd->ent = debugfs_create_file("rd", S_IFREG | S_IRUGO,
|
||||
minor->debugfs_root, rd, &rd_debugfs_fops);
|
||||
if (!rd->ent) {
|
||||
DRM_ERROR("Cannot create /sys/kernel/debug/dri/%s/rd\n",
|
||||
minor->debugfs_root->d_name.name);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rd->node->minor = minor;
|
||||
rd->node->dent = rd->ent;
|
||||
rd->node->info_ent = NULL;
|
||||
|
||||
mutex_lock(&minor->debugfs_lock);
|
||||
list_add(&rd->node->list, &minor->debugfs_list);
|
||||
mutex_unlock(&minor->debugfs_lock);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
msm_rd_debugfs_cleanup(minor);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void msm_rd_debugfs_cleanup(struct drm_minor *minor)
|
||||
{
|
||||
struct msm_drm_private *priv = minor->dev->dev_private;
|
||||
struct msm_rd_state *rd = priv->rd;
|
||||
|
||||
if (!rd)
|
||||
return;
|
||||
|
||||
priv->rd = NULL;
|
||||
|
||||
debugfs_remove(rd->ent);
|
||||
|
||||
if (rd->node) {
|
||||
mutex_lock(&minor->debugfs_lock);
|
||||
list_del(&rd->node->list);
|
||||
mutex_unlock(&minor->debugfs_lock);
|
||||
kfree(rd->node);
|
||||
}
|
||||
|
||||
mutex_destroy(&rd->read_lock);
|
||||
|
||||
kfree(rd);
|
||||
}
|
||||
|
||||
/* called under struct_mutex */
|
||||
void msm_rd_dump_submit(struct msm_gem_submit *submit)
|
||||
{
|
||||
struct drm_device *dev = submit->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_rd_state *rd = priv->rd;
|
||||
char msg[128];
|
||||
int i, n;
|
||||
|
||||
if (!rd->open)
|
||||
return;
|
||||
|
||||
/* writing into fifo is serialized by caller, and
|
||||
* rd->read_lock is used to serialize the reads
|
||||
*/
|
||||
WARN_ON(!mutex_is_locked(&dev->struct_mutex));
|
||||
|
||||
n = snprintf(msg, sizeof(msg), "%.*s/%d: fence=%u",
|
||||
TASK_COMM_LEN, current->comm, task_pid_nr(current),
|
||||
submit->fence);
|
||||
|
||||
rd_write_section(rd, RD_CMD, msg, ALIGN(n, 4));
|
||||
|
||||
/* could be nice to have an option (module-param?) to snapshot
|
||||
* all the bo's associated with the submit. Handy to see vtx
|
||||
* buffers, etc. For now just the cmdstream bo's is enough.
|
||||
*/
|
||||
|
||||
for (i = 0; i < submit->nr_cmds; i++) {
|
||||
uint32_t idx = submit->cmd[i].idx;
|
||||
uint32_t iova = submit->cmd[i].iova;
|
||||
uint32_t szd = submit->cmd[i].size; /* in dwords */
|
||||
struct msm_gem_object *obj = submit->bos[idx].obj;
|
||||
const char *buf = msm_gem_vaddr_locked(&obj->base);
|
||||
|
||||
buf += iova - submit->bos[idx].iova;
|
||||
|
||||
rd_write_section(rd, RD_GPUADDR,
|
||||
(uint32_t[2]){ iova, szd * 4 }, 8);
|
||||
rd_write_section(rd, RD_BUFFER_CONTENTS,
|
||||
buf, szd * 4);
|
||||
|
||||
switch (submit->cmd[i].type) {
|
||||
case MSM_SUBMIT_CMD_IB_TARGET_BUF:
|
||||
/* ignore IB-targets, we've logged the buffer, the
|
||||
* parser tool will follow the IB based on the logged
|
||||
* buffer/gpuaddr, so nothing more to do.
|
||||
*/
|
||||
break;
|
||||
case MSM_SUBMIT_CMD_CTX_RESTORE_BUF:
|
||||
case MSM_SUBMIT_CMD_BUF:
|
||||
rd_write_section(rd, RD_CMDSTREAM_ADDR,
|
||||
(uint32_t[2]){ iova, szd }, 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
61
drivers/gpu/drm/msm/msm_ringbuffer.c
Normal file
61
drivers/gpu/drm/msm/msm_ringbuffer.c
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "msm_ringbuffer.h"
|
||||
#include "msm_gpu.h"
|
||||
|
||||
struct msm_ringbuffer *msm_ringbuffer_new(struct msm_gpu *gpu, int size)
|
||||
{
|
||||
struct msm_ringbuffer *ring;
|
||||
int ret;
|
||||
|
||||
size = ALIGN(size, 4); /* size should be dword aligned */
|
||||
|
||||
ring = kzalloc(sizeof(*ring), GFP_KERNEL);
|
||||
if (!ring) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ring->gpu = gpu;
|
||||
ring->bo = msm_gem_new(gpu->dev, size, MSM_BO_WC);
|
||||
if (IS_ERR(ring->bo)) {
|
||||
ret = PTR_ERR(ring->bo);
|
||||
ring->bo = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ring->start = msm_gem_vaddr_locked(ring->bo);
|
||||
ring->end = ring->start + (size / 4);
|
||||
ring->cur = ring->start;
|
||||
|
||||
ring->size = size;
|
||||
|
||||
return ring;
|
||||
|
||||
fail:
|
||||
if (ring)
|
||||
msm_ringbuffer_destroy(ring);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
void msm_ringbuffer_destroy(struct msm_ringbuffer *ring)
|
||||
{
|
||||
if (ring->bo)
|
||||
drm_gem_object_unreference(ring->bo);
|
||||
kfree(ring);
|
||||
}
|
||||
43
drivers/gpu/drm/msm/msm_ringbuffer.h
Normal file
43
drivers/gpu/drm/msm/msm_ringbuffer.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MSM_RINGBUFFER_H__
|
||||
#define __MSM_RINGBUFFER_H__
|
||||
|
||||
#include "msm_drv.h"
|
||||
|
||||
struct msm_ringbuffer {
|
||||
struct msm_gpu *gpu;
|
||||
int size;
|
||||
struct drm_gem_object *bo;
|
||||
uint32_t *start, *end, *cur;
|
||||
};
|
||||
|
||||
struct msm_ringbuffer *msm_ringbuffer_new(struct msm_gpu *gpu, int size);
|
||||
void msm_ringbuffer_destroy(struct msm_ringbuffer *ring);
|
||||
|
||||
/* ringbuffer helpers (the parts that are same for a3xx/a2xx/z180..) */
|
||||
|
||||
static inline void
|
||||
OUT_RING(struct msm_ringbuffer *ring, uint32_t data)
|
||||
{
|
||||
if (ring->cur == ring->end)
|
||||
ring->cur = ring->start;
|
||||
*(ring->cur++) = data;
|
||||
}
|
||||
|
||||
#endif /* __MSM_RINGBUFFER_H__ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue