mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-03 02:49:09 +08:00
Merge branch 'drm-next' of git://people.freedesktop.org/~airlied/linux
Pull drm updates from Dave Airlie: "Highlights: - drm: Generic display port aux features, primary plane support, drm master management fixes, logging cleanups, enforced locking checks (instead of docs), documentation improvements, minor number handling cleanup, pseudofs for shared inodes. - ttm: add ability to allocate from both ends - i915: broadwell features, power domain and runtime pm, per-process address space infrastructure (not enabled) - msm: power management, hdmi audio support - nouveau: ongoing GPU fault recovery, initial maxwell support, random fixes - exynos: refactored driver to clean up a lot of abstraction, DP support moved into drm, LVDS bridge support added, parallel panel support - gma500: SGX MMU support, SGX irq handling, asle irq work fixes - radeon: video engine bringup, ring handling fixes, use dp aux helpers - vmwgfx: add rendernode support" * 'drm-next' of git://people.freedesktop.org/~airlied/linux: (849 commits) DRM: armada: fix corruption while loading cursors drm/dp_helper: don't return EPROTO for defers (v2) drm/bridge: export ptn3460_init function drm/exynos: remove MODULE_DEVICE_TABLE definitions ARM: dts: exynos4412-trats2: enable exynos/fimd node ARM: dts: exynos4210-trats: enable exynos/fimd node ARM: dts: exynos4412-trats2: add panel node ARM: dts: exynos4210-trats: add panel node ARM: dts: exynos4: add MIPI DSI Master node drm/panel: add S6E8AA0 driver ARM: dts: exynos4210-universal_c210: add proper panel node drm/panel: add ld9040 driver panel/ld9040: add DT bindings panel/s6e8aa0: add DT bindings drm/exynos: add DSIM driver exynos/dsim: add DT bindings drm/exynos: disallow fbdev initialization if no device is connected drm/mipi_dsi: create dsi devices only for nodes with reg property drm/mipi_dsi: add flags to DSI messages Skip intel_crt_init for Dell XPS 8700 ...
This commit is contained in:
commit
e9f37d3a8d
@ -29,12 +29,26 @@
|
||||
</address>
|
||||
</affiliation>
|
||||
</author>
|
||||
<author>
|
||||
<firstname>Daniel</firstname>
|
||||
<surname>Vetter</surname>
|
||||
<contrib>Contributions all over the place</contrib>
|
||||
<affiliation>
|
||||
<orgname>Intel Corporation</orgname>
|
||||
<address>
|
||||
<email>daniel.vetter@ffwll.ch</email>
|
||||
</address>
|
||||
</affiliation>
|
||||
</author>
|
||||
</authorgroup>
|
||||
|
||||
<copyright>
|
||||
<year>2008-2009</year>
|
||||
<year>2012</year>
|
||||
<year>2013-2014</year>
|
||||
<holder>Intel Corporation</holder>
|
||||
</copyright>
|
||||
<copyright>
|
||||
<year>2012</year>
|
||||
<holder>Laurent Pinchart</holder>
|
||||
</copyright>
|
||||
|
||||
@ -60,7 +74,15 @@
|
||||
|
||||
<toc></toc>
|
||||
|
||||
<!-- Introduction -->
|
||||
<part id="drmCore">
|
||||
<title>DRM Core</title>
|
||||
<partintro>
|
||||
<para>
|
||||
This first part of the DRM Developer's Guide documents core DRM code,
|
||||
helper libraries for writting drivers and generic userspace interfaces
|
||||
exposed by DRM drivers.
|
||||
</para>
|
||||
</partintro>
|
||||
|
||||
<chapter id="drmIntroduction">
|
||||
<title>Introduction</title>
|
||||
@ -264,8 +286,8 @@ char *date;</synopsis>
|
||||
<para>
|
||||
The <methodname>load</methodname> method is the driver and device
|
||||
initialization entry point. The method is responsible for allocating and
|
||||
initializing driver private data, specifying supported performance
|
||||
counters, performing resource allocation and mapping (e.g. acquiring
|
||||
initializing driver private data, performing resource allocation and
|
||||
mapping (e.g. acquiring
|
||||
clocks, mapping registers or allocating command buffers), initializing
|
||||
the memory manager (<xref linkend="drm-memory-management"/>), installing
|
||||
the IRQ handler (<xref linkend="drm-irq-registration"/>), setting up
|
||||
@ -295,7 +317,7 @@ char *date;</synopsis>
|
||||
their <methodname>load</methodname> method called with flags to 0.
|
||||
</para>
|
||||
<sect3>
|
||||
<title>Driver Private & Performance Counters</title>
|
||||
<title>Driver Private Data</title>
|
||||
<para>
|
||||
The driver private hangs off the main
|
||||
<structname>drm_device</structname> structure and can be used for
|
||||
@ -307,14 +329,6 @@ char *date;</synopsis>
|
||||
<structname>drm_device</structname>.<structfield>dev_priv</structfield>
|
||||
set to NULL when the driver is unloaded.
|
||||
</para>
|
||||
<para>
|
||||
DRM supports several counters which were used for rough performance
|
||||
characterization. This stat counter system is deprecated and should not
|
||||
be used. If performance monitoring is desired, the developer should
|
||||
investigate and potentially enhance the kernel perf and tracing
|
||||
infrastructure to export GPU related performance information for
|
||||
consumption by performance monitoring tools and applications.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect3 id="drm-irq-registration">
|
||||
<title>IRQ Registration</title>
|
||||
@ -697,55 +711,16 @@ char *date;</synopsis>
|
||||
respectively. The conversion is handled by the DRM core without any
|
||||
driver-specific support.
|
||||
</para>
|
||||
<para>
|
||||
Similar to global names, GEM file descriptors are also used to share GEM
|
||||
objects across processes. They offer additional security: as file
|
||||
descriptors must be explicitly sent over UNIX domain sockets to be shared
|
||||
between applications, they can't be guessed like the globally unique GEM
|
||||
names.
|
||||
</para>
|
||||
<para>
|
||||
Drivers that support GEM file descriptors, also known as the DRM PRIME
|
||||
API, must set the DRIVER_PRIME bit in the struct
|
||||
<structname>drm_driver</structname>
|
||||
<structfield>driver_features</structfield> field, and implement the
|
||||
<methodname>prime_handle_to_fd</methodname> and
|
||||
<methodname>prime_fd_to_handle</methodname> operations.
|
||||
</para>
|
||||
<para>
|
||||
<synopsis>int (*prime_handle_to_fd)(struct drm_device *dev,
|
||||
struct drm_file *file_priv, uint32_t handle,
|
||||
uint32_t flags, int *prime_fd);
|
||||
int (*prime_fd_to_handle)(struct drm_device *dev,
|
||||
struct drm_file *file_priv, int prime_fd,
|
||||
uint32_t *handle);</synopsis>
|
||||
Those two operations convert a handle to a PRIME file descriptor and
|
||||
vice versa. Drivers must use the kernel dma-buf buffer sharing framework
|
||||
to manage the PRIME file descriptors.
|
||||
</para>
|
||||
<para>
|
||||
While non-GEM drivers must implement the operations themselves, GEM
|
||||
drivers must use the <function>drm_gem_prime_handle_to_fd</function>
|
||||
and <function>drm_gem_prime_fd_to_handle</function> helper functions.
|
||||
Those helpers rely on the driver
|
||||
<methodname>gem_prime_export</methodname> and
|
||||
<methodname>gem_prime_import</methodname> operations to create a dma-buf
|
||||
instance from a GEM object (dma-buf exporter role) and to create a GEM
|
||||
object from a dma-buf instance (dma-buf importer role).
|
||||
</para>
|
||||
<para>
|
||||
<synopsis>struct dma_buf * (*gem_prime_export)(struct drm_device *dev,
|
||||
struct drm_gem_object *obj,
|
||||
int flags);
|
||||
struct drm_gem_object * (*gem_prime_import)(struct drm_device *dev,
|
||||
struct dma_buf *dma_buf);</synopsis>
|
||||
These two operations are mandatory for GEM drivers that support DRM
|
||||
PRIME.
|
||||
</para>
|
||||
<sect4>
|
||||
<title>DRM PRIME Helper Functions Reference</title>
|
||||
!Pdrivers/gpu/drm/drm_prime.c PRIME Helpers
|
||||
</sect4>
|
||||
<para>
|
||||
GEM also supports buffer sharing with dma-buf file descriptors through
|
||||
PRIME. GEM-based drivers must use the provided helpers functions to
|
||||
implement the exporting and importing correctly. See <xref linkend="drm-prime-support" />.
|
||||
Since sharing file descriptors is inherently more secure than the
|
||||
easily guessable and global GEM names it is the preferred buffer
|
||||
sharing mechanism. Sharing buffers through GEM names is only supported
|
||||
for legacy userspace. Furthermore PRIME also allows cross-device
|
||||
buffer sharing since it is based on dma-bufs.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect3 id="drm-gem-objects-mapping">
|
||||
<title>GEM Objects Mapping</title>
|
||||
@ -829,62 +804,6 @@ char *date;</synopsis>
|
||||
faults can implement their own mmap file operation handler.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect3>
|
||||
<title>Dumb GEM Objects</title>
|
||||
<para>
|
||||
The GEM API doesn't standardize GEM objects creation and leaves it to
|
||||
driver-specific ioctls. While not an issue for full-fledged graphics
|
||||
stacks that include device-specific userspace components (in libdrm for
|
||||
instance), this limit makes DRM-based early boot graphics unnecessarily
|
||||
complex.
|
||||
</para>
|
||||
<para>
|
||||
Dumb GEM objects partly alleviate the problem by providing a standard
|
||||
API to create dumb buffers suitable for scanout, which can then be used
|
||||
to create KMS frame buffers.
|
||||
</para>
|
||||
<para>
|
||||
To support dumb GEM objects drivers must implement the
|
||||
<methodname>dumb_create</methodname>,
|
||||
<methodname>dumb_destroy</methodname> and
|
||||
<methodname>dumb_map_offset</methodname> operations.
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<synopsis>int (*dumb_create)(struct drm_file *file_priv, struct drm_device *dev,
|
||||
struct drm_mode_create_dumb *args);</synopsis>
|
||||
<para>
|
||||
The <methodname>dumb_create</methodname> operation creates a GEM
|
||||
object suitable for scanout based on the width, height and depth
|
||||
from the struct <structname>drm_mode_create_dumb</structname>
|
||||
argument. It fills the argument's <structfield>handle</structfield>,
|
||||
<structfield>pitch</structfield> and <structfield>size</structfield>
|
||||
fields with a handle for the newly created GEM object and its line
|
||||
pitch and size in bytes.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<synopsis>int (*dumb_destroy)(struct drm_file *file_priv, struct drm_device *dev,
|
||||
uint32_t handle);</synopsis>
|
||||
<para>
|
||||
The <methodname>dumb_destroy</methodname> operation destroys a dumb
|
||||
GEM object created by <methodname>dumb_create</methodname>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<synopsis>int (*dumb_map_offset)(struct drm_file *file_priv, struct drm_device *dev,
|
||||
uint32_t handle, uint64_t *offset);</synopsis>
|
||||
<para>
|
||||
The <methodname>dumb_map_offset</methodname> operation associates an
|
||||
mmap fake offset with the GEM object given by the handle and returns
|
||||
it. Drivers must use the
|
||||
<function>drm_gem_create_mmap_offset</function> function to
|
||||
associate the fake offset as described in
|
||||
<xref linkend="drm-gem-objects-mapping"/>.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</sect3>
|
||||
<sect3>
|
||||
<title>Memory Coherency</title>
|
||||
<para>
|
||||
@ -924,7 +843,99 @@ char *date;</synopsis>
|
||||
abstracted from the client in libdrm.
|
||||
</para>
|
||||
</sect3>
|
||||
</sect2>
|
||||
<sect3>
|
||||
<title>GEM Function Reference</title>
|
||||
!Edrivers/gpu/drm/drm_gem.c
|
||||
</sect3>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>VMA Offset Manager</title>
|
||||
!Pdrivers/gpu/drm/drm_vma_manager.c vma offset manager
|
||||
!Edrivers/gpu/drm/drm_vma_manager.c
|
||||
!Iinclude/drm/drm_vma_manager.h
|
||||
</sect2>
|
||||
<sect2 id="drm-prime-support">
|
||||
<title>PRIME Buffer Sharing</title>
|
||||
<para>
|
||||
PRIME is the cross device buffer sharing framework in drm, originally
|
||||
created for the OPTIMUS range of multi-gpu platforms. To userspace
|
||||
PRIME buffers are dma-buf based file descriptors.
|
||||
</para>
|
||||
<sect3>
|
||||
<title>Overview and Driver Interface</title>
|
||||
<para>
|
||||
Similar to GEM global names, PRIME file descriptors are
|
||||
also used to share buffer objects across processes. They offer
|
||||
additional security: as file descriptors must be explicitly sent over
|
||||
UNIX domain sockets to be shared between applications, they can't be
|
||||
guessed like the globally unique GEM names.
|
||||
</para>
|
||||
<para>
|
||||
Drivers that support the PRIME
|
||||
API must set the DRIVER_PRIME bit in the struct
|
||||
<structname>drm_driver</structname>
|
||||
<structfield>driver_features</structfield> field, and implement the
|
||||
<methodname>prime_handle_to_fd</methodname> and
|
||||
<methodname>prime_fd_to_handle</methodname> operations.
|
||||
</para>
|
||||
<para>
|
||||
<synopsis>int (*prime_handle_to_fd)(struct drm_device *dev,
|
||||
struct drm_file *file_priv, uint32_t handle,
|
||||
uint32_t flags, int *prime_fd);
|
||||
int (*prime_fd_to_handle)(struct drm_device *dev,
|
||||
struct drm_file *file_priv, int prime_fd,
|
||||
uint32_t *handle);</synopsis>
|
||||
Those two operations convert a handle to a PRIME file descriptor and
|
||||
vice versa. Drivers must use the kernel dma-buf buffer sharing framework
|
||||
to manage the PRIME file descriptors. Similar to the mode setting
|
||||
API PRIME is agnostic to the underlying buffer object manager, as
|
||||
long as handles are 32bit unsinged integers.
|
||||
</para>
|
||||
<para>
|
||||
While non-GEM drivers must implement the operations themselves, GEM
|
||||
drivers must use the <function>drm_gem_prime_handle_to_fd</function>
|
||||
and <function>drm_gem_prime_fd_to_handle</function> helper functions.
|
||||
Those helpers rely on the driver
|
||||
<methodname>gem_prime_export</methodname> and
|
||||
<methodname>gem_prime_import</methodname> operations to create a dma-buf
|
||||
instance from a GEM object (dma-buf exporter role) and to create a GEM
|
||||
object from a dma-buf instance (dma-buf importer role).
|
||||
</para>
|
||||
<para>
|
||||
<synopsis>struct dma_buf * (*gem_prime_export)(struct drm_device *dev,
|
||||
struct drm_gem_object *obj,
|
||||
int flags);
|
||||
struct drm_gem_object * (*gem_prime_import)(struct drm_device *dev,
|
||||
struct dma_buf *dma_buf);</synopsis>
|
||||
These two operations are mandatory for GEM drivers that support
|
||||
PRIME.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect3>
|
||||
<title>PRIME Helper Functions</title>
|
||||
!Pdrivers/gpu/drm/drm_prime.c PRIME Helpers
|
||||
</sect3>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>PRIME Function References</title>
|
||||
!Edrivers/gpu/drm/drm_prime.c
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>DRM MM Range Allocator</title>
|
||||
<sect3>
|
||||
<title>Overview</title>
|
||||
!Pdrivers/gpu/drm/drm_mm.c Overview
|
||||
</sect3>
|
||||
<sect3>
|
||||
<title>LRU Scan/Eviction Support</title>
|
||||
!Pdrivers/gpu/drm/drm_mm.c lru scan roaster
|
||||
</sect3>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>DRM MM Range Allocator Function References</title>
|
||||
!Edrivers/gpu/drm/drm_mm.c
|
||||
!Iinclude/drm/drm_mm.h
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<!-- Internals: mode setting -->
|
||||
@ -952,6 +963,11 @@ int max_width, max_height;</synopsis>
|
||||
<para>Mode setting functions.</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<sect2>
|
||||
<title>Display Modes Function Reference</title>
|
||||
!Iinclude/drm/drm_modes.h
|
||||
!Edrivers/gpu/drm/drm_modes.c
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Frame Buffer Creation</title>
|
||||
<synopsis>struct drm_framebuffer *(*fb_create)(struct drm_device *dev,
|
||||
@ -968,9 +984,11 @@ int max_width, max_height;</synopsis>
|
||||
Frame buffers rely on the underneath memory manager for low-level memory
|
||||
operations. When creating a frame buffer applications pass a memory
|
||||
handle (or a list of memory handles for multi-planar formats) through
|
||||
the <parameter>drm_mode_fb_cmd2</parameter> argument. This document
|
||||
assumes that the driver uses GEM, those handles thus reference GEM
|
||||
objects.
|
||||
the <parameter>drm_mode_fb_cmd2</parameter> argument. For drivers using
|
||||
GEM as their userspace buffer management interface this would be a GEM
|
||||
handle. Drivers are however free to use their own backing storage object
|
||||
handles, e.g. vmwgfx directly exposes special TTM handles to userspace
|
||||
and so expects TTM handles in the create ioctl and not GEM handles.
|
||||
</para>
|
||||
<para>
|
||||
Drivers must first validate the requested frame buffer parameters passed
|
||||
@ -992,7 +1010,7 @@ int max_width, max_height;</synopsis>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The initailization of the new framebuffer instance is finalized with a
|
||||
The initialization of the new framebuffer instance is finalized with a
|
||||
call to <function>drm_framebuffer_init</function> which takes a pointer
|
||||
to DRM frame buffer operations (struct
|
||||
<structname>drm_framebuffer_funcs</structname>). Note that this function
|
||||
@ -1042,7 +1060,7 @@ int max_width, max_height;</synopsis>
|
||||
<para>
|
||||
The lifetime of a drm framebuffer is controlled with a reference count,
|
||||
drivers can grab additional references with
|
||||
<function>drm_framebuffer_reference</function> </para> and drop them
|
||||
<function>drm_framebuffer_reference</function>and drop them
|
||||
again with <function>drm_framebuffer_unreference</function>. For
|
||||
driver-private framebuffers for which the last reference is never
|
||||
dropped (e.g. for the fbdev framebuffer when the struct
|
||||
@ -1050,6 +1068,72 @@ int max_width, max_height;</synopsis>
|
||||
helper struct) drivers can manually clean up a framebuffer at module
|
||||
unload time with
|
||||
<function>drm_framebuffer_unregister_private</function>.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Dumb Buffer Objects</title>
|
||||
<para>
|
||||
The KMS API doesn't standardize backing storage object creation and
|
||||
leaves it to driver-specific ioctls. Furthermore actually creating a
|
||||
buffer object even for GEM-based drivers is done through a
|
||||
driver-specific ioctl - GEM only has a common userspace interface for
|
||||
sharing and destroying objects. While not an issue for full-fledged
|
||||
graphics stacks that include device-specific userspace components (in
|
||||
libdrm for instance), this limit makes DRM-based early boot graphics
|
||||
unnecessarily complex.
|
||||
</para>
|
||||
<para>
|
||||
Dumb objects partly alleviate the problem by providing a standard
|
||||
API to create dumb buffers suitable for scanout, which can then be used
|
||||
to create KMS frame buffers.
|
||||
</para>
|
||||
<para>
|
||||
To support dumb objects drivers must implement the
|
||||
<methodname>dumb_create</methodname>,
|
||||
<methodname>dumb_destroy</methodname> and
|
||||
<methodname>dumb_map_offset</methodname> operations.
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<synopsis>int (*dumb_create)(struct drm_file *file_priv, struct drm_device *dev,
|
||||
struct drm_mode_create_dumb *args);</synopsis>
|
||||
<para>
|
||||
The <methodname>dumb_create</methodname> operation creates a driver
|
||||
object (GEM or TTM handle) suitable for scanout based on the
|
||||
width, height and depth from the struct
|
||||
<structname>drm_mode_create_dumb</structname> argument. It fills the
|
||||
argument's <structfield>handle</structfield>,
|
||||
<structfield>pitch</structfield> and <structfield>size</structfield>
|
||||
fields with a handle for the newly created object and its line
|
||||
pitch and size in bytes.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<synopsis>int (*dumb_destroy)(struct drm_file *file_priv, struct drm_device *dev,
|
||||
uint32_t handle);</synopsis>
|
||||
<para>
|
||||
The <methodname>dumb_destroy</methodname> operation destroys a dumb
|
||||
object created by <methodname>dumb_create</methodname>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<synopsis>int (*dumb_map_offset)(struct drm_file *file_priv, struct drm_device *dev,
|
||||
uint32_t handle, uint64_t *offset);</synopsis>
|
||||
<para>
|
||||
The <methodname>dumb_map_offset</methodname> operation associates an
|
||||
mmap fake offset with the object given by the handle and returns
|
||||
it. Drivers must use the
|
||||
<function>drm_gem_create_mmap_offset</function> function to
|
||||
associate the fake offset as described in
|
||||
<xref linkend="drm-gem-objects-mapping"/>.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<para>
|
||||
Note that dumb objects may not be used for gpu acceleration, as has been
|
||||
attempted on some ARM embedded platforms. Such drivers really must have
|
||||
a hardware-specific ioctl to allocate suitable buffer objects.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Output Polling</title>
|
||||
@ -1110,7 +1194,7 @@ int max_width, max_height;</synopsis>
|
||||
pointer to CRTC functions.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect3>
|
||||
<sect3 id="drm-kms-crtcops">
|
||||
<title>CRTC Operations</title>
|
||||
<sect4>
|
||||
<title>Set Configuration</title>
|
||||
@ -1130,8 +1214,11 @@ int max_width, max_height;</synopsis>
|
||||
This operation is called with the mode config lock held.
|
||||
</para>
|
||||
<note><para>
|
||||
FIXME: How should set_config interact with DPMS? If the CRTC is
|
||||
suspended, should it be resumed?
|
||||
Note that the drm core has no notion of restoring the mode setting
|
||||
state after resume, since all resume handling is in the full
|
||||
responsibility of the driver. The common mode setting helper library
|
||||
though provides a helper which can be used for this:
|
||||
<function>drm_helper_resume_force_mode</function>.
|
||||
</para></note>
|
||||
</sect4>
|
||||
<sect4>
|
||||
@ -1248,15 +1335,47 @@ int max_width, max_height;</synopsis>
|
||||
optionally scale it to a destination size. The result is then blended
|
||||
with or overlayed on top of a CRTC.
|
||||
</para>
|
||||
<para>
|
||||
The DRM core recognizes three types of planes:
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
DRM_PLANE_TYPE_PRIMARY represents a "main" plane for a CRTC. Primary
|
||||
planes are the planes operated upon by by CRTC modesetting and flipping
|
||||
operations described in <xref linkend="drm-kms-crtcops"/>.
|
||||
</listitem>
|
||||
<listitem>
|
||||
DRM_PLANE_TYPE_CURSOR represents a "cursor" plane for a CRTC. Cursor
|
||||
planes are the planes operated upon by the DRM_IOCTL_MODE_CURSOR and
|
||||
DRM_IOCTL_MODE_CURSOR2 ioctls.
|
||||
</listitem>
|
||||
<listitem>
|
||||
DRM_PLANE_TYPE_OVERLAY represents all non-primary, non-cursor planes.
|
||||
Some drivers refer to these types of planes as "sprites" internally.
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
For compatibility with legacy userspace, only overlay planes are made
|
||||
available to userspace by default. Userspace clients may set the
|
||||
DRM_CLIENT_CAP_UNIVERSAL_PLANES client capability bit to indicate that
|
||||
they wish to receive a universal plane list containing all plane types.
|
||||
</para>
|
||||
<sect3>
|
||||
<title>Plane Initialization</title>
|
||||
<para>
|
||||
Planes are optional. To create a plane, a KMS drivers allocates and
|
||||
To create a plane, a KMS drivers allocates and
|
||||
zeroes an instances of struct <structname>drm_plane</structname>
|
||||
(possibly as part of a larger structure) and registers it with a call
|
||||
to <function>drm_plane_init</function>. The function takes a bitmask
|
||||
to <function>drm_universal_plane_init</function>. The function takes a bitmask
|
||||
of the CRTCs that can be associated with the plane, a pointer to the
|
||||
plane functions and a list of format supported formats.
|
||||
plane functions, a list of format supported formats, and the type of
|
||||
plane (primary, cursor, or overlay) being initialized.
|
||||
</para>
|
||||
<para>
|
||||
Cursor and overlay planes are optional. All drivers should provide
|
||||
one primary plane per CRTC (although this requirement may change in
|
||||
the future); drivers that do not wish to provide special handling for
|
||||
primary planes may make use of the helper functions described in
|
||||
<xref linkend="drm-kms-planehelpers"/> to create and register a
|
||||
primary plane with standard capabilities.
|
||||
</para>
|
||||
</sect3>
|
||||
<sect3>
|
||||
@ -1687,7 +1806,7 @@ void intel_crt_init(struct drm_device *dev)
|
||||
<sect1>
|
||||
<title>Mode Setting Helper Functions</title>
|
||||
<para>
|
||||
The CRTC, encoder and connector functions provided by the drivers
|
||||
The plane, CRTC, encoder and connector functions provided by the drivers
|
||||
implement the DRM API. They're called by the DRM core and ioctl handlers
|
||||
to handle device state changes and configuration request. As implementing
|
||||
those functions often requires logic not specific to drivers, mid-layer
|
||||
@ -1695,8 +1814,8 @@ void intel_crt_init(struct drm_device *dev)
|
||||
</para>
|
||||
<para>
|
||||
The DRM core contains one mid-layer implementation. The mid-layer provides
|
||||
implementations of several CRTC, encoder and connector functions (called
|
||||
from the top of the mid-layer) that pre-process requests and call
|
||||
implementations of several plane, CRTC, encoder and connector functions
|
||||
(called from the top of the mid-layer) that pre-process requests and call
|
||||
lower-level functions provided by the driver (at the bottom of the
|
||||
mid-layer). For instance, the
|
||||
<function>drm_crtc_helper_set_config</function> function can be used to
|
||||
@ -2134,7 +2253,7 @@ void intel_crt_init(struct drm_device *dev)
|
||||
set the <structfield>display_info</structfield>
|
||||
<structfield>width_mm</structfield> and
|
||||
<structfield>height_mm</structfield> fields if they haven't been set
|
||||
already (for instance at initilization time when a fixed-size panel is
|
||||
already (for instance at initialization time when a fixed-size panel is
|
||||
attached to the connector). The mode <structfield>width_mm</structfield>
|
||||
and <structfield>height_mm</structfield> fields are only used internally
|
||||
during EDID parsing and should not be set when creating modes manually.
|
||||
@ -2196,10 +2315,19 @@ void intel_crt_init(struct drm_device *dev)
|
||||
!Edrivers/gpu/drm/drm_flip_work.c
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>VMA Offset Manager</title>
|
||||
!Pdrivers/gpu/drm/drm_vma_manager.c vma offset manager
|
||||
!Edrivers/gpu/drm/drm_vma_manager.c
|
||||
!Iinclude/drm/drm_vma_manager.h
|
||||
<title>HDMI Infoframes Helper Reference</title>
|
||||
<para>
|
||||
Strictly speaking this is not a DRM helper library but generally useable
|
||||
by any driver interfacing with HDMI outputs like v4l or alsa drivers.
|
||||
But it nicely fits into the overall topic of mode setting helper
|
||||
libraries and hence is also included here.
|
||||
</para>
|
||||
!Iinclude/linux/hdmi.h
|
||||
!Edrivers/video/hdmi.c
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title id="drm-kms-planehelpers">Plane Helper Reference</title>
|
||||
!Edrivers/gpu/drm/drm_plane_helper.c Plane Helpers
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
@ -2561,42 +2689,44 @@ int num_ioctls;</synopsis>
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Command submission & fencing</title>
|
||||
<title>Legacy Support Code</title>
|
||||
<para>
|
||||
This should cover a few device-specific command submission
|
||||
implementations.
|
||||
The section very brievely covers some of the old legacy support code which
|
||||
is only used by old DRM drivers which have done a so-called shadow-attach
|
||||
to the underlying device instead of registering as a real driver. This
|
||||
also includes some of the old generic buffer mangement and command
|
||||
submission code. Do not use any of this in new and modern drivers.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<!-- Internals: suspend/resume -->
|
||||
<sect2>
|
||||
<title>Legacy Suspend/Resume</title>
|
||||
<para>
|
||||
The DRM core provides some suspend/resume code, but drivers wanting full
|
||||
suspend/resume support should provide save() and restore() functions.
|
||||
These are called at suspend, hibernate, or resume time, and should perform
|
||||
any state save or restore required by your device across suspend or
|
||||
hibernate states.
|
||||
</para>
|
||||
<synopsis>int (*suspend) (struct drm_device *, pm_message_t state);
|
||||
int (*resume) (struct drm_device *);</synopsis>
|
||||
<para>
|
||||
Those are legacy suspend and resume methods which
|
||||
<emphasis>only</emphasis> work with the legacy shadow-attach driver
|
||||
registration functions. New driver should use the power management
|
||||
interface provided by their bus type (usually through
|
||||
the struct <structname>device_driver</structname> dev_pm_ops) and set
|
||||
these methods to NULL.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
<sect1>
|
||||
<title>Suspend/Resume</title>
|
||||
<para>
|
||||
The DRM core provides some suspend/resume code, but drivers wanting full
|
||||
suspend/resume support should provide save() and restore() functions.
|
||||
These are called at suspend, hibernate, or resume time, and should perform
|
||||
any state save or restore required by your device across suspend or
|
||||
hibernate states.
|
||||
</para>
|
||||
<synopsis>int (*suspend) (struct drm_device *, pm_message_t state);
|
||||
int (*resume) (struct drm_device *);</synopsis>
|
||||
<para>
|
||||
Those are legacy suspend and resume methods. New driver should use the
|
||||
power management interface provided by their bus type (usually through
|
||||
the struct <structname>device_driver</structname> dev_pm_ops) and set
|
||||
these methods to NULL.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>DMA services</title>
|
||||
<para>
|
||||
This should cover how DMA mapping etc. is supported by the core.
|
||||
These functions are deprecated and should not be used.
|
||||
</para>
|
||||
<sect2>
|
||||
<title>Legacy DMA Services</title>
|
||||
<para>
|
||||
This should cover how DMA mapping etc. is supported by the core.
|
||||
These functions are deprecated and should not be used.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
</chapter>
|
||||
|
||||
@ -2658,8 +2788,8 @@ int (*resume) (struct drm_device *);</synopsis>
|
||||
DRM core provides multiple character-devices for user-space to use.
|
||||
Depending on which device is opened, user-space can perform a different
|
||||
set of operations (mainly ioctls). The primary node is always created
|
||||
and called <term>card<num></term>. Additionally, a currently
|
||||
unused control node, called <term>controlD<num></term> is also
|
||||
and called card<num>. Additionally, a currently
|
||||
unused control node, called controlD<num> is also
|
||||
created. The primary node provides all legacy operations and
|
||||
historically was the only interface used by userspace. With KMS, the
|
||||
control node was introduced. However, the planned KMS control interface
|
||||
@ -2674,21 +2804,21 @@ int (*resume) (struct drm_device *);</synopsis>
|
||||
nodes were introduced. Render nodes solely serve render clients, that
|
||||
is, no modesetting or privileged ioctls can be issued on render nodes.
|
||||
Only non-global rendering commands are allowed. If a driver supports
|
||||
render nodes, it must advertise it via the <term>DRIVER_RENDER</term>
|
||||
render nodes, it must advertise it via the DRIVER_RENDER
|
||||
DRM driver capability. If not supported, the primary node must be used
|
||||
for render clients together with the legacy drmAuth authentication
|
||||
procedure.
|
||||
</para>
|
||||
<para>
|
||||
If a driver advertises render node support, DRM core will create a
|
||||
separate render node called <term>renderD<num></term>. There will
|
||||
separate render node called renderD<num>. There will
|
||||
be one render node per device. No ioctls except PRIME-related ioctls
|
||||
will be allowed on this node. Especially <term>GEM_OPEN</term> will be
|
||||
will be allowed on this node. Especially GEM_OPEN will be
|
||||
explicitly prohibited. Render nodes are designed to avoid the
|
||||
buffer-leaks, which occur if clients guess the flink names or mmap
|
||||
offsets on the legacy interface. Additionally to this basic interface,
|
||||
drivers must mark their driver-dependent render-only ioctls as
|
||||
<term>DRM_RENDER_ALLOW</term> so render clients can use them. Driver
|
||||
DRM_RENDER_ALLOW so render clients can use them. Driver
|
||||
authors must be careful not to allow any privileged ioctls on render
|
||||
nodes.
|
||||
</para>
|
||||
@ -2749,15 +2879,73 @@ int (*resume) (struct drm_device *);</synopsis>
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
</part>
|
||||
<part id="drmDrivers">
|
||||
<title>DRM Drivers</title>
|
||||
|
||||
<!-- API reference -->
|
||||
|
||||
<appendix id="drmDriverApi">
|
||||
<title>DRM Driver API</title>
|
||||
<partintro>
|
||||
<para>
|
||||
Include auto-generated API reference here (need to reference it
|
||||
from paragraphs above too).
|
||||
This second part of the DRM Developer's Guide documents driver code,
|
||||
implementation details and also all the driver-specific userspace
|
||||
interfaces. Especially since all hardware-acceleration interfaces to
|
||||
userspace are driver specific for efficiency and other reasons these
|
||||
interfaces can be rather substantial. Hence every driver has its own
|
||||
chapter.
|
||||
</para>
|
||||
</appendix>
|
||||
</partintro>
|
||||
|
||||
<chapter id="drmI915">
|
||||
<title>drm/i915 Intel GFX Driver</title>
|
||||
<para>
|
||||
The drm/i915 driver supports all (with the exception of some very early
|
||||
models) integrated GFX chipsets with both Intel display and rendering
|
||||
blocks. This excludes a set of SoC platforms with an SGX rendering unit,
|
||||
those have basic support through the gma500 drm driver.
|
||||
</para>
|
||||
<sect1>
|
||||
<title>Display Hardware Handling</title>
|
||||
<para>
|
||||
This section covers everything related to the display hardware including
|
||||
the mode setting infrastructure, plane, sprite and cursor handling and
|
||||
display, output probing and related topics.
|
||||
</para>
|
||||
<sect2>
|
||||
<title>Mode Setting Infrastructure</title>
|
||||
<para>
|
||||
The i915 driver is thus far the only DRM driver which doesn't use the
|
||||
common DRM helper code to implement mode setting sequences. Thus it
|
||||
has its own tailor-made infrastructure for executing a display
|
||||
configuration change.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Plane Configuration</title>
|
||||
<para>
|
||||
This section covers plane configuration and composition with the
|
||||
primary plane, sprites, cursors and overlays. This includes the
|
||||
infrastructure to do atomic vsync'ed updates of all this state and
|
||||
also tightly coupled topics like watermark setup and computation,
|
||||
framebuffer compression and panel self refresh.
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Output Probing</title>
|
||||
<para>
|
||||
This section covers output probing and related infrastructure like the
|
||||
hotplug interrupt storm detection and mitigation code. Note that the
|
||||
i915 driver still uses most of the common DRM helper code for output
|
||||
probing, so those sections fully apply.
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<sect1>
|
||||
<title>Memory Management and Command Submission</title>
|
||||
<para>
|
||||
This sections covers all things related to the GEM implementation in the
|
||||
i915 driver.
|
||||
</para>
|
||||
</sect1>
|
||||
</chapter>
|
||||
</part>
|
||||
</book>
|
||||
|
27
Documentation/devicetree/bindings/drm/bridge/ptn3460.txt
Normal file
27
Documentation/devicetree/bindings/drm/bridge/ptn3460.txt
Normal file
@ -0,0 +1,27 @@
|
||||
ptn3460 bridge bindings
|
||||
|
||||
Required properties:
|
||||
- compatible: "nxp,ptn3460"
|
||||
- reg: i2c address of the bridge
|
||||
- powerdown-gpio: OF device-tree gpio specification
|
||||
- reset-gpio: OF device-tree gpio specification
|
||||
- edid-emulation: The EDID emulation entry to use
|
||||
+-------+------------+------------------+
|
||||
| Value | Resolution | Description |
|
||||
| 0 | 1024x768 | NXP Generic |
|
||||
| 1 | 1920x1080 | NXP Generic |
|
||||
| 2 | 1920x1080 | NXP Generic |
|
||||
| 3 | 1600x900 | Samsung LTM200KT |
|
||||
| 4 | 1920x1080 | Samsung LTM230HT |
|
||||
| 5 | 1366x768 | NXP Generic |
|
||||
| 6 | 1600x900 | ChiMei M215HGE |
|
||||
+-------+------------+------------------+
|
||||
|
||||
Example:
|
||||
lvds-bridge@20 {
|
||||
compatible = "nxp,ptn3460";
|
||||
reg = <0x20>;
|
||||
powerdown-gpio = <&gpy2 5 1 0 0>;
|
||||
reset-gpio = <&gpx1 5 1 0 0>;
|
||||
edid-emulation = <5>;
|
||||
};
|
27
Documentation/devicetree/bindings/drm/i2c/tda998x.txt
Normal file
27
Documentation/devicetree/bindings/drm/i2c/tda998x.txt
Normal file
@ -0,0 +1,27 @@
|
||||
Device-Tree bindings for the NXP TDA998x HDMI transmitter
|
||||
|
||||
Required properties;
|
||||
- compatible: must be "nxp,tda998x"
|
||||
|
||||
Optional properties:
|
||||
- interrupts: interrupt number and trigger type
|
||||
default: polling
|
||||
|
||||
- pinctrl-0: pin control group to be used for
|
||||
screen plug/unplug interrupt.
|
||||
|
||||
- pinctrl-names: must contain a "default" entry.
|
||||
|
||||
- video-ports: 24 bits value which defines how the video controller
|
||||
output is wired to the TDA998x input - default: <0x230145>
|
||||
|
||||
Example:
|
||||
|
||||
tda998x: hdmi-encoder {
|
||||
compatible = "nxp,tda998x";
|
||||
reg = <0x70>;
|
||||
interrupt-parent = <&gpio0>;
|
||||
interrupts = <27 2>; /* falling edge */
|
||||
pinctrl-0 = <&pmx_camera>;
|
||||
pinctrl-names = "default";
|
||||
};
|
@ -190,6 +190,48 @@ of the following host1x client modules:
|
||||
- nvidia,edid: supplies a binary EDID blob
|
||||
- nvidia,panel: phandle of a display panel
|
||||
|
||||
- sor: serial output resource
|
||||
|
||||
Required properties:
|
||||
- compatible: "nvidia,tegra124-sor"
|
||||
- reg: Physical base address and length of the controller's registers.
|
||||
- interrupts: The interrupt outputs from the controller.
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
See ../clocks/clock-bindings.txt for details.
|
||||
- clock-names: Must include the following entries:
|
||||
- sor: clock input for the SOR hardware
|
||||
- parent: input for the pixel clock
|
||||
- dp: reference clock for the SOR clock
|
||||
- safe: safe reference for the SOR clock during power up
|
||||
- resets: Must contain an entry for each entry in reset-names.
|
||||
See ../reset/reset.txt for details.
|
||||
- reset-names: Must include the following entries:
|
||||
- sor
|
||||
|
||||
Optional properties:
|
||||
- nvidia,ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
|
||||
- nvidia,hpd-gpio: specifies a GPIO used for hotplug detection
|
||||
- nvidia,edid: supplies a binary EDID blob
|
||||
- nvidia,panel: phandle of a display panel
|
||||
|
||||
Optional properties when driving an eDP output:
|
||||
- nvidia,dpaux: phandle to a DispayPort AUX interface
|
||||
|
||||
- dpaux: DisplayPort AUX interface
|
||||
- compatible: "nvidia,tegra124-dpaux"
|
||||
- reg: Physical base address and length of the controller's registers.
|
||||
- interrupts: The interrupt outputs from the controller.
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
See ../clocks/clock-bindings.txt for details.
|
||||
- clock-names: Must include the following entries:
|
||||
- dpaux: clock input for the DPAUX hardware
|
||||
- parent: reference clock
|
||||
- resets: Must contain an entry for each entry in reset-names.
|
||||
See ../reset/reset.txt for details.
|
||||
- reset-names: Must include the following entries:
|
||||
- dpaux
|
||||
- vdd-supply: phandle of a supply that powers the DisplayPort link
|
||||
|
||||
Example:
|
||||
|
||||
/ {
|
||||
|
@ -0,0 +1,7 @@
|
||||
LG Corporation 7" WXGA TFT LCD panel
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "lg,ld070wx3-sl01"
|
||||
|
||||
This binding is compatible with the simple-panel binding, which is specified
|
||||
in simple-panel.txt in this directory.
|
@ -0,0 +1,7 @@
|
||||
LG Corporation 5" HD TFT LCD panel
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "lg,lh500wx1-sd03"
|
||||
|
||||
This binding is compatible with the simple-panel binding, which is specified
|
||||
in simple-panel.txt in this directory.
|
7
Documentation/devicetree/bindings/panel/lg,lp129qe.txt
Normal file
7
Documentation/devicetree/bindings/panel/lg,lp129qe.txt
Normal file
@ -0,0 +1,7 @@
|
||||
LG 12.9" (2560x1700 pixels) TFT LCD panel
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "lg,lp129qe"
|
||||
|
||||
This binding is compatible with the simple-panel binding, which is specified
|
||||
in simple-panel.txt in this directory.
|
66
Documentation/devicetree/bindings/panel/samsung,ld9040.txt
Normal file
66
Documentation/devicetree/bindings/panel/samsung,ld9040.txt
Normal file
@ -0,0 +1,66 @@
|
||||
Samsung LD9040 AMOLED LCD parallel RGB panel with SPI control bus
|
||||
|
||||
Required properties:
|
||||
- compatible: "samsung,ld9040"
|
||||
- reg: address of the panel on SPI bus
|
||||
- vdd3-supply: core voltage supply
|
||||
- vci-supply: voltage supply for analog circuits
|
||||
- reset-gpios: a GPIO spec for the reset pin
|
||||
- display-timings: timings for the connected panel according to [1]
|
||||
|
||||
The panel must obey rules for SPI slave device specified in document [2].
|
||||
|
||||
Optional properties:
|
||||
- power-on-delay: delay after turning regulators on [ms]
|
||||
- reset-delay: delay after reset sequence [ms]
|
||||
- panel-width-mm: physical panel width [mm]
|
||||
- panel-height-mm: physical panel height [mm]
|
||||
|
||||
The device node can contain one 'port' child node with one child
|
||||
'endpoint' node, according to the bindings defined in [3]. This
|
||||
node should describe panel's video bus.
|
||||
|
||||
[1]: Documentation/devicetree/bindings/video/display-timing.txt
|
||||
[2]: Documentation/devicetree/bindings/spi/spi-bus.txt
|
||||
[3]: Documentation/devicetree/bindings/media/video-interfaces.txt
|
||||
|
||||
Example:
|
||||
|
||||
lcd@0 {
|
||||
compatible = "samsung,ld9040";
|
||||
reg = <0>;
|
||||
vdd3-supply = <&ldo7_reg>;
|
||||
vci-supply = <&ldo17_reg>;
|
||||
reset-gpios = <&gpy4 5 0>;
|
||||
spi-max-frequency = <1200000>;
|
||||
spi-cpol;
|
||||
spi-cpha;
|
||||
power-on-delay = <10>;
|
||||
reset-delay = <10>;
|
||||
panel-width-mm = <90>;
|
||||
panel-height-mm = <154>;
|
||||
|
||||
display-timings {
|
||||
timing {
|
||||
clock-frequency = <23492370>;
|
||||
hactive = <480>;
|
||||
vactive = <800>;
|
||||
hback-porch = <16>;
|
||||
hfront-porch = <16>;
|
||||
vback-porch = <2>;
|
||||
vfront-porch = <28>;
|
||||
hsync-len = <2>;
|
||||
vsync-len = <1>;
|
||||
hsync-active = <0>;
|
||||
vsync-active = <0>;
|
||||
de-active = <0>;
|
||||
pixelclk-active = <0>;
|
||||
};
|
||||
};
|
||||
|
||||
port {
|
||||
lcd_ep: endpoint {
|
||||
remote-endpoint = <&fimd_dpi_ep>;
|
||||
};
|
||||
};
|
||||
};
|
56
Documentation/devicetree/bindings/panel/samsung,s6e8aa0.txt
Normal file
56
Documentation/devicetree/bindings/panel/samsung,s6e8aa0.txt
Normal file
@ -0,0 +1,56 @@
|
||||
Samsung S6E8AA0 AMOLED LCD 5.3 inch panel
|
||||
|
||||
Required properties:
|
||||
- compatible: "samsung,s6e8aa0"
|
||||
- reg: the virtual channel number of a DSI peripheral
|
||||
- vdd3-supply: core voltage supply
|
||||
- vci-supply: voltage supply for analog circuits
|
||||
- reset-gpios: a GPIO spec for the reset pin
|
||||
- display-timings: timings for the connected panel as described by [1]
|
||||
|
||||
Optional properties:
|
||||
- power-on-delay: delay after turning regulators on [ms]
|
||||
- reset-delay: delay after reset sequence [ms]
|
||||
- init-delay: delay after initialization sequence [ms]
|
||||
- panel-width-mm: physical panel width [mm]
|
||||
- panel-height-mm: physical panel height [mm]
|
||||
- flip-horizontal: boolean to flip image horizontally
|
||||
- flip-vertical: boolean to flip image vertically
|
||||
|
||||
The device node can contain one 'port' child node with one child
|
||||
'endpoint' node, according to the bindings defined in [2]. This
|
||||
node should describe panel's video bus.
|
||||
|
||||
[1]: Documentation/devicetree/bindings/video/display-timing.txt
|
||||
[2]: Documentation/devicetree/bindings/media/video-interfaces.txt
|
||||
|
||||
Example:
|
||||
|
||||
panel {
|
||||
compatible = "samsung,s6e8aa0";
|
||||
reg = <0>;
|
||||
vdd3-supply = <&vcclcd_reg>;
|
||||
vci-supply = <&vlcd_reg>;
|
||||
reset-gpios = <&gpy4 5 0>;
|
||||
power-on-delay= <50>;
|
||||
reset-delay = <100>;
|
||||
init-delay = <100>;
|
||||
panel-width-mm = <58>;
|
||||
panel-height-mm = <103>;
|
||||
flip-horizontal;
|
||||
flip-vertical;
|
||||
|
||||
display-timings {
|
||||
timing0: timing-0 {
|
||||
clock-frequency = <57153600>;
|
||||
hactive = <720>;
|
||||
vactive = <1280>;
|
||||
hfront-porch = <5>;
|
||||
hback-porch = <5>;
|
||||
hsync-len = <5>;
|
||||
vfront-porch = <13>;
|
||||
vback-porch = <1>;
|
||||
vsync-len = <2>;
|
||||
};
|
||||
};
|
||||
};
|
@ -49,6 +49,8 @@ Required properties for dp-controller:
|
||||
-samsung,lane-count:
|
||||
number of lanes supported by the panel.
|
||||
LANE_COUNT1 = 1, LANE_COUNT2 = 2, LANE_COUNT4 = 4
|
||||
- display-timings: timings for the connected panel as described by
|
||||
Documentation/devicetree/bindings/video/display-timing.txt
|
||||
|
||||
Optional properties for dp-controller:
|
||||
-interlaced:
|
||||
@ -84,4 +86,19 @@ Board Specific portion:
|
||||
samsung,color-depth = <1>;
|
||||
samsung,link-rate = <0x0a>;
|
||||
samsung,lane-count = <4>;
|
||||
|
||||
display-timings {
|
||||
native-mode = <&lcd_timing>;
|
||||
lcd_timing: 1366x768 {
|
||||
clock-frequency = <70589280>;
|
||||
hactive = <1366>;
|
||||
vactive = <768>;
|
||||
hfront-porch = <40>;
|
||||
hback-porch = <40>;
|
||||
hsync-len = <32>;
|
||||
vback-porch = <10>;
|
||||
vfront-porch = <12>;
|
||||
vsync-len = <6>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
80
Documentation/devicetree/bindings/video/exynos_dsim.txt
Normal file
80
Documentation/devicetree/bindings/video/exynos_dsim.txt
Normal file
@ -0,0 +1,80 @@
|
||||
Exynos MIPI DSI Master
|
||||
|
||||
Required properties:
|
||||
- compatible: "samsung,exynos4210-mipi-dsi"
|
||||
- reg: physical base address and length of the registers set for the device
|
||||
- interrupts: should contain DSI interrupt
|
||||
- clocks: list of clock specifiers, must contain an entry for each required
|
||||
entry in clock-names
|
||||
- clock-names: should include "bus_clk"and "pll_clk" entries
|
||||
- phys: list of phy specifiers, must contain an entry for each required
|
||||
entry in phy-names
|
||||
- phy-names: should include "dsim" entry
|
||||
- vddcore-supply: MIPI DSIM Core voltage supply (e.g. 1.1V)
|
||||
- vddio-supply: MIPI DSIM I/O and PLL voltage supply (e.g. 1.8V)
|
||||
- samsung,pll-clock-frequency: specifies frequency of the "pll_clk" clock
|
||||
- #address-cells, #size-cells: should be set respectively to <1> and <0>
|
||||
according to DSI host bindings (see MIPI DSI bindings [1])
|
||||
|
||||
Optional properties:
|
||||
- samsung,power-domain: a phandle to DSIM power domain node
|
||||
|
||||
Child nodes:
|
||||
Should contain DSI peripheral nodes (see MIPI DSI bindings [1]).
|
||||
|
||||
Video interfaces:
|
||||
Device node can contain video interface port nodes according to [2].
|
||||
The following are properties specific to those nodes:
|
||||
|
||||
port node:
|
||||
- reg: (required) can be 0 for input RGB/I80 port or 1 for DSI port;
|
||||
|
||||
endpoint node of DSI port (reg = 1):
|
||||
- samsung,burst-clock-frequency: specifies DSI frequency in high-speed burst
|
||||
mode
|
||||
- samsung,esc-clock-frequency: specifies DSI frequency in escape mode
|
||||
|
||||
[1]: Documentation/devicetree/bindings/mipi/dsi/mipi-dsi-bus.txt
|
||||
[2]: Documentation/devicetree/bindings/media/video-interfaces.txt
|
||||
|
||||
Example:
|
||||
|
||||
dsi@11C80000 {
|
||||
compatible = "samsung,exynos4210-mipi-dsi";
|
||||
reg = <0x11C80000 0x10000>;
|
||||
interrupts = <0 79 0>;
|
||||
clocks = <&clock 286>, <&clock 143>;
|
||||
clock-names = "bus_clk", "pll_clk";
|
||||
phys = <&mipi_phy 1>;
|
||||
phy-names = "dsim";
|
||||
vddcore-supply = <&vusb_reg>;
|
||||
vddio-supply = <&vmipi_reg>;
|
||||
samsung,power-domain = <&pd_lcd0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
samsung,pll-clock-frequency = <24000000>;
|
||||
|
||||
panel@1 {
|
||||
reg = <0>;
|
||||
...
|
||||
port {
|
||||
panel_ep: endpoint {
|
||||
remote-endpoint = <&dsi_ep>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@1 {
|
||||
dsi_ep: endpoint {
|
||||
reg = <0>;
|
||||
samsung,burst-clock-frequency = <500000000>;
|
||||
samsung,esc-clock-frequency = <20000000>;
|
||||
remote-endpoint = <&panel_ep>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
@ -25,6 +25,9 @@ Required properties:
|
||||
sclk_pixel.
|
||||
- clock-names: aliases as per driver requirements for above clock IDs:
|
||||
"hdmi", "sclk_hdmi", "sclk_pixel", "sclk_hdmiphy" and "mout_hdmi".
|
||||
- ddc: phandle to the hdmi ddc node
|
||||
- phy: phandle to the hdmi phy node
|
||||
|
||||
Example:
|
||||
|
||||
hdmi {
|
||||
@ -32,4 +35,6 @@ Example:
|
||||
reg = <0x14530000 0x100000>;
|
||||
interrupts = <0 95 0>;
|
||||
hpd-gpio = <&gpx3 7 1>;
|
||||
ddc = <&hdmi_ddc_node>;
|
||||
phy = <&hdmi_phy_node>;
|
||||
};
|
||||
|
@ -39,6 +39,23 @@ Required properties:
|
||||
|
||||
Optional Properties:
|
||||
- samsung,power-domain: a phandle to FIMD power domain node.
|
||||
- samsung,invert-vden: video enable signal is inverted
|
||||
- samsung,invert-vclk: video clock signal is inverted
|
||||
- display-timings: timing settings for FIMD, as described in document [1].
|
||||
Can be used in case timings cannot be provided otherwise
|
||||
or to override timings provided by the panel.
|
||||
|
||||
The device node can contain 'port' child nodes according to the bindings defined
|
||||
in [2]. The following are properties specific to those nodes:
|
||||
- reg: (required) port index, can be:
|
||||
0 - for CAMIF0 input,
|
||||
1 - for CAMIF1 input,
|
||||
2 - for CAMIF2 input,
|
||||
3 - for parallel output,
|
||||
4 - for write-back interface
|
||||
|
||||
[1]: Documentation/devicetree/bindings/video/display-timing.txt
|
||||
[2]: Documentation/devicetree/bindings/media/video-interfaces.txt
|
||||
|
||||
Example:
|
||||
|
||||
|
16
MAINTAINERS
16
MAINTAINERS
@ -2945,6 +2945,16 @@ F: drivers/gpu/drm/radeon/
|
||||
F: include/drm/radeon*
|
||||
F: include/uapi/drm/radeon*
|
||||
|
||||
DRM PANEL DRIVERS
|
||||
M: Thierry Reding <thierry.reding@gmail.com>
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
T: git git://anongit.freedesktop.org/tegra/linux.git
|
||||
S: Maintained
|
||||
F: drivers/gpu/drm/drm_panel.c
|
||||
F: drivers/gpu/drm/panel/
|
||||
F: include/drm/drm_panel.h
|
||||
F: Documentation/devicetree/bindings/panel/
|
||||
|
||||
INTEL DRM DRIVERS (excluding Poulsbo, Moorestown and derivative chipsets)
|
||||
M: Daniel Vetter <daniel.vetter@ffwll.ch>
|
||||
M: Jani Nikula <jani.nikula@linux.intel.com>
|
||||
@ -3474,12 +3484,6 @@ S: Maintained
|
||||
F: drivers/extcon/
|
||||
F: Documentation/extcon/
|
||||
|
||||
EXYNOS DP DRIVER
|
||||
M: Jingoo Han <jg1.han@samsung.com>
|
||||
L: linux-fbdev@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/video/exynos/exynos_dp*
|
||||
|
||||
EXYNOS MIPI DISPLAY DRIVERS
|
||||
M: Inki Dae <inki.dae@samsung.com>
|
||||
M: Donghwa Lee <dh09.lee@samsung.com>
|
||||
|
@ -110,6 +110,20 @@
|
||||
reg = <0x10010000 0x400>;
|
||||
};
|
||||
|
||||
dsi_0: dsi@11C80000 {
|
||||
compatible = "samsung,exynos4210-mipi-dsi";
|
||||
reg = <0x11C80000 0x10000>;
|
||||
interrupts = <0 79 0>;
|
||||
samsung,power-domain = <&pd_lcd0>;
|
||||
phys = <&mipi_phy 1>;
|
||||
phy-names = "dsim";
|
||||
clocks = <&clock 286>, <&clock 143>;
|
||||
clock-names = "bus_clk", "pll_clk";
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
camera {
|
||||
compatible = "samsung,fimc", "simple-bus";
|
||||
status = "disabled";
|
||||
|
@ -353,6 +353,67 @@
|
||||
};
|
||||
};
|
||||
|
||||
dsi_0: dsi@11C80000 {
|
||||
vddcore-supply = <&vusb_reg>;
|
||||
vddio-supply = <&vmipi_reg>;
|
||||
samsung,pll-clock-frequency = <24000000>;
|
||||
status = "okay";
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
|
||||
dsi_out: endpoint {
|
||||
remote-endpoint = <&dsi_in>;
|
||||
samsung,burst-clock-frequency = <500000000>;
|
||||
samsung,esc-clock-frequency = <20000000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
panel@0 {
|
||||
reg = <0>;
|
||||
compatible = "samsung,s6e8aa0";
|
||||
vdd3-supply = <&vcclcd_reg>;
|
||||
vci-supply = <&vlcd_reg>;
|
||||
reset-gpios = <&gpy4 5 0>;
|
||||
power-on-delay= <50>;
|
||||
reset-delay = <100>;
|
||||
init-delay = <100>;
|
||||
flip-horizontal;
|
||||
flip-vertical;
|
||||
panel-width-mm = <58>;
|
||||
panel-height-mm = <103>;
|
||||
|
||||
display-timings {
|
||||
timing-0 {
|
||||
clock-frequency = <57153600>;
|
||||
hactive = <720>;
|
||||
vactive = <1280>;
|
||||
hfront-porch = <5>;
|
||||
hback-porch = <5>;
|
||||
hsync-len = <5>;
|
||||
vfront-porch = <13>;
|
||||
vback-porch = <1>;
|
||||
vsync-len = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
port {
|
||||
dsi_in: endpoint {
|
||||
remote-endpoint = <&dsi_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fimd@11c00000 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
camera {
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <>;
|
||||
|
@ -345,6 +345,70 @@
|
||||
};
|
||||
};
|
||||
|
||||
spi-lcd {
|
||||
compatible = "spi-gpio";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
gpio-sck = <&gpy3 1 0>;
|
||||
gpio-mosi = <&gpy3 3 0>;
|
||||
num-chipselects = <1>;
|
||||
cs-gpios = <&gpy4 3 0>;
|
||||
|
||||
lcd@0 {
|
||||
compatible = "samsung,ld9040";
|
||||
reg = <0>;
|
||||
vdd3-supply = <&ldo7_reg>;
|
||||
vci-supply = <&ldo17_reg>;
|
||||
reset-gpios = <&gpy4 5 0>;
|
||||
spi-max-frequency = <1200000>;
|
||||
spi-cpol;
|
||||
spi-cpha;
|
||||
power-on-delay = <10>;
|
||||
reset-delay = <10>;
|
||||
panel-width-mm = <90>;
|
||||
panel-height-mm = <154>;
|
||||
display-timings {
|
||||
timing {
|
||||
clock-frequency = <23492370>;
|
||||
hactive = <480>;
|
||||
vactive = <800>;
|
||||
hback-porch = <16>;
|
||||
hfront-porch = <16>;
|
||||
vback-porch = <2>;
|
||||
vfront-porch = <28>;
|
||||
hsync-len = <2>;
|
||||
vsync-len = <1>;
|
||||
hsync-active = <0>;
|
||||
vsync-active = <0>;
|
||||
de-active = <0>;
|
||||
pixelclk-active = <0>;
|
||||
};
|
||||
};
|
||||
port {
|
||||
lcd_ep: endpoint {
|
||||
remote-endpoint = <&fimd_dpi_ep>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fimd: fimd@11c00000 {
|
||||
pinctrl-0 = <&lcd_clk>, <&lcd_data24>;
|
||||
pinctrl-names = "default";
|
||||
status = "okay";
|
||||
samsung,invert-vden;
|
||||
samsung,invert-vclk;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
port@3 {
|
||||
reg = <3>;
|
||||
fimd_dpi_ep: endpoint {
|
||||
remote-endpoint = <&lcd_ep>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pwm@139D0000 {
|
||||
compatible = "samsung,s5p6440-pwm";
|
||||
status = "okay";
|
||||
|
@ -71,6 +71,15 @@
|
||||
enable-active-high;
|
||||
};
|
||||
|
||||
lcd_vdd3_reg: voltage-regulator-2 {
|
||||
compatible = "regulator-fixed";
|
||||
regulator-name = "LCD_VDD_2.2V";
|
||||
regulator-min-microvolt = <2200000>;
|
||||
regulator-max-microvolt = <2200000>;
|
||||
gpio = <&gpc0 1 0>;
|
||||
enable-active-high;
|
||||
};
|
||||
|
||||
/* More to come */
|
||||
};
|
||||
|
||||
@ -516,6 +525,67 @@
|
||||
};
|
||||
};
|
||||
|
||||
dsi_0: dsi@11C80000 {
|
||||
vddcore-supply = <&ldo8_reg>;
|
||||
vddio-supply = <&ldo10_reg>;
|
||||
samsung,pll-clock-frequency = <24000000>;
|
||||
status = "okay";
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
|
||||
dsi_out: endpoint {
|
||||
remote-endpoint = <&dsi_in>;
|
||||
samsung,burst-clock-frequency = <500000000>;
|
||||
samsung,esc-clock-frequency = <20000000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
panel@0 {
|
||||
compatible = "samsung,s6e8aa0";
|
||||
reg = <0>;
|
||||
vdd3-supply = <&lcd_vdd3_reg>;
|
||||
vci-supply = <&ldo25_reg>;
|
||||
reset-gpios = <&gpy4 5 0>;
|
||||
power-on-delay= <50>;
|
||||
reset-delay = <100>;
|
||||
init-delay = <100>;
|
||||
flip-horizontal;
|
||||
flip-vertical;
|
||||
panel-width-mm = <58>;
|
||||
panel-height-mm = <103>;
|
||||
|
||||
display-timings {
|
||||
timing-0 {
|
||||
clock-frequency = <0>;
|
||||
hactive = <720>;
|
||||
vactive = <1280>;
|
||||
hfront-porch = <5>;
|
||||
hback-porch = <5>;
|
||||
hsync-len = <5>;
|
||||
vfront-porch = <13>;
|
||||
vback-porch = <1>;
|
||||
vsync-len = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
port {
|
||||
dsi_in: endpoint {
|
||||
remote-endpoint = <&dsi_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fimd@11c00000 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
camera {
|
||||
pinctrl-0 = <&cam_port_b_clk_active>;
|
||||
pinctrl-names = "default";
|
||||
|
@ -199,3 +199,5 @@ source "drivers/gpu/drm/msm/Kconfig"
|
||||
source "drivers/gpu/drm/tegra/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/panel/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/bridge/Kconfig"
|
||||
|
@ -13,7 +13,8 @@ drm-y := drm_auth.o drm_buffer.o drm_bufs.o drm_cache.o \
|
||||
drm_crtc.o drm_modes.o drm_edid.o \
|
||||
drm_info.o drm_debugfs.o drm_encoder_slave.o \
|
||||
drm_trace_points.o drm_global.o drm_prime.o \
|
||||
drm_rect.o drm_vma_manager.o drm_flip_work.o
|
||||
drm_rect.o drm_vma_manager.o drm_flip_work.o \
|
||||
drm_plane_helper.o
|
||||
|
||||
drm-$(CONFIG_COMPAT) += drm_ioc32.o
|
||||
drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
|
||||
@ -63,3 +64,4 @@ obj-$(CONFIG_DRM_MSM) += msm/
|
||||
obj-$(CONFIG_DRM_TEGRA) += tegra/
|
||||
obj-y += i2c/
|
||||
obj-y += panel/
|
||||
obj-y += bridge/
|
||||
|
@ -478,11 +478,12 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
|
||||
unsigned i;
|
||||
bool interlaced;
|
||||
|
||||
drm_framebuffer_reference(crtc->fb);
|
||||
drm_framebuffer_reference(crtc->primary->fb);
|
||||
|
||||
interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE);
|
||||
|
||||
i = armada_drm_crtc_calc_fb(dcrtc->crtc.fb, x, y, regs, interlaced);
|
||||
i = armada_drm_crtc_calc_fb(dcrtc->crtc.primary->fb,
|
||||
x, y, regs, interlaced);
|
||||
|
||||
rm = adj->crtc_hsync_start - adj->crtc_hdisplay;
|
||||
lm = adj->crtc_htotal - adj->crtc_hsync_end;
|
||||
@ -567,10 +568,10 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
|
||||
}
|
||||
|
||||
val = CFG_GRA_ENA | CFG_GRA_HSMOOTH;
|
||||
val |= CFG_GRA_FMT(drm_fb_to_armada_fb(dcrtc->crtc.fb)->fmt);
|
||||
val |= CFG_GRA_MOD(drm_fb_to_armada_fb(dcrtc->crtc.fb)->mod);
|
||||
val |= CFG_GRA_FMT(drm_fb_to_armada_fb(dcrtc->crtc.primary->fb)->fmt);
|
||||
val |= CFG_GRA_MOD(drm_fb_to_armada_fb(dcrtc->crtc.primary->fb)->mod);
|
||||
|
||||
if (drm_fb_to_armada_fb(dcrtc->crtc.fb)->fmt > CFG_420)
|
||||
if (drm_fb_to_armada_fb(dcrtc->crtc.primary->fb)->fmt > CFG_420)
|
||||
val |= CFG_PALETTE_ENA;
|
||||
|
||||
if (interlaced)
|
||||
@ -608,7 +609,7 @@ static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
struct armada_regs regs[4];
|
||||
unsigned i;
|
||||
|
||||
i = armada_drm_crtc_calc_fb(crtc->fb, crtc->x, crtc->y, regs,
|
||||
i = armada_drm_crtc_calc_fb(crtc->primary->fb, crtc->x, crtc->y, regs,
|
||||
dcrtc->interlaced);
|
||||
armada_reg_queue_end(regs, i);
|
||||
|
||||
@ -616,7 +617,7 @@ static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
wait_event(dcrtc->frame_wait, !dcrtc->frame_work);
|
||||
|
||||
/* Take a reference to the new fb as we're using it */
|
||||
drm_framebuffer_reference(crtc->fb);
|
||||
drm_framebuffer_reference(crtc->primary->fb);
|
||||
|
||||
/* Update the base in the CRTC */
|
||||
armada_drm_crtc_update_regs(dcrtc, regs);
|
||||
@ -637,7 +638,7 @@ static void armada_drm_crtc_disable(struct drm_crtc *crtc)
|
||||
struct armada_crtc *dcrtc = drm_to_armada_crtc(crtc);
|
||||
|
||||
armada_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
armada_drm_crtc_finish_fb(dcrtc, crtc->fb, true);
|
||||
armada_drm_crtc_finish_fb(dcrtc, crtc->primary->fb, true);
|
||||
|
||||
/* Power down most RAMs and FIFOs */
|
||||
writel_relaxed(CFG_PDWN256x32 | CFG_PDWN256x24 | CFG_PDWN256x8 |
|
||||
@ -678,6 +679,7 @@ static void armada_load_cursor_argb(void __iomem *base, uint32_t *pix,
|
||||
base + LCD_SPU_SRAM_WRDAT);
|
||||
writel_relaxed(addr | SRAM_WRITE,
|
||||
base + LCD_SPU_SRAM_CTRL);
|
||||
readl_relaxed(base + LCD_SPU_HWC_OVSA_HPXL_VLN);
|
||||
addr += 1;
|
||||
if ((addr & 0x00ff) == 0)
|
||||
addr += 0xf00;
|
||||
@ -904,7 +906,7 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
|
||||
int ret;
|
||||
|
||||
/* We don't support changing the pixel format */
|
||||
if (fb->pixel_format != crtc->fb->pixel_format)
|
||||
if (fb->pixel_format != crtc->primary->fb->pixel_format)
|
||||
return -EINVAL;
|
||||
|
||||
work = kmalloc(sizeof(*work), GFP_KERNEL);
|
||||
@ -912,7 +914,7 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
|
||||
return -ENOMEM;
|
||||
|
||||
work->event = event;
|
||||
work->old_fb = dcrtc->crtc.fb;
|
||||
work->old_fb = dcrtc->crtc.primary->fb;
|
||||
|
||||
i = armada_drm_crtc_calc_fb(fb, crtc->x, crtc->y, work->regs,
|
||||
dcrtc->interlaced);
|
||||
@ -941,7 +943,7 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
|
||||
* will _not_ drop that reference on successful return from this
|
||||
* function. Simply mark this new framebuffer as the current one.
|
||||
*/
|
||||
dcrtc->crtc.fb = fb;
|
||||
dcrtc->crtc.primary->fb = fb;
|
||||
|
||||
/*
|
||||
* Finally, if the display is blanked, we won't receive an
|
||||
|
@ -81,7 +81,7 @@ static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mo
|
||||
u32 refresh_rate_index = 0, mode_id, color_index, refresh_rate;
|
||||
u32 hborder, vborder;
|
||||
|
||||
switch (crtc->fb->bits_per_pixel) {
|
||||
switch (crtc->primary->fb->bits_per_pixel) {
|
||||
case 8:
|
||||
vbios_mode->std_table = &vbios_stdtable[VGAModeIndex];
|
||||
color_index = VGAModeIndex - 1;
|
||||
@ -176,7 +176,7 @@ static bool ast_get_vbios_mode_info(struct drm_crtc *crtc, struct drm_display_mo
|
||||
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x8e, mode_id & 0xff);
|
||||
|
||||
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x91, 0xa8);
|
||||
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x92, crtc->fb->bits_per_pixel);
|
||||
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x92, crtc->primary->fb->bits_per_pixel);
|
||||
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x93, adjusted_mode->clock / 1000);
|
||||
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x94, adjusted_mode->crtc_hdisplay);
|
||||
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x95, adjusted_mode->crtc_hdisplay >> 8);
|
||||
@ -340,7 +340,7 @@ static void ast_set_offset_reg(struct drm_crtc *crtc)
|
||||
|
||||
u16 offset;
|
||||
|
||||
offset = crtc->fb->pitches[0] >> 3;
|
||||
offset = crtc->primary->fb->pitches[0] >> 3;
|
||||
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0x13, (offset & 0xff));
|
||||
ast_set_index_reg(ast, AST_IO_CRTC_PORT, 0xb0, (offset >> 8) & 0x3f);
|
||||
}
|
||||
@ -365,7 +365,7 @@ static void ast_set_ext_reg(struct drm_crtc *crtc, struct drm_display_mode *mode
|
||||
struct ast_private *ast = crtc->dev->dev_private;
|
||||
u8 jregA0 = 0, jregA3 = 0, jregA8 = 0;
|
||||
|
||||
switch (crtc->fb->bits_per_pixel) {
|
||||
switch (crtc->primary->fb->bits_per_pixel) {
|
||||
case 8:
|
||||
jregA0 = 0x70;
|
||||
jregA3 = 0x01;
|
||||
@ -418,7 +418,7 @@ static void ast_set_sync_reg(struct drm_device *dev, struct drm_display_mode *mo
|
||||
static bool ast_set_dac_reg(struct drm_crtc *crtc, struct drm_display_mode *mode,
|
||||
struct ast_vbios_mode_info *vbios_mode)
|
||||
{
|
||||
switch (crtc->fb->bits_per_pixel) {
|
||||
switch (crtc->primary->fb->bits_per_pixel) {
|
||||
case 8:
|
||||
break;
|
||||
default:
|
||||
@ -490,7 +490,7 @@ static int ast_crtc_do_set_base(struct drm_crtc *crtc,
|
||||
ast_bo_unreserve(bo);
|
||||
}
|
||||
|
||||
ast_fb = to_ast_framebuffer(crtc->fb);
|
||||
ast_fb = to_ast_framebuffer(crtc->primary->fb);
|
||||
obj = ast_fb->obj;
|
||||
bo = gem_to_ast_bo(obj);
|
||||
|
||||
|
@ -259,7 +259,9 @@ int ast_mm_init(struct ast_private *ast)
|
||||
|
||||
ret = ttm_bo_device_init(&ast->ttm.bdev,
|
||||
ast->ttm.bo_global_ref.ref.object,
|
||||
&ast_bo_driver, DRM_FILE_PAGE_OFFSET,
|
||||
&ast_bo_driver,
|
||||
dev->anon_inode->i_mapping,
|
||||
DRM_FILE_PAGE_OFFSET,
|
||||
true);
|
||||
if (ret) {
|
||||
DRM_ERROR("Error initialising bo driver; %d\n", ret);
|
||||
@ -324,7 +326,6 @@ int ast_bo_create(struct drm_device *dev, int size, int align,
|
||||
}
|
||||
|
||||
astbo->bo.bdev = &ast->ttm.bdev;
|
||||
astbo->bo.bdev->dev_mapping = dev->dev_mapping;
|
||||
|
||||
ast_ttm_placement(astbo, TTM_PL_FLAG_VRAM | TTM_PL_FLAG_SYSTEM);
|
||||
|
||||
|
@ -62,10 +62,10 @@ static int bochs_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
}
|
||||
}
|
||||
|
||||
if (WARN_ON(crtc->fb == NULL))
|
||||
if (WARN_ON(crtc->primary->fb == NULL))
|
||||
return -EINVAL;
|
||||
|
||||
bochs_fb = to_bochs_framebuffer(crtc->fb);
|
||||
bochs_fb = to_bochs_framebuffer(crtc->primary->fb);
|
||||
bo = gem_to_bochs_bo(bochs_fb->obj);
|
||||
ret = ttm_bo_reserve(&bo->bo, true, false, false, 0);
|
||||
if (ret)
|
||||
|
@ -225,7 +225,9 @@ int bochs_mm_init(struct bochs_device *bochs)
|
||||
|
||||
ret = ttm_bo_device_init(&bochs->ttm.bdev,
|
||||
bochs->ttm.bo_global_ref.ref.object,
|
||||
&bochs_bo_driver, DRM_FILE_PAGE_OFFSET,
|
||||
&bochs_bo_driver,
|
||||
bochs->dev->anon_inode->i_mapping,
|
||||
DRM_FILE_PAGE_OFFSET,
|
||||
true);
|
||||
if (ret) {
|
||||
DRM_ERROR("Error initialising bo driver; %d\n", ret);
|
||||
@ -359,7 +361,7 @@ static int bochs_bo_create(struct drm_device *dev, int size, int align,
|
||||
}
|
||||
|
||||
bochsbo->bo.bdev = &bochs->ttm.bdev;
|
||||
bochsbo->bo.bdev->dev_mapping = dev->dev_mapping;
|
||||
bochsbo->bo.bdev->dev_mapping = dev->anon_inode->i_mapping;
|
||||
|
||||
bochs_ttm_placement(bochsbo, TTM_PL_FLAG_VRAM | TTM_PL_FLAG_SYSTEM);
|
||||
|
||||
|
5
drivers/gpu/drm/bridge/Kconfig
Normal file
5
drivers/gpu/drm/bridge/Kconfig
Normal file
@ -0,0 +1,5 @@
|
||||
config DRM_PTN3460
|
||||
tristate "PTN3460 DP/LVDS bridge"
|
||||
depends on DRM
|
||||
select DRM_KMS_HELPER
|
||||
---help---
|
3
drivers/gpu/drm/bridge/Makefile
Normal file
3
drivers/gpu/drm/bridge/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
ccflags-y := -Iinclude/drm
|
||||
|
||||
obj-$(CONFIG_DRM_PTN3460) += ptn3460.o
|
350
drivers/gpu/drm/bridge/ptn3460.c
Normal file
350
drivers/gpu/drm/bridge/ptn3460.c
Normal file
@ -0,0 +1,350 @@
|
||||
/*
|
||||
* NXP PTN3460 DP/LVDS bridge driver
|
||||
*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "drmP.h"
|
||||
#include "drm_edid.h"
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_crtc_helper.h"
|
||||
|
||||
#include "bridge/ptn3460.h"
|
||||
|
||||
#define PTN3460_EDID_ADDR 0x0
|
||||
#define PTN3460_EDID_EMULATION_ADDR 0x84
|
||||
#define PTN3460_EDID_ENABLE_EMULATION 0
|
||||
#define PTN3460_EDID_EMULATION_SELECTION 1
|
||||
#define PTN3460_EDID_SRAM_LOAD_ADDR 0x85
|
||||
|
||||
struct ptn3460_bridge {
|
||||
struct drm_connector connector;
|
||||
struct i2c_client *client;
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_bridge *bridge;
|
||||
struct edid *edid;
|
||||
int gpio_pd_n;
|
||||
int gpio_rst_n;
|
||||
u32 edid_emulation;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
static int ptn3460_read_bytes(struct ptn3460_bridge *ptn_bridge, char addr,
|
||||
u8 *buf, int len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_master_send(ptn_bridge->client, &addr, 1);
|
||||
if (ret <= 0) {
|
||||
DRM_ERROR("Failed to send i2c command, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = i2c_master_recv(ptn_bridge->client, buf, len);
|
||||
if (ret <= 0) {
|
||||
DRM_ERROR("Failed to recv i2c data, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ptn3460_write_byte(struct ptn3460_bridge *ptn_bridge, char addr,
|
||||
char val)
|
||||
{
|
||||
int ret;
|
||||
char buf[2];
|
||||
|
||||
buf[0] = addr;
|
||||
buf[1] = val;
|
||||
|
||||
ret = i2c_master_send(ptn_bridge->client, buf, ARRAY_SIZE(buf));
|
||||
if (ret <= 0) {
|
||||
DRM_ERROR("Failed to send i2c command, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ptn3460_select_edid(struct ptn3460_bridge *ptn_bridge)
|
||||
{
|
||||
int ret;
|
||||
char val;
|
||||
|
||||
/* Load the selected edid into SRAM (accessed at PTN3460_EDID_ADDR) */
|
||||
ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_SRAM_LOAD_ADDR,
|
||||
ptn_bridge->edid_emulation);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to transfer edid to sram, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Enable EDID emulation and select the desired EDID */
|
||||
val = 1 << PTN3460_EDID_ENABLE_EMULATION |
|
||||
ptn_bridge->edid_emulation << PTN3460_EDID_EMULATION_SELECTION;
|
||||
|
||||
ret = ptn3460_write_byte(ptn_bridge, PTN3460_EDID_EMULATION_ADDR, val);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to write edid value, ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ptn3460_pre_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ptn3460_bridge *ptn_bridge = bridge->driver_private;
|
||||
int ret;
|
||||
|
||||
if (ptn_bridge->enabled)
|
||||
return;
|
||||
|
||||
if (gpio_is_valid(ptn_bridge->gpio_pd_n))
|
||||
gpio_set_value(ptn_bridge->gpio_pd_n, 1);
|
||||
|
||||
if (gpio_is_valid(ptn_bridge->gpio_rst_n)) {
|
||||
gpio_set_value(ptn_bridge->gpio_rst_n, 0);
|
||||
udelay(10);
|
||||
gpio_set_value(ptn_bridge->gpio_rst_n, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* There's a bug in the PTN chip where it falsely asserts hotplug before
|
||||
* it is fully functional. We're forced to wait for the maximum start up
|
||||
* time specified in the chip's datasheet to make sure we're really up.
|
||||
*/
|
||||
msleep(90);
|
||||
|
||||
ret = ptn3460_select_edid(ptn_bridge);
|
||||
if (ret)
|
||||
DRM_ERROR("Select edid failed ret=%d\n", ret);
|
||||
|
||||
ptn_bridge->enabled = true;
|
||||
}
|
||||
|
||||
static void ptn3460_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
}
|
||||
|
||||
static void ptn3460_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ptn3460_bridge *ptn_bridge = bridge->driver_private;
|
||||
|
||||
if (!ptn_bridge->enabled)
|
||||
return;
|
||||
|
||||
ptn_bridge->enabled = false;
|
||||
|
||||
if (gpio_is_valid(ptn_bridge->gpio_rst_n))
|
||||
gpio_set_value(ptn_bridge->gpio_rst_n, 1);
|
||||
|
||||
if (gpio_is_valid(ptn_bridge->gpio_pd_n))
|
||||
gpio_set_value(ptn_bridge->gpio_pd_n, 0);
|
||||
}
|
||||
|
||||
static void ptn3460_post_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
}
|
||||
|
||||
void ptn3460_bridge_destroy(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ptn3460_bridge *ptn_bridge = bridge->driver_private;
|
||||
|
||||
drm_bridge_cleanup(bridge);
|
||||
if (gpio_is_valid(ptn_bridge->gpio_pd_n))
|
||||
gpio_free(ptn_bridge->gpio_pd_n);
|
||||
if (gpio_is_valid(ptn_bridge->gpio_rst_n))
|
||||
gpio_free(ptn_bridge->gpio_rst_n);
|
||||
/* Nothing else to free, we've got devm allocated memory */
|
||||
}
|
||||
|
||||
struct drm_bridge_funcs ptn3460_bridge_funcs = {
|
||||
.pre_enable = ptn3460_pre_enable,
|
||||
.enable = ptn3460_enable,
|
||||
.disable = ptn3460_disable,
|
||||
.post_disable = ptn3460_post_disable,
|
||||
.destroy = ptn3460_bridge_destroy,
|
||||
};
|
||||
|
||||
int ptn3460_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct ptn3460_bridge *ptn_bridge;
|
||||
u8 *edid;
|
||||
int ret, num_modes;
|
||||
bool power_off;
|
||||
|
||||
ptn_bridge = container_of(connector, struct ptn3460_bridge, connector);
|
||||
|
||||
if (ptn_bridge->edid)
|
||||
return drm_add_edid_modes(connector, ptn_bridge->edid);
|
||||
|
||||
power_off = !ptn_bridge->enabled;
|
||||
ptn3460_pre_enable(ptn_bridge->bridge);
|
||||
|
||||
edid = kmalloc(EDID_LENGTH, GFP_KERNEL);
|
||||
if (!edid) {
|
||||
DRM_ERROR("Failed to allocate edid\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = ptn3460_read_bytes(ptn_bridge, PTN3460_EDID_ADDR, edid,
|
||||
EDID_LENGTH);
|
||||
if (ret) {
|
||||
kfree(edid);
|
||||
num_modes = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ptn_bridge->edid = (struct edid *)edid;
|
||||
drm_mode_connector_update_edid_property(connector, ptn_bridge->edid);
|
||||
|
||||
num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
|
||||
|
||||
out:
|
||||
if (power_off)
|
||||
ptn3460_disable(ptn_bridge->bridge);
|
||||
|
||||
return num_modes;
|
||||
}
|
||||
|
||||
static int ptn3460_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
struct drm_encoder *ptn3460_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct ptn3460_bridge *ptn_bridge;
|
||||
|
||||
ptn_bridge = container_of(connector, struct ptn3460_bridge, connector);
|
||||
|
||||
return ptn_bridge->encoder;
|
||||
}
|
||||
|
||||
struct drm_connector_helper_funcs ptn3460_connector_helper_funcs = {
|
||||
.get_modes = ptn3460_get_modes,
|
||||
.mode_valid = ptn3460_mode_valid,
|
||||
.best_encoder = ptn3460_best_encoder,
|
||||
};
|
||||
|
||||
enum drm_connector_status ptn3460_detect(struct drm_connector *connector,
|
||||
bool force)
|
||||
{
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
void ptn3460_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
drm_connector_cleanup(connector);
|
||||
}
|
||||
|
||||
struct drm_connector_funcs ptn3460_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.detect = ptn3460_detect,
|
||||
.destroy = ptn3460_connector_destroy,
|
||||
};
|
||||
|
||||
int ptn3460_init(struct drm_device *dev, struct drm_encoder *encoder,
|
||||
struct i2c_client *client, struct device_node *node)
|
||||
{
|
||||
int ret;
|
||||
struct drm_bridge *bridge;
|
||||
struct ptn3460_bridge *ptn_bridge;
|
||||
|
||||
bridge = devm_kzalloc(dev->dev, sizeof(*bridge), GFP_KERNEL);
|
||||
if (!bridge) {
|
||||
DRM_ERROR("Failed to allocate drm bridge\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ptn_bridge = devm_kzalloc(dev->dev, sizeof(*ptn_bridge), GFP_KERNEL);
|
||||
if (!ptn_bridge) {
|
||||
DRM_ERROR("Failed to allocate ptn bridge\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ptn_bridge->client = client;
|
||||
ptn_bridge->encoder = encoder;
|
||||
ptn_bridge->bridge = bridge;
|
||||
ptn_bridge->gpio_pd_n = of_get_named_gpio(node, "powerdown-gpio", 0);
|
||||
if (gpio_is_valid(ptn_bridge->gpio_pd_n)) {
|
||||
ret = gpio_request_one(ptn_bridge->gpio_pd_n,
|
||||
GPIOF_OUT_INIT_HIGH, "PTN3460_PD_N");
|
||||
if (ret) {
|
||||
DRM_ERROR("Request powerdown-gpio failed (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ptn_bridge->gpio_rst_n = of_get_named_gpio(node, "reset-gpio", 0);
|
||||
if (gpio_is_valid(ptn_bridge->gpio_rst_n)) {
|
||||
/*
|
||||
* Request the reset pin low to avoid the bridge being
|
||||
* initialized prematurely
|
||||
*/
|
||||
ret = gpio_request_one(ptn_bridge->gpio_rst_n,
|
||||
GPIOF_OUT_INIT_LOW, "PTN3460_RST_N");
|
||||
if (ret) {
|
||||
DRM_ERROR("Request reset-gpio failed (%d)\n", ret);
|
||||
gpio_free(ptn_bridge->gpio_pd_n);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(node, "edid-emulation",
|
||||
&ptn_bridge->edid_emulation);
|
||||
if (ret) {
|
||||
DRM_ERROR("Can't read edid emulation value\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = drm_bridge_init(dev, bridge, &ptn3460_bridge_funcs);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to initialize bridge with drm\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
bridge->driver_private = ptn_bridge;
|
||||
encoder->bridge = bridge;
|
||||
|
||||
ret = drm_connector_init(dev, &ptn_bridge->connector,
|
||||
&ptn3460_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to initialize connector with drm\n");
|
||||
goto err;
|
||||
}
|
||||
drm_connector_helper_add(&ptn_bridge->connector,
|
||||
&ptn3460_connector_helper_funcs);
|
||||
drm_sysfs_connector_add(&ptn_bridge->connector);
|
||||
drm_mode_connector_attach_encoder(&ptn_bridge->connector, encoder);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (gpio_is_valid(ptn_bridge->gpio_pd_n))
|
||||
gpio_free(ptn_bridge->gpio_pd_n);
|
||||
if (gpio_is_valid(ptn_bridge->gpio_rst_n))
|
||||
gpio_free(ptn_bridge->gpio_rst_n);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(ptn3460_init);
|
@ -149,7 +149,7 @@ static int cirrus_crtc_do_set_base(struct drm_crtc *crtc,
|
||||
cirrus_bo_unreserve(bo);
|
||||
}
|
||||
|
||||
cirrus_fb = to_cirrus_framebuffer(crtc->fb);
|
||||
cirrus_fb = to_cirrus_framebuffer(crtc->primary->fb);
|
||||
obj = cirrus_fb->obj;
|
||||
bo = gem_to_cirrus_bo(obj);
|
||||
|
||||
@ -268,7 +268,7 @@ static int cirrus_crtc_mode_set(struct drm_crtc *crtc,
|
||||
sr07 = RREG8(SEQ_DATA);
|
||||
sr07 &= 0xe0;
|
||||
hdr = 0;
|
||||
switch (crtc->fb->bits_per_pixel) {
|
||||
switch (crtc->primary->fb->bits_per_pixel) {
|
||||
case 8:
|
||||
sr07 |= 0x11;
|
||||
break;
|
||||
@ -291,13 +291,13 @@ static int cirrus_crtc_mode_set(struct drm_crtc *crtc,
|
||||
WREG_SEQ(0x7, sr07);
|
||||
|
||||
/* Program the pitch */
|
||||
tmp = crtc->fb->pitches[0] / 8;
|
||||
tmp = crtc->primary->fb->pitches[0] / 8;
|
||||
WREG_CRT(VGA_CRTC_OFFSET, tmp);
|
||||
|
||||
/* Enable extended blanking and pitch bits, and enable full memory */
|
||||
tmp = 0x22;
|
||||
tmp |= (crtc->fb->pitches[0] >> 7) & 0x10;
|
||||
tmp |= (crtc->fb->pitches[0] >> 6) & 0x40;
|
||||
tmp |= (crtc->primary->fb->pitches[0] >> 7) & 0x10;
|
||||
tmp |= (crtc->primary->fb->pitches[0] >> 6) & 0x40;
|
||||
WREG_CRT(0x1b, tmp);
|
||||
|
||||
/* Enable high-colour modes */
|
||||
|
@ -259,7 +259,9 @@ int cirrus_mm_init(struct cirrus_device *cirrus)
|
||||
|
||||
ret = ttm_bo_device_init(&cirrus->ttm.bdev,
|
||||
cirrus->ttm.bo_global_ref.ref.object,
|
||||
&cirrus_bo_driver, DRM_FILE_PAGE_OFFSET,
|
||||
&cirrus_bo_driver,
|
||||
dev->anon_inode->i_mapping,
|
||||
DRM_FILE_PAGE_OFFSET,
|
||||
true);
|
||||
if (ret) {
|
||||
DRM_ERROR("Error initialising bo driver; %d\n", ret);
|
||||
@ -329,7 +331,6 @@ int cirrus_bo_create(struct drm_device *dev, int size, int align,
|
||||
}
|
||||
|
||||
cirrusbo->bo.bdev = &cirrus->ttm.bdev;
|
||||
cirrusbo->bo.bdev->dev_mapping = dev->dev_mapping;
|
||||
|
||||
cirrus_ttm_placement(cirrusbo, TTM_PL_FLAG_VRAM | TTM_PL_FLAG_SYSTEM);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -105,9 +105,6 @@ static void drm_mode_validate_flag(struct drm_connector *connector,
|
||||
* @maxX: max width for modes
|
||||
* @maxY: max height for modes
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold mode config lock.
|
||||
*
|
||||
* Based on the helper callbacks implemented by @connector try to detect all
|
||||
* valid modes. Modes will first be added to the connector's probed_modes list,
|
||||
* then culled (based on validity and the @maxX, @maxY parameters) and put into
|
||||
@ -117,8 +114,8 @@ static void drm_mode_validate_flag(struct drm_connector *connector,
|
||||
* @connector vfunc for drivers that use the crtc helpers for output mode
|
||||
* filtering and detection.
|
||||
*
|
||||
* RETURNS:
|
||||
* Number of modes found on @connector.
|
||||
* Returns:
|
||||
* The number of modes found on @connector.
|
||||
*/
|
||||
int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
|
||||
uint32_t maxX, uint32_t maxY)
|
||||
@ -131,6 +128,8 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
|
||||
int mode_flags = 0;
|
||||
bool verbose_prune = true;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->mode_config.mutex));
|
||||
|
||||
DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id,
|
||||
drm_get_connector_name(connector));
|
||||
/* set all modes to the unverified state */
|
||||
@ -176,8 +175,7 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
|
||||
drm_mode_connector_list_update(connector);
|
||||
|
||||
if (maxX && maxY)
|
||||
drm_mode_validate_size(dev, &connector->modes, maxX,
|
||||
maxY, 0);
|
||||
drm_mode_validate_size(dev, &connector->modes, maxX, maxY);
|
||||
|
||||
if (connector->interlace_allowed)
|
||||
mode_flags |= DRM_MODE_FLAG_INTERLACE;
|
||||
@ -219,18 +217,19 @@ EXPORT_SYMBOL(drm_helper_probe_single_connector_modes);
|
||||
* drm_helper_encoder_in_use - check if a given encoder is in use
|
||||
* @encoder: encoder to check
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold mode config lock.
|
||||
* Checks whether @encoder is with the current mode setting output configuration
|
||||
* in use by any connector. This doesn't mean that it is actually enabled since
|
||||
* the DPMS state is tracked separately.
|
||||
*
|
||||
* Walk @encoders's DRM device's mode_config and see if it's in use.
|
||||
*
|
||||
* RETURNS:
|
||||
* True if @encoder is part of the mode_config, false otherwise.
|
||||
* Returns:
|
||||
* True if @encoder is used, false otherwise.
|
||||
*/
|
||||
bool drm_helper_encoder_in_use(struct drm_encoder *encoder)
|
||||
{
|
||||
struct drm_connector *connector;
|
||||
struct drm_device *dev = encoder->dev;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->mode_config.mutex));
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head)
|
||||
if (connector->encoder == encoder)
|
||||
return true;
|
||||
@ -242,19 +241,19 @@ EXPORT_SYMBOL(drm_helper_encoder_in_use);
|
||||
* drm_helper_crtc_in_use - check if a given CRTC is in a mode_config
|
||||
* @crtc: CRTC to check
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold mode config lock.
|
||||
* Checks whether @crtc is with the current mode setting output configuration
|
||||
* in use by any connector. This doesn't mean that it is actually enabled since
|
||||
* the DPMS state is tracked separately.
|
||||
*
|
||||
* Walk @crtc's DRM device's mode_config and see if it's in use.
|
||||
*
|
||||
* RETURNS:
|
||||
* True if @crtc is part of the mode_config, false otherwise.
|
||||
* Returns:
|
||||
* True if @crtc is used, false otherwise.
|
||||
*/
|
||||
bool drm_helper_crtc_in_use(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_device *dev = crtc->dev;
|
||||
/* FIXME: Locking around list access? */
|
||||
|
||||
WARN_ON(!mutex_is_locked(&dev->mode_config.mutex));
|
||||
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head)
|
||||
if (encoder->crtc == crtc && drm_helper_encoder_in_use(encoder))
|
||||
return true;
|
||||
@ -279,27 +278,17 @@ drm_encoder_disable(struct drm_encoder *encoder)
|
||||
encoder->bridge->funcs->post_disable(encoder->bridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_helper_disable_unused_functions - disable unused objects
|
||||
* @dev: DRM device
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold mode config lock.
|
||||
*
|
||||
* If an connector or CRTC isn't part of @dev's mode_config, it can be disabled
|
||||
* by calling its dpms function, which should power it off.
|
||||
*/
|
||||
void drm_helper_disable_unused_functions(struct drm_device *dev)
|
||||
static void __drm_helper_disable_unused_functions(struct drm_device *dev)
|
||||
{
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector *connector;
|
||||
struct drm_crtc *crtc;
|
||||
|
||||
drm_warn_on_modeset_not_all_locked(dev);
|
||||
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
if (!connector->encoder)
|
||||
continue;
|
||||
if (connector->status == connector_status_disconnected)
|
||||
connector->encoder = NULL;
|
||||
}
|
||||
|
||||
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
||||
@ -318,10 +307,27 @@ void drm_helper_disable_unused_functions(struct drm_device *dev)
|
||||
(*crtc_funcs->disable)(crtc);
|
||||
else
|
||||
(*crtc_funcs->dpms)(crtc, DRM_MODE_DPMS_OFF);
|
||||
crtc->fb = NULL;
|
||||
crtc->primary->fb = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_helper_disable_unused_functions - disable unused objects
|
||||
* @dev: DRM device
|
||||
*
|
||||
* This function walks through the entire mode setting configuration of @dev. It
|
||||
* will remove any crtc links of unused encoders and encoder links of
|
||||
* disconnected connectors. Then it will disable all unused encoders and crtcs
|
||||
* either by calling their disable callback if available or by calling their
|
||||
* dpms callback with DRM_MODE_DPMS_OFF.
|
||||
*/
|
||||
void drm_helper_disable_unused_functions(struct drm_device *dev)
|
||||
{
|
||||
drm_modeset_lock_all(dev);
|
||||
__drm_helper_disable_unused_functions(dev);
|
||||
drm_modeset_unlock_all(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_helper_disable_unused_functions);
|
||||
|
||||
/*
|
||||
@ -355,9 +361,6 @@ drm_crtc_prepare_encoders(struct drm_device *dev)
|
||||
* @y: vertical offset into the surface
|
||||
* @old_fb: old framebuffer, for cleanup
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold mode config lock.
|
||||
*
|
||||
* Try to set @mode on @crtc. Give @crtc and its associated connectors a chance
|
||||
* to fixup or reject the mode prior to trying to set it. This is an internal
|
||||
* helper that drivers could e.g. use to update properties that require the
|
||||
@ -367,8 +370,8 @@ drm_crtc_prepare_encoders(struct drm_device *dev)
|
||||
* drm_crtc_helper_set_config() helper function to drive the mode setting
|
||||
* sequence.
|
||||
*
|
||||
* RETURNS:
|
||||
* True if the mode was set successfully, or false otherwise.
|
||||
* Returns:
|
||||
* True if the mode was set successfully, false otherwise.
|
||||
*/
|
||||
bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
|
||||
struct drm_display_mode *mode,
|
||||
@ -384,6 +387,8 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
|
||||
struct drm_encoder *encoder;
|
||||
bool ret = true;
|
||||
|
||||
drm_warn_on_modeset_not_all_locked(dev);
|
||||
|
||||
saved_enabled = crtc->enabled;
|
||||
crtc->enabled = drm_helper_crtc_in_use(crtc);
|
||||
if (!crtc->enabled)
|
||||
@ -552,7 +557,7 @@ drm_crtc_helper_disable(struct drm_crtc *crtc)
|
||||
}
|
||||
}
|
||||
|
||||
drm_helper_disable_unused_functions(dev);
|
||||
__drm_helper_disable_unused_functions(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -560,17 +565,14 @@ drm_crtc_helper_disable(struct drm_crtc *crtc)
|
||||
* drm_crtc_helper_set_config - set a new config from userspace
|
||||
* @set: mode set configuration
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold mode config lock.
|
||||
*
|
||||
* Setup a new configuration, provided by the upper layers (either an ioctl call
|
||||
* from userspace or internally e.g. from the fbdev support code) in @set, and
|
||||
* enable it. This is the main helper functions for drivers that implement
|
||||
* kernel mode setting with the crtc helper functions and the assorted
|
||||
* ->prepare(), ->modeset() and ->commit() helper callbacks.
|
||||
*
|
||||
* RETURNS:
|
||||
* Returns 0 on success, -ERRNO on failure.
|
||||
* Returns:
|
||||
* Returns 0 on success, negative errno numbers on failure.
|
||||
*/
|
||||
int drm_crtc_helper_set_config(struct drm_mode_set *set)
|
||||
{
|
||||
@ -612,6 +614,8 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
|
||||
|
||||
dev = set->crtc->dev;
|
||||
|
||||
drm_warn_on_modeset_not_all_locked(dev);
|
||||
|
||||
/*
|
||||
* Allocate space for the backup of all (non-pointer) encoder and
|
||||
* connector data.
|
||||
@ -647,19 +651,19 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
|
||||
save_set.mode = &set->crtc->mode;
|
||||
save_set.x = set->crtc->x;
|
||||
save_set.y = set->crtc->y;
|
||||
save_set.fb = set->crtc->fb;
|
||||
save_set.fb = set->crtc->primary->fb;
|
||||
|
||||
/* We should be able to check here if the fb has the same properties
|
||||
* and then just flip_or_move it */
|
||||
if (set->crtc->fb != set->fb) {
|
||||
if (set->crtc->primary->fb != set->fb) {
|
||||
/* If we have no fb then treat it as a full mode set */
|
||||
if (set->crtc->fb == NULL) {
|
||||
if (set->crtc->primary->fb == NULL) {
|
||||
DRM_DEBUG_KMS("crtc has no fb, full mode set\n");
|
||||
mode_changed = true;
|
||||
} else if (set->fb == NULL) {
|
||||
mode_changed = true;
|
||||
} else if (set->fb->pixel_format !=
|
||||
set->crtc->fb->pixel_format) {
|
||||
set->crtc->primary->fb->pixel_format) {
|
||||
mode_changed = true;
|
||||
} else
|
||||
fb_changed = true;
|
||||
@ -689,12 +693,13 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
|
||||
if (new_encoder == NULL)
|
||||
/* don't break so fail path works correct */
|
||||
fail = 1;
|
||||
break;
|
||||
|
||||
if (connector->dpms != DRM_MODE_DPMS_ON) {
|
||||
DRM_DEBUG_KMS("connector dpms not on, full mode switch\n");
|
||||
mode_changed = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -760,13 +765,13 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
|
||||
DRM_DEBUG_KMS("attempting to set mode from"
|
||||
" userspace\n");
|
||||
drm_mode_debug_printmodeline(set->mode);
|
||||
set->crtc->fb = set->fb;
|
||||
set->crtc->primary->fb = set->fb;
|
||||
if (!drm_crtc_helper_set_mode(set->crtc, set->mode,
|
||||
set->x, set->y,
|
||||
save_set.fb)) {
|
||||
DRM_ERROR("failed to set mode on [CRTC:%d]\n",
|
||||
set->crtc->base.id);
|
||||
set->crtc->fb = save_set.fb;
|
||||
set->crtc->primary->fb = save_set.fb;
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
@ -777,17 +782,17 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
|
||||
set->connectors[i]->funcs->dpms(set->connectors[i], DRM_MODE_DPMS_ON);
|
||||
}
|
||||
}
|
||||
drm_helper_disable_unused_functions(dev);
|
||||
__drm_helper_disable_unused_functions(dev);
|
||||
} else if (fb_changed) {
|
||||
set->crtc->x = set->x;
|
||||
set->crtc->y = set->y;
|
||||
set->crtc->fb = set->fb;
|
||||
set->crtc->primary->fb = set->fb;
|
||||
ret = crtc_funcs->mode_set_base(set->crtc,
|
||||
set->x, set->y, save_set.fb);
|
||||
if (ret != 0) {
|
||||
set->crtc->x = save_set.x;
|
||||
set->crtc->y = save_set.y;
|
||||
set->crtc->fb = save_set.fb;
|
||||
set->crtc->primary->fb = save_set.fb;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
@ -924,8 +929,16 @@ void drm_helper_connector_dpms(struct drm_connector *connector, int mode)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_helper_connector_dpms);
|
||||
|
||||
int drm_helper_mode_fill_fb_struct(struct drm_framebuffer *fb,
|
||||
struct drm_mode_fb_cmd2 *mode_cmd)
|
||||
/**
|
||||
* drm_helper_mode_fill_fb_struct - fill out framebuffer metadata
|
||||
* @fb: drm_framebuffer object to fill out
|
||||
* @mode_cmd: metadata from the userspace fb creation request
|
||||
*
|
||||
* This helper can be used in a drivers fb_create callback to pre-fill the fb's
|
||||
* metadata fields.
|
||||
*/
|
||||
void drm_helper_mode_fill_fb_struct(struct drm_framebuffer *fb,
|
||||
struct drm_mode_fb_cmd2 *mode_cmd)
|
||||
{
|
||||
int i;
|
||||
|
||||
@ -938,26 +951,47 @@ int drm_helper_mode_fill_fb_struct(struct drm_framebuffer *fb,
|
||||
drm_fb_get_bpp_depth(mode_cmd->pixel_format, &fb->depth,
|
||||
&fb->bits_per_pixel);
|
||||
fb->pixel_format = mode_cmd->pixel_format;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_helper_mode_fill_fb_struct);
|
||||
|
||||
int drm_helper_resume_force_mode(struct drm_device *dev)
|
||||
/**
|
||||
* drm_helper_resume_force_mode - force-restore mode setting configuration
|
||||
* @dev: drm_device which should be restored
|
||||
*
|
||||
* Drivers which use the mode setting helpers can use this function to
|
||||
* force-restore the mode setting configuration e.g. on resume or when something
|
||||
* else might have trampled over the hw state (like some overzealous old BIOSen
|
||||
* tended to do).
|
||||
*
|
||||
* This helper doesn't provide a error return value since restoring the old
|
||||
* config should never fail due to resource allocation issues since the driver
|
||||
* has successfully set the restored configuration already. Hence this should
|
||||
* boil down to the equivalent of a few dpms on calls, which also don't provide
|
||||
* an error code.
|
||||
*
|
||||
* Drivers where simply restoring an old configuration again might fail (e.g.
|
||||
* due to slight differences in allocating shared resources when the
|
||||
* configuration is restored in a different order than when userspace set it up)
|
||||
* need to use their own restore logic.
|
||||
*/
|
||||
void drm_helper_resume_force_mode(struct drm_device *dev)
|
||||
{
|
||||
struct drm_crtc *crtc;
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_crtc_helper_funcs *crtc_funcs;
|
||||
int ret, encoder_dpms;
|
||||
int encoder_dpms;
|
||||
bool ret;
|
||||
|
||||
drm_modeset_lock_all(dev);
|
||||
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
|
||||
|
||||
if (!crtc->enabled)
|
||||
continue;
|
||||
|
||||
ret = drm_crtc_helper_set_mode(crtc, &crtc->mode,
|
||||
crtc->x, crtc->y, crtc->fb);
|
||||
crtc->x, crtc->y, crtc->primary->fb);
|
||||
|
||||
/* Restoring the old config should never fail! */
|
||||
if (ret == false)
|
||||
DRM_ERROR("failed to set mode on crtc %p\n", crtc);
|
||||
|
||||
@ -980,12 +1014,29 @@ int drm_helper_resume_force_mode(struct drm_device *dev)
|
||||
drm_helper_choose_crtc_dpms(crtc));
|
||||
}
|
||||
}
|
||||
|
||||
/* disable the unused connectors while restoring the modesetting */
|
||||
drm_helper_disable_unused_functions(dev);
|
||||
return 0;
|
||||
__drm_helper_disable_unused_functions(dev);
|
||||
drm_modeset_unlock_all(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_helper_resume_force_mode);
|
||||
|
||||
/**
|
||||
* drm_kms_helper_hotplug_event - fire off KMS hotplug events
|
||||
* @dev: drm_device whose connector state changed
|
||||
*
|
||||
* This function fires off the uevent for userspace and also calls the
|
||||
* output_poll_changed function, which is most commonly used to inform the fbdev
|
||||
* emulation code and allow it to update the fbcon output configuration.
|
||||
*
|
||||
* Drivers should call this from their hotplug handling code when a change is
|
||||
* detected. Note that this function does not do any output detection of its
|
||||
* own, like drm_helper_hpd_irq_event() does - this is assumed to be done by the
|
||||
* driver already.
|
||||
*
|
||||
* This function must be called from process context with no mode
|
||||
* setting locks held.
|
||||
*/
|
||||
void drm_kms_helper_hotplug_event(struct drm_device *dev)
|
||||
{
|
||||
/* send a uevent + call fbdev */
|
||||
@ -1054,6 +1105,16 @@ static void output_poll_execute(struct work_struct *work)
|
||||
schedule_delayed_work(delayed_work, DRM_OUTPUT_POLL_PERIOD);
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_kms_helper_poll_disable - disable output polling
|
||||
* @dev: drm_device
|
||||
*
|
||||
* This function disables the output polling work.
|
||||
*
|
||||
* Drivers can call this helper from their device suspend implementation. It is
|
||||
* not an error to call this even when output polling isn't enabled or arlready
|
||||
* disabled.
|
||||
*/
|
||||
void drm_kms_helper_poll_disable(struct drm_device *dev)
|
||||
{
|
||||
if (!dev->mode_config.poll_enabled)
|
||||
@ -1062,6 +1123,16 @@ void drm_kms_helper_poll_disable(struct drm_device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_kms_helper_poll_disable);
|
||||
|
||||
/**
|
||||
* drm_kms_helper_poll_enable - re-enable output polling.
|
||||
* @dev: drm_device
|
||||
*
|
||||
* This function re-enables the output polling work.
|
||||
*
|
||||
* Drivers can call this helper from their device resume implementation. It is
|
||||
* an error to call this when the output polling support has not yet been set
|
||||
* up.
|
||||
*/
|
||||
void drm_kms_helper_poll_enable(struct drm_device *dev)
|
||||
{
|
||||
bool poll = false;
|
||||
@ -1081,6 +1152,25 @@ void drm_kms_helper_poll_enable(struct drm_device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_kms_helper_poll_enable);
|
||||
|
||||
/**
|
||||
* drm_kms_helper_poll_init - initialize and enable output polling
|
||||
* @dev: drm_device
|
||||
*
|
||||
* This function intializes and then also enables output polling support for
|
||||
* @dev. Drivers which do not have reliable hotplug support in hardware can use
|
||||
* this helper infrastructure to regularly poll such connectors for changes in
|
||||
* their connection state.
|
||||
*
|
||||
* Drivers can control which connectors are polled by setting the
|
||||
* DRM_CONNECTOR_POLL_CONNECT and DRM_CONNECTOR_POLL_DISCONNECT flags. On
|
||||
* connectors where probing live outputs can result in visual distortion drivers
|
||||
* should not set the DRM_CONNECTOR_POLL_DISCONNECT flag to avoid this.
|
||||
* Connectors which have no flag or only DRM_CONNECTOR_POLL_HPD set are
|
||||
* completely ignored by the polling logic.
|
||||
*
|
||||
* Note that a connector can be both polled and probed from the hotplug handler,
|
||||
* in case the hotplug interrupt is known to be unreliable.
|
||||
*/
|
||||
void drm_kms_helper_poll_init(struct drm_device *dev)
|
||||
{
|
||||
INIT_DELAYED_WORK(&dev->mode_config.output_poll_work, output_poll_execute);
|
||||
@ -1090,12 +1180,39 @@ void drm_kms_helper_poll_init(struct drm_device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_kms_helper_poll_init);
|
||||
|
||||
/**
|
||||
* drm_kms_helper_poll_fini - disable output polling and clean it up
|
||||
* @dev: drm_device
|
||||
*/
|
||||
void drm_kms_helper_poll_fini(struct drm_device *dev)
|
||||
{
|
||||
drm_kms_helper_poll_disable(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_kms_helper_poll_fini);
|
||||
|
||||
/**
|
||||
* drm_helper_hpd_irq_event - hotplug processing
|
||||
* @dev: drm_device
|
||||
*
|
||||
* Drivers can use this helper function to run a detect cycle on all connectors
|
||||
* which have the DRM_CONNECTOR_POLL_HPD flag set in their &polled member. All
|
||||
* other connectors are ignored, which is useful to avoid reprobing fixed
|
||||
* panels.
|
||||
*
|
||||
* This helper function is useful for drivers which can't or don't track hotplug
|
||||
* interrupts for each connector.
|
||||
*
|
||||
* Drivers which support hotplug interrupts for each connector individually and
|
||||
* which have a more fine-grained detect logic should bypass this code and
|
||||
* directly call drm_kms_helper_hotplug_event() in case the connector state
|
||||
* changed.
|
||||
*
|
||||
* This function must be called from process context with no mode
|
||||
* setting locks held.
|
||||
*
|
||||
* Note that a connector can be both polled and probed from the hotplug handler,
|
||||
* in case the hotplug interrupt is known to be unreliable.
|
||||
*/
|
||||
bool drm_helper_hpd_irq_event(struct drm_device *dev)
|
||||
{
|
||||
struct drm_connector *connector;
|
||||
|
38
drivers/gpu/drm/drm_crtc_internal.h
Normal file
38
drivers/gpu/drm/drm_crtc_internal.h
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright © 2006 Keith Packard
|
||||
* Copyright © 2007-2008 Dave Airlie
|
||||
* Copyright © 2007-2008 Intel Corporation
|
||||
* Jesse Barnes <jesse.barnes@intel.com>
|
||||
* Copyright © 2014 Intel Corporation
|
||||
* Daniel Vetter <daniel.vetter@ffwll.ch>
|
||||
*
|
||||
* 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 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 HOLDER(S) OR AUTHOR(S) 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This header file contains mode setting related functions and definitions
|
||||
* which are only used within the drm module as internal implementation details
|
||||
* and are not exported to drivers.
|
||||
*/
|
||||
|
||||
int drm_mode_object_get(struct drm_device *dev,
|
||||
struct drm_mode_object *obj, uint32_t obj_type);
|
||||
void drm_mode_object_put(struct drm_device *dev,
|
||||
struct drm_mode_object *object);
|
||||
|
@ -346,3 +346,399 @@ int drm_dp_bw_code_to_link_rate(u8 link_bw)
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_bw_code_to_link_rate);
|
||||
|
||||
/**
|
||||
* DOC: dp helpers
|
||||
*
|
||||
* The DisplayPort AUX channel is an abstraction to allow generic, driver-
|
||||
* independent access to AUX functionality. Drivers can take advantage of
|
||||
* this by filling in the fields of the drm_dp_aux structure.
|
||||
*
|
||||
* Transactions are described using a hardware-independent drm_dp_aux_msg
|
||||
* structure, which is passed into a driver's .transfer() implementation.
|
||||
* Both native and I2C-over-AUX transactions are supported.
|
||||
*/
|
||||
|
||||
static int drm_dp_dpcd_access(struct drm_dp_aux *aux, u8 request,
|
||||
unsigned int offset, void *buffer, size_t size)
|
||||
{
|
||||
struct drm_dp_aux_msg msg;
|
||||
unsigned int retry;
|
||||
int err;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.address = offset;
|
||||
msg.request = request;
|
||||
msg.buffer = buffer;
|
||||
msg.size = size;
|
||||
|
||||
/*
|
||||
* The specification doesn't give any recommendation on how often to
|
||||
* retry native transactions, so retry 7 times like for I2C-over-AUX
|
||||
* transactions.
|
||||
*/
|
||||
for (retry = 0; retry < 7; retry++) {
|
||||
err = aux->transfer(aux, &msg);
|
||||
if (err < 0) {
|
||||
if (err == -EBUSY)
|
||||
continue;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
switch (msg.reply & DP_AUX_NATIVE_REPLY_MASK) {
|
||||
case DP_AUX_NATIVE_REPLY_ACK:
|
||||
if (err < size)
|
||||
return -EPROTO;
|
||||
return err;
|
||||
|
||||
case DP_AUX_NATIVE_REPLY_NACK:
|
||||
return -EIO;
|
||||
|
||||
case DP_AUX_NATIVE_REPLY_DEFER:
|
||||
usleep_range(400, 500);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DRM_DEBUG_KMS("too many retries, giving up\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_dp_dpcd_read() - read a series of bytes from the DPCD
|
||||
* @aux: DisplayPort AUX channel
|
||||
* @offset: address of the (first) register to read
|
||||
* @buffer: buffer to store the register values
|
||||
* @size: number of bytes in @buffer
|
||||
*
|
||||
* Returns the number of bytes transferred on success, or a negative error
|
||||
* code on failure. -EIO is returned if the request was NAKed by the sink or
|
||||
* if the retry count was exceeded. If not all bytes were transferred, this
|
||||
* function returns -EPROTO. Errors from the underlying AUX channel transfer
|
||||
* function, with the exception of -EBUSY (which causes the transaction to
|
||||
* be retried), are propagated to the caller.
|
||||
*/
|
||||
ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset,
|
||||
void *buffer, size_t size)
|
||||
{
|
||||
return drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset, buffer,
|
||||
size);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_dpcd_read);
|
||||
|
||||
/**
|
||||
* drm_dp_dpcd_write() - write a series of bytes to the DPCD
|
||||
* @aux: DisplayPort AUX channel
|
||||
* @offset: address of the (first) register to write
|
||||
* @buffer: buffer containing the values to write
|
||||
* @size: number of bytes in @buffer
|
||||
*
|
||||
* Returns the number of bytes transferred on success, or a negative error
|
||||
* code on failure. -EIO is returned if the request was NAKed by the sink or
|
||||
* if the retry count was exceeded. If not all bytes were transferred, this
|
||||
* function returns -EPROTO. Errors from the underlying AUX channel transfer
|
||||
* function, with the exception of -EBUSY (which causes the transaction to
|
||||
* be retried), are propagated to the caller.
|
||||
*/
|
||||
ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset,
|
||||
void *buffer, size_t size)
|
||||
{
|
||||
return drm_dp_dpcd_access(aux, DP_AUX_NATIVE_WRITE, offset, buffer,
|
||||
size);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_dpcd_write);
|
||||
|
||||
/**
|
||||
* drm_dp_dpcd_read_link_status() - read DPCD link status (bytes 0x202-0x207)
|
||||
* @aux: DisplayPort AUX channel
|
||||
* @status: buffer to store the link status in (must be at least 6 bytes)
|
||||
*
|
||||
* Returns the number of bytes transferred on success or a negative error
|
||||
* code on failure.
|
||||
*/
|
||||
int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
|
||||
u8 status[DP_LINK_STATUS_SIZE])
|
||||
{
|
||||
return drm_dp_dpcd_read(aux, DP_LANE0_1_STATUS, status,
|
||||
DP_LINK_STATUS_SIZE);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_dpcd_read_link_status);
|
||||
|
||||
/**
|
||||
* drm_dp_link_probe() - probe a DisplayPort link for capabilities
|
||||
* @aux: DisplayPort AUX channel
|
||||
* @link: pointer to structure in which to return link capabilities
|
||||
*
|
||||
* The structure filled in by this function can usually be passed directly
|
||||
* into drm_dp_link_power_up() and drm_dp_link_configure() to power up and
|
||||
* configure the link based on the link's capabilities.
|
||||
*
|
||||
* Returns 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int drm_dp_link_probe(struct drm_dp_aux *aux, struct drm_dp_link *link)
|
||||
{
|
||||
u8 values[3];
|
||||
int err;
|
||||
|
||||
memset(link, 0, sizeof(*link));
|
||||
|
||||
err = drm_dp_dpcd_read(aux, DP_DPCD_REV, values, sizeof(values));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
link->revision = values[0];
|
||||
link->rate = drm_dp_bw_code_to_link_rate(values[1]);
|
||||
link->num_lanes = values[2] & DP_MAX_LANE_COUNT_MASK;
|
||||
|
||||
if (values[2] & DP_ENHANCED_FRAME_CAP)
|
||||
link->capabilities |= DP_LINK_CAP_ENHANCED_FRAMING;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_link_probe);
|
||||
|
||||
/**
|
||||
* drm_dp_link_power_up() - power up a DisplayPort link
|
||||
* @aux: DisplayPort AUX channel
|
||||
* @link: pointer to a structure containing the link configuration
|
||||
*
|
||||
* Returns 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int drm_dp_link_power_up(struct drm_dp_aux *aux, struct drm_dp_link *link)
|
||||
{
|
||||
u8 value;
|
||||
int err;
|
||||
|
||||
/* DP_SET_POWER register is only available on DPCD v1.1 and later */
|
||||
if (link->revision < 0x11)
|
||||
return 0;
|
||||
|
||||
err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
value &= ~DP_SET_POWER_MASK;
|
||||
value |= DP_SET_POWER_D0;
|
||||
|
||||
err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* According to the DP 1.1 specification, a "Sink Device must exit the
|
||||
* power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink
|
||||
* Control Field" (register 0x600).
|
||||
*/
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_link_power_up);
|
||||
|
||||
/**
|
||||
* drm_dp_link_configure() - configure a DisplayPort link
|
||||
* @aux: DisplayPort AUX channel
|
||||
* @link: pointer to a structure containing the link configuration
|
||||
*
|
||||
* Returns 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int drm_dp_link_configure(struct drm_dp_aux *aux, struct drm_dp_link *link)
|
||||
{
|
||||
u8 values[2];
|
||||
int err;
|
||||
|
||||
values[0] = drm_dp_link_rate_to_bw_code(link->rate);
|
||||
values[1] = link->num_lanes;
|
||||
|
||||
if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING)
|
||||
values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
|
||||
|
||||
err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_link_configure);
|
||||
|
||||
/*
|
||||
* I2C-over-AUX implementation
|
||||
*/
|
||||
|
||||
static u32 drm_dp_i2c_functionality(struct i2c_adapter *adapter)
|
||||
{
|
||||
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
|
||||
I2C_FUNC_SMBUS_READ_BLOCK_DATA |
|
||||
I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
|
||||
I2C_FUNC_10BIT_ADDR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Transfer a single I2C-over-AUX message and handle various error conditions,
|
||||
* retrying the transaction as appropriate.
|
||||
*/
|
||||
static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
|
||||
{
|
||||
unsigned int retry;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device
|
||||
* is required to retry at least seven times upon receiving AUX_DEFER
|
||||
* before giving up the AUX transaction.
|
||||
*/
|
||||
for (retry = 0; retry < 7; retry++) {
|
||||
err = aux->transfer(aux, msg);
|
||||
if (err < 0) {
|
||||
if (err == -EBUSY)
|
||||
continue;
|
||||
|
||||
DRM_DEBUG_KMS("transaction failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
switch (msg->reply & DP_AUX_NATIVE_REPLY_MASK) {
|
||||
case DP_AUX_NATIVE_REPLY_ACK:
|
||||
/*
|
||||
* For I2C-over-AUX transactions this isn't enough, we
|
||||
* need to check for the I2C ACK reply.
|
||||
*/
|
||||
break;
|
||||
|
||||
case DP_AUX_NATIVE_REPLY_NACK:
|
||||
DRM_DEBUG_KMS("native nack\n");
|
||||
return -EREMOTEIO;
|
||||
|
||||
case DP_AUX_NATIVE_REPLY_DEFER:
|
||||
DRM_DEBUG_KMS("native defer");
|
||||
/*
|
||||
* We could check for I2C bit rate capabilities and if
|
||||
* available adjust this interval. We could also be
|
||||
* more careful with DP-to-legacy adapters where a
|
||||
* long legacy cable may force very low I2C bit rates.
|
||||
*
|
||||
* For now just defer for long enough to hopefully be
|
||||
* safe for all use-cases.
|
||||
*/
|
||||
usleep_range(500, 600);
|
||||
continue;
|
||||
|
||||
default:
|
||||
DRM_ERROR("invalid native reply %#04x\n", msg->reply);
|
||||
return -EREMOTEIO;
|
||||
}
|
||||
|
||||
switch (msg->reply & DP_AUX_I2C_REPLY_MASK) {
|
||||
case DP_AUX_I2C_REPLY_ACK:
|
||||
/*
|
||||
* Both native ACK and I2C ACK replies received. We
|
||||
* can assume the transfer was successful.
|
||||
*/
|
||||
if (err < msg->size)
|
||||
return -EPROTO;
|
||||
return 0;
|
||||
|
||||
case DP_AUX_I2C_REPLY_NACK:
|
||||
DRM_DEBUG_KMS("I2C nack\n");
|
||||
return -EREMOTEIO;
|
||||
|
||||
case DP_AUX_I2C_REPLY_DEFER:
|
||||
DRM_DEBUG_KMS("I2C defer\n");
|
||||
usleep_range(400, 500);
|
||||
continue;
|
||||
|
||||
default:
|
||||
DRM_ERROR("invalid I2C reply %#04x\n", msg->reply);
|
||||
return -EREMOTEIO;
|
||||
}
|
||||
}
|
||||
|
||||
DRM_DEBUG_KMS("too many retries, giving up\n");
|
||||
return -EREMOTEIO;
|
||||
}
|
||||
|
||||
static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
|
||||
int num)
|
||||
{
|
||||
struct drm_dp_aux *aux = adapter->algo_data;
|
||||
unsigned int i, j;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
struct drm_dp_aux_msg msg;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Many hardware implementations support FIFOs larger than a
|
||||
* single byte, but it has been empirically determined that
|
||||
* transferring data in larger chunks can actually lead to
|
||||
* decreased performance. Therefore each message is simply
|
||||
* transferred byte-by-byte.
|
||||
*/
|
||||
for (j = 0; j < msgs[i].len; j++) {
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.address = msgs[i].addr;
|
||||
|
||||
msg.request = (msgs[i].flags & I2C_M_RD) ?
|
||||
DP_AUX_I2C_READ :
|
||||
DP_AUX_I2C_WRITE;
|
||||
|
||||
/*
|
||||
* All messages except the last one are middle-of-
|
||||
* transfer messages.
|
||||
*/
|
||||
if ((i < num - 1) || (j < msgs[i].len - 1))
|
||||
msg.request |= DP_AUX_I2C_MOT;
|
||||
|
||||
msg.buffer = msgs[i].buf + j;
|
||||
msg.size = 1;
|
||||
|
||||
err = drm_dp_i2c_do_msg(aux, &msg);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm drm_dp_i2c_algo = {
|
||||
.functionality = drm_dp_i2c_functionality,
|
||||
.master_xfer = drm_dp_i2c_xfer,
|
||||
};
|
||||
|
||||
/**
|
||||
* drm_dp_aux_register_i2c_bus() - register an I2C adapter for I2C-over-AUX
|
||||
* @aux: DisplayPort AUX channel
|
||||
*
|
||||
* Returns 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int drm_dp_aux_register_i2c_bus(struct drm_dp_aux *aux)
|
||||
{
|
||||
aux->ddc.algo = &drm_dp_i2c_algo;
|
||||
aux->ddc.algo_data = aux;
|
||||
aux->ddc.retries = 3;
|
||||
|
||||
aux->ddc.class = I2C_CLASS_DDC;
|
||||
aux->ddc.owner = THIS_MODULE;
|
||||
aux->ddc.dev.parent = aux->dev;
|
||||
aux->ddc.dev.of_node = aux->dev->of_node;
|
||||
|
||||
strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(aux->dev),
|
||||
sizeof(aux->ddc.name));
|
||||
|
||||
return i2c_add_adapter(&aux->ddc);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_aux_register_i2c_bus);
|
||||
|
||||
/**
|
||||
* drm_dp_aux_unregister_i2c_bus() - unregister an I2C-over-AUX adapter
|
||||
* @aux: DisplayPort AUX channel
|
||||
*/
|
||||
void drm_dp_aux_unregister_i2c_bus(struct drm_dp_aux *aux)
|
||||
{
|
||||
i2c_del_adapter(&aux->ddc);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_aux_unregister_i2c_bus);
|
||||
|
@ -285,6 +285,45 @@ static int drm_version(struct drm_device *dev, void *data,
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_ioctl_permit - Check ioctl permissions against caller
|
||||
*
|
||||
* @flags: ioctl permission flags.
|
||||
* @file_priv: Pointer to struct drm_file identifying the caller.
|
||||
*
|
||||
* Checks whether the caller is allowed to run an ioctl with the
|
||||
* indicated permissions. If so, returns zero. Otherwise returns an
|
||||
* error code suitable for ioctl return.
|
||||
*/
|
||||
static int drm_ioctl_permit(u32 flags, struct drm_file *file_priv)
|
||||
{
|
||||
/* ROOT_ONLY is only for CAP_SYS_ADMIN */
|
||||
if (unlikely((flags & DRM_ROOT_ONLY) && !capable(CAP_SYS_ADMIN)))
|
||||
return -EACCES;
|
||||
|
||||
/* AUTH is only for authenticated or render client */
|
||||
if (unlikely((flags & DRM_AUTH) && !drm_is_render_client(file_priv) &&
|
||||
!file_priv->authenticated))
|
||||
return -EACCES;
|
||||
|
||||
/* MASTER is only for master or control clients */
|
||||
if (unlikely((flags & DRM_MASTER) && !file_priv->is_master &&
|
||||
!drm_is_control_client(file_priv)))
|
||||
return -EACCES;
|
||||
|
||||
/* Control clients must be explicitly allowed */
|
||||
if (unlikely(!(flags & DRM_CONTROL_ALLOW) &&
|
||||
drm_is_control_client(file_priv)))
|
||||
return -EACCES;
|
||||
|
||||
/* Render clients must be explicitly allowed */
|
||||
if (unlikely(!(flags & DRM_RENDER_ALLOW) &&
|
||||
drm_is_render_client(file_priv)))
|
||||
return -EACCES;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a process performs an ioctl on /dev/drm.
|
||||
*
|
||||
@ -344,65 +383,64 @@ long drm_ioctl(struct file *filp,
|
||||
|
||||
DRM_DEBUG("pid=%d, dev=0x%lx, auth=%d, %s\n",
|
||||
task_pid_nr(current),
|
||||
(long)old_encode_dev(file_priv->minor->device),
|
||||
(long)old_encode_dev(file_priv->minor->kdev->devt),
|
||||
file_priv->authenticated, ioctl->name);
|
||||
|
||||
/* Do not trust userspace, use our own definition */
|
||||
func = ioctl->func;
|
||||
|
||||
if (!func) {
|
||||
if (unlikely(!func)) {
|
||||
DRM_DEBUG("no function\n");
|
||||
retcode = -EINVAL;
|
||||
} else if (((ioctl->flags & DRM_ROOT_ONLY) && !capable(CAP_SYS_ADMIN)) ||
|
||||
((ioctl->flags & DRM_AUTH) && !drm_is_render_client(file_priv) && !file_priv->authenticated) ||
|
||||
((ioctl->flags & DRM_MASTER) && !file_priv->is_master) ||
|
||||
(!(ioctl->flags & DRM_CONTROL_ALLOW) && (file_priv->minor->type == DRM_MINOR_CONTROL)) ||
|
||||
(!(ioctl->flags & DRM_RENDER_ALLOW) && drm_is_render_client(file_priv))) {
|
||||
retcode = -EACCES;
|
||||
} else {
|
||||
if (cmd & (IOC_IN | IOC_OUT)) {
|
||||
if (asize <= sizeof(stack_kdata)) {
|
||||
kdata = stack_kdata;
|
||||
} else {
|
||||
kdata = kmalloc(asize, GFP_KERNEL);
|
||||
if (!kdata) {
|
||||
retcode = -ENOMEM;
|
||||
goto err_i1;
|
||||
}
|
||||
}
|
||||
if (asize > usize)
|
||||
memset(kdata + usize, 0, asize - usize);
|
||||
}
|
||||
goto err_i1;
|
||||
}
|
||||
|
||||
if (cmd & IOC_IN) {
|
||||
if (copy_from_user(kdata, (void __user *)arg,
|
||||
usize) != 0) {
|
||||
retcode = -EFAULT;
|
||||
retcode = drm_ioctl_permit(ioctl->flags, file_priv);
|
||||
if (unlikely(retcode))
|
||||
goto err_i1;
|
||||
|
||||
if (cmd & (IOC_IN | IOC_OUT)) {
|
||||
if (asize <= sizeof(stack_kdata)) {
|
||||
kdata = stack_kdata;
|
||||
} else {
|
||||
kdata = kmalloc(asize, GFP_KERNEL);
|
||||
if (!kdata) {
|
||||
retcode = -ENOMEM;
|
||||
goto err_i1;
|
||||
}
|
||||
} else
|
||||
memset(kdata, 0, usize);
|
||||
|
||||
if (ioctl->flags & DRM_UNLOCKED)
|
||||
retcode = func(dev, kdata, file_priv);
|
||||
else {
|
||||
mutex_lock(&drm_global_mutex);
|
||||
retcode = func(dev, kdata, file_priv);
|
||||
mutex_unlock(&drm_global_mutex);
|
||||
}
|
||||
if (asize > usize)
|
||||
memset(kdata + usize, 0, asize - usize);
|
||||
}
|
||||
|
||||
if (cmd & IOC_OUT) {
|
||||
if (copy_to_user((void __user *)arg, kdata,
|
||||
usize) != 0)
|
||||
retcode = -EFAULT;
|
||||
if (cmd & IOC_IN) {
|
||||
if (copy_from_user(kdata, (void __user *)arg,
|
||||
usize) != 0) {
|
||||
retcode = -EFAULT;
|
||||
goto err_i1;
|
||||
}
|
||||
} else
|
||||
memset(kdata, 0, usize);
|
||||
|
||||
if (ioctl->flags & DRM_UNLOCKED)
|
||||
retcode = func(dev, kdata, file_priv);
|
||||
else {
|
||||
mutex_lock(&drm_global_mutex);
|
||||
retcode = func(dev, kdata, file_priv);
|
||||
mutex_unlock(&drm_global_mutex);
|
||||
}
|
||||
|
||||
if (cmd & IOC_OUT) {
|
||||
if (copy_to_user((void __user *)arg, kdata,
|
||||
usize) != 0)
|
||||
retcode = -EFAULT;
|
||||
}
|
||||
|
||||
err_i1:
|
||||
if (!ioctl)
|
||||
DRM_DEBUG("invalid ioctl: pid=%d, dev=0x%lx, auth=%d, cmd=0x%02x, nr=0x%02x\n",
|
||||
task_pid_nr(current),
|
||||
(long)old_encode_dev(file_priv->minor->device),
|
||||
(long)old_encode_dev(file_priv->minor->kdev->devt),
|
||||
file_priv->authenticated, cmd, nr);
|
||||
|
||||
if (kdata != stack_kdata)
|
||||
@ -412,3 +450,21 @@ long drm_ioctl(struct file *filp,
|
||||
return retcode;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_ioctl);
|
||||
|
||||
/**
|
||||
* drm_ioctl_flags - Check for core ioctl and return ioctl permission flags
|
||||
*
|
||||
* @nr: Ioctl number.
|
||||
* @flags: Where to return the ioctl permission flags
|
||||
*/
|
||||
bool drm_ioctl_flags(unsigned int nr, unsigned int *flags)
|
||||
{
|
||||
if ((nr >= DRM_COMMAND_END && nr < DRM_CORE_IOCTL_COUNT) ||
|
||||
(nr < DRM_COMMAND_BASE)) {
|
||||
*flags = drm_ioctls[nr].flags;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_ioctl_flags);
|
||||
|
@ -1098,10 +1098,14 @@ EXPORT_SYMBOL(drm_edid_is_valid);
|
||||
/**
|
||||
* Get EDID information via I2C.
|
||||
*
|
||||
* \param adapter : i2c device adaptor
|
||||
* \param buf : EDID data buffer to be filled
|
||||
* \param len : EDID data buffer length
|
||||
* \return 0 on success or -1 on failure.
|
||||
* @adapter : i2c device adaptor
|
||||
* @buf: EDID data buffer to be filled
|
||||
* @block: 128 byte EDID block to start fetching from
|
||||
* @len: EDID data buffer length to fetch
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* 0 on success or -1 on failure.
|
||||
*
|
||||
* Try to fetch EDID information by calling i2c driver function.
|
||||
*/
|
||||
@ -1243,9 +1247,11 @@ out:
|
||||
|
||||
/**
|
||||
* Probe DDC presence.
|
||||
* @adapter: i2c adapter to probe
|
||||
*
|
||||
* \param adapter : i2c device adaptor
|
||||
* \return 1 on success
|
||||
* Returns:
|
||||
*
|
||||
* 1 on success
|
||||
*/
|
||||
bool
|
||||
drm_probe_ddc(struct i2c_adapter *adapter)
|
||||
@ -1586,8 +1592,10 @@ bad_std_timing(u8 a, u8 b)
|
||||
|
||||
/**
|
||||
* drm_mode_std - convert standard mode info (width, height, refresh) into mode
|
||||
* @connector: connector of for the EDID block
|
||||
* @edid: EDID block to scan
|
||||
* @t: standard timing params
|
||||
* @timing_level: standard timing level
|
||||
* @revision: standard timing level
|
||||
*
|
||||
* Take the standard timing params (in this case width, aspect, and refresh)
|
||||
* and convert them into a real mode using CVT/GTF/DMT.
|
||||
@ -2132,6 +2140,7 @@ do_established_modes(struct detailed_timing *timing, void *c)
|
||||
|
||||
/**
|
||||
* add_established_modes - get est. modes from EDID and add them
|
||||
* @connector: connector of for the EDID block
|
||||
* @edid: EDID block to scan
|
||||
*
|
||||
* Each EDID block contains a bitmap of the supported "established modes" list
|
||||
@ -2194,6 +2203,7 @@ do_standard_modes(struct detailed_timing *timing, void *c)
|
||||
|
||||
/**
|
||||
* add_standard_modes - get std. modes from EDID and add them
|
||||
* @connector: connector of for the EDID block
|
||||
* @edid: EDID block to scan
|
||||
*
|
||||
* Standard modes can be calculated using the appropriate standard (DMT,
|
||||
@ -2580,6 +2590,9 @@ drm_display_mode_from_vic_index(struct drm_connector *connector,
|
||||
return NULL;
|
||||
|
||||
newmode = drm_mode_duplicate(dev, &edid_cea_modes[cea_mode]);
|
||||
if (!newmode)
|
||||
return NULL;
|
||||
|
||||
newmode->vrefresh = 0;
|
||||
|
||||
return newmode;
|
||||
@ -3300,6 +3313,7 @@ EXPORT_SYMBOL(drm_detect_hdmi_monitor);
|
||||
|
||||
/**
|
||||
* drm_detect_monitor_audio - check monitor audio capability
|
||||
* @edid: EDID block to scan
|
||||
*
|
||||
* Monitor should have CEA extension block.
|
||||
* If monitor has 'basic audio', but no CEA audio blocks, it's 'basic
|
||||
@ -3345,6 +3359,7 @@ EXPORT_SYMBOL(drm_detect_monitor_audio);
|
||||
|
||||
/**
|
||||
* drm_rgb_quant_range_selectable - is RGB quantization range selectable?
|
||||
* @edid: EDID block to scan
|
||||
*
|
||||
* Check whether the monitor reports the RGB quantization range selection
|
||||
* as supported. The AVI infoframe can then be used to inform the monitor
|
||||
@ -3564,8 +3579,8 @@ void drm_set_preferred_mode(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode;
|
||||
|
||||
list_for_each_entry(mode, &connector->probed_modes, head) {
|
||||
if (drm_mode_width(mode) == hpref &&
|
||||
drm_mode_height(mode) == vpref)
|
||||
if (mode->hdisplay == hpref &&
|
||||
mode->vdisplay == vpref)
|
||||
mode->type |= DRM_MODE_TYPE_PREFERRED;
|
||||
}
|
||||
}
|
||||
@ -3599,6 +3614,7 @@ drm_hdmi_avi_infoframe_from_display_mode(struct hdmi_avi_infoframe *frame,
|
||||
|
||||
frame->picture_aspect = HDMI_PICTURE_ASPECT_NONE;
|
||||
frame->active_aspect = HDMI_ACTIVE_ASPECT_PICTURE;
|
||||
frame->scan_mode = HDMI_SCAN_MODE_UNDERSCAN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ static struct drm_framebuffer *drm_mode_config_fb(struct drm_crtc *crtc)
|
||||
|
||||
list_for_each_entry(c, &dev->mode_config.crtc_list, head) {
|
||||
if (crtc->base.id == c->base.id)
|
||||
return c->fb;
|
||||
return c->primary->fb;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -291,7 +291,8 @@ bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper)
|
||||
drm_warn_on_modeset_not_all_locked(dev);
|
||||
|
||||
list_for_each_entry(plane, &dev->mode_config.plane_list, head)
|
||||
drm_plane_force_disable(plane);
|
||||
if (plane->type != DRM_PLANE_TYPE_PRIMARY)
|
||||
drm_plane_force_disable(plane);
|
||||
|
||||
for (i = 0; i < fb_helper->crtc_count; i++) {
|
||||
struct drm_mode_set *mode_set = &fb_helper->crtc_info[i].mode_set;
|
||||
@ -365,9 +366,9 @@ static bool drm_fb_helper_is_bound(struct drm_fb_helper *fb_helper)
|
||||
return false;
|
||||
|
||||
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
|
||||
if (crtc->fb)
|
||||
if (crtc->primary->fb)
|
||||
crtcs_bound++;
|
||||
if (crtc->fb == fb_helper->fb)
|
||||
if (crtc->primary->fb == fb_helper->fb)
|
||||
bound++;
|
||||
}
|
||||
|
||||
@ -516,6 +517,9 @@ int drm_fb_helper_init(struct drm_device *dev,
|
||||
struct drm_crtc *crtc;
|
||||
int i;
|
||||
|
||||
if (!max_conn_count)
|
||||
return -EINVAL;
|
||||
|
||||
fb_helper->dev = dev;
|
||||
|
||||
INIT_LIST_HEAD(&fb_helper->kernel_fb_list);
|
||||
@ -809,8 +813,6 @@ int drm_fb_helper_set_par(struct fb_info *info)
|
||||
struct drm_fb_helper *fb_helper = info->par;
|
||||
struct drm_device *dev = fb_helper->dev;
|
||||
struct fb_var_screeninfo *var = &info->var;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (var->pixclock != 0) {
|
||||
DRM_ERROR("PIXEL CLOCK SET\n");
|
||||
@ -818,13 +820,7 @@ int drm_fb_helper_set_par(struct fb_info *info)
|
||||
}
|
||||
|
||||
drm_modeset_lock_all(dev);
|
||||
for (i = 0; i < fb_helper->crtc_count; i++) {
|
||||
ret = drm_mode_set_config_internal(&fb_helper->crtc_info[i].mode_set);
|
||||
if (ret) {
|
||||
drm_modeset_unlock_all(dev);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
drm_fb_helper_restore_fbdev_mode(fb_helper);
|
||||
drm_modeset_unlock_all(dev);
|
||||
|
||||
if (fb_helper->delayed_hotplug) {
|
||||
@ -1136,19 +1132,20 @@ static int drm_fb_helper_probe_connector_modes(struct drm_fb_helper *fb_helper,
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct drm_display_mode *drm_has_preferred_mode(struct drm_fb_helper_connector *fb_connector, int width, int height)
|
||||
struct drm_display_mode *drm_has_preferred_mode(struct drm_fb_helper_connector *fb_connector, int width, int height)
|
||||
{
|
||||
struct drm_display_mode *mode;
|
||||
|
||||
list_for_each_entry(mode, &fb_connector->connector->modes, head) {
|
||||
if (drm_mode_width(mode) > width ||
|
||||
drm_mode_height(mode) > height)
|
||||
if (mode->hdisplay > width ||
|
||||
mode->vdisplay > height)
|
||||
continue;
|
||||
if (mode->type & DRM_MODE_TYPE_PREFERRED)
|
||||
return mode;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_has_preferred_mode);
|
||||
|
||||
static bool drm_has_cmdline_mode(struct drm_fb_helper_connector *fb_connector)
|
||||
{
|
||||
@ -1157,11 +1154,12 @@ static bool drm_has_cmdline_mode(struct drm_fb_helper_connector *fb_connector)
|
||||
return cmdline_mode->specified;
|
||||
}
|
||||
|
||||
static struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *fb_helper_conn,
|
||||
struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *fb_helper_conn,
|
||||
int width, int height)
|
||||
{
|
||||
struct drm_cmdline_mode *cmdline_mode;
|
||||
struct drm_display_mode *mode = NULL;
|
||||
bool prefer_non_interlace;
|
||||
|
||||
cmdline_mode = &fb_helper_conn->cmdline_mode;
|
||||
if (cmdline_mode->specified == false)
|
||||
@ -1173,6 +1171,8 @@ static struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_conne
|
||||
if (cmdline_mode->rb || cmdline_mode->margins)
|
||||
goto create_mode;
|
||||
|
||||
prefer_non_interlace = !cmdline_mode->interlace;
|
||||
again:
|
||||
list_for_each_entry(mode, &fb_helper_conn->connector->modes, head) {
|
||||
/* check width/height */
|
||||
if (mode->hdisplay != cmdline_mode->xres ||
|
||||
@ -1187,16 +1187,25 @@ static struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_conne
|
||||
if (cmdline_mode->interlace) {
|
||||
if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
|
||||
continue;
|
||||
} else if (prefer_non_interlace) {
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
||||
continue;
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
if (prefer_non_interlace) {
|
||||
prefer_non_interlace = false;
|
||||
goto again;
|
||||
}
|
||||
|
||||
create_mode:
|
||||
mode = drm_mode_create_from_cmdline_mode(fb_helper_conn->connector->dev,
|
||||
cmdline_mode);
|
||||
list_add(&mode->head, &fb_helper_conn->connector->modes);
|
||||
return mode;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_pick_cmdline_mode);
|
||||
|
||||
static bool drm_connector_enabled(struct drm_connector *connector, bool strict)
|
||||
{
|
||||
@ -1539,9 +1548,11 @@ bool drm_fb_helper_initial_config(struct drm_fb_helper *fb_helper, int bpp_sel)
|
||||
|
||||
drm_fb_helper_parse_command_line(fb_helper);
|
||||
|
||||
mutex_lock(&dev->mode_config.mutex);
|
||||
count = drm_fb_helper_probe_connector_modes(fb_helper,
|
||||
dev->mode_config.max_width,
|
||||
dev->mode_config.max_height);
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
/*
|
||||
* we shouldn't end up with no modes here.
|
||||
*/
|
||||
|
@ -39,12 +39,12 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
/* from BKL pushdown: note that nothing else serializes idr_find() */
|
||||
/* from BKL pushdown */
|
||||
DEFINE_MUTEX(drm_global_mutex);
|
||||
EXPORT_SYMBOL(drm_global_mutex);
|
||||
|
||||
static int drm_open_helper(struct inode *inode, struct file *filp,
|
||||
struct drm_device * dev);
|
||||
struct drm_minor *minor);
|
||||
|
||||
static int drm_setup(struct drm_device * dev)
|
||||
{
|
||||
@ -79,38 +79,23 @@ static int drm_setup(struct drm_device * dev)
|
||||
*/
|
||||
int drm_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct drm_device *dev = NULL;
|
||||
int minor_id = iminor(inode);
|
||||
struct drm_device *dev;
|
||||
struct drm_minor *minor;
|
||||
int retcode = 0;
|
||||
int retcode;
|
||||
int need_setup = 0;
|
||||
struct address_space *old_mapping;
|
||||
struct address_space *old_imapping;
|
||||
|
||||
minor = idr_find(&drm_minors_idr, minor_id);
|
||||
if (!minor)
|
||||
return -ENODEV;
|
||||
|
||||
if (!(dev = minor->dev))
|
||||
return -ENODEV;
|
||||
|
||||
if (drm_device_is_unplugged(dev))
|
||||
return -ENODEV;
|
||||
minor = drm_minor_acquire(iminor(inode));
|
||||
if (IS_ERR(minor))
|
||||
return PTR_ERR(minor);
|
||||
|
||||
dev = minor->dev;
|
||||
if (!dev->open_count++)
|
||||
need_setup = 1;
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
old_imapping = inode->i_mapping;
|
||||
old_mapping = dev->dev_mapping;
|
||||
if (old_mapping == NULL)
|
||||
dev->dev_mapping = &inode->i_data;
|
||||
/* ihold ensures nobody can remove inode with our i_data */
|
||||
ihold(container_of(dev->dev_mapping, struct inode, i_data));
|
||||
inode->i_mapping = dev->dev_mapping;
|
||||
filp->f_mapping = dev->dev_mapping;
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
retcode = drm_open_helper(inode, filp, dev);
|
||||
/* share address_space across all char-devs of a single device */
|
||||
filp->f_mapping = dev->anon_inode->i_mapping;
|
||||
|
||||
retcode = drm_open_helper(inode, filp, minor);
|
||||
if (retcode)
|
||||
goto err_undo;
|
||||
if (need_setup) {
|
||||
@ -121,13 +106,8 @@ int drm_open(struct inode *inode, struct file *filp)
|
||||
return 0;
|
||||
|
||||
err_undo:
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
filp->f_mapping = old_imapping;
|
||||
inode->i_mapping = old_imapping;
|
||||
iput(container_of(dev->dev_mapping, struct inode, i_data));
|
||||
dev->dev_mapping = old_mapping;
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
dev->open_count--;
|
||||
drm_minor_release(minor);
|
||||
return retcode;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_open);
|
||||
@ -143,33 +123,30 @@ EXPORT_SYMBOL(drm_open);
|
||||
*/
|
||||
int drm_stub_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct drm_device *dev = NULL;
|
||||
struct drm_device *dev;
|
||||
struct drm_minor *minor;
|
||||
int minor_id = iminor(inode);
|
||||
int err = -ENODEV;
|
||||
const struct file_operations *new_fops;
|
||||
|
||||
DRM_DEBUG("\n");
|
||||
|
||||
mutex_lock(&drm_global_mutex);
|
||||
minor = idr_find(&drm_minors_idr, minor_id);
|
||||
if (!minor)
|
||||
goto out;
|
||||
|
||||
if (!(dev = minor->dev))
|
||||
goto out;
|
||||
|
||||
if (drm_device_is_unplugged(dev))
|
||||
goto out;
|
||||
minor = drm_minor_acquire(iminor(inode));
|
||||
if (IS_ERR(minor))
|
||||
goto out_unlock;
|
||||
|
||||
dev = minor->dev;
|
||||
new_fops = fops_get(dev->driver->fops);
|
||||
if (!new_fops)
|
||||
goto out;
|
||||
goto out_release;
|
||||
|
||||
replace_fops(filp, new_fops);
|
||||
if (filp->f_op->open)
|
||||
err = filp->f_op->open(inode, filp);
|
||||
out:
|
||||
|
||||
out_release:
|
||||
drm_minor_release(minor);
|
||||
out_unlock:
|
||||
mutex_unlock(&drm_global_mutex);
|
||||
return err;
|
||||
}
|
||||
@ -196,16 +173,16 @@ static int drm_cpu_valid(void)
|
||||
*
|
||||
* \param inode device inode.
|
||||
* \param filp file pointer.
|
||||
* \param dev device.
|
||||
* \param minor acquired minor-object.
|
||||
* \return zero on success or a negative number on failure.
|
||||
*
|
||||
* Creates and initializes a drm_file structure for the file private data in \p
|
||||
* filp and add it into the double linked list in \p dev.
|
||||
*/
|
||||
static int drm_open_helper(struct inode *inode, struct file *filp,
|
||||
struct drm_device * dev)
|
||||
struct drm_minor *minor)
|
||||
{
|
||||
int minor_id = iminor(inode);
|
||||
struct drm_device *dev = minor->dev;
|
||||
struct drm_file *priv;
|
||||
int ret;
|
||||
|
||||
@ -216,7 +193,7 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
|
||||
if (dev->switch_power_state != DRM_SWITCH_POWER_ON && dev->switch_power_state != DRM_SWITCH_POWER_DYNAMIC_OFF)
|
||||
return -EINVAL;
|
||||
|
||||
DRM_DEBUG("pid = %d, minor = %d\n", task_pid_nr(current), minor_id);
|
||||
DRM_DEBUG("pid = %d, minor = %d\n", task_pid_nr(current), minor->index);
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
@ -226,11 +203,7 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
|
||||
priv->filp = filp;
|
||||
priv->uid = current_euid();
|
||||
priv->pid = get_pid(task_pid(current));
|
||||
priv->minor = idr_find(&drm_minors_idr, minor_id);
|
||||
if (!priv->minor) {
|
||||
ret = -ENODEV;
|
||||
goto out_put_pid;
|
||||
}
|
||||
priv->minor = minor;
|
||||
|
||||
/* for compatibility root is always authenticated */
|
||||
priv->always_authenticated = capable(CAP_SYS_ADMIN);
|
||||
@ -258,12 +231,11 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
|
||||
|
||||
/* if there is no current master make this fd it, but do not create
|
||||
* any master object for render clients */
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
if (!priv->minor->master && !drm_is_render_client(priv)) {
|
||||
mutex_lock(&dev->master_mutex);
|
||||
if (drm_is_primary_client(priv) && !priv->minor->master) {
|
||||
/* create a new master */
|
||||
priv->minor->master = drm_master_create(priv->minor);
|
||||
if (!priv->minor->master) {
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
ret = -ENOMEM;
|
||||
goto out_close;
|
||||
}
|
||||
@ -271,37 +243,31 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
|
||||
priv->is_master = 1;
|
||||
/* take another reference for the copy in the local file priv */
|
||||
priv->master = drm_master_get(priv->minor->master);
|
||||
|
||||
priv->authenticated = 1;
|
||||
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
if (dev->driver->master_create) {
|
||||
ret = dev->driver->master_create(dev, priv->master);
|
||||
if (ret) {
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
/* drop both references if this fails */
|
||||
drm_master_put(&priv->minor->master);
|
||||
drm_master_put(&priv->master);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
goto out_close;
|
||||
}
|
||||
}
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
if (dev->driver->master_set) {
|
||||
ret = dev->driver->master_set(dev, priv, true);
|
||||
if (ret) {
|
||||
/* drop both references if this fails */
|
||||
drm_master_put(&priv->minor->master);
|
||||
drm_master_put(&priv->master);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
goto out_close;
|
||||
}
|
||||
}
|
||||
} else if (!drm_is_render_client(priv)) {
|
||||
} else if (drm_is_primary_client(priv)) {
|
||||
/* get a reference to the master */
|
||||
priv->master = drm_master_get(priv->minor->master);
|
||||
}
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
mutex_unlock(&dev->master_mutex);
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
list_add(&priv->lhead, &dev->filelist);
|
||||
@ -330,6 +296,7 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
|
||||
return 0;
|
||||
|
||||
out_close:
|
||||
mutex_unlock(&dev->master_mutex);
|
||||
if (dev->driver->postclose)
|
||||
dev->driver->postclose(dev, priv);
|
||||
out_prime_destroy:
|
||||
@ -337,7 +304,6 @@ out_prime_destroy:
|
||||
drm_prime_destroy_file_private(&priv->prime);
|
||||
if (dev->driver->driver_features & DRIVER_GEM)
|
||||
drm_gem_release(dev, priv);
|
||||
out_put_pid:
|
||||
put_pid(priv->pid);
|
||||
kfree(priv);
|
||||
filp->private_data = NULL;
|
||||
@ -435,7 +401,6 @@ int drm_lastclose(struct drm_device * dev)
|
||||
|
||||
drm_legacy_dma_takedown(dev);
|
||||
|
||||
dev->dev_mapping = NULL;
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
drm_legacy_dev_reinit(dev);
|
||||
@ -459,7 +424,8 @@ int drm_lastclose(struct drm_device * dev)
|
||||
int drm_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct drm_file *file_priv = filp->private_data;
|
||||
struct drm_device *dev = file_priv->minor->dev;
|
||||
struct drm_minor *minor = file_priv->minor;
|
||||
struct drm_device *dev = minor->dev;
|
||||
int retcode = 0;
|
||||
|
||||
mutex_lock(&drm_global_mutex);
|
||||
@ -475,7 +441,7 @@ int drm_release(struct inode *inode, struct file *filp)
|
||||
|
||||
DRM_DEBUG("pid = %d, device = 0x%lx, open_count = %d\n",
|
||||
task_pid_nr(current),
|
||||
(long)old_encode_dev(file_priv->minor->device),
|
||||
(long)old_encode_dev(file_priv->minor->kdev->devt),
|
||||
dev->open_count);
|
||||
|
||||
/* Release any auth tokens that might point to this file_priv,
|
||||
@ -518,11 +484,13 @@ int drm_release(struct inode *inode, struct file *filp)
|
||||
}
|
||||
mutex_unlock(&dev->ctxlist_mutex);
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
mutex_lock(&dev->master_mutex);
|
||||
|
||||
if (file_priv->is_master) {
|
||||
struct drm_master *master = file_priv->master;
|
||||
struct drm_file *temp;
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
list_for_each_entry(temp, &dev->filelist, lhead) {
|
||||
if ((temp->master == file_priv->master) &&
|
||||
(temp != file_priv))
|
||||
@ -541,6 +509,7 @@ int drm_release(struct inode *inode, struct file *filp)
|
||||
master->lock.file_priv = NULL;
|
||||
wake_up_interruptible_all(&master->lock.lock_queue);
|
||||
}
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
if (file_priv->minor->master == file_priv->master) {
|
||||
/* drop the reference held my the minor */
|
||||
@ -550,13 +519,13 @@ int drm_release(struct inode *inode, struct file *filp)
|
||||
}
|
||||
}
|
||||
|
||||
BUG_ON(dev->dev_mapping == NULL);
|
||||
iput(container_of(dev->dev_mapping, struct inode, i_data));
|
||||
|
||||
/* drop the reference held my the file priv */
|
||||
/* drop the master reference held by the file priv */
|
||||
if (file_priv->master)
|
||||
drm_master_put(&file_priv->master);
|
||||
file_priv->is_master = 0;
|
||||
mutex_unlock(&dev->master_mutex);
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
list_del(&file_priv->lhead);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
@ -581,6 +550,8 @@ int drm_release(struct inode *inode, struct file *filp)
|
||||
}
|
||||
mutex_unlock(&drm_global_mutex);
|
||||
|
||||
drm_minor_release(minor);
|
||||
|
||||
return retcode;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_release);
|
||||
|
@ -85,9 +85,9 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialize the GEM device fields
|
||||
* drm_gem_init - Initialize the GEM device fields
|
||||
* @dev: drm_devic structure to initialize
|
||||
*/
|
||||
|
||||
int
|
||||
drm_gem_init(struct drm_device *dev)
|
||||
{
|
||||
@ -120,6 +120,11 @@ drm_gem_destroy(struct drm_device *dev)
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_object_init - initialize an allocated shmem-backed GEM object
|
||||
* @dev: drm_device the object should be initialized for
|
||||
* @obj: drm_gem_object to initialize
|
||||
* @size: object size
|
||||
*
|
||||
* Initialize an already allocated GEM object of the specified size with
|
||||
* shmfs backing store.
|
||||
*/
|
||||
@ -141,6 +146,11 @@ int drm_gem_object_init(struct drm_device *dev,
|
||||
EXPORT_SYMBOL(drm_gem_object_init);
|
||||
|
||||
/**
|
||||
* drm_gem_object_init - initialize an allocated private GEM object
|
||||
* @dev: drm_device the object should be initialized for
|
||||
* @obj: drm_gem_object to initialize
|
||||
* @size: object size
|
||||
*
|
||||
* Initialize an already allocated GEM object of the specified size with
|
||||
* no GEM provided backing store. Instead the caller is responsible for
|
||||
* backing the object and handling it.
|
||||
@ -176,6 +186,9 @@ drm_gem_remove_prime_handles(struct drm_gem_object *obj, struct drm_file *filp)
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_object_free - release resources bound to userspace handles
|
||||
* @obj: GEM object to clean up.
|
||||
*
|
||||
* Called after the last handle to the object has been closed
|
||||
*
|
||||
* Removes any name for the object. Note that this must be
|
||||
@ -225,7 +238,12 @@ drm_gem_object_handle_unreference_unlocked(struct drm_gem_object *obj)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping from handle to filp for this object.
|
||||
* drm_gem_handle_delete - deletes the given file-private handle
|
||||
* @filp: drm file-private structure to use for the handle look up
|
||||
* @handle: userspace handle to delete
|
||||
*
|
||||
* Removes the GEM handle from the @filp lookup table and if this is the last
|
||||
* handle also cleans up linked resources like GEM names.
|
||||
*/
|
||||
int
|
||||
drm_gem_handle_delete(struct drm_file *filp, u32 handle)
|
||||
@ -270,6 +288,9 @@ EXPORT_SYMBOL(drm_gem_handle_delete);
|
||||
|
||||
/**
|
||||
* drm_gem_dumb_destroy - dumb fb callback helper for gem based drivers
|
||||
* @file: drm file-private structure to remove the dumb handle from
|
||||
* @dev: corresponding drm_device
|
||||
* @handle: the dumb handle to remove
|
||||
*
|
||||
* This implements the ->dumb_destroy kms driver callback for drivers which use
|
||||
* gem to manage their backing storage.
|
||||
@ -284,6 +305,9 @@ EXPORT_SYMBOL(drm_gem_dumb_destroy);
|
||||
|
||||
/**
|
||||
* drm_gem_handle_create_tail - internal functions to create a handle
|
||||
* @file_priv: drm file-private structure to register the handle for
|
||||
* @obj: object to register
|
||||
* @handlep: pionter to return the created handle to the caller
|
||||
*
|
||||
* This expects the dev->object_name_lock to be held already and will drop it
|
||||
* before returning. Used to avoid races in establishing new handles when
|
||||
@ -336,6 +360,11 @@ drm_gem_handle_create_tail(struct drm_file *file_priv,
|
||||
}
|
||||
|
||||
/**
|
||||
* gem_handle_create - create a gem handle for an object
|
||||
* @file_priv: drm file-private structure to register the handle for
|
||||
* @obj: object to register
|
||||
* @handlep: pionter to return the created handle to the caller
|
||||
*
|
||||
* Create a handle for this object. This adds a handle reference
|
||||
* to the object, which includes a regular reference count. Callers
|
||||
* will likely want to dereference the object afterwards.
|
||||
@ -536,6 +565,11 @@ drm_gem_object_lookup(struct drm_device *dev, struct drm_file *filp,
|
||||
EXPORT_SYMBOL(drm_gem_object_lookup);
|
||||
|
||||
/**
|
||||
* drm_gem_close_ioctl - implementation of the GEM_CLOSE ioctl
|
||||
* @dev: drm_device
|
||||
* @data: ioctl data
|
||||
* @file_priv: drm file-private structure
|
||||
*
|
||||
* Releases the handle to an mm object.
|
||||
*/
|
||||
int
|
||||
@ -554,6 +588,11 @@ drm_gem_close_ioctl(struct drm_device *dev, void *data,
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_flink_ioctl - implementation of the GEM_FLINK ioctl
|
||||
* @dev: drm_device
|
||||
* @data: ioctl data
|
||||
* @file_priv: drm file-private structure
|
||||
*
|
||||
* Create a global name for an object, returning the name.
|
||||
*
|
||||
* Note that the name does not hold a reference; when the object
|
||||
@ -601,6 +640,11 @@ err:
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_open - implementation of the GEM_OPEN ioctl
|
||||
* @dev: drm_device
|
||||
* @data: ioctl data
|
||||
* @file_priv: drm file-private structure
|
||||
*
|
||||
* Open an object using the global name, returning a handle and the size.
|
||||
*
|
||||
* This handle (of course) holds a reference to the object, so the object
|
||||
@ -640,6 +684,10 @@ drm_gem_open_ioctl(struct drm_device *dev, void *data,
|
||||
}
|
||||
|
||||
/**
|
||||
* gem_gem_open - initalizes GEM file-private structures at devnode open time
|
||||
* @dev: drm_device which is being opened by userspace
|
||||
* @file_private: drm file-private structure to set up
|
||||
*
|
||||
* Called at device open time, sets up the structure for handling refcounting
|
||||
* of mm objects.
|
||||
*/
|
||||
@ -650,7 +698,7 @@ drm_gem_open(struct drm_device *dev, struct drm_file *file_private)
|
||||
spin_lock_init(&file_private->table_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Called at device close to release the file's
|
||||
* handle references on objects.
|
||||
*/
|
||||
@ -674,6 +722,10 @@ drm_gem_object_release_handle(int id, void *ptr, void *data)
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_release - release file-private GEM resources
|
||||
* @dev: drm_device which is being closed by userspace
|
||||
* @file_private: drm file-private structure to clean up
|
||||
*
|
||||
* Called at close time when the filp is going away.
|
||||
*
|
||||
* Releases any remaining references on objects by this filp.
|
||||
@ -692,11 +744,16 @@ drm_gem_object_release(struct drm_gem_object *obj)
|
||||
WARN_ON(obj->dma_buf);
|
||||
|
||||
if (obj->filp)
|
||||
fput(obj->filp);
|
||||
fput(obj->filp);
|
||||
|
||||
drm_gem_free_mmap_offset(obj);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_gem_object_release);
|
||||
|
||||
/**
|
||||
* drm_gem_object_free - free a GEM object
|
||||
* @kref: kref of the object to free
|
||||
*
|
||||
* Called after the last reference to the object has been lost.
|
||||
* Must be called holding struct_ mutex
|
||||
*
|
||||
@ -782,7 +839,7 @@ int drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size,
|
||||
vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
|
||||
vma->vm_ops = dev->driver->gem_vm_ops;
|
||||
vma->vm_private_data = obj;
|
||||
vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
|
||||
vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
|
||||
|
||||
/* Take a ref for this mapping of the object, so that the fault
|
||||
* handler can dereference the mmap offset's pointer to the object.
|
||||
@ -818,7 +875,7 @@ int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
|
||||
struct drm_device *dev = priv->minor->dev;
|
||||
struct drm_gem_object *obj;
|
||||
struct drm_vma_offset_node *node;
|
||||
int ret = 0;
|
||||
int ret;
|
||||
|
||||
if (drm_device_is_unplugged(dev))
|
||||
return -ENODEV;
|
||||
|
@ -79,7 +79,6 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
|
||||
unsigned int size)
|
||||
{
|
||||
struct drm_gem_cma_object *cma_obj;
|
||||
struct sg_table *sgt = NULL;
|
||||
int ret;
|
||||
|
||||
size = round_up(size, PAGE_SIZE);
|
||||
@ -97,23 +96,9 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
|
||||
goto error;
|
||||
}
|
||||
|
||||
sgt = kzalloc(sizeof(*cma_obj->sgt), GFP_KERNEL);
|
||||
if (sgt == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = dma_get_sgtable(drm->dev, sgt, cma_obj->vaddr,
|
||||
cma_obj->paddr, size);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
cma_obj->sgt = sgt;
|
||||
|
||||
return cma_obj;
|
||||
|
||||
error:
|
||||
kfree(sgt);
|
||||
drm_gem_cma_free_object(&cma_obj->base);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
@ -175,10 +160,6 @@ void drm_gem_cma_free_object(struct drm_gem_object *gem_obj)
|
||||
if (cma_obj->vaddr) {
|
||||
dma_free_writecombine(gem_obj->dev->dev, cma_obj->base.size,
|
||||
cma_obj->vaddr, cma_obj->paddr);
|
||||
if (cma_obj->sgt) {
|
||||
sg_free_table(cma_obj->sgt);
|
||||
kfree(cma_obj->sgt);
|
||||
}
|
||||
} else if (gem_obj->import_attach) {
|
||||
drm_prime_gem_destroy(gem_obj, cma_obj->sgt);
|
||||
}
|
||||
@ -253,8 +234,17 @@ static int drm_gem_cma_mmap_obj(struct drm_gem_cma_object *cma_obj,
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = remap_pfn_range(vma, vma->vm_start, cma_obj->paddr >> PAGE_SHIFT,
|
||||
vma->vm_end - vma->vm_start, vma->vm_page_prot);
|
||||
/*
|
||||
* Clear the VM_PFNMAP flag that was set by drm_gem_mmap(), and set the
|
||||
* vm_pgoff (used as a fake buffer offset by DRM) to 0 as we want to map
|
||||
* the whole buffer.
|
||||
*/
|
||||
vma->vm_flags &= ~VM_PFNMAP;
|
||||
vma->vm_pgoff = 0;
|
||||
|
||||
ret = dma_mmap_writecombine(cma_obj->base.dev->dev, vma,
|
||||
cma_obj->vaddr, cma_obj->paddr,
|
||||
vma->vm_end - vma->vm_start);
|
||||
if (ret)
|
||||
drm_gem_vm_close(vma);
|
||||
|
||||
@ -292,9 +282,9 @@ void drm_gem_cma_describe(struct drm_gem_cma_object *cma_obj, struct seq_file *m
|
||||
|
||||
off = drm_vma_node_start(&obj->vma_node);
|
||||
|
||||
seq_printf(m, "%2d (%2d) %08llx %08Zx %p %d",
|
||||
seq_printf(m, "%2d (%2d) %08llx %pad %p %d",
|
||||
obj->name, obj->refcount.refcount.counter,
|
||||
off, cma_obj->paddr, cma_obj->vaddr, obj->size);
|
||||
off, &cma_obj->paddr, cma_obj->vaddr, obj->size);
|
||||
|
||||
seq_printf(m, "\n");
|
||||
}
|
||||
@ -342,7 +332,7 @@ drm_gem_cma_prime_import_sg_table(struct drm_device *dev, size_t size,
|
||||
cma_obj->paddr = sg_dma_address(sgt->sgl);
|
||||
cma_obj->sgt = sgt;
|
||||
|
||||
DRM_DEBUG_PRIME("dma_addr = 0x%x, size = %zu\n", cma_obj->paddr, size);
|
||||
DRM_DEBUG_PRIME("dma_addr = %pad, size = %zu\n", &cma_obj->paddr, size);
|
||||
|
||||
return &cma_obj->base;
|
||||
}
|
||||
|
@ -328,6 +328,13 @@ drm_setclientcap(struct drm_device *dev, void *data, struct drm_file *file_priv)
|
||||
return -EINVAL;
|
||||
file_priv->stereo_allowed = req->value;
|
||||
break;
|
||||
case DRM_CLIENT_CAP_UNIVERSAL_PLANES:
|
||||
if (!drm_universal_planes)
|
||||
return -EINVAL;
|
||||
if (req->value > 1)
|
||||
return -EINVAL;
|
||||
file_priv->universal_planes = req->value;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
@ -142,8 +142,12 @@ int mipi_dsi_host_register(struct mipi_dsi_host *host)
|
||||
{
|
||||
struct device_node *node;
|
||||
|
||||
for_each_available_child_of_node(host->dev->of_node, node)
|
||||
for_each_available_child_of_node(host->dev->of_node, node) {
|
||||
/* skip nodes without reg property */
|
||||
if (!of_find_property(node, "reg", NULL))
|
||||
continue;
|
||||
of_mipi_dsi_device_add(host, node);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -47,7 +47,48 @@
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
#define MM_UNUSED_TARGET 4
|
||||
/**
|
||||
* DOC: Overview
|
||||
*
|
||||
* drm_mm provides a simple range allocator. The drivers are free to use the
|
||||
* resource allocator from the linux core if it suits them, the upside of drm_mm
|
||||
* is that it's in the DRM core. Which means that it's easier to extend for
|
||||
* some of the crazier special purpose needs of gpus.
|
||||
*
|
||||
* The main data struct is &drm_mm, allocations are tracked in &drm_mm_node.
|
||||
* Drivers are free to embed either of them into their own suitable
|
||||
* datastructures. drm_mm itself will not do any allocations of its own, so if
|
||||
* drivers choose not to embed nodes they need to still allocate them
|
||||
* themselves.
|
||||
*
|
||||
* The range allocator also supports reservation of preallocated blocks. This is
|
||||
* useful for taking over initial mode setting configurations from the firmware,
|
||||
* where an object needs to be created which exactly matches the firmware's
|
||||
* scanout target. As long as the range is still free it can be inserted anytime
|
||||
* after the allocator is initialized, which helps with avoiding looped
|
||||
* depencies in the driver load sequence.
|
||||
*
|
||||
* drm_mm maintains a stack of most recently freed holes, which of all
|
||||
* simplistic datastructures seems to be a fairly decent approach to clustering
|
||||
* allocations and avoiding too much fragmentation. This means free space
|
||||
* searches are O(num_holes). Given that all the fancy features drm_mm supports
|
||||
* something better would be fairly complex and since gfx thrashing is a fairly
|
||||
* steep cliff not a real concern. Removing a node again is O(1).
|
||||
*
|
||||
* drm_mm supports a few features: Alignment and range restrictions can be
|
||||
* supplied. Further more every &drm_mm_node has a color value (which is just an
|
||||
* opaqua unsigned long) which in conjunction with a driver callback can be used
|
||||
* to implement sophisticated placement restrictions. The i915 DRM driver uses
|
||||
* this to implement guard pages between incompatible caching domains in the
|
||||
* graphics TT.
|
||||
*
|
||||
* Two behaviors are supported for searching and allocating: bottom-up and top-down.
|
||||
* The default is bottom-up. Top-down allocation can be used if the memory area
|
||||
* has different restrictions, or just to reduce fragmentation.
|
||||
*
|
||||
* Finally iteration helpers to walk all nodes and all holes are provided as are
|
||||
* some basic allocator dumpers for debugging.
|
||||
*/
|
||||
|
||||
static struct drm_mm_node *drm_mm_search_free_generic(const struct drm_mm *mm,
|
||||
unsigned long size,
|
||||
@ -65,7 +106,8 @@ static struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_
|
||||
static void drm_mm_insert_helper(struct drm_mm_node *hole_node,
|
||||
struct drm_mm_node *node,
|
||||
unsigned long size, unsigned alignment,
|
||||
unsigned long color)
|
||||
unsigned long color,
|
||||
enum drm_mm_allocator_flags flags)
|
||||
{
|
||||
struct drm_mm *mm = hole_node->mm;
|
||||
unsigned long hole_start = drm_mm_hole_node_start(hole_node);
|
||||
@ -78,12 +120,22 @@ static void drm_mm_insert_helper(struct drm_mm_node *hole_node,
|
||||
if (mm->color_adjust)
|
||||
mm->color_adjust(hole_node, color, &adj_start, &adj_end);
|
||||
|
||||
if (flags & DRM_MM_CREATE_TOP)
|
||||
adj_start = adj_end - size;
|
||||
|
||||
if (alignment) {
|
||||
unsigned tmp = adj_start % alignment;
|
||||
if (tmp)
|
||||
adj_start += alignment - tmp;
|
||||
if (tmp) {
|
||||
if (flags & DRM_MM_CREATE_TOP)
|
||||
adj_start -= tmp;
|
||||
else
|
||||
adj_start += alignment - tmp;
|
||||
}
|
||||
}
|
||||
|
||||
BUG_ON(adj_start < hole_start);
|
||||
BUG_ON(adj_end > hole_end);
|
||||
|
||||
if (adj_start == hole_start) {
|
||||
hole_node->hole_follows = 0;
|
||||
list_del(&hole_node->hole_stack);
|
||||
@ -107,6 +159,20 @@ static void drm_mm_insert_helper(struct drm_mm_node *hole_node,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_mm_reserve_node - insert an pre-initialized node
|
||||
* @mm: drm_mm allocator to insert @node into
|
||||
* @node: drm_mm_node to insert
|
||||
*
|
||||
* This functions inserts an already set-up drm_mm_node into the allocator,
|
||||
* meaning that start, size and color must be set by the caller. This is useful
|
||||
* to initialize the allocator with preallocated objects which must be set-up
|
||||
* before the range allocator can be set-up, e.g. when taking over a firmware
|
||||
* framebuffer.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success, -ENOSPC if there's no hole where @node is.
|
||||
*/
|
||||
int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node)
|
||||
{
|
||||
struct drm_mm_node *hole;
|
||||
@ -148,23 +214,34 @@ int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node)
|
||||
EXPORT_SYMBOL(drm_mm_reserve_node);
|
||||
|
||||
/**
|
||||
* Search for free space and insert a preallocated memory node. Returns
|
||||
* -ENOSPC if no suitable free area is available. The preallocated memory node
|
||||
* must be cleared.
|
||||
* drm_mm_insert_node_generic - search for space and insert @node
|
||||
* @mm: drm_mm to allocate from
|
||||
* @node: preallocate node to insert
|
||||
* @size: size of the allocation
|
||||
* @alignment: alignment of the allocation
|
||||
* @color: opaque tag value to use for this node
|
||||
* @sflags: flags to fine-tune the allocation search
|
||||
* @aflags: flags to fine-tune the allocation behavior
|
||||
*
|
||||
* The preallocated node must be cleared to 0.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success, -ENOSPC if there's no suitable hole.
|
||||
*/
|
||||
int drm_mm_insert_node_generic(struct drm_mm *mm, struct drm_mm_node *node,
|
||||
unsigned long size, unsigned alignment,
|
||||
unsigned long color,
|
||||
enum drm_mm_search_flags flags)
|
||||
enum drm_mm_search_flags sflags,
|
||||
enum drm_mm_allocator_flags aflags)
|
||||
{
|
||||
struct drm_mm_node *hole_node;
|
||||
|
||||
hole_node = drm_mm_search_free_generic(mm, size, alignment,
|
||||
color, flags);
|
||||
color, sflags);
|
||||
if (!hole_node)
|
||||
return -ENOSPC;
|
||||
|
||||
drm_mm_insert_helper(hole_node, node, size, alignment, color);
|
||||
drm_mm_insert_helper(hole_node, node, size, alignment, color, aflags);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mm_insert_node_generic);
|
||||
@ -173,7 +250,8 @@ static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node,
|
||||
struct drm_mm_node *node,
|
||||
unsigned long size, unsigned alignment,
|
||||
unsigned long color,
|
||||
unsigned long start, unsigned long end)
|
||||
unsigned long start, unsigned long end,
|
||||
enum drm_mm_allocator_flags flags)
|
||||
{
|
||||
struct drm_mm *mm = hole_node->mm;
|
||||
unsigned long hole_start = drm_mm_hole_node_start(hole_node);
|
||||
@ -188,13 +266,20 @@ static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node,
|
||||
if (adj_end > end)
|
||||
adj_end = end;
|
||||
|
||||
if (flags & DRM_MM_CREATE_TOP)
|
||||
adj_start = adj_end - size;
|
||||
|
||||
if (mm->color_adjust)
|
||||
mm->color_adjust(hole_node, color, &adj_start, &adj_end);
|
||||
|
||||
if (alignment) {
|
||||
unsigned tmp = adj_start % alignment;
|
||||
if (tmp)
|
||||
adj_start += alignment - tmp;
|
||||
if (tmp) {
|
||||
if (flags & DRM_MM_CREATE_TOP)
|
||||
adj_start -= tmp;
|
||||
else
|
||||
adj_start += alignment - tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (adj_start == hole_start) {
|
||||
@ -211,6 +296,8 @@ static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node,
|
||||
INIT_LIST_HEAD(&node->hole_stack);
|
||||
list_add(&node->node_list, &hole_node->node_list);
|
||||
|
||||
BUG_ON(node->start < start);
|
||||
BUG_ON(node->start < adj_start);
|
||||
BUG_ON(node->start + node->size > adj_end);
|
||||
BUG_ON(node->start + node->size > end);
|
||||
|
||||
@ -222,32 +309,51 @@ static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node,
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for free space and insert a preallocated memory node. Returns
|
||||
* -ENOSPC if no suitable free area is available. This is for range
|
||||
* restricted allocations. The preallocated memory node must be cleared.
|
||||
* drm_mm_insert_node_in_range_generic - ranged search for space and insert @node
|
||||
* @mm: drm_mm to allocate from
|
||||
* @node: preallocate node to insert
|
||||
* @size: size of the allocation
|
||||
* @alignment: alignment of the allocation
|
||||
* @color: opaque tag value to use for this node
|
||||
* @start: start of the allowed range for this node
|
||||
* @end: end of the allowed range for this node
|
||||
* @sflags: flags to fine-tune the allocation search
|
||||
* @aflags: flags to fine-tune the allocation behavior
|
||||
*
|
||||
* The preallocated node must be cleared to 0.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success, -ENOSPC if there's no suitable hole.
|
||||
*/
|
||||
int drm_mm_insert_node_in_range_generic(struct drm_mm *mm, struct drm_mm_node *node,
|
||||
unsigned long size, unsigned alignment, unsigned long color,
|
||||
unsigned long size, unsigned alignment,
|
||||
unsigned long color,
|
||||
unsigned long start, unsigned long end,
|
||||
enum drm_mm_search_flags flags)
|
||||
enum drm_mm_search_flags sflags,
|
||||
enum drm_mm_allocator_flags aflags)
|
||||
{
|
||||
struct drm_mm_node *hole_node;
|
||||
|
||||
hole_node = drm_mm_search_free_in_range_generic(mm,
|
||||
size, alignment, color,
|
||||
start, end, flags);
|
||||
start, end, sflags);
|
||||
if (!hole_node)
|
||||
return -ENOSPC;
|
||||
|
||||
drm_mm_insert_helper_range(hole_node, node,
|
||||
size, alignment, color,
|
||||
start, end);
|
||||
start, end, aflags);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mm_insert_node_in_range_generic);
|
||||
|
||||
/**
|
||||
* Remove a memory node from the allocator.
|
||||
* drm_mm_remove_node - Remove a memory node from the allocator.
|
||||
* @node: drm_mm_node to remove
|
||||
*
|
||||
* This just removes a node from its drm_mm allocator. The node does not need to
|
||||
* be cleared again before it can be re-inserted into this or any other drm_mm
|
||||
* allocator. It is a bug to call this function on a un-allocated node.
|
||||
*/
|
||||
void drm_mm_remove_node(struct drm_mm_node *node)
|
||||
{
|
||||
@ -315,7 +421,10 @@ static struct drm_mm_node *drm_mm_search_free_generic(const struct drm_mm *mm,
|
||||
best = NULL;
|
||||
best_size = ~0UL;
|
||||
|
||||
drm_mm_for_each_hole(entry, mm, adj_start, adj_end) {
|
||||
__drm_mm_for_each_hole(entry, mm, adj_start, adj_end,
|
||||
flags & DRM_MM_SEARCH_BELOW) {
|
||||
unsigned long hole_size = adj_end - adj_start;
|
||||
|
||||
if (mm->color_adjust) {
|
||||
mm->color_adjust(entry, color, &adj_start, &adj_end);
|
||||
if (adj_end <= adj_start)
|
||||
@ -328,9 +437,9 @@ static struct drm_mm_node *drm_mm_search_free_generic(const struct drm_mm *mm,
|
||||
if (!(flags & DRM_MM_SEARCH_BEST))
|
||||
return entry;
|
||||
|
||||
if (entry->size < best_size) {
|
||||
if (hole_size < best_size) {
|
||||
best = entry;
|
||||
best_size = entry->size;
|
||||
best_size = hole_size;
|
||||
}
|
||||
}
|
||||
|
||||
@ -356,7 +465,10 @@ static struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_
|
||||
best = NULL;
|
||||
best_size = ~0UL;
|
||||
|
||||
drm_mm_for_each_hole(entry, mm, adj_start, adj_end) {
|
||||
__drm_mm_for_each_hole(entry, mm, adj_start, adj_end,
|
||||
flags & DRM_MM_SEARCH_BELOW) {
|
||||
unsigned long hole_size = adj_end - adj_start;
|
||||
|
||||
if (adj_start < start)
|
||||
adj_start = start;
|
||||
if (adj_end > end)
|
||||
@ -374,9 +486,9 @@ static struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_
|
||||
if (!(flags & DRM_MM_SEARCH_BEST))
|
||||
return entry;
|
||||
|
||||
if (entry->size < best_size) {
|
||||
if (hole_size < best_size) {
|
||||
best = entry;
|
||||
best_size = entry->size;
|
||||
best_size = hole_size;
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,7 +496,13 @@ static struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves an allocation. To be used with embedded struct drm_mm_node.
|
||||
* drm_mm_replace_node - move an allocation from @old to @new
|
||||
* @old: drm_mm_node to remove from the allocator
|
||||
* @new: drm_mm_node which should inherit @old's allocation
|
||||
*
|
||||
* This is useful for when drivers embed the drm_mm_node structure and hence
|
||||
* can't move allocations by reassigning pointers. It's a combination of remove
|
||||
* and insert with the guarantee that the allocation start will match.
|
||||
*/
|
||||
void drm_mm_replace_node(struct drm_mm_node *old, struct drm_mm_node *new)
|
||||
{
|
||||
@ -402,12 +520,46 @@ void drm_mm_replace_node(struct drm_mm_node *old, struct drm_mm_node *new)
|
||||
EXPORT_SYMBOL(drm_mm_replace_node);
|
||||
|
||||
/**
|
||||
* Initializa lru scanning.
|
||||
* DOC: lru scan roaster
|
||||
*
|
||||
* Very often GPUs need to have continuous allocations for a given object. When
|
||||
* evicting objects to make space for a new one it is therefore not most
|
||||
* efficient when we simply start to select all objects from the tail of an LRU
|
||||
* until there's a suitable hole: Especially for big objects or nodes that
|
||||
* otherwise have special allocation constraints there's a good chance we evict
|
||||
* lots of (smaller) objects unecessarily.
|
||||
*
|
||||
* The DRM range allocator supports this use-case through the scanning
|
||||
* interfaces. First a scan operation needs to be initialized with
|
||||
* drm_mm_init_scan() or drm_mm_init_scan_with_range(). The the driver adds
|
||||
* objects to the roaster (probably by walking an LRU list, but this can be
|
||||
* freely implemented) until a suitable hole is found or there's no further
|
||||
* evitable object.
|
||||
*
|
||||
* The the driver must walk through all objects again in exactly the reverse
|
||||
* order to restore the allocator state. Note that while the allocator is used
|
||||
* in the scan mode no other operation is allowed.
|
||||
*
|
||||
* Finally the driver evicts all objects selected in the scan. Adding and
|
||||
* removing an object is O(1), and since freeing a node is also O(1) the overall
|
||||
* complexity is O(scanned_objects). So like the free stack which needs to be
|
||||
* walked before a scan operation even begins this is linear in the number of
|
||||
* objects. It doesn't seem to hurt badly.
|
||||
*/
|
||||
|
||||
/**
|
||||
* drm_mm_init_scan - initialize lru scanning
|
||||
* @mm: drm_mm to scan
|
||||
* @size: size of the allocation
|
||||
* @alignment: alignment of the allocation
|
||||
* @color: opaque tag value to use for the allocation
|
||||
*
|
||||
* This simply sets up the scanning routines with the parameters for the desired
|
||||
* hole.
|
||||
* hole. Note that there's no need to specify allocation flags, since they only
|
||||
* change the place a node is allocated from within a suitable hole.
|
||||
*
|
||||
* Warning: As long as the scan list is non-empty, no other operations than
|
||||
* Warning:
|
||||
* As long as the scan list is non-empty, no other operations than
|
||||
* adding/removing nodes to/from the scan list are allowed.
|
||||
*/
|
||||
void drm_mm_init_scan(struct drm_mm *mm,
|
||||
@ -427,12 +579,20 @@ void drm_mm_init_scan(struct drm_mm *mm,
|
||||
EXPORT_SYMBOL(drm_mm_init_scan);
|
||||
|
||||
/**
|
||||
* Initializa lru scanning.
|
||||
* drm_mm_init_scan - initialize range-restricted lru scanning
|
||||
* @mm: drm_mm to scan
|
||||
* @size: size of the allocation
|
||||
* @alignment: alignment of the allocation
|
||||
* @color: opaque tag value to use for the allocation
|
||||
* @start: start of the allowed range for the allocation
|
||||
* @end: end of the allowed range for the allocation
|
||||
*
|
||||
* This simply sets up the scanning routines with the parameters for the desired
|
||||
* hole. This version is for range-restricted scans.
|
||||
* hole. Note that there's no need to specify allocation flags, since they only
|
||||
* change the place a node is allocated from within a suitable hole.
|
||||
*
|
||||
* Warning: As long as the scan list is non-empty, no other operations than
|
||||
* Warning:
|
||||
* As long as the scan list is non-empty, no other operations than
|
||||
* adding/removing nodes to/from the scan list are allowed.
|
||||
*/
|
||||
void drm_mm_init_scan_with_range(struct drm_mm *mm,
|
||||
@ -456,12 +616,16 @@ void drm_mm_init_scan_with_range(struct drm_mm *mm,
|
||||
EXPORT_SYMBOL(drm_mm_init_scan_with_range);
|
||||
|
||||
/**
|
||||
* drm_mm_scan_add_block - add a node to the scan list
|
||||
* @node: drm_mm_node to add
|
||||
*
|
||||
* Add a node to the scan list that might be freed to make space for the desired
|
||||
* hole.
|
||||
*
|
||||
* Returns non-zero, if a hole has been found, zero otherwise.
|
||||
* Returns:
|
||||
* True if a hole has been found, false otherwise.
|
||||
*/
|
||||
int drm_mm_scan_add_block(struct drm_mm_node *node)
|
||||
bool drm_mm_scan_add_block(struct drm_mm_node *node)
|
||||
{
|
||||
struct drm_mm *mm = node->mm;
|
||||
struct drm_mm_node *prev_node;
|
||||
@ -501,15 +665,16 @@ int drm_mm_scan_add_block(struct drm_mm_node *node)
|
||||
mm->scan_size, mm->scan_alignment)) {
|
||||
mm->scan_hit_start = hole_start;
|
||||
mm->scan_hit_end = hole_end;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mm_scan_add_block);
|
||||
|
||||
/**
|
||||
* Remove a node from the scan list.
|
||||
* drm_mm_scan_remove_block - remove a node from the scan list
|
||||
* @node: drm_mm_node to remove
|
||||
*
|
||||
* Nodes _must_ be removed in the exact same order from the scan list as they
|
||||
* have been added, otherwise the internal state of the memory manager will be
|
||||
@ -519,10 +684,11 @@ EXPORT_SYMBOL(drm_mm_scan_add_block);
|
||||
* immediately following drm_mm_search_free with !DRM_MM_SEARCH_BEST will then
|
||||
* return the just freed block (because its at the top of the free_stack list).
|
||||
*
|
||||
* Returns one if this block should be evicted, zero otherwise. Will always
|
||||
* return zero when no hole has been found.
|
||||
* Returns:
|
||||
* True if this block should be evicted, false otherwise. Will always
|
||||
* return false when no hole has been found.
|
||||
*/
|
||||
int drm_mm_scan_remove_block(struct drm_mm_node *node)
|
||||
bool drm_mm_scan_remove_block(struct drm_mm_node *node)
|
||||
{
|
||||
struct drm_mm *mm = node->mm;
|
||||
struct drm_mm_node *prev_node;
|
||||
@ -543,7 +709,15 @@ int drm_mm_scan_remove_block(struct drm_mm_node *node)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mm_scan_remove_block);
|
||||
|
||||
int drm_mm_clean(struct drm_mm * mm)
|
||||
/**
|
||||
* drm_mm_clean - checks whether an allocator is clean
|
||||
* @mm: drm_mm allocator to check
|
||||
*
|
||||
* Returns:
|
||||
* True if the allocator is completely free, false if there's still a node
|
||||
* allocated in it.
|
||||
*/
|
||||
bool drm_mm_clean(struct drm_mm * mm)
|
||||
{
|
||||
struct list_head *head = &mm->head_node.node_list;
|
||||
|
||||
@ -551,6 +725,14 @@ int drm_mm_clean(struct drm_mm * mm)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mm_clean);
|
||||
|
||||
/**
|
||||
* drm_mm_init - initialize a drm-mm allocator
|
||||
* @mm: the drm_mm structure to initialize
|
||||
* @start: start of the range managed by @mm
|
||||
* @size: end of the range managed by @mm
|
||||
*
|
||||
* Note that @mm must be cleared to 0 before calling this function.
|
||||
*/
|
||||
void drm_mm_init(struct drm_mm * mm, unsigned long start, unsigned long size)
|
||||
{
|
||||
INIT_LIST_HEAD(&mm->hole_stack);
|
||||
@ -572,6 +754,13 @@ void drm_mm_init(struct drm_mm * mm, unsigned long start, unsigned long size)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mm_init);
|
||||
|
||||
/**
|
||||
* drm_mm_takedown - clean up a drm_mm allocator
|
||||
* @mm: drm_mm allocator to clean up
|
||||
*
|
||||
* Note that it is a bug to call this function on an allocator which is not
|
||||
* clean.
|
||||
*/
|
||||
void drm_mm_takedown(struct drm_mm * mm)
|
||||
{
|
||||
WARN(!list_empty(&mm->head_node.node_list),
|
||||
@ -597,6 +786,11 @@ static unsigned long drm_mm_debug_hole(struct drm_mm_node *entry,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_mm_debug_table - dump allocator state to dmesg
|
||||
* @mm: drm_mm allocator to dump
|
||||
* @prefix: prefix to use for dumping to dmesg
|
||||
*/
|
||||
void drm_mm_debug_table(struct drm_mm *mm, const char *prefix)
|
||||
{
|
||||
struct drm_mm_node *entry;
|
||||
@ -635,6 +829,11 @@ static unsigned long drm_mm_dump_hole(struct seq_file *m, struct drm_mm_node *en
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_mm_dump_table - dump allocator state to a seq_file
|
||||
* @m: seq_file to dump to
|
||||
* @mm: drm_mm allocator to dump
|
||||
*/
|
||||
int drm_mm_dump_table(struct seq_file *m, struct drm_mm *mm)
|
||||
{
|
||||
struct drm_mm_node *entry;
|
||||
|
@ -37,15 +37,14 @@
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <video/of_videomode.h>
|
||||
#include <video/videomode.h>
|
||||
#include <drm/drm_modes.h>
|
||||
|
||||
#include "drm_crtc_internal.h"
|
||||
|
||||
/**
|
||||
* drm_mode_debug_printmodeline - debug print a mode
|
||||
* @dev: DRM device
|
||||
* drm_mode_debug_printmodeline - print a mode to dmesg
|
||||
* @mode: mode to print
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Describe @mode using DRM_DEBUG.
|
||||
*/
|
||||
void drm_mode_debug_printmodeline(const struct drm_display_mode *mode)
|
||||
@ -61,18 +60,77 @@ void drm_mode_debug_printmodeline(const struct drm_display_mode *mode)
|
||||
EXPORT_SYMBOL(drm_mode_debug_printmodeline);
|
||||
|
||||
/**
|
||||
* drm_cvt_mode -create a modeline based on CVT algorithm
|
||||
* drm_mode_create - create a new display mode
|
||||
* @dev: DRM device
|
||||
*
|
||||
* Create a new, cleared drm_display_mode with kzalloc, allocate an ID for it
|
||||
* and return it.
|
||||
*
|
||||
* Returns:
|
||||
* Pointer to new mode on success, NULL on error.
|
||||
*/
|
||||
struct drm_display_mode *drm_mode_create(struct drm_device *dev)
|
||||
{
|
||||
struct drm_display_mode *nmode;
|
||||
|
||||
nmode = kzalloc(sizeof(struct drm_display_mode), GFP_KERNEL);
|
||||
if (!nmode)
|
||||
return NULL;
|
||||
|
||||
if (drm_mode_object_get(dev, &nmode->base, DRM_MODE_OBJECT_MODE)) {
|
||||
kfree(nmode);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return nmode;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_create);
|
||||
|
||||
/**
|
||||
* drm_mode_destroy - remove a mode
|
||||
* @dev: DRM device
|
||||
* @mode: mode to remove
|
||||
*
|
||||
* Release @mode's unique ID, then free it @mode structure itself using kfree.
|
||||
*/
|
||||
void drm_mode_destroy(struct drm_device *dev, struct drm_display_mode *mode)
|
||||
{
|
||||
if (!mode)
|
||||
return;
|
||||
|
||||
drm_mode_object_put(dev, &mode->base);
|
||||
|
||||
kfree(mode);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_destroy);
|
||||
|
||||
/**
|
||||
* drm_mode_probed_add - add a mode to a connector's probed_mode list
|
||||
* @connector: connector the new mode
|
||||
* @mode: mode data
|
||||
*
|
||||
* Add @mode to @connector's probed_mode list for later use. This list should
|
||||
* then in a second step get filtered and all the modes actually supported by
|
||||
* the hardware moved to the @connector's modes list.
|
||||
*/
|
||||
void drm_mode_probed_add(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
WARN_ON(!mutex_is_locked(&connector->dev->mode_config.mutex));
|
||||
|
||||
list_add_tail(&mode->head, &connector->probed_modes);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_probed_add);
|
||||
|
||||
/**
|
||||
* drm_cvt_mode -create a modeline based on the CVT algorithm
|
||||
* @dev: drm device
|
||||
* @hdisplay: hdisplay size
|
||||
* @vdisplay: vdisplay size
|
||||
* @vrefresh : vrefresh rate
|
||||
* @reduced : Whether the GTF calculation is simplified
|
||||
* @interlaced:Whether the interlace is supported
|
||||
*
|
||||
* LOCKING:
|
||||
* none.
|
||||
*
|
||||
* return the modeline based on CVT algorithm
|
||||
* @vrefresh: vrefresh rate
|
||||
* @reduced: whether to use reduced blanking
|
||||
* @interlaced: whether to compute an interlaced mode
|
||||
* @margins: whether to add margins (borders)
|
||||
*
|
||||
* This function is called to generate the modeline based on CVT algorithm
|
||||
* according to the hdisplay, vdisplay, vrefresh.
|
||||
@ -82,12 +140,17 @@ EXPORT_SYMBOL(drm_mode_debug_printmodeline);
|
||||
*
|
||||
* And it is copied from xf86CVTmode in xserver/hw/xfree86/modes/xf86cvt.c.
|
||||
* What I have done is to translate it by using integer calculation.
|
||||
*
|
||||
* Returns:
|
||||
* The modeline based on the CVT algorithm stored in a drm_display_mode object.
|
||||
* The display mode object is allocated with drm_mode_create(). Returns NULL
|
||||
* when no mode could be allocated.
|
||||
*/
|
||||
#define HV_FACTOR 1000
|
||||
struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay,
|
||||
int vdisplay, int vrefresh,
|
||||
bool reduced, bool interlaced, bool margins)
|
||||
{
|
||||
#define HV_FACTOR 1000
|
||||
/* 1) top/bottom margin size (% of height) - default: 1.8, */
|
||||
#define CVT_MARGIN_PERCENTAGE 18
|
||||
/* 2) character cell horizontal granularity (pixels) - default 8 */
|
||||
@ -281,23 +344,25 @@ struct drm_display_mode *drm_cvt_mode(struct drm_device *dev, int hdisplay,
|
||||
EXPORT_SYMBOL(drm_cvt_mode);
|
||||
|
||||
/**
|
||||
* drm_gtf_mode_complex - create the modeline based on full GTF algorithm
|
||||
*
|
||||
* @dev :drm device
|
||||
* @hdisplay :hdisplay size
|
||||
* @vdisplay :vdisplay size
|
||||
* @vrefresh :vrefresh rate.
|
||||
* @interlaced :whether the interlace is supported
|
||||
* @margins :desired margin size
|
||||
* @GTF_[MCKJ] :extended GTF formula parameters
|
||||
*
|
||||
* LOCKING.
|
||||
* none.
|
||||
*
|
||||
* return the modeline based on full GTF algorithm.
|
||||
* drm_gtf_mode_complex - create the modeline based on the full GTF algorithm
|
||||
* @dev: drm device
|
||||
* @hdisplay: hdisplay size
|
||||
* @vdisplay: vdisplay size
|
||||
* @vrefresh: vrefresh rate.
|
||||
* @interlaced: whether to compute an interlaced mode
|
||||
* @margins: desired margin (borders) size
|
||||
* @GTF_M: extended GTF formula parameters
|
||||
* @GTF_2C: extended GTF formula parameters
|
||||
* @GTF_K: extended GTF formula parameters
|
||||
* @GTF_2J: extended GTF formula parameters
|
||||
*
|
||||
* GTF feature blocks specify C and J in multiples of 0.5, so we pass them
|
||||
* in here multiplied by two. For a C of 40, pass in 80.
|
||||
*
|
||||
* Returns:
|
||||
* The modeline based on the full GTF algorithm stored in a drm_display_mode object.
|
||||
* The display mode object is allocated with drm_mode_create(). Returns NULL
|
||||
* when no mode could be allocated.
|
||||
*/
|
||||
struct drm_display_mode *
|
||||
drm_gtf_mode_complex(struct drm_device *dev, int hdisplay, int vdisplay,
|
||||
@ -467,17 +532,13 @@ drm_gtf_mode_complex(struct drm_device *dev, int hdisplay, int vdisplay,
|
||||
EXPORT_SYMBOL(drm_gtf_mode_complex);
|
||||
|
||||
/**
|
||||
* drm_gtf_mode - create the modeline based on GTF algorithm
|
||||
*
|
||||
* @dev :drm device
|
||||
* @hdisplay :hdisplay size
|
||||
* @vdisplay :vdisplay size
|
||||
* @vrefresh :vrefresh rate.
|
||||
* @interlaced :whether the interlace is supported
|
||||
* @margins :whether the margin is supported
|
||||
*
|
||||
* LOCKING.
|
||||
* none.
|
||||
* drm_gtf_mode - create the modeline based on the GTF algorithm
|
||||
* @dev: drm device
|
||||
* @hdisplay: hdisplay size
|
||||
* @vdisplay: vdisplay size
|
||||
* @vrefresh: vrefresh rate.
|
||||
* @interlaced: whether to compute an interlaced mode
|
||||
* @margins: desired margin (borders) size
|
||||
*
|
||||
* return the modeline based on GTF algorithm
|
||||
*
|
||||
@ -496,19 +557,32 @@ EXPORT_SYMBOL(drm_gtf_mode_complex);
|
||||
* C = 40
|
||||
* K = 128
|
||||
* J = 20
|
||||
*
|
||||
* Returns:
|
||||
* The modeline based on the GTF algorithm stored in a drm_display_mode object.
|
||||
* The display mode object is allocated with drm_mode_create(). Returns NULL
|
||||
* when no mode could be allocated.
|
||||
*/
|
||||
struct drm_display_mode *
|
||||
drm_gtf_mode(struct drm_device *dev, int hdisplay, int vdisplay, int vrefresh,
|
||||
bool lace, int margins)
|
||||
bool interlaced, int margins)
|
||||
{
|
||||
return drm_gtf_mode_complex(dev, hdisplay, vdisplay, vrefresh, lace,
|
||||
margins, 600, 40 * 2, 128, 20 * 2);
|
||||
return drm_gtf_mode_complex(dev, hdisplay, vdisplay, vrefresh,
|
||||
interlaced, margins,
|
||||
600, 40 * 2, 128, 20 * 2);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_gtf_mode);
|
||||
|
||||
#ifdef CONFIG_VIDEOMODE_HELPERS
|
||||
int drm_display_mode_from_videomode(const struct videomode *vm,
|
||||
struct drm_display_mode *dmode)
|
||||
/**
|
||||
* drm_display_mode_from_videomode - fill in @dmode using @vm,
|
||||
* @vm: videomode structure to use as source
|
||||
* @dmode: drm_display_mode structure to use as destination
|
||||
*
|
||||
* Fills out @dmode using the display mode specified in @vm.
|
||||
*/
|
||||
void drm_display_mode_from_videomode(const struct videomode *vm,
|
||||
struct drm_display_mode *dmode)
|
||||
{
|
||||
dmode->hdisplay = vm->hactive;
|
||||
dmode->hsync_start = dmode->hdisplay + vm->hfront_porch;
|
||||
@ -538,8 +612,6 @@ int drm_display_mode_from_videomode(const struct videomode *vm,
|
||||
if (vm->flags & DISPLAY_FLAGS_DOUBLECLK)
|
||||
dmode->flags |= DRM_MODE_FLAG_DBLCLK;
|
||||
drm_mode_set_name(dmode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_display_mode_from_videomode);
|
||||
|
||||
@ -553,6 +625,9 @@ EXPORT_SYMBOL_GPL(drm_display_mode_from_videomode);
|
||||
* This function is expensive and should only be used, if only one mode is to be
|
||||
* read from DT. To get multiple modes start with of_get_display_timings and
|
||||
* work with that instead.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success, a negative errno code when no of videomode node was found.
|
||||
*/
|
||||
int of_get_drm_display_mode(struct device_node *np,
|
||||
struct drm_display_mode *dmode, int index)
|
||||
@ -580,10 +655,8 @@ EXPORT_SYMBOL_GPL(of_get_drm_display_mode);
|
||||
* drm_mode_set_name - set the name on a mode
|
||||
* @mode: name will be set in this mode
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Set the name of @mode to a standard format.
|
||||
* Set the name of @mode to a standard format which is <hdisplay>x<vdisplay>
|
||||
* with an optional 'i' suffix for interlaced modes.
|
||||
*/
|
||||
void drm_mode_set_name(struct drm_display_mode *mode)
|
||||
{
|
||||
@ -595,54 +668,12 @@ void drm_mode_set_name(struct drm_display_mode *mode)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_set_name);
|
||||
|
||||
/**
|
||||
* drm_mode_width - get the width of a mode
|
||||
* @mode: mode
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Return @mode's width (hdisplay) value.
|
||||
*
|
||||
* FIXME: is this needed?
|
||||
*
|
||||
* RETURNS:
|
||||
* @mode->hdisplay
|
||||
*/
|
||||
int drm_mode_width(const struct drm_display_mode *mode)
|
||||
{
|
||||
return mode->hdisplay;
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_width);
|
||||
|
||||
/**
|
||||
* drm_mode_height - get the height of a mode
|
||||
* @mode: mode
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Return @mode's height (vdisplay) value.
|
||||
*
|
||||
* FIXME: is this needed?
|
||||
*
|
||||
* RETURNS:
|
||||
* @mode->vdisplay
|
||||
*/
|
||||
int drm_mode_height(const struct drm_display_mode *mode)
|
||||
{
|
||||
return mode->vdisplay;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_height);
|
||||
|
||||
/** drm_mode_hsync - get the hsync of a mode
|
||||
* @mode: mode
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Return @modes's hsync rate in kHz, rounded to the nearest int.
|
||||
* Returns:
|
||||
* @modes's hsync rate in kHz, rounded to the nearest integer. Calculates the
|
||||
* value first if it is not yet set.
|
||||
*/
|
||||
int drm_mode_hsync(const struct drm_display_mode *mode)
|
||||
{
|
||||
@ -666,17 +697,9 @@ EXPORT_SYMBOL(drm_mode_hsync);
|
||||
* drm_mode_vrefresh - get the vrefresh of a mode
|
||||
* @mode: mode
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Return @mode's vrefresh rate in Hz or calculate it if necessary.
|
||||
*
|
||||
* FIXME: why is this needed? shouldn't vrefresh be set already?
|
||||
*
|
||||
* RETURNS:
|
||||
* Vertical refresh rate. It will be the result of actual value plus 0.5.
|
||||
* If it is 70.288, it will return 70Hz.
|
||||
* If it is 59.6, it will return 60Hz.
|
||||
* Returns:
|
||||
* @modes's vrefresh rate in Hz, rounded to the nearest integer. Calculates the
|
||||
* value first if it is not yet set.
|
||||
*/
|
||||
int drm_mode_vrefresh(const struct drm_display_mode *mode)
|
||||
{
|
||||
@ -705,14 +728,11 @@ int drm_mode_vrefresh(const struct drm_display_mode *mode)
|
||||
EXPORT_SYMBOL(drm_mode_vrefresh);
|
||||
|
||||
/**
|
||||
* drm_mode_set_crtcinfo - set CRTC modesetting parameters
|
||||
* drm_mode_set_crtcinfo - set CRTC modesetting timing parameters
|
||||
* @p: mode
|
||||
* @adjust_flags: a combination of adjustment flags
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Setup the CRTC modesetting parameters for @p, adjusting if necessary.
|
||||
* Setup the CRTC modesetting timing parameters for @p, adjusting if necessary.
|
||||
*
|
||||
* - The CRTC_INTERLACE_HALVE_V flag can be used to halve vertical timings of
|
||||
* interlaced modes.
|
||||
@ -780,15 +800,11 @@ void drm_mode_set_crtcinfo(struct drm_display_mode *p, int adjust_flags)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_set_crtcinfo);
|
||||
|
||||
|
||||
/**
|
||||
* drm_mode_copy - copy the mode
|
||||
* @dst: mode to overwrite
|
||||
* @src: mode to copy
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Copy an existing mode into another mode, preserving the object id and
|
||||
* list head of the destination mode.
|
||||
*/
|
||||
@ -805,13 +821,14 @@ EXPORT_SYMBOL(drm_mode_copy);
|
||||
|
||||
/**
|
||||
* drm_mode_duplicate - allocate and duplicate an existing mode
|
||||
* @m: mode to duplicate
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
* @dev: drm_device to allocate the duplicated mode for
|
||||
* @mode: mode to duplicate
|
||||
*
|
||||
* Just allocate a new mode, copy the existing mode into it, and return
|
||||
* a pointer to it. Used to create new instances of established modes.
|
||||
*
|
||||
* Returns:
|
||||
* Pointer to duplicated mode on success, NULL on error.
|
||||
*/
|
||||
struct drm_display_mode *drm_mode_duplicate(struct drm_device *dev,
|
||||
const struct drm_display_mode *mode)
|
||||
@ -833,12 +850,9 @@ EXPORT_SYMBOL(drm_mode_duplicate);
|
||||
* @mode1: first mode
|
||||
* @mode2: second mode
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Check to see if @mode1 and @mode2 are equivalent.
|
||||
*
|
||||
* RETURNS:
|
||||
* Returns:
|
||||
* True if the modes are equal, false otherwise.
|
||||
*/
|
||||
bool drm_mode_equal(const struct drm_display_mode *mode1, const struct drm_display_mode *mode2)
|
||||
@ -864,13 +878,10 @@ EXPORT_SYMBOL(drm_mode_equal);
|
||||
* @mode1: first mode
|
||||
* @mode2: second mode
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Check to see if @mode1 and @mode2 are equivalent, but
|
||||
* don't check the pixel clocks nor the stereo layout.
|
||||
*
|
||||
* RETURNS:
|
||||
* Returns:
|
||||
* True if the modes are equal, false otherwise.
|
||||
*/
|
||||
bool drm_mode_equal_no_clocks_no_stereo(const struct drm_display_mode *mode1,
|
||||
@ -900,25 +911,19 @@ EXPORT_SYMBOL(drm_mode_equal_no_clocks_no_stereo);
|
||||
* @mode_list: list of modes to check
|
||||
* @maxX: maximum width
|
||||
* @maxY: maximum height
|
||||
* @maxPitch: max pitch
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold a lock protecting @mode_list.
|
||||
*
|
||||
* The DRM device (@dev) has size and pitch limits. Here we validate the
|
||||
* modes we probed for @dev against those limits and set their status as
|
||||
* necessary.
|
||||
* This function is a helper which can be used to validate modes against size
|
||||
* limitations of the DRM device/connector. If a mode is too big its status
|
||||
* memeber is updated with the appropriate validation failure code. The list
|
||||
* itself is not changed.
|
||||
*/
|
||||
void drm_mode_validate_size(struct drm_device *dev,
|
||||
struct list_head *mode_list,
|
||||
int maxX, int maxY, int maxPitch)
|
||||
int maxX, int maxY)
|
||||
{
|
||||
struct drm_display_mode *mode;
|
||||
|
||||
list_for_each_entry(mode, mode_list, head) {
|
||||
if (maxPitch > 0 && mode->hdisplay > maxPitch)
|
||||
mode->status = MODE_BAD_WIDTH;
|
||||
|
||||
if (maxX > 0 && mode->hdisplay > maxX)
|
||||
mode->status = MODE_VIRTUAL_X;
|
||||
|
||||
@ -934,12 +939,10 @@ EXPORT_SYMBOL(drm_mode_validate_size);
|
||||
* @mode_list: list of modes to check
|
||||
* @verbose: be verbose about it
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold a lock protecting @mode_list.
|
||||
*
|
||||
* Once mode list generation is complete, a caller can use this routine to
|
||||
* remove invalid modes from a mode list. If any of the modes have a
|
||||
* status other than %MODE_OK, they are removed from @mode_list and freed.
|
||||
* This helper function can be used to prune a display mode list after
|
||||
* validation has been completed. All modes who's status is not MODE_OK will be
|
||||
* removed from the list, and if @verbose the status code and mode name is also
|
||||
* printed to dmesg.
|
||||
*/
|
||||
void drm_mode_prune_invalid(struct drm_device *dev,
|
||||
struct list_head *mode_list, bool verbose)
|
||||
@ -966,13 +969,10 @@ EXPORT_SYMBOL(drm_mode_prune_invalid);
|
||||
* @lh_a: list_head for first mode
|
||||
* @lh_b: list_head for second mode
|
||||
*
|
||||
* LOCKING:
|
||||
* None.
|
||||
*
|
||||
* Compare two modes, given by @lh_a and @lh_b, returning a value indicating
|
||||
* which is better.
|
||||
*
|
||||
* RETURNS:
|
||||
* Returns:
|
||||
* Negative if @lh_a is better than @lh_b, zero if they're equivalent, or
|
||||
* positive if @lh_b is better than @lh_a.
|
||||
*/
|
||||
@ -1000,12 +1000,9 @@ static int drm_mode_compare(void *priv, struct list_head *lh_a, struct list_head
|
||||
|
||||
/**
|
||||
* drm_mode_sort - sort mode list
|
||||
* @mode_list: list to sort
|
||||
* @mode_list: list of drm_display_mode structures to sort
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold a lock protecting @mode_list.
|
||||
*
|
||||
* Sort @mode_list by favorability, putting good modes first.
|
||||
* Sort @mode_list by favorability, moving good modes to the head of the list.
|
||||
*/
|
||||
void drm_mode_sort(struct list_head *mode_list)
|
||||
{
|
||||
@ -1017,13 +1014,12 @@ EXPORT_SYMBOL(drm_mode_sort);
|
||||
* drm_mode_connector_list_update - update the mode list for the connector
|
||||
* @connector: the connector to update
|
||||
*
|
||||
* LOCKING:
|
||||
* Caller must hold a lock protecting @mode_list.
|
||||
*
|
||||
* This moves the modes from the @connector probed_modes list
|
||||
* to the actual mode list. It compares the probed mode against the current
|
||||
* list and only adds different modes. All modes unverified after this point
|
||||
* will be removed by the prune invalid modes.
|
||||
* list and only adds different/new modes.
|
||||
*
|
||||
* This is just a helper functions doesn't validate any modes itself and also
|
||||
* doesn't prune any invalid modes. Callers need to do that themselves.
|
||||
*/
|
||||
void drm_mode_connector_list_update(struct drm_connector *connector)
|
||||
{
|
||||
@ -1031,6 +1027,8 @@ void drm_mode_connector_list_update(struct drm_connector *connector)
|
||||
struct drm_display_mode *pmode, *pt;
|
||||
int found_it;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&connector->dev->mode_config.mutex));
|
||||
|
||||
list_for_each_entry_safe(pmode, pt, &connector->probed_modes,
|
||||
head) {
|
||||
found_it = 0;
|
||||
@ -1056,17 +1054,25 @@ void drm_mode_connector_list_update(struct drm_connector *connector)
|
||||
EXPORT_SYMBOL(drm_mode_connector_list_update);
|
||||
|
||||
/**
|
||||
* drm_mode_parse_command_line_for_connector - parse command line for connector
|
||||
* @mode_option - per connector mode option
|
||||
* @connector - connector to parse line for
|
||||
* drm_mode_parse_command_line_for_connector - parse command line modeline for connector
|
||||
* @mode_option: optional per connector mode option
|
||||
* @connector: connector to parse modeline for
|
||||
* @mode: preallocated drm_cmdline_mode structure to fill out
|
||||
*
|
||||
* This parses the connector specific then generic command lines for
|
||||
* modes and options to configure the connector.
|
||||
* This parses @mode_option command line modeline for modes and options to
|
||||
* configure the connector. If @mode_option is NULL the default command line
|
||||
* modeline in fb_mode_option will be parsed instead.
|
||||
*
|
||||
* This uses the same parameters as the fb modedb.c, except for an extra
|
||||
* force-enable, force-enable-digital and force-disable bit at the end:
|
||||
*
|
||||
* This uses the same parameters as the fb modedb.c, except for extra
|
||||
* <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
|
||||
*
|
||||
* enable/enable Digital/disable bit at the end
|
||||
* The intermediate drm_cmdline_mode structure is required to store additional
|
||||
* options from the command line modline like the force-enabel/disable flag.
|
||||
*
|
||||
* Returns:
|
||||
* True if a valid modeline has been parsed, false otherwise.
|
||||
*/
|
||||
bool drm_mode_parse_command_line_for_connector(const char *mode_option,
|
||||
struct drm_connector *connector,
|
||||
@ -1219,6 +1225,14 @@ done:
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_parse_command_line_for_connector);
|
||||
|
||||
/**
|
||||
* drm_mode_create_from_cmdline_mode - convert a command line modeline into a DRM display mode
|
||||
* @dev: DRM device to create the new mode for
|
||||
* @cmd: input command line modeline
|
||||
*
|
||||
* Returns:
|
||||
* Pointer to converted mode on success, NULL on error.
|
||||
*/
|
||||
struct drm_display_mode *
|
||||
drm_mode_create_from_cmdline_mode(struct drm_device *dev,
|
||||
struct drm_cmdline_mode *cmd)
|
||||
|
@ -351,7 +351,7 @@ err_agp:
|
||||
drm_pci_agp_destroy(dev);
|
||||
pci_disable_device(pdev);
|
||||
err_free:
|
||||
drm_dev_free(dev);
|
||||
drm_dev_unref(dev);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_get_pci_dev);
|
||||
|
333
drivers/gpu/drm/drm_plane_helper.c
Normal file
333
drivers/gpu/drm/drm_plane_helper.c
Normal file
@ -0,0 +1,333 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Intel Corporation
|
||||
*
|
||||
* DRM universal plane helper functions
|
||||
*
|
||||
* 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 AUTHORS OR COPYRIGHT HOLDERS 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.
|
||||
*/
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_rect.h>
|
||||
|
||||
#define SUBPIXEL_MASK 0xffff
|
||||
|
||||
/*
|
||||
* This is the minimal list of formats that seem to be safe for modeset use
|
||||
* with all current DRM drivers. Most hardware can actually support more
|
||||
* formats than this and drivers may specify a more accurate list when
|
||||
* creating the primary plane. However drivers that still call
|
||||
* drm_plane_init() will use this minimal format list as the default.
|
||||
*/
|
||||
const static uint32_t safe_modeset_formats[] = {
|
||||
DRM_FORMAT_XRGB8888,
|
||||
DRM_FORMAT_ARGB8888,
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns the connectors currently associated with a CRTC. This function
|
||||
* should be called twice: once with a NULL connector list to retrieve
|
||||
* the list size, and once with the properly allocated list to be filled in.
|
||||
*/
|
||||
static int get_connectors_for_crtc(struct drm_crtc *crtc,
|
||||
struct drm_connector **connector_list,
|
||||
int num_connectors)
|
||||
{
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_connector *connector;
|
||||
int count = 0;
|
||||
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head)
|
||||
if (connector->encoder && connector->encoder->crtc == crtc) {
|
||||
if (connector_list != NULL && count < num_connectors)
|
||||
*(connector_list++) = connector;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_primary_helper_update() - Helper for primary plane update
|
||||
* @plane: plane object to update
|
||||
* @crtc: owning CRTC of owning plane
|
||||
* @fb: framebuffer to flip onto plane
|
||||
* @crtc_x: x offset of primary plane on crtc
|
||||
* @crtc_y: y offset of primary plane on crtc
|
||||
* @crtc_w: width of primary plane rectangle on crtc
|
||||
* @crtc_h: height of primary plane rectangle on crtc
|
||||
* @src_x: x offset of @fb for panning
|
||||
* @src_y: y offset of @fb for panning
|
||||
* @src_w: width of source rectangle in @fb
|
||||
* @src_h: height of source rectangle in @fb
|
||||
*
|
||||
* Provides a default plane update handler for primary planes. This is handler
|
||||
* is called in response to a userspace SetPlane operation on the plane with a
|
||||
* non-NULL framebuffer. We call the driver's modeset handler to update the
|
||||
* framebuffer.
|
||||
*
|
||||
* SetPlane() on a primary plane of a disabled CRTC is not supported, and will
|
||||
* return an error.
|
||||
*
|
||||
* Note that we make some assumptions about hardware limitations that may not be
|
||||
* true for all hardware --
|
||||
* 1) Primary plane cannot be repositioned.
|
||||
* 2) Primary plane cannot be scaled.
|
||||
* 3) Primary plane must cover the entire CRTC.
|
||||
* 4) Subpixel positioning is not supported.
|
||||
* Drivers for hardware that don't have these restrictions can provide their
|
||||
* own implementation rather than using this helper.
|
||||
*
|
||||
* RETURNS:
|
||||
* Zero on success, error code on failure
|
||||
*/
|
||||
int drm_primary_helper_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 drm_mode_set set = {
|
||||
.crtc = crtc,
|
||||
.fb = fb,
|
||||
.mode = &crtc->mode,
|
||||
.x = src_x >> 16,
|
||||
.y = src_y >> 16,
|
||||
};
|
||||
struct drm_rect dest = {
|
||||
.x1 = crtc_x,
|
||||
.y1 = crtc_y,
|
||||
.x2 = crtc_x + crtc_w,
|
||||
.y2 = crtc_y + crtc_h,
|
||||
};
|
||||
struct drm_rect clip = {
|
||||
.x2 = crtc->mode.hdisplay,
|
||||
.y2 = crtc->mode.vdisplay,
|
||||
};
|
||||
struct drm_connector **connector_list;
|
||||
struct drm_framebuffer *tmpfb;
|
||||
int num_connectors, ret;
|
||||
|
||||
if (!crtc->enabled) {
|
||||
DRM_DEBUG_KMS("Cannot update primary plane of a disabled CRTC.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Disallow subpixel positioning */
|
||||
if ((src_x | src_y | src_w | src_h) & SUBPIXEL_MASK) {
|
||||
DRM_DEBUG_KMS("Primary plane does not support subpixel positioning\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Primary planes are locked to their owning CRTC */
|
||||
if (plane->possible_crtcs != drm_crtc_mask(crtc)) {
|
||||
DRM_DEBUG_KMS("Cannot change primary plane CRTC\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Disallow scaling */
|
||||
if (crtc_w != src_w || crtc_h != src_h) {
|
||||
DRM_DEBUG_KMS("Can't scale primary plane\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Make sure primary plane covers entire CRTC */
|
||||
drm_rect_intersect(&dest, &clip);
|
||||
if (dest.x1 != 0 || dest.y1 != 0 ||
|
||||
dest.x2 != crtc->mode.hdisplay || dest.y2 != crtc->mode.vdisplay) {
|
||||
DRM_DEBUG_KMS("Primary plane must cover entire CRTC\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Framebuffer must be big enough to cover entire plane */
|
||||
ret = drm_crtc_check_viewport(crtc, crtc_x, crtc_y, &crtc->mode, fb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Find current connectors for CRTC */
|
||||
num_connectors = get_connectors_for_crtc(crtc, NULL, 0);
|
||||
BUG_ON(num_connectors == 0);
|
||||
connector_list = kzalloc(num_connectors * sizeof(*connector_list),
|
||||
GFP_KERNEL);
|
||||
if (!connector_list)
|
||||
return -ENOMEM;
|
||||
get_connectors_for_crtc(crtc, connector_list, num_connectors);
|
||||
|
||||
set.connectors = connector_list;
|
||||
set.num_connectors = num_connectors;
|
||||
|
||||
/*
|
||||
* set_config() adjusts crtc->primary->fb; however the DRM setplane
|
||||
* code that called us expects to handle the framebuffer update and
|
||||
* reference counting; save and restore the current fb before
|
||||
* calling it.
|
||||
*
|
||||
* N.B., we call set_config() directly here rather than using
|
||||
* drm_mode_set_config_internal. We're reprogramming the same
|
||||
* connectors that were already in use, so we shouldn't need the extra
|
||||
* cross-CRTC fb refcounting to accomodate stealing connectors.
|
||||
* drm_mode_setplane() already handles the basic refcounting for the
|
||||
* framebuffers involved in this operation.
|
||||
*/
|
||||
tmpfb = plane->fb;
|
||||
ret = crtc->funcs->set_config(&set);
|
||||
plane->fb = tmpfb;
|
||||
|
||||
kfree(connector_list);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_primary_helper_update);
|
||||
|
||||
/**
|
||||
* drm_primary_helper_disable() - Helper for primary plane disable
|
||||
* @plane: plane to disable
|
||||
*
|
||||
* Provides a default plane disable handler for primary planes. This is handler
|
||||
* is called in response to a userspace SetPlane operation on the plane with a
|
||||
* NULL framebuffer parameter. We call the driver's modeset handler with a NULL
|
||||
* framebuffer to disable the CRTC if no other planes are currently enabled.
|
||||
* If other planes are still enabled on the same CRTC, we return -EBUSY.
|
||||
*
|
||||
* Note that some hardware may be able to disable the primary plane without
|
||||
* disabling the whole CRTC. Drivers for such hardware should provide their
|
||||
* own disable handler that disables just the primary plane (and they'll likely
|
||||
* need to provide their own update handler as well to properly re-enable a
|
||||
* disabled primary plane).
|
||||
*
|
||||
* RETURNS:
|
||||
* Zero on success, error code on failure
|
||||
*/
|
||||
int drm_primary_helper_disable(struct drm_plane *plane)
|
||||
{
|
||||
struct drm_plane *p;
|
||||
struct drm_mode_set set = {
|
||||
.crtc = plane->crtc,
|
||||
.fb = NULL,
|
||||
};
|
||||
|
||||
if (plane->crtc == NULL || plane->fb == NULL)
|
||||
/* Already disabled */
|
||||
return 0;
|
||||
|
||||
list_for_each_entry(p, &plane->dev->mode_config.plane_list, head)
|
||||
if (p != plane && p->fb) {
|
||||
DRM_DEBUG_KMS("Cannot disable primary plane while other planes are still active on CRTC.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/*
|
||||
* N.B. We call set_config() directly here rather than
|
||||
* drm_mode_set_config_internal() since drm_mode_setplane() already
|
||||
* handles the basic refcounting and we don't need the special
|
||||
* cross-CRTC refcounting (no chance of stealing connectors from
|
||||
* other CRTC's with this update).
|
||||
*/
|
||||
return plane->crtc->funcs->set_config(&set);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_primary_helper_disable);
|
||||
|
||||
/**
|
||||
* drm_primary_helper_destroy() - Helper for primary plane destruction
|
||||
* @plane: plane to destroy
|
||||
*
|
||||
* Provides a default plane destroy handler for primary planes. This handler
|
||||
* is called during CRTC destruction. We disable the primary plane, remove
|
||||
* it from the DRM plane list, and deallocate the plane structure.
|
||||
*/
|
||||
void drm_primary_helper_destroy(struct drm_plane *plane)
|
||||
{
|
||||
plane->funcs->disable_plane(plane);
|
||||
drm_plane_cleanup(plane);
|
||||
kfree(plane);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_primary_helper_destroy);
|
||||
|
||||
const struct drm_plane_funcs drm_primary_helper_funcs = {
|
||||
.update_plane = drm_primary_helper_update,
|
||||
.disable_plane = drm_primary_helper_disable,
|
||||
.destroy = drm_primary_helper_destroy,
|
||||
};
|
||||
EXPORT_SYMBOL(drm_primary_helper_funcs);
|
||||
|
||||
/**
|
||||
* drm_primary_helper_create_plane() - Create a generic primary plane
|
||||
* @dev: drm device
|
||||
* @formats: pixel formats supported, or NULL for a default safe list
|
||||
* @num_formats: size of @formats; ignored if @formats is NULL
|
||||
*
|
||||
* Allocates and initializes a primary plane that can be used with the primary
|
||||
* plane helpers. Drivers that wish to use driver-specific plane structures or
|
||||
* provide custom handler functions may perform their own allocation and
|
||||
* initialization rather than calling this function.
|
||||
*/
|
||||
struct drm_plane *drm_primary_helper_create_plane(struct drm_device *dev,
|
||||
const uint32_t *formats,
|
||||
int num_formats)
|
||||
{
|
||||
struct drm_plane *primary;
|
||||
int ret;
|
||||
|
||||
primary = kzalloc(sizeof(*primary), GFP_KERNEL);
|
||||
if (primary == NULL) {
|
||||
DRM_DEBUG_KMS("Failed to allocate primary plane\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (formats == NULL) {
|
||||
formats = safe_modeset_formats;
|
||||
num_formats = ARRAY_SIZE(safe_modeset_formats);
|
||||
}
|
||||
|
||||
/* possible_crtc's will be filled in later by crtc_init */
|
||||
ret = drm_plane_init(dev, primary, 0, &drm_primary_helper_funcs,
|
||||
formats, num_formats,
|
||||
DRM_PLANE_TYPE_PRIMARY);
|
||||
if (ret) {
|
||||
kfree(primary);
|
||||
primary = NULL;
|
||||
}
|
||||
|
||||
return primary;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_primary_helper_create_plane);
|
||||
|
||||
/**
|
||||
* drm_crtc_init - Legacy CRTC initialization function
|
||||
* @dev: DRM device
|
||||
* @crtc: CRTC object to init
|
||||
* @funcs: callbacks for the new CRTC
|
||||
*
|
||||
* Initialize a CRTC object with a default helper-provided primary plane and no
|
||||
* cursor plane.
|
||||
*
|
||||
* Returns:
|
||||
* Zero on success, error code on failure.
|
||||
*/
|
||||
int drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
|
||||
const struct drm_crtc_funcs *funcs)
|
||||
{
|
||||
struct drm_plane *primary;
|
||||
|
||||
primary = drm_primary_helper_create_plane(dev, NULL, 0);
|
||||
return drm_crtc_init_with_planes(dev, crtc, primary, NULL, funcs);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_crtc_init);
|
@ -64,7 +64,7 @@ static int drm_get_platform_dev(struct platform_device *platdev,
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
drm_dev_free(dev);
|
||||
drm_dev_unref(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,8 @@ struct drm_prime_attachment {
|
||||
enum dma_data_direction dir;
|
||||
};
|
||||
|
||||
static int drm_prime_add_buf_handle(struct drm_prime_file_private *prime_fpriv, struct dma_buf *dma_buf, uint32_t handle)
|
||||
static int drm_prime_add_buf_handle(struct drm_prime_file_private *prime_fpriv,
|
||||
struct dma_buf *dma_buf, uint32_t handle)
|
||||
{
|
||||
struct drm_prime_member *member;
|
||||
|
||||
@ -174,7 +175,7 @@ void drm_prime_remove_buf_handle_locked(struct drm_prime_file_private *prime_fpr
|
||||
}
|
||||
|
||||
static struct sg_table *drm_gem_map_dma_buf(struct dma_buf_attachment *attach,
|
||||
enum dma_data_direction dir)
|
||||
enum dma_data_direction dir)
|
||||
{
|
||||
struct drm_prime_attachment *prime_attach = attach->priv;
|
||||
struct drm_gem_object *obj = attach->dmabuf->priv;
|
||||
@ -211,11 +212,19 @@ static struct sg_table *drm_gem_map_dma_buf(struct dma_buf_attachment *attach,
|
||||
}
|
||||
|
||||
static void drm_gem_unmap_dma_buf(struct dma_buf_attachment *attach,
|
||||
struct sg_table *sgt, enum dma_data_direction dir)
|
||||
struct sg_table *sgt,
|
||||
enum dma_data_direction dir)
|
||||
{
|
||||
/* nothing to be done here */
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_dmabuf_release - dma_buf release implementation for GEM
|
||||
* @dma_buf: buffer to be released
|
||||
*
|
||||
* Generic release function for dma_bufs exported as PRIME buffers. GEM drivers
|
||||
* must use this in their dma_buf ops structure as the release callback.
|
||||
*/
|
||||
void drm_gem_dmabuf_release(struct dma_buf *dma_buf)
|
||||
{
|
||||
struct drm_gem_object *obj = dma_buf->priv;
|
||||
@ -242,30 +251,30 @@ static void drm_gem_dmabuf_vunmap(struct dma_buf *dma_buf, void *vaddr)
|
||||
}
|
||||
|
||||
static void *drm_gem_dmabuf_kmap_atomic(struct dma_buf *dma_buf,
|
||||
unsigned long page_num)
|
||||
unsigned long page_num)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void drm_gem_dmabuf_kunmap_atomic(struct dma_buf *dma_buf,
|
||||
unsigned long page_num, void *addr)
|
||||
unsigned long page_num, void *addr)
|
||||
{
|
||||
|
||||
}
|
||||
static void *drm_gem_dmabuf_kmap(struct dma_buf *dma_buf,
|
||||
unsigned long page_num)
|
||||
unsigned long page_num)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void drm_gem_dmabuf_kunmap(struct dma_buf *dma_buf,
|
||||
unsigned long page_num, void *addr)
|
||||
unsigned long page_num, void *addr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static int drm_gem_dmabuf_mmap(struct dma_buf *dma_buf,
|
||||
struct vm_area_struct *vma)
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct drm_gem_object *obj = dma_buf->priv;
|
||||
struct drm_device *dev = obj->dev;
|
||||
@ -315,6 +324,15 @@ static const struct dma_buf_ops drm_gem_prime_dmabuf_ops = {
|
||||
* driver's scatter/gather table
|
||||
*/
|
||||
|
||||
/**
|
||||
* drm_gem_prime_export - helper library implemention of the export callback
|
||||
* @dev: drm_device to export from
|
||||
* @obj: GEM object to export
|
||||
* @flags: flags like DRM_CLOEXEC
|
||||
*
|
||||
* This is the implementation of the gem_prime_export functions for GEM drivers
|
||||
* using the PRIME helpers.
|
||||
*/
|
||||
struct dma_buf *drm_gem_prime_export(struct drm_device *dev,
|
||||
struct drm_gem_object *obj, int flags)
|
||||
{
|
||||
@ -355,9 +373,23 @@ static struct dma_buf *export_and_register_object(struct drm_device *dev,
|
||||
return dmabuf;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_gem_prime_handle_to_fd - PRIME export function for GEM drivers
|
||||
* @dev: dev to export the buffer from
|
||||
* @file_priv: drm file-private structure
|
||||
* @handle: buffer handle to export
|
||||
* @flags: flags like DRM_CLOEXEC
|
||||
* @prime_fd: pointer to storage for the fd id of the create dma-buf
|
||||
*
|
||||
* This is the PRIME export function which must be used mandatorily by GEM
|
||||
* drivers to ensure correct lifetime management of the underlying GEM object.
|
||||
* The actual exporting from GEM object to a dma-buf is done through the
|
||||
* gem_prime_export driver callback.
|
||||
*/
|
||||
int drm_gem_prime_handle_to_fd(struct drm_device *dev,
|
||||
struct drm_file *file_priv, uint32_t handle, uint32_t flags,
|
||||
int *prime_fd)
|
||||
struct drm_file *file_priv, uint32_t handle,
|
||||
uint32_t flags,
|
||||
int *prime_fd)
|
||||
{
|
||||
struct drm_gem_object *obj;
|
||||
int ret = 0;
|
||||
@ -441,6 +473,14 @@ out_unlock:
|
||||
}
|
||||
EXPORT_SYMBOL(drm_gem_prime_handle_to_fd);
|
||||
|
||||
/**
|
||||
* drm_gem_prime_import - helper library implemention of the import callback
|
||||
* @dev: drm_device to import into
|
||||
* @dma_buf: dma-buf object to import
|
||||
*
|
||||
* This is the implementation of the gem_prime_import functions for GEM drivers
|
||||
* using the PRIME helpers.
|
||||
*/
|
||||
struct drm_gem_object *drm_gem_prime_import(struct drm_device *dev,
|
||||
struct dma_buf *dma_buf)
|
||||
{
|
||||
@ -496,8 +536,21 @@ fail_detach:
|
||||
}
|
||||
EXPORT_SYMBOL(drm_gem_prime_import);
|
||||
|
||||
/**
|
||||
* drm_gem_prime_fd_to_handle - PRIME import function for GEM drivers
|
||||
* @dev: dev to export the buffer from
|
||||
* @file_priv: drm file-private structure
|
||||
* @prime_fd: fd id of the dma-buf which should be imported
|
||||
* @handle: pointer to storage for the handle of the imported buffer object
|
||||
*
|
||||
* This is the PRIME import function which must be used mandatorily by GEM
|
||||
* drivers to ensure correct lifetime management of the underlying GEM object.
|
||||
* The actual importing of GEM object from the dma-buf is done through the
|
||||
* gem_import_export driver callback.
|
||||
*/
|
||||
int drm_gem_prime_fd_to_handle(struct drm_device *dev,
|
||||
struct drm_file *file_priv, int prime_fd, uint32_t *handle)
|
||||
struct drm_file *file_priv, int prime_fd,
|
||||
uint32_t *handle)
|
||||
{
|
||||
struct dma_buf *dma_buf;
|
||||
struct drm_gem_object *obj;
|
||||
@ -598,12 +651,14 @@ int drm_prime_fd_to_handle_ioctl(struct drm_device *dev, void *data,
|
||||
args->fd, &args->handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* drm_prime_pages_to_sg
|
||||
/**
|
||||
* drm_prime_pages_to_sg - converts a page array into an sg list
|
||||
* @pages: pointer to the array of page pointers to convert
|
||||
* @nr_pages: length of the page vector
|
||||
*
|
||||
* this helper creates an sg table object from a set of pages
|
||||
* This helper creates an sg table object from a set of pages
|
||||
* the driver is responsible for mapping the pages into the
|
||||
* importers address space
|
||||
* importers address space for use with dma_buf itself.
|
||||
*/
|
||||
struct sg_table *drm_prime_pages_to_sg(struct page **pages, int nr_pages)
|
||||
{
|
||||
@ -628,9 +683,16 @@ out:
|
||||
}
|
||||
EXPORT_SYMBOL(drm_prime_pages_to_sg);
|
||||
|
||||
/* export an sg table into an array of pages and addresses
|
||||
this is currently required by the TTM driver in order to do correct fault
|
||||
handling */
|
||||
/**
|
||||
* drm_prime_sg_to_page_addr_arrays - convert an sg table into a page array
|
||||
* @sgt: scatter-gather table to convert
|
||||
* @pages: array of page pointers to store the page array in
|
||||
* @addrs: optional array to store the dma bus address of each page
|
||||
* @max_pages: size of both the passed-in arrays
|
||||
*
|
||||
* Exports an sg table into an array of pages and addresses. This is currently
|
||||
* required by the TTM driver in order to do correct fault handling.
|
||||
*/
|
||||
int drm_prime_sg_to_page_addr_arrays(struct sg_table *sgt, struct page **pages,
|
||||
dma_addr_t *addrs, int max_pages)
|
||||
{
|
||||
@ -663,7 +725,15 @@ int drm_prime_sg_to_page_addr_arrays(struct sg_table *sgt, struct page **pages,
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_prime_sg_to_page_addr_arrays);
|
||||
/* helper function to cleanup a GEM/prime object */
|
||||
|
||||
/**
|
||||
* drm_prime_gem_destroy - helper to clean up a PRIME-imported GEM object
|
||||
* @obj: GEM object which was created from a dma-buf
|
||||
* @sg: the sg-table which was pinned at import time
|
||||
*
|
||||
* This is the cleanup functions which GEM drivers need to call when they use
|
||||
* @drm_gem_prime_import to import dma-bufs.
|
||||
*/
|
||||
void drm_prime_gem_destroy(struct drm_gem_object *obj, struct sg_table *sg)
|
||||
{
|
||||
struct dma_buf_attachment *attach;
|
||||
@ -683,11 +753,9 @@ void drm_prime_init_file_private(struct drm_prime_file_private *prime_fpriv)
|
||||
INIT_LIST_HEAD(&prime_fpriv->head);
|
||||
mutex_init(&prime_fpriv->lock);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_prime_init_file_private);
|
||||
|
||||
void drm_prime_destroy_file_private(struct drm_prime_file_private *prime_fpriv)
|
||||
{
|
||||
/* by now drm_gem_release should've made sure the list is empty */
|
||||
WARN_ON(!list_empty(&prime_fpriv->head));
|
||||
}
|
||||
EXPORT_SYMBOL(drm_prime_destroy_file_private);
|
||||
|
@ -31,8 +31,10 @@
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/slab.h>
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_core.h>
|
||||
@ -43,6 +45,10 @@ EXPORT_SYMBOL(drm_debug);
|
||||
unsigned int drm_rnodes = 0; /* 1 to enable experimental render nodes API */
|
||||
EXPORT_SYMBOL(drm_rnodes);
|
||||
|
||||
/* 1 to allow user space to request universal planes (experimental) */
|
||||
unsigned int drm_universal_planes = 0;
|
||||
EXPORT_SYMBOL(drm_universal_planes);
|
||||
|
||||
unsigned int drm_vblank_offdelay = 5000; /* Default to 5000 msecs. */
|
||||
EXPORT_SYMBOL(drm_vblank_offdelay);
|
||||
|
||||
@ -66,10 +72,12 @@ MODULE_PARM_DESC(timestamp_monotonic, "Use monotonic timestamps");
|
||||
|
||||
module_param_named(debug, drm_debug, int, 0600);
|
||||
module_param_named(rnodes, drm_rnodes, int, 0600);
|
||||
module_param_named(universal_planes, drm_universal_planes, int, 0600);
|
||||
module_param_named(vblankoffdelay, drm_vblank_offdelay, int, 0600);
|
||||
module_param_named(timestamp_precision_usec, drm_timestamp_precision, int, 0600);
|
||||
module_param_named(timestamp_monotonic, drm_timestamp_monotonic, int, 0600);
|
||||
|
||||
static DEFINE_SPINLOCK(drm_minor_lock);
|
||||
struct idr drm_minors_idr;
|
||||
|
||||
struct class *drm_class;
|
||||
@ -94,49 +102,21 @@ int drm_err(const char *func, const char *format, ...)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_err);
|
||||
|
||||
void drm_ut_debug_printk(unsigned int request_level,
|
||||
const char *prefix,
|
||||
const char *function_name,
|
||||
const char *format, ...)
|
||||
void drm_ut_debug_printk(const char *function_name, const char *format, ...)
|
||||
{
|
||||
struct va_format vaf;
|
||||
va_list args;
|
||||
|
||||
if (drm_debug & request_level) {
|
||||
va_start(args, format);
|
||||
vaf.fmt = format;
|
||||
vaf.va = &args;
|
||||
va_start(args, format);
|
||||
vaf.fmt = format;
|
||||
vaf.va = &args;
|
||||
|
||||
if (function_name)
|
||||
printk(KERN_DEBUG "[%s:%s], %pV", prefix,
|
||||
function_name, &vaf);
|
||||
else
|
||||
printk(KERN_DEBUG "%pV", &vaf);
|
||||
va_end(args);
|
||||
}
|
||||
printk(KERN_DEBUG "[" DRM_NAME ":%s] %pV", function_name, &vaf);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_ut_debug_printk);
|
||||
|
||||
static int drm_minor_get_id(struct drm_device *dev, int type)
|
||||
{
|
||||
int ret;
|
||||
int base = 0, limit = 63;
|
||||
|
||||
if (type == DRM_MINOR_CONTROL) {
|
||||
base += 64;
|
||||
limit = base + 63;
|
||||
} else if (type == DRM_MINOR_RENDER) {
|
||||
base += 128;
|
||||
limit = base + 63;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
ret = idr_alloc(&drm_minors_idr, NULL, base, limit, GFP_KERNEL);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
return ret == -ENOSPC ? -EINVAL : ret;
|
||||
}
|
||||
|
||||
struct drm_master *drm_master_create(struct drm_minor *minor)
|
||||
{
|
||||
struct drm_master *master;
|
||||
@ -152,8 +132,6 @@ struct drm_master *drm_master_create(struct drm_minor *minor)
|
||||
INIT_LIST_HEAD(&master->magicfree);
|
||||
master->minor = minor;
|
||||
|
||||
list_add_tail(&master->head, &minor->master_list);
|
||||
|
||||
return master;
|
||||
}
|
||||
|
||||
@ -171,8 +149,7 @@ static void drm_master_destroy(struct kref *kref)
|
||||
struct drm_device *dev = master->minor->dev;
|
||||
struct drm_map_list *r_list, *list_temp;
|
||||
|
||||
list_del(&master->head);
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
if (dev->driver->master_destroy)
|
||||
dev->driver->master_destroy(dev, master);
|
||||
|
||||
@ -200,6 +177,7 @@ static void drm_master_destroy(struct kref *kref)
|
||||
|
||||
drm_ht_remove(&master->magiclist);
|
||||
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
kfree(master);
|
||||
}
|
||||
|
||||
@ -215,19 +193,20 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&dev->master_mutex);
|
||||
if (file_priv->is_master)
|
||||
return 0;
|
||||
goto out_unlock;
|
||||
|
||||
if (file_priv->minor->master && file_priv->minor->master != file_priv->master)
|
||||
return -EINVAL;
|
||||
if (file_priv->minor->master) {
|
||||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (!file_priv->master)
|
||||
return -EINVAL;
|
||||
if (!file_priv->master) {
|
||||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (file_priv->minor->master)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
file_priv->minor->master = drm_master_get(file_priv->master);
|
||||
file_priv->is_master = 1;
|
||||
if (dev->driver->master_set) {
|
||||
@ -237,142 +216,211 @@ int drm_setmaster_ioctl(struct drm_device *dev, void *data,
|
||||
drm_master_put(&file_priv->minor->master);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&dev->master_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int drm_dropmaster_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
|
||||
mutex_lock(&dev->master_mutex);
|
||||
if (!file_priv->is_master)
|
||||
return -EINVAL;
|
||||
goto out_unlock;
|
||||
|
||||
if (!file_priv->minor->master)
|
||||
return -EINVAL;
|
||||
goto out_unlock;
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
ret = 0;
|
||||
if (dev->driver->master_drop)
|
||||
dev->driver->master_drop(dev, file_priv, false);
|
||||
drm_master_put(&file_priv->minor->master);
|
||||
file_priv->is_master = 0;
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&dev->master_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* DRM Minors
|
||||
* A DRM device can provide several char-dev interfaces on the DRM-Major. Each
|
||||
* of them is represented by a drm_minor object. Depending on the capabilities
|
||||
* of the device-driver, different interfaces are registered.
|
||||
*
|
||||
* Minors can be accessed via dev->$minor_name. This pointer is either
|
||||
* NULL or a valid drm_minor pointer and stays valid as long as the device is
|
||||
* valid. This means, DRM minors have the same life-time as the underlying
|
||||
* device. However, this doesn't mean that the minor is active. Minors are
|
||||
* registered and unregistered dynamically according to device-state.
|
||||
*/
|
||||
|
||||
static struct drm_minor **drm_minor_get_slot(struct drm_device *dev,
|
||||
unsigned int type)
|
||||
{
|
||||
switch (type) {
|
||||
case DRM_MINOR_LEGACY:
|
||||
return &dev->primary;
|
||||
case DRM_MINOR_RENDER:
|
||||
return &dev->render;
|
||||
case DRM_MINOR_CONTROL:
|
||||
return &dev->control;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int drm_minor_alloc(struct drm_device *dev, unsigned int type)
|
||||
{
|
||||
struct drm_minor *minor;
|
||||
|
||||
minor = kzalloc(sizeof(*minor), GFP_KERNEL);
|
||||
if (!minor)
|
||||
return -ENOMEM;
|
||||
|
||||
minor->type = type;
|
||||
minor->dev = dev;
|
||||
|
||||
*drm_minor_get_slot(dev, type) = minor;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_get_minor - Allocate and register new DRM minor
|
||||
* @dev: DRM device
|
||||
* @minor: Pointer to where new minor is stored
|
||||
* @type: Type of minor
|
||||
*
|
||||
* Allocate a new minor of the given type and register it. A pointer to the new
|
||||
* minor is returned in @minor.
|
||||
* Caller must hold the global DRM mutex.
|
||||
*
|
||||
* RETURNS:
|
||||
* 0 on success, negative error code on failure.
|
||||
*/
|
||||
static int drm_get_minor(struct drm_device *dev, struct drm_minor **minor,
|
||||
int type)
|
||||
static void drm_minor_free(struct drm_device *dev, unsigned int type)
|
||||
{
|
||||
struct drm_minor **slot;
|
||||
|
||||
slot = drm_minor_get_slot(dev, type);
|
||||
if (*slot) {
|
||||
kfree(*slot);
|
||||
*slot = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int drm_minor_register(struct drm_device *dev, unsigned int type)
|
||||
{
|
||||
struct drm_minor *new_minor;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
int minor_id;
|
||||
|
||||
DRM_DEBUG("\n");
|
||||
|
||||
minor_id = drm_minor_get_id(dev, type);
|
||||
new_minor = *drm_minor_get_slot(dev, type);
|
||||
if (!new_minor)
|
||||
return 0;
|
||||
|
||||
idr_preload(GFP_KERNEL);
|
||||
spin_lock_irqsave(&drm_minor_lock, flags);
|
||||
minor_id = idr_alloc(&drm_minors_idr,
|
||||
NULL,
|
||||
64 * type,
|
||||
64 * (type + 1),
|
||||
GFP_NOWAIT);
|
||||
spin_unlock_irqrestore(&drm_minor_lock, flags);
|
||||
idr_preload_end();
|
||||
|
||||
if (minor_id < 0)
|
||||
return minor_id;
|
||||
|
||||
new_minor = kzalloc(sizeof(struct drm_minor), GFP_KERNEL);
|
||||
if (!new_minor) {
|
||||
ret = -ENOMEM;
|
||||
goto err_idr;
|
||||
}
|
||||
|
||||
new_minor->type = type;
|
||||
new_minor->device = MKDEV(DRM_MAJOR, minor_id);
|
||||
new_minor->dev = dev;
|
||||
new_minor->index = minor_id;
|
||||
INIT_LIST_HEAD(&new_minor->master_list);
|
||||
|
||||
idr_replace(&drm_minors_idr, new_minor, minor_id);
|
||||
|
||||
#if defined(CONFIG_DEBUG_FS)
|
||||
ret = drm_debugfs_init(new_minor, minor_id, drm_debugfs_root);
|
||||
if (ret) {
|
||||
DRM_ERROR("DRM: Failed to initialize /sys/kernel/debug/dri.\n");
|
||||
goto err_mem;
|
||||
goto err_id;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = drm_sysfs_device_add(new_minor);
|
||||
if (ret) {
|
||||
printk(KERN_ERR
|
||||
"DRM: Error sysfs_device_add.\n");
|
||||
DRM_ERROR("DRM: Error sysfs_device_add.\n");
|
||||
goto err_debugfs;
|
||||
}
|
||||
*minor = new_minor;
|
||||
|
||||
/* replace NULL with @minor so lookups will succeed from now on */
|
||||
spin_lock_irqsave(&drm_minor_lock, flags);
|
||||
idr_replace(&drm_minors_idr, new_minor, new_minor->index);
|
||||
spin_unlock_irqrestore(&drm_minor_lock, flags);
|
||||
|
||||
DRM_DEBUG("new minor assigned %d\n", minor_id);
|
||||
return 0;
|
||||
|
||||
|
||||
err_debugfs:
|
||||
#if defined(CONFIG_DEBUG_FS)
|
||||
drm_debugfs_cleanup(new_minor);
|
||||
err_mem:
|
||||
#endif
|
||||
kfree(new_minor);
|
||||
err_idr:
|
||||
err_id:
|
||||
spin_lock_irqsave(&drm_minor_lock, flags);
|
||||
idr_remove(&drm_minors_idr, minor_id);
|
||||
*minor = NULL;
|
||||
spin_unlock_irqrestore(&drm_minor_lock, flags);
|
||||
new_minor->index = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_unplug_minor - Unplug DRM minor
|
||||
* @minor: Minor to unplug
|
||||
*
|
||||
* Unplugs the given DRM minor but keeps the object. So after this returns,
|
||||
* minor->dev is still valid so existing open-files can still access it to get
|
||||
* device information from their drm_file ojects.
|
||||
* If the minor is already unplugged or if @minor is NULL, nothing is done.
|
||||
* The global DRM mutex must be held by the caller.
|
||||
*/
|
||||
static void drm_unplug_minor(struct drm_minor *minor)
|
||||
static void drm_minor_unregister(struct drm_device *dev, unsigned int type)
|
||||
{
|
||||
struct drm_minor *minor;
|
||||
unsigned long flags;
|
||||
|
||||
minor = *drm_minor_get_slot(dev, type);
|
||||
if (!minor || !minor->kdev)
|
||||
return;
|
||||
|
||||
#if defined(CONFIG_DEBUG_FS)
|
||||
drm_debugfs_cleanup(minor);
|
||||
#endif
|
||||
|
||||
drm_sysfs_device_remove(minor);
|
||||
spin_lock_irqsave(&drm_minor_lock, flags);
|
||||
idr_remove(&drm_minors_idr, minor->index);
|
||||
spin_unlock_irqrestore(&drm_minor_lock, flags);
|
||||
minor->index = 0;
|
||||
|
||||
drm_debugfs_cleanup(minor);
|
||||
drm_sysfs_device_remove(minor);
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_put_minor - Destroy DRM minor
|
||||
* @minor: Minor to destroy
|
||||
* drm_minor_acquire - Acquire a DRM minor
|
||||
* @minor_id: Minor ID of the DRM-minor
|
||||
*
|
||||
* This calls drm_unplug_minor() on the given minor and then frees it. Nothing
|
||||
* is done if @minor is NULL. It is fine to call this on already unplugged
|
||||
* minors.
|
||||
* The global DRM mutex must be held by the caller.
|
||||
* Looks up the given minor-ID and returns the respective DRM-minor object. The
|
||||
* refence-count of the underlying device is increased so you must release this
|
||||
* object with drm_minor_release().
|
||||
*
|
||||
* As long as you hold this minor, it is guaranteed that the object and the
|
||||
* minor->dev pointer will stay valid! However, the device may get unplugged and
|
||||
* unregistered while you hold the minor.
|
||||
*
|
||||
* Returns:
|
||||
* Pointer to minor-object with increased device-refcount, or PTR_ERR on
|
||||
* failure.
|
||||
*/
|
||||
static void drm_put_minor(struct drm_minor *minor)
|
||||
struct drm_minor *drm_minor_acquire(unsigned int minor_id)
|
||||
{
|
||||
if (!minor)
|
||||
return;
|
||||
struct drm_minor *minor;
|
||||
unsigned long flags;
|
||||
|
||||
DRM_DEBUG("release secondary minor %d\n", minor->index);
|
||||
spin_lock_irqsave(&drm_minor_lock, flags);
|
||||
minor = idr_find(&drm_minors_idr, minor_id);
|
||||
if (minor)
|
||||
drm_dev_ref(minor->dev);
|
||||
spin_unlock_irqrestore(&drm_minor_lock, flags);
|
||||
|
||||
drm_unplug_minor(minor);
|
||||
kfree(minor);
|
||||
if (!minor) {
|
||||
return ERR_PTR(-ENODEV);
|
||||
} else if (drm_device_is_unplugged(minor->dev)) {
|
||||
drm_dev_unref(minor->dev);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
return minor;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_minor_release - Release DRM minor
|
||||
* @minor: Pointer to DRM minor object
|
||||
*
|
||||
* Release a minor that was previously acquired via drm_minor_acquire().
|
||||
*/
|
||||
void drm_minor_release(struct drm_minor *minor)
|
||||
{
|
||||
drm_dev_unref(minor->dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -392,18 +440,16 @@ void drm_put_dev(struct drm_device *dev)
|
||||
}
|
||||
|
||||
drm_dev_unregister(dev);
|
||||
drm_dev_free(dev);
|
||||
drm_dev_unref(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_put_dev);
|
||||
|
||||
void drm_unplug_dev(struct drm_device *dev)
|
||||
{
|
||||
/* for a USB device */
|
||||
if (drm_core_check_feature(dev, DRIVER_MODESET))
|
||||
drm_unplug_minor(dev->control);
|
||||
if (dev->render)
|
||||
drm_unplug_minor(dev->render);
|
||||
drm_unplug_minor(dev->primary);
|
||||
drm_minor_unregister(dev, DRM_MINOR_LEGACY);
|
||||
drm_minor_unregister(dev, DRM_MINOR_RENDER);
|
||||
drm_minor_unregister(dev, DRM_MINOR_CONTROL);
|
||||
|
||||
mutex_lock(&drm_global_mutex);
|
||||
|
||||
@ -416,6 +462,78 @@ void drm_unplug_dev(struct drm_device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_unplug_dev);
|
||||
|
||||
/*
|
||||
* DRM internal mount
|
||||
* We want to be able to allocate our own "struct address_space" to control
|
||||
* memory-mappings in VRAM (or stolen RAM, ...). However, core MM does not allow
|
||||
* stand-alone address_space objects, so we need an underlying inode. As there
|
||||
* is no way to allocate an independent inode easily, we need a fake internal
|
||||
* VFS mount-point.
|
||||
*
|
||||
* The drm_fs_inode_new() function allocates a new inode, drm_fs_inode_free()
|
||||
* frees it again. You are allowed to use iget() and iput() to get references to
|
||||
* the inode. But each drm_fs_inode_new() call must be paired with exactly one
|
||||
* drm_fs_inode_free() call (which does not have to be the last iput()).
|
||||
* We use drm_fs_inode_*() to manage our internal VFS mount-point and share it
|
||||
* between multiple inode-users. You could, technically, call
|
||||
* iget() + drm_fs_inode_free() directly after alloc and sometime later do an
|
||||
* iput(), but this way you'd end up with a new vfsmount for each inode.
|
||||
*/
|
||||
|
||||
static int drm_fs_cnt;
|
||||
static struct vfsmount *drm_fs_mnt;
|
||||
|
||||
static const struct dentry_operations drm_fs_dops = {
|
||||
.d_dname = simple_dname,
|
||||
};
|
||||
|
||||
static const struct super_operations drm_fs_sops = {
|
||||
.statfs = simple_statfs,
|
||||
};
|
||||
|
||||
static struct dentry *drm_fs_mount(struct file_system_type *fs_type, int flags,
|
||||
const char *dev_name, void *data)
|
||||
{
|
||||
return mount_pseudo(fs_type,
|
||||
"drm:",
|
||||
&drm_fs_sops,
|
||||
&drm_fs_dops,
|
||||
0x010203ff);
|
||||
}
|
||||
|
||||
static struct file_system_type drm_fs_type = {
|
||||
.name = "drm",
|
||||
.owner = THIS_MODULE,
|
||||
.mount = drm_fs_mount,
|
||||
.kill_sb = kill_anon_super,
|
||||
};
|
||||
|
||||
static struct inode *drm_fs_inode_new(void)
|
||||
{
|
||||
struct inode *inode;
|
||||
int r;
|
||||
|
||||
r = simple_pin_fs(&drm_fs_type, &drm_fs_mnt, &drm_fs_cnt);
|
||||
if (r < 0) {
|
||||
DRM_ERROR("Cannot mount pseudo fs: %d\n", r);
|
||||
return ERR_PTR(r);
|
||||
}
|
||||
|
||||
inode = alloc_anon_inode(drm_fs_mnt->mnt_sb);
|
||||
if (IS_ERR(inode))
|
||||
simple_release_fs(&drm_fs_mnt, &drm_fs_cnt);
|
||||
|
||||
return inode;
|
||||
}
|
||||
|
||||
static void drm_fs_inode_free(struct inode *inode)
|
||||
{
|
||||
if (inode) {
|
||||
iput(inode);
|
||||
simple_release_fs(&drm_fs_mnt, &drm_fs_cnt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_dev_alloc - Allocate new drm device
|
||||
* @driver: DRM driver to allocate device for
|
||||
@ -425,6 +543,9 @@ EXPORT_SYMBOL(drm_unplug_dev);
|
||||
* Call drm_dev_register() to advertice the device to user space and register it
|
||||
* with other core subsystems.
|
||||
*
|
||||
* The initial ref-count of the object is 1. Use drm_dev_ref() and
|
||||
* drm_dev_unref() to take and drop further ref-counts.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to new DRM device, or NULL if out of memory.
|
||||
*/
|
||||
@ -438,6 +559,7 @@ struct drm_device *drm_dev_alloc(struct drm_driver *driver,
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
kref_init(&dev->ref);
|
||||
dev->dev = parent;
|
||||
dev->driver = driver;
|
||||
|
||||
@ -451,9 +573,33 @@ struct drm_device *drm_dev_alloc(struct drm_driver *driver,
|
||||
spin_lock_init(&dev->event_lock);
|
||||
mutex_init(&dev->struct_mutex);
|
||||
mutex_init(&dev->ctxlist_mutex);
|
||||
mutex_init(&dev->master_mutex);
|
||||
|
||||
dev->anon_inode = drm_fs_inode_new();
|
||||
if (IS_ERR(dev->anon_inode)) {
|
||||
ret = PTR_ERR(dev->anon_inode);
|
||||
DRM_ERROR("Cannot allocate anonymous inode: %d\n", ret);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
if (drm_core_check_feature(dev, DRIVER_MODESET)) {
|
||||
ret = drm_minor_alloc(dev, DRM_MINOR_CONTROL);
|
||||
if (ret)
|
||||
goto err_minors;
|
||||
}
|
||||
|
||||
if (drm_core_check_feature(dev, DRIVER_RENDER) && drm_rnodes) {
|
||||
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);
|
||||
if (ret)
|
||||
goto err_minors;
|
||||
}
|
||||
|
||||
ret = drm_minor_alloc(dev, DRM_MINOR_LEGACY);
|
||||
if (ret)
|
||||
goto err_minors;
|
||||
|
||||
if (drm_ht_create(&dev->map_hash, 12))
|
||||
goto err_free;
|
||||
goto err_minors;
|
||||
|
||||
ret = drm_ctxbitmap_init(dev);
|
||||
if (ret) {
|
||||
@ -475,38 +621,71 @@ err_ctxbitmap:
|
||||
drm_ctxbitmap_cleanup(dev);
|
||||
err_ht:
|
||||
drm_ht_remove(&dev->map_hash);
|
||||
err_minors:
|
||||
drm_minor_free(dev, DRM_MINOR_LEGACY);
|
||||
drm_minor_free(dev, DRM_MINOR_RENDER);
|
||||
drm_minor_free(dev, DRM_MINOR_CONTROL);
|
||||
drm_fs_inode_free(dev->anon_inode);
|
||||
err_free:
|
||||
mutex_destroy(&dev->master_mutex);
|
||||
kfree(dev);
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dev_alloc);
|
||||
|
||||
/**
|
||||
* drm_dev_free - Free DRM device
|
||||
* @dev: DRM device to free
|
||||
*
|
||||
* Free a DRM device that has previously been allocated via drm_dev_alloc().
|
||||
* You must not use kfree() instead or you will leak memory.
|
||||
*
|
||||
* This must not be called once the device got registered. Use drm_put_dev()
|
||||
* instead, which then calls drm_dev_free().
|
||||
*/
|
||||
void drm_dev_free(struct drm_device *dev)
|
||||
static void drm_dev_release(struct kref *ref)
|
||||
{
|
||||
drm_put_minor(dev->control);
|
||||
drm_put_minor(dev->render);
|
||||
drm_put_minor(dev->primary);
|
||||
struct drm_device *dev = container_of(ref, struct drm_device, ref);
|
||||
|
||||
if (dev->driver->driver_features & DRIVER_GEM)
|
||||
drm_gem_destroy(dev);
|
||||
|
||||
drm_ctxbitmap_cleanup(dev);
|
||||
drm_ht_remove(&dev->map_hash);
|
||||
drm_fs_inode_free(dev->anon_inode);
|
||||
|
||||
drm_minor_free(dev, DRM_MINOR_LEGACY);
|
||||
drm_minor_free(dev, DRM_MINOR_RENDER);
|
||||
drm_minor_free(dev, DRM_MINOR_CONTROL);
|
||||
|
||||
kfree(dev->devname);
|
||||
|
||||
mutex_destroy(&dev->master_mutex);
|
||||
kfree(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dev_free);
|
||||
|
||||
/**
|
||||
* drm_dev_ref - Take reference of a DRM device
|
||||
* @dev: device to take reference of or NULL
|
||||
*
|
||||
* This increases the ref-count of @dev by one. You *must* already own a
|
||||
* reference when calling this. Use drm_dev_unref() to drop this reference
|
||||
* again.
|
||||
*
|
||||
* This function never fails. However, this function does not provide *any*
|
||||
* guarantee whether the device is alive or running. It only provides a
|
||||
* reference to the object and the memory associated with it.
|
||||
*/
|
||||
void drm_dev_ref(struct drm_device *dev)
|
||||
{
|
||||
if (dev)
|
||||
kref_get(&dev->ref);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dev_ref);
|
||||
|
||||
/**
|
||||
* drm_dev_unref - Drop reference of a DRM device
|
||||
* @dev: device to drop reference of or NULL
|
||||
*
|
||||
* This decreases the ref-count of @dev by one. The device is destroyed if the
|
||||
* ref-count drops to zero.
|
||||
*/
|
||||
void drm_dev_unref(struct drm_device *dev)
|
||||
{
|
||||
if (dev)
|
||||
kref_put(&dev->ref, drm_dev_release);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dev_unref);
|
||||
|
||||
/**
|
||||
* drm_dev_register - Register DRM device
|
||||
@ -527,26 +706,22 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
|
||||
|
||||
mutex_lock(&drm_global_mutex);
|
||||
|
||||
if (drm_core_check_feature(dev, DRIVER_MODESET)) {
|
||||
ret = drm_get_minor(dev, &dev->control, DRM_MINOR_CONTROL);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (drm_core_check_feature(dev, DRIVER_RENDER) && drm_rnodes) {
|
||||
ret = drm_get_minor(dev, &dev->render, DRM_MINOR_RENDER);
|
||||
if (ret)
|
||||
goto err_control_node;
|
||||
}
|
||||
|
||||
ret = drm_get_minor(dev, &dev->primary, DRM_MINOR_LEGACY);
|
||||
ret = drm_minor_register(dev, DRM_MINOR_CONTROL);
|
||||
if (ret)
|
||||
goto err_render_node;
|
||||
goto err_minors;
|
||||
|
||||
ret = drm_minor_register(dev, DRM_MINOR_RENDER);
|
||||
if (ret)
|
||||
goto err_minors;
|
||||
|
||||
ret = drm_minor_register(dev, DRM_MINOR_LEGACY);
|
||||
if (ret)
|
||||
goto err_minors;
|
||||
|
||||
if (dev->driver->load) {
|
||||
ret = dev->driver->load(dev, flags);
|
||||
if (ret)
|
||||
goto err_primary_node;
|
||||
goto err_minors;
|
||||
}
|
||||
|
||||
/* setup grouping for legacy outputs */
|
||||
@ -563,12 +738,10 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
|
||||
err_unload:
|
||||
if (dev->driver->unload)
|
||||
dev->driver->unload(dev);
|
||||
err_primary_node:
|
||||
drm_unplug_minor(dev->primary);
|
||||
err_render_node:
|
||||
drm_unplug_minor(dev->render);
|
||||
err_control_node:
|
||||
drm_unplug_minor(dev->control);
|
||||
err_minors:
|
||||
drm_minor_unregister(dev, DRM_MINOR_LEGACY);
|
||||
drm_minor_unregister(dev, DRM_MINOR_RENDER);
|
||||
drm_minor_unregister(dev, DRM_MINOR_CONTROL);
|
||||
out_unlock:
|
||||
mutex_unlock(&drm_global_mutex);
|
||||
return ret;
|
||||
@ -581,7 +754,7 @@ EXPORT_SYMBOL(drm_dev_register);
|
||||
*
|
||||
* Unregister the DRM device from the system. This does the reverse of
|
||||
* drm_dev_register() but does not deallocate the device. The caller must call
|
||||
* drm_dev_free() to free all resources.
|
||||
* drm_dev_unref() to drop their final reference.
|
||||
*/
|
||||
void drm_dev_unregister(struct drm_device *dev)
|
||||
{
|
||||
@ -600,8 +773,8 @@ void drm_dev_unregister(struct drm_device *dev)
|
||||
list_for_each_entry_safe(r_list, list_temp, &dev->maplist, head)
|
||||
drm_rmmap(dev, r_list->map);
|
||||
|
||||
drm_unplug_minor(dev->control);
|
||||
drm_unplug_minor(dev->render);
|
||||
drm_unplug_minor(dev->primary);
|
||||
drm_minor_unregister(dev, DRM_MINOR_LEGACY);
|
||||
drm_minor_unregister(dev, DRM_MINOR_RENDER);
|
||||
drm_minor_unregister(dev, DRM_MINOR_CONTROL);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dev_unregister);
|
||||
|
@ -30,7 +30,7 @@ int drm_get_usb_dev(struct usb_interface *interface,
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
drm_dev_free(dev);
|
||||
drm_dev_unref(dev);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,30 @@ config DRM_EXYNOS_FIMD
|
||||
help
|
||||
Choose this option if you want to use Exynos FIMD for DRM.
|
||||
|
||||
config DRM_EXYNOS_DPI
|
||||
bool "EXYNOS DRM parallel output support"
|
||||
depends on DRM_EXYNOS
|
||||
select DRM_PANEL
|
||||
default n
|
||||
help
|
||||
This enables support for Exynos parallel output.
|
||||
|
||||
config DRM_EXYNOS_DSI
|
||||
bool "EXYNOS DRM MIPI-DSI driver support"
|
||||
depends on DRM_EXYNOS
|
||||
select DRM_MIPI_DSI
|
||||
select DRM_PANEL
|
||||
default n
|
||||
help
|
||||
This enables support for Exynos MIPI-DSI device.
|
||||
|
||||
config DRM_EXYNOS_DP
|
||||
bool "EXYNOS DRM DP driver support"
|
||||
depends on DRM_EXYNOS && ARCH_EXYNOS
|
||||
default DRM_EXYNOS
|
||||
help
|
||||
This enables support for DP device.
|
||||
|
||||
config DRM_EXYNOS_HDMI
|
||||
bool "Exynos DRM HDMI"
|
||||
depends on DRM_EXYNOS && !VIDEO_SAMSUNG_S5P_TV
|
||||
|
@ -3,7 +3,7 @@
|
||||
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
|
||||
|
||||
ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/exynos
|
||||
exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o exynos_drm_connector.o \
|
||||
exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o \
|
||||
exynos_drm_crtc.o exynos_drm_fbdev.o exynos_drm_fb.o \
|
||||
exynos_drm_buf.o exynos_drm_gem.o exynos_drm_core.o \
|
||||
exynos_drm_plane.o
|
||||
@ -11,9 +11,10 @@ exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o exynos_drm_connector.o \
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_IOMMU) += exynos_drm_iommu.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_DMABUF) += exynos_drm_dmabuf.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o \
|
||||
exynos_ddc.o exynos_hdmiphy.o \
|
||||
exynos_drm_hdmi.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp_core.o exynos_dp_reg.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI) += exynos_drm_vidi.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_G2D) += exynos_drm_g2d.o
|
||||
exynosdrm-$(CONFIG_DRM_EXYNOS_IPP) += exynos_drm_ipp.o
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
@ -20,9 +19,25 @@
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <video/of_display_timing.h>
|
||||
#include <video/of_videomode.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/bridge/ptn3460.h>
|
||||
|
||||
#include "exynos_drm_drv.h"
|
||||
#include "exynos_dp_core.h"
|
||||
|
||||
#define ctx_from_connector(c) container_of(c, struct exynos_dp_device, \
|
||||
connector)
|
||||
|
||||
struct bridge_init {
|
||||
struct i2c_client *client;
|
||||
struct device_node *node;
|
||||
};
|
||||
|
||||
static int exynos_dp_init_dp(struct exynos_dp_device *dp)
|
||||
{
|
||||
exynos_dp_reset(dp);
|
||||
@ -893,6 +908,214 @@ static void exynos_dp_hotplug(struct work_struct *work)
|
||||
dev_err(dp->dev, "unable to config video\n");
|
||||
}
|
||||
|
||||
static enum drm_connector_status exynos_dp_detect(
|
||||
struct drm_connector *connector, bool force)
|
||||
{
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
static void exynos_dp_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
}
|
||||
|
||||
static struct drm_connector_funcs exynos_dp_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.detect = exynos_dp_detect,
|
||||
.destroy = exynos_dp_connector_destroy,
|
||||
};
|
||||
|
||||
static int exynos_dp_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct exynos_dp_device *dp = ctx_from_connector(connector);
|
||||
struct drm_display_mode *mode;
|
||||
|
||||
mode = drm_mode_create(connector->dev);
|
||||
if (!mode) {
|
||||
DRM_ERROR("failed to create a new display mode.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
drm_display_mode_from_videomode(&dp->panel.vm, mode);
|
||||
mode->width_mm = dp->panel.width_mm;
|
||||
mode->height_mm = dp->panel.height_mm;
|
||||
connector->display_info.width_mm = mode->width_mm;
|
||||
connector->display_info.height_mm = mode->height_mm;
|
||||
|
||||
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
|
||||
drm_mode_set_name(mode);
|
||||
drm_mode_probed_add(connector, mode);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int exynos_dp_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static struct drm_encoder *exynos_dp_best_encoder(
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct exynos_dp_device *dp = ctx_from_connector(connector);
|
||||
|
||||
return dp->encoder;
|
||||
}
|
||||
|
||||
static struct drm_connector_helper_funcs exynos_dp_connector_helper_funcs = {
|
||||
.get_modes = exynos_dp_get_modes,
|
||||
.mode_valid = exynos_dp_mode_valid,
|
||||
.best_encoder = exynos_dp_best_encoder,
|
||||
};
|
||||
|
||||
static int exynos_dp_initialize(struct exynos_drm_display *display,
|
||||
struct drm_device *drm_dev)
|
||||
{
|
||||
struct exynos_dp_device *dp = display->ctx;
|
||||
|
||||
dp->drm_dev = drm_dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool find_bridge(const char *compat, struct bridge_init *bridge)
|
||||
{
|
||||
bridge->client = NULL;
|
||||
bridge->node = of_find_compatible_node(NULL, NULL, compat);
|
||||
if (!bridge->node)
|
||||
return false;
|
||||
|
||||
bridge->client = of_find_i2c_device_by_node(bridge->node);
|
||||
if (!bridge->client)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* returns the number of bridges attached */
|
||||
static int exynos_drm_attach_lcd_bridge(struct drm_device *dev,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
struct bridge_init bridge;
|
||||
int ret;
|
||||
|
||||
if (find_bridge("nxp,ptn3460", &bridge)) {
|
||||
ret = ptn3460_init(dev, encoder, bridge.client, bridge.node);
|
||||
if (!ret)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_dp_create_connector(struct exynos_drm_display *display,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
struct exynos_dp_device *dp = display->ctx;
|
||||
struct drm_connector *connector = &dp->connector;
|
||||
int ret;
|
||||
|
||||
dp->encoder = encoder;
|
||||
|
||||
/* Pre-empt DP connector creation if there's a bridge */
|
||||
ret = exynos_drm_attach_lcd_bridge(dp->drm_dev, encoder);
|
||||
if (ret)
|
||||
return 0;
|
||||
|
||||
connector->polled = DRM_CONNECTOR_POLL_HPD;
|
||||
|
||||
ret = drm_connector_init(dp->drm_dev, connector,
|
||||
&exynos_dp_connector_funcs, DRM_MODE_CONNECTOR_eDP);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to initialize connector with drm\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(connector, &exynos_dp_connector_helper_funcs);
|
||||
drm_sysfs_connector_add(connector);
|
||||
drm_mode_connector_attach_encoder(connector, encoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exynos_dp_phy_init(struct exynos_dp_device *dp)
|
||||
{
|
||||
if (dp->phy) {
|
||||
phy_power_on(dp->phy);
|
||||
} else if (dp->phy_addr) {
|
||||
u32 reg;
|
||||
|
||||
reg = __raw_readl(dp->phy_addr);
|
||||
reg |= dp->enable_mask;
|
||||
__raw_writel(reg, dp->phy_addr);
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos_dp_phy_exit(struct exynos_dp_device *dp)
|
||||
{
|
||||
if (dp->phy) {
|
||||
phy_power_off(dp->phy);
|
||||
} else if (dp->phy_addr) {
|
||||
u32 reg;
|
||||
|
||||
reg = __raw_readl(dp->phy_addr);
|
||||
reg &= ~(dp->enable_mask);
|
||||
__raw_writel(reg, dp->phy_addr);
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos_dp_poweron(struct exynos_dp_device *dp)
|
||||
{
|
||||
if (dp->dpms_mode == DRM_MODE_DPMS_ON)
|
||||
return;
|
||||
|
||||
clk_prepare_enable(dp->clock);
|
||||
exynos_dp_phy_init(dp);
|
||||
exynos_dp_init_dp(dp);
|
||||
enable_irq(dp->irq);
|
||||
}
|
||||
|
||||
static void exynos_dp_poweroff(struct exynos_dp_device *dp)
|
||||
{
|
||||
if (dp->dpms_mode != DRM_MODE_DPMS_ON)
|
||||
return;
|
||||
|
||||
disable_irq(dp->irq);
|
||||
flush_work(&dp->hotplug_work);
|
||||
exynos_dp_phy_exit(dp);
|
||||
clk_disable_unprepare(dp->clock);
|
||||
}
|
||||
|
||||
static void exynos_dp_dpms(struct exynos_drm_display *display, int mode)
|
||||
{
|
||||
struct exynos_dp_device *dp = display->ctx;
|
||||
|
||||
switch (mode) {
|
||||
case DRM_MODE_DPMS_ON:
|
||||
exynos_dp_poweron(dp);
|
||||
break;
|
||||
case DRM_MODE_DPMS_STANDBY:
|
||||
case DRM_MODE_DPMS_SUSPEND:
|
||||
case DRM_MODE_DPMS_OFF:
|
||||
exynos_dp_poweroff(dp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
dp->dpms_mode = mode;
|
||||
}
|
||||
|
||||
static struct exynos_drm_display_ops exynos_dp_display_ops = {
|
||||
.initialize = exynos_dp_initialize,
|
||||
.create_connector = exynos_dp_create_connector,
|
||||
.dpms = exynos_dp_dpms,
|
||||
};
|
||||
|
||||
static struct exynos_drm_display exynos_dp_display = {
|
||||
.type = EXYNOS_DISPLAY_TYPE_LCD,
|
||||
.ops = &exynos_dp_display_ops,
|
||||
};
|
||||
|
||||
static struct video_info *exynos_dp_dt_parse_pdata(struct device *dev)
|
||||
{
|
||||
struct device_node *dp_node = dev->of_node;
|
||||
@ -994,30 +1217,17 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void exynos_dp_phy_init(struct exynos_dp_device *dp)
|
||||
static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp)
|
||||
{
|
||||
if (dp->phy) {
|
||||
phy_power_on(dp->phy);
|
||||
} else if (dp->phy_addr) {
|
||||
u32 reg;
|
||||
int ret;
|
||||
|
||||
reg = __raw_readl(dp->phy_addr);
|
||||
reg |= dp->enable_mask;
|
||||
__raw_writel(reg, dp->phy_addr);
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos_dp_phy_exit(struct exynos_dp_device *dp)
|
||||
{
|
||||
if (dp->phy) {
|
||||
phy_power_off(dp->phy);
|
||||
} else if (dp->phy_addr) {
|
||||
u32 reg;
|
||||
|
||||
reg = __raw_readl(dp->phy_addr);
|
||||
reg &= ~(dp->enable_mask);
|
||||
__raw_writel(reg, dp->phy_addr);
|
||||
ret = of_get_videomode(dp->dev->of_node, &dp->panel.vm,
|
||||
OF_USE_NATIVE_MODE);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed: of_get_videomode() : %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_dp_probe(struct platform_device *pdev)
|
||||
@ -1035,6 +1245,7 @@ static int exynos_dp_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
dp->dev = &pdev->dev;
|
||||
dp->dpms_mode = DRM_MODE_DPMS_OFF;
|
||||
|
||||
dp->video_info = exynos_dp_dt_parse_pdata(&pdev->dev);
|
||||
if (IS_ERR(dp->video_info))
|
||||
@ -1044,6 +1255,10 @@ static int exynos_dp_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = exynos_dp_dt_parse_panel(dp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dp->clock = devm_clk_get(&pdev->dev, "dp");
|
||||
if (IS_ERR(dp->clock)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
@ -1076,22 +1291,22 @@ static int exynos_dp_probe(struct platform_device *pdev)
|
||||
dev_err(&pdev->dev, "failed to request irq\n");
|
||||
return ret;
|
||||
}
|
||||
disable_irq(dp->irq);
|
||||
|
||||
platform_set_drvdata(pdev, dp);
|
||||
exynos_dp_display.ctx = dp;
|
||||
|
||||
platform_set_drvdata(pdev, &exynos_dp_display);
|
||||
exynos_drm_display_register(&exynos_dp_display);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_dp_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_dp_device *dp = platform_get_drvdata(pdev);
|
||||
|
||||
flush_work(&dp->hotplug_work);
|
||||
|
||||
exynos_dp_phy_exit(dp);
|
||||
|
||||
clk_disable_unprepare(dp->clock);
|
||||
struct exynos_drm_display *display = platform_get_drvdata(pdev);
|
||||
|
||||
exynos_dp_dpms(display, DRM_MODE_DPMS_OFF);
|
||||
exynos_drm_display_unregister(&exynos_dp_display);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1099,31 +1314,19 @@ static int exynos_dp_remove(struct platform_device *pdev)
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int exynos_dp_suspend(struct device *dev)
|
||||
{
|
||||
struct exynos_dp_device *dp = dev_get_drvdata(dev);
|
||||
|
||||
disable_irq(dp->irq);
|
||||
|
||||
flush_work(&dp->hotplug_work);
|
||||
|
||||
exynos_dp_phy_exit(dp);
|
||||
|
||||
clk_disable_unprepare(dp->clock);
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct exynos_drm_display *display = platform_get_drvdata(pdev);
|
||||
|
||||
exynos_dp_dpms(display, DRM_MODE_DPMS_OFF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_dp_resume(struct device *dev)
|
||||
{
|
||||
struct exynos_dp_device *dp = dev_get_drvdata(dev);
|
||||
|
||||
exynos_dp_phy_init(dp);
|
||||
|
||||
clk_prepare_enable(dp->clock);
|
||||
|
||||
exynos_dp_init_dp(dp);
|
||||
|
||||
enable_irq(dp->irq);
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct exynos_drm_display *display = platform_get_drvdata(pdev);
|
||||
|
||||
exynos_dp_dpms(display, DRM_MODE_DPMS_ON);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@ -1136,9 +1339,8 @@ static const struct of_device_id exynos_dp_match[] = {
|
||||
{ .compatible = "samsung,exynos5-dp" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, exynos_dp_match);
|
||||
|
||||
static struct platform_driver exynos_dp_driver = {
|
||||
struct platform_driver dp_driver = {
|
||||
.probe = exynos_dp_probe,
|
||||
.remove = exynos_dp_remove,
|
||||
.driver = {
|
||||
@ -1149,8 +1351,6 @@ static struct platform_driver exynos_dp_driver = {
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(exynos_dp_driver);
|
||||
|
||||
MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
|
||||
MODULE_DESCRIPTION("Samsung SoC DP Driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -13,6 +13,9 @@
|
||||
#ifndef _EXYNOS_DP_CORE_H
|
||||
#define _EXYNOS_DP_CORE_H
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/exynos_drm.h>
|
||||
|
||||
#define DP_TIMEOUT_LOOP_COUNT 100
|
||||
#define MAX_CR_LOOP 5
|
||||
#define MAX_EQ_LOOP 5
|
||||
@ -142,6 +145,9 @@ struct link_train {
|
||||
|
||||
struct exynos_dp_device {
|
||||
struct device *dev;
|
||||
struct drm_device *drm_dev;
|
||||
struct drm_connector connector;
|
||||
struct drm_encoder *encoder;
|
||||
struct clk *clock;
|
||||
unsigned int irq;
|
||||
void __iomem *reg_base;
|
||||
@ -152,6 +158,9 @@ struct exynos_dp_device {
|
||||
struct link_train link_train;
|
||||
struct work_struct hotplug_work;
|
||||
struct phy *phy;
|
||||
int dpms_mode;
|
||||
|
||||
struct exynos_drm_panel_info panel;
|
||||
};
|
||||
|
||||
/* exynos_dp_reg.c */
|
@ -23,27 +23,20 @@
|
||||
drm_connector)
|
||||
|
||||
struct exynos_drm_connector {
|
||||
struct drm_connector drm_connector;
|
||||
uint32_t encoder_id;
|
||||
struct exynos_drm_manager *manager;
|
||||
uint32_t dpms;
|
||||
struct drm_connector drm_connector;
|
||||
uint32_t encoder_id;
|
||||
struct exynos_drm_display *display;
|
||||
};
|
||||
|
||||
static int exynos_drm_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct exynos_drm_connector *exynos_connector =
|
||||
to_exynos_connector(connector);
|
||||
struct exynos_drm_manager *manager = exynos_connector->manager;
|
||||
struct exynos_drm_display_ops *display_ops = manager->display_ops;
|
||||
struct exynos_drm_display *display = exynos_connector->display;
|
||||
struct edid *edid = NULL;
|
||||
unsigned int count = 0;
|
||||
int ret;
|
||||
|
||||
if (!display_ops) {
|
||||
DRM_DEBUG_KMS("display_ops is null.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* if get_edid() exists then get_edid() callback of hdmi side
|
||||
* is called to get edid data through i2c interface else
|
||||
@ -52,8 +45,8 @@ static int exynos_drm_connector_get_modes(struct drm_connector *connector)
|
||||
* P.S. in case of lcd panel, count is always 1 if success
|
||||
* because lcd panel has only one mode.
|
||||
*/
|
||||
if (display_ops->get_edid) {
|
||||
edid = display_ops->get_edid(manager->dev, connector);
|
||||
if (display->ops->get_edid) {
|
||||
edid = display->ops->get_edid(display, connector);
|
||||
if (IS_ERR_OR_NULL(edid)) {
|
||||
ret = PTR_ERR(edid);
|
||||
edid = NULL;
|
||||
@ -76,8 +69,8 @@ static int exynos_drm_connector_get_modes(struct drm_connector *connector)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (display_ops->get_panel)
|
||||
panel = display_ops->get_panel(manager->dev);
|
||||
if (display->ops->get_panel)
|
||||
panel = display->ops->get_panel(display);
|
||||
else {
|
||||
drm_mode_destroy(connector->dev, mode);
|
||||
return 0;
|
||||
@ -106,20 +99,20 @@ static int exynos_drm_connector_mode_valid(struct drm_connector *connector,
|
||||
{
|
||||
struct exynos_drm_connector *exynos_connector =
|
||||
to_exynos_connector(connector);
|
||||
struct exynos_drm_manager *manager = exynos_connector->manager;
|
||||
struct exynos_drm_display_ops *display_ops = manager->display_ops;
|
||||
struct exynos_drm_display *display = exynos_connector->display;
|
||||
int ret = MODE_BAD;
|
||||
|
||||
DRM_DEBUG_KMS("%s\n", __FILE__);
|
||||
|
||||
if (display_ops && display_ops->check_mode)
|
||||
if (!display_ops->check_mode(manager->dev, mode))
|
||||
if (display->ops->check_mode)
|
||||
if (!display->ops->check_mode(display, mode))
|
||||
ret = MODE_OK;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct drm_encoder *exynos_drm_best_encoder(struct drm_connector *connector)
|
||||
static struct drm_encoder *exynos_drm_best_encoder(
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct drm_device *dev = connector->dev;
|
||||
struct exynos_drm_connector *exynos_connector =
|
||||
@ -146,48 +139,12 @@ static struct drm_connector_helper_funcs exynos_connector_helper_funcs = {
|
||||
.best_encoder = exynos_drm_best_encoder,
|
||||
};
|
||||
|
||||
void exynos_drm_display_power(struct drm_connector *connector, int mode)
|
||||
{
|
||||
struct drm_encoder *encoder = exynos_drm_best_encoder(connector);
|
||||
struct exynos_drm_connector *exynos_connector;
|
||||
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder);
|
||||
struct exynos_drm_display_ops *display_ops = manager->display_ops;
|
||||
|
||||
exynos_connector = to_exynos_connector(connector);
|
||||
|
||||
if (exynos_connector->dpms == mode) {
|
||||
DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (display_ops && display_ops->power_on)
|
||||
display_ops->power_on(manager->dev, mode);
|
||||
|
||||
exynos_connector->dpms = mode;
|
||||
}
|
||||
|
||||
static void exynos_drm_connector_dpms(struct drm_connector *connector,
|
||||
int mode)
|
||||
{
|
||||
/*
|
||||
* in case that drm_crtc_helper_set_mode() is called,
|
||||
* encoder/crtc->funcs->dpms() will be just returned
|
||||
* because they already were DRM_MODE_DPMS_ON so only
|
||||
* exynos_drm_display_power() will be called.
|
||||
*/
|
||||
drm_helper_connector_dpms(connector, mode);
|
||||
|
||||
exynos_drm_display_power(connector, mode);
|
||||
|
||||
}
|
||||
|
||||
static int exynos_drm_connector_fill_modes(struct drm_connector *connector,
|
||||
unsigned int max_width, unsigned int max_height)
|
||||
{
|
||||
struct exynos_drm_connector *exynos_connector =
|
||||
to_exynos_connector(connector);
|
||||
struct exynos_drm_manager *manager = exynos_connector->manager;
|
||||
struct exynos_drm_manager_ops *ops = manager->ops;
|
||||
struct exynos_drm_display *display = exynos_connector->display;
|
||||
unsigned int width, height;
|
||||
|
||||
width = max_width;
|
||||
@ -197,8 +154,8 @@ static int exynos_drm_connector_fill_modes(struct drm_connector *connector,
|
||||
* if specific driver want to find desired_mode using maxmum
|
||||
* resolution then get max width and height from that driver.
|
||||
*/
|
||||
if (ops && ops->get_max_resol)
|
||||
ops->get_max_resol(manager->dev, &width, &height);
|
||||
if (display->ops->get_max_resol)
|
||||
display->ops->get_max_resol(display, &width, &height);
|
||||
|
||||
return drm_helper_probe_single_connector_modes(connector, width,
|
||||
height);
|
||||
@ -210,13 +167,11 @@ exynos_drm_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct exynos_drm_connector *exynos_connector =
|
||||
to_exynos_connector(connector);
|
||||
struct exynos_drm_manager *manager = exynos_connector->manager;
|
||||
struct exynos_drm_display_ops *display_ops =
|
||||
manager->display_ops;
|
||||
struct exynos_drm_display *display = exynos_connector->display;
|
||||
enum drm_connector_status status = connector_status_disconnected;
|
||||
|
||||
if (display_ops && display_ops->is_connected) {
|
||||
if (display_ops->is_connected(manager->dev))
|
||||
if (display->ops->is_connected) {
|
||||
if (display->ops->is_connected(display))
|
||||
status = connector_status_connected;
|
||||
else
|
||||
status = connector_status_disconnected;
|
||||
@ -236,7 +191,7 @@ static void exynos_drm_connector_destroy(struct drm_connector *connector)
|
||||
}
|
||||
|
||||
static struct drm_connector_funcs exynos_connector_funcs = {
|
||||
.dpms = exynos_drm_connector_dpms,
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.fill_modes = exynos_drm_connector_fill_modes,
|
||||
.detect = exynos_drm_connector_detect,
|
||||
.destroy = exynos_drm_connector_destroy,
|
||||
@ -246,7 +201,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
struct exynos_drm_connector *exynos_connector;
|
||||
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder);
|
||||
struct exynos_drm_display *display = exynos_drm_get_display(encoder);
|
||||
struct drm_connector *connector;
|
||||
int type;
|
||||
int err;
|
||||
@ -257,7 +212,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev,
|
||||
|
||||
connector = &exynos_connector->drm_connector;
|
||||
|
||||
switch (manager->display_ops->type) {
|
||||
switch (display->type) {
|
||||
case EXYNOS_DISPLAY_TYPE_HDMI:
|
||||
type = DRM_MODE_CONNECTOR_HDMIA;
|
||||
connector->interlace_allowed = true;
|
||||
@ -280,8 +235,7 @@ struct drm_connector *exynos_drm_connector_create(struct drm_device *dev,
|
||||
goto err_connector;
|
||||
|
||||
exynos_connector->encoder_id = encoder->base.id;
|
||||
exynos_connector->manager = manager;
|
||||
exynos_connector->dpms = DRM_MODE_DPMS_OFF;
|
||||
exynos_connector->display = display;
|
||||
connector->dpms = DRM_MODE_DPMS_OFF;
|
||||
connector->encoder = encoder;
|
||||
|
||||
|
@ -17,8 +17,4 @@
|
||||
struct drm_connector *exynos_drm_connector_create(struct drm_device *dev,
|
||||
struct drm_encoder *encoder);
|
||||
|
||||
struct drm_encoder *exynos_drm_best_encoder(struct drm_connector *connector);
|
||||
|
||||
void exynos_drm_display_power(struct drm_connector *connector, int mode);
|
||||
|
||||
#endif
|
||||
|
@ -14,43 +14,42 @@
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include "exynos_drm_drv.h"
|
||||
#include "exynos_drm_crtc.h"
|
||||
#include "exynos_drm_encoder.h"
|
||||
#include "exynos_drm_connector.h"
|
||||
#include "exynos_drm_fbdev.h"
|
||||
|
||||
static LIST_HEAD(exynos_drm_subdrv_list);
|
||||
static LIST_HEAD(exynos_drm_manager_list);
|
||||
static LIST_HEAD(exynos_drm_display_list);
|
||||
|
||||
static int exynos_drm_create_enc_conn(struct drm_device *dev,
|
||||
struct exynos_drm_subdrv *subdrv)
|
||||
struct exynos_drm_display *display)
|
||||
{
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector *connector;
|
||||
struct exynos_drm_manager *manager;
|
||||
int ret;
|
||||
unsigned long possible_crtcs = 0;
|
||||
|
||||
subdrv->manager->dev = subdrv->dev;
|
||||
/* Find possible crtcs for this display */
|
||||
list_for_each_entry(manager, &exynos_drm_manager_list, list)
|
||||
if (manager->type == display->type)
|
||||
possible_crtcs |= 1 << manager->pipe;
|
||||
|
||||
/* create and initialize a encoder for this sub driver. */
|
||||
encoder = exynos_drm_encoder_create(dev, subdrv->manager,
|
||||
(1 << MAX_CRTC) - 1);
|
||||
encoder = exynos_drm_encoder_create(dev, display, possible_crtcs);
|
||||
if (!encoder) {
|
||||
DRM_ERROR("failed to create encoder\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/*
|
||||
* create and initialize a connector for this sub driver and
|
||||
* attach the encoder created above to the connector.
|
||||
*/
|
||||
connector = exynos_drm_connector_create(dev, encoder);
|
||||
if (!connector) {
|
||||
DRM_ERROR("failed to create connector\n");
|
||||
ret = -EFAULT;
|
||||
display->encoder = encoder;
|
||||
|
||||
ret = display->ops->create_connector(display, encoder);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to create connector ret = %d\n", ret);
|
||||
goto err_destroy_encoder;
|
||||
}
|
||||
|
||||
subdrv->encoder = encoder;
|
||||
subdrv->connector = connector;
|
||||
|
||||
return 0;
|
||||
|
||||
err_destroy_encoder:
|
||||
@ -58,21 +57,6 @@ err_destroy_encoder:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void exynos_drm_destroy_enc_conn(struct exynos_drm_subdrv *subdrv)
|
||||
{
|
||||
if (subdrv->encoder) {
|
||||
struct drm_encoder *encoder = subdrv->encoder;
|
||||
encoder->funcs->destroy(encoder);
|
||||
subdrv->encoder = NULL;
|
||||
}
|
||||
|
||||
if (subdrv->connector) {
|
||||
struct drm_connector *connector = subdrv->connector;
|
||||
connector->funcs->destroy(connector);
|
||||
subdrv->connector = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int exynos_drm_subdrv_probe(struct drm_device *dev,
|
||||
struct exynos_drm_subdrv *subdrv)
|
||||
{
|
||||
@ -104,10 +88,98 @@ static void exynos_drm_subdrv_remove(struct drm_device *dev,
|
||||
subdrv->remove(dev, subdrv->dev);
|
||||
}
|
||||
|
||||
int exynos_drm_initialize_managers(struct drm_device *dev)
|
||||
{
|
||||
struct exynos_drm_manager *manager, *n;
|
||||
int ret, pipe = 0;
|
||||
|
||||
list_for_each_entry(manager, &exynos_drm_manager_list, list) {
|
||||
if (manager->ops->initialize) {
|
||||
ret = manager->ops->initialize(manager, dev, pipe);
|
||||
if (ret) {
|
||||
DRM_ERROR("Mgr init [%d] failed with %d\n",
|
||||
manager->type, ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
manager->drm_dev = dev;
|
||||
manager->pipe = pipe++;
|
||||
|
||||
ret = exynos_drm_crtc_create(manager);
|
||||
if (ret) {
|
||||
DRM_ERROR("CRTC create [%d] failed with %d\n",
|
||||
manager->type, ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
err:
|
||||
list_for_each_entry_safe(manager, n, &exynos_drm_manager_list, list) {
|
||||
if (pipe-- > 0)
|
||||
exynos_drm_manager_unregister(manager);
|
||||
else
|
||||
list_del(&manager->list);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void exynos_drm_remove_managers(struct drm_device *dev)
|
||||
{
|
||||
struct exynos_drm_manager *manager, *n;
|
||||
|
||||
list_for_each_entry_safe(manager, n, &exynos_drm_manager_list, list)
|
||||
exynos_drm_manager_unregister(manager);
|
||||
}
|
||||
|
||||
int exynos_drm_initialize_displays(struct drm_device *dev)
|
||||
{
|
||||
struct exynos_drm_display *display, *n;
|
||||
int ret, initialized = 0;
|
||||
|
||||
list_for_each_entry(display, &exynos_drm_display_list, list) {
|
||||
if (display->ops->initialize) {
|
||||
ret = display->ops->initialize(display, dev);
|
||||
if (ret) {
|
||||
DRM_ERROR("Display init [%d] failed with %d\n",
|
||||
display->type, ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
initialized++;
|
||||
|
||||
ret = exynos_drm_create_enc_conn(dev, display);
|
||||
if (ret) {
|
||||
DRM_ERROR("Encoder create [%d] failed with %d\n",
|
||||
display->type, ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
err:
|
||||
list_for_each_entry_safe(display, n, &exynos_drm_display_list, list) {
|
||||
if (initialized-- > 0)
|
||||
exynos_drm_display_unregister(display);
|
||||
else
|
||||
list_del(&display->list);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void exynos_drm_remove_displays(struct drm_device *dev)
|
||||
{
|
||||
struct exynos_drm_display *display, *n;
|
||||
|
||||
list_for_each_entry_safe(display, n, &exynos_drm_display_list, list)
|
||||
exynos_drm_display_unregister(display);
|
||||
}
|
||||
|
||||
int exynos_drm_device_register(struct drm_device *dev)
|
||||
{
|
||||
struct exynos_drm_subdrv *subdrv, *n;
|
||||
unsigned int fine_cnt = 0;
|
||||
int err;
|
||||
|
||||
if (!dev)
|
||||
@ -120,30 +192,8 @@ int exynos_drm_device_register(struct drm_device *dev)
|
||||
list_del(&subdrv->list);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* if manager is null then it means that this sub driver
|
||||
* doesn't need encoder and connector.
|
||||
*/
|
||||
if (!subdrv->manager) {
|
||||
fine_cnt++;
|
||||
continue;
|
||||
}
|
||||
|
||||
err = exynos_drm_create_enc_conn(dev, subdrv);
|
||||
if (err) {
|
||||
DRM_DEBUG("failed to create encoder and connector.\n");
|
||||
exynos_drm_subdrv_remove(dev, subdrv);
|
||||
list_del(&subdrv->list);
|
||||
continue;
|
||||
}
|
||||
|
||||
fine_cnt++;
|
||||
}
|
||||
|
||||
if (!fine_cnt)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(exynos_drm_device_register);
|
||||
@ -159,13 +209,44 @@ int exynos_drm_device_unregister(struct drm_device *dev)
|
||||
|
||||
list_for_each_entry(subdrv, &exynos_drm_subdrv_list, list) {
|
||||
exynos_drm_subdrv_remove(dev, subdrv);
|
||||
exynos_drm_destroy_enc_conn(subdrv);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(exynos_drm_device_unregister);
|
||||
|
||||
int exynos_drm_manager_register(struct exynos_drm_manager *manager)
|
||||
{
|
||||
BUG_ON(!manager->ops);
|
||||
list_add_tail(&manager->list, &exynos_drm_manager_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_drm_manager_unregister(struct exynos_drm_manager *manager)
|
||||
{
|
||||
if (manager->ops->remove)
|
||||
manager->ops->remove(manager);
|
||||
|
||||
list_del(&manager->list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_drm_display_register(struct exynos_drm_display *display)
|
||||
{
|
||||
BUG_ON(!display->ops);
|
||||
list_add_tail(&display->list, &exynos_drm_display_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_drm_display_unregister(struct exynos_drm_display *display)
|
||||
{
|
||||
if (display->ops->remove)
|
||||
display->ops->remove(display);
|
||||
|
||||
list_del(&display->list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_drm_subdrv_register(struct exynos_drm_subdrv *subdrv)
|
||||
{
|
||||
if (!subdrv)
|
||||
|
@ -33,6 +33,7 @@ enum exynos_crtc_mode {
|
||||
*
|
||||
* @drm_crtc: crtc object.
|
||||
* @drm_plane: pointer of private plane object for this crtc
|
||||
* @manager: the manager associated with this crtc
|
||||
* @pipe: a crtc index created at load() with a new crtc object creation
|
||||
* and the crtc object would be set to private->crtc array
|
||||
* to get a crtc object corresponding to this pipe from private->crtc
|
||||
@ -46,6 +47,7 @@ enum exynos_crtc_mode {
|
||||
struct exynos_drm_crtc {
|
||||
struct drm_crtc drm_crtc;
|
||||
struct drm_plane *plane;
|
||||
struct exynos_drm_manager *manager;
|
||||
unsigned int pipe;
|
||||
unsigned int dpms;
|
||||
enum exynos_crtc_mode mode;
|
||||
@ -56,6 +58,7 @@ struct exynos_drm_crtc {
|
||||
static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
|
||||
{
|
||||
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
|
||||
struct exynos_drm_manager *manager = exynos_crtc->manager;
|
||||
|
||||
DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode);
|
||||
|
||||
@ -71,7 +74,9 @@ static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
|
||||
drm_vblank_off(crtc->dev, exynos_crtc->pipe);
|
||||
}
|
||||
|
||||
exynos_drm_fn_encoder(crtc, &mode, exynos_drm_encoder_crtc_dpms);
|
||||
if (manager->ops->dpms)
|
||||
manager->ops->dpms(manager, mode);
|
||||
|
||||
exynos_crtc->dpms = mode;
|
||||
}
|
||||
|
||||
@ -83,9 +88,15 @@ static void exynos_drm_crtc_prepare(struct drm_crtc *crtc)
|
||||
static void exynos_drm_crtc_commit(struct drm_crtc *crtc)
|
||||
{
|
||||
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
|
||||
struct exynos_drm_manager *manager = exynos_crtc->manager;
|
||||
|
||||
exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
|
||||
|
||||
exynos_plane_commit(exynos_crtc->plane);
|
||||
|
||||
if (manager->ops->commit)
|
||||
manager->ops->commit(manager);
|
||||
|
||||
exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
@ -94,7 +105,12 @@ exynos_drm_crtc_mode_fixup(struct drm_crtc *crtc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/* drm framework doesn't check NULL */
|
||||
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
|
||||
struct exynos_drm_manager *manager = exynos_crtc->manager;
|
||||
|
||||
if (manager->ops->mode_fixup)
|
||||
return manager->ops->mode_fixup(manager, mode, adjusted_mode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -104,10 +120,10 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
|
||||
struct exynos_drm_manager *manager = exynos_crtc->manager;
|
||||
struct drm_plane *plane = exynos_crtc->plane;
|
||||
unsigned int crtc_w;
|
||||
unsigned int crtc_h;
|
||||
int pipe = exynos_crtc->pipe;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
@ -116,18 +132,19 @@ exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
|
||||
*/
|
||||
memcpy(&crtc->mode, adjusted_mode, sizeof(*adjusted_mode));
|
||||
|
||||
crtc_w = crtc->fb->width - x;
|
||||
crtc_h = crtc->fb->height - y;
|
||||
crtc_w = crtc->primary->fb->width - x;
|
||||
crtc_h = crtc->primary->fb->height - y;
|
||||
|
||||
ret = exynos_plane_mode_set(plane, crtc, crtc->fb, 0, 0, crtc_w, crtc_h,
|
||||
if (manager->ops->mode_set)
|
||||
manager->ops->mode_set(manager, &crtc->mode);
|
||||
|
||||
ret = exynos_plane_mode_set(plane, crtc, crtc->primary->fb, 0, 0, crtc_w, crtc_h,
|
||||
x, y, crtc_w, crtc_h);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
plane->crtc = crtc;
|
||||
plane->fb = crtc->fb;
|
||||
|
||||
exynos_drm_fn_encoder(crtc, &pipe, exynos_drm_encoder_crtc_pipe);
|
||||
plane->fb = crtc->primary->fb;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -147,10 +164,10 @@ static int exynos_drm_crtc_mode_set_commit(struct drm_crtc *crtc, int x, int y,
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
crtc_w = crtc->fb->width - x;
|
||||
crtc_h = crtc->fb->height - y;
|
||||
crtc_w = crtc->primary->fb->width - x;
|
||||
crtc_h = crtc->primary->fb->height - y;
|
||||
|
||||
ret = exynos_plane_mode_set(plane, crtc, crtc->fb, 0, 0, crtc_w, crtc_h,
|
||||
ret = exynos_plane_mode_set(plane, crtc, crtc->primary->fb, 0, 0, crtc_w, crtc_h,
|
||||
x, y, crtc_w, crtc_h);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -168,10 +185,19 @@ static int exynos_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
|
||||
static void exynos_drm_crtc_disable(struct drm_crtc *crtc)
|
||||
{
|
||||
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
|
||||
struct drm_plane *plane;
|
||||
int ret;
|
||||
|
||||
exynos_plane_dpms(exynos_crtc->plane, DRM_MODE_DPMS_OFF);
|
||||
exynos_drm_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
|
||||
drm_for_each_legacy_plane(plane, &crtc->dev->mode_config.plane_list) {
|
||||
if (plane->crtc != crtc)
|
||||
continue;
|
||||
|
||||
ret = plane->funcs->disable_plane(plane);
|
||||
if (ret)
|
||||
DRM_ERROR("Failed to disable plane %d\n", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = {
|
||||
@ -192,7 +218,7 @@ static int exynos_drm_crtc_page_flip(struct drm_crtc *crtc,
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct exynos_drm_private *dev_priv = dev->dev_private;
|
||||
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
|
||||
struct drm_framebuffer *old_fb = crtc->fb;
|
||||
struct drm_framebuffer *old_fb = crtc->primary->fb;
|
||||
int ret = -EINVAL;
|
||||
|
||||
/* when the page flip is requested, crtc's dpms should be on */
|
||||
@ -223,11 +249,11 @@ static int exynos_drm_crtc_page_flip(struct drm_crtc *crtc,
|
||||
atomic_set(&exynos_crtc->pending_flip, 1);
|
||||
spin_unlock_irq(&dev->event_lock);
|
||||
|
||||
crtc->fb = fb;
|
||||
crtc->primary->fb = fb;
|
||||
ret = exynos_drm_crtc_mode_set_commit(crtc, crtc->x, crtc->y,
|
||||
NULL);
|
||||
if (ret) {
|
||||
crtc->fb = old_fb;
|
||||
crtc->primary->fb = old_fb;
|
||||
|
||||
spin_lock_irq(&dev->event_lock);
|
||||
drm_vblank_put(dev, exynos_crtc->pipe);
|
||||
@ -318,21 +344,24 @@ static void exynos_drm_crtc_attach_mode_property(struct drm_crtc *crtc)
|
||||
drm_object_attach_property(&crtc->base, prop, 0);
|
||||
}
|
||||
|
||||
int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr)
|
||||
int exynos_drm_crtc_create(struct exynos_drm_manager *manager)
|
||||
{
|
||||
struct exynos_drm_crtc *exynos_crtc;
|
||||
struct exynos_drm_private *private = dev->dev_private;
|
||||
struct exynos_drm_private *private = manager->drm_dev->dev_private;
|
||||
struct drm_crtc *crtc;
|
||||
|
||||
exynos_crtc = kzalloc(sizeof(*exynos_crtc), GFP_KERNEL);
|
||||
if (!exynos_crtc)
|
||||
return -ENOMEM;
|
||||
|
||||
exynos_crtc->pipe = nr;
|
||||
exynos_crtc->dpms = DRM_MODE_DPMS_OFF;
|
||||
init_waitqueue_head(&exynos_crtc->pending_flip_queue);
|
||||
atomic_set(&exynos_crtc->pending_flip, 0);
|
||||
exynos_crtc->plane = exynos_plane_init(dev, 1 << nr, true);
|
||||
|
||||
exynos_crtc->dpms = DRM_MODE_DPMS_OFF;
|
||||
exynos_crtc->manager = manager;
|
||||
exynos_crtc->pipe = manager->pipe;
|
||||
exynos_crtc->plane = exynos_plane_init(manager->drm_dev,
|
||||
1 << manager->pipe, true);
|
||||
if (!exynos_crtc->plane) {
|
||||
kfree(exynos_crtc);
|
||||
return -ENOMEM;
|
||||
@ -340,9 +369,9 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr)
|
||||
|
||||
crtc = &exynos_crtc->drm_crtc;
|
||||
|
||||
private->crtc[nr] = crtc;
|
||||
private->crtc[manager->pipe] = crtc;
|
||||
|
||||
drm_crtc_init(dev, crtc, &exynos_crtc_funcs);
|
||||
drm_crtc_init(manager->drm_dev, crtc, &exynos_crtc_funcs);
|
||||
drm_crtc_helper_add(crtc, &exynos_crtc_helper_funcs);
|
||||
|
||||
exynos_drm_crtc_attach_mode_property(crtc);
|
||||
@ -350,39 +379,41 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc)
|
||||
int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int pipe)
|
||||
{
|
||||
struct exynos_drm_private *private = dev->dev_private;
|
||||
struct exynos_drm_crtc *exynos_crtc =
|
||||
to_exynos_crtc(private->crtc[crtc]);
|
||||
to_exynos_crtc(private->crtc[pipe]);
|
||||
struct exynos_drm_manager *manager = exynos_crtc->manager;
|
||||
|
||||
if (exynos_crtc->dpms != DRM_MODE_DPMS_ON)
|
||||
return -EPERM;
|
||||
|
||||
exynos_drm_fn_encoder(private->crtc[crtc], &crtc,
|
||||
exynos_drm_enable_vblank);
|
||||
if (manager->ops->enable_vblank)
|
||||
manager->ops->enable_vblank(manager);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc)
|
||||
void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int pipe)
|
||||
{
|
||||
struct exynos_drm_private *private = dev->dev_private;
|
||||
struct exynos_drm_crtc *exynos_crtc =
|
||||
to_exynos_crtc(private->crtc[crtc]);
|
||||
to_exynos_crtc(private->crtc[pipe]);
|
||||
struct exynos_drm_manager *manager = exynos_crtc->manager;
|
||||
|
||||
if (exynos_crtc->dpms != DRM_MODE_DPMS_ON)
|
||||
return;
|
||||
|
||||
exynos_drm_fn_encoder(private->crtc[crtc], &crtc,
|
||||
exynos_drm_disable_vblank);
|
||||
if (manager->ops->disable_vblank)
|
||||
manager->ops->disable_vblank(manager);
|
||||
}
|
||||
|
||||
void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc)
|
||||
void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe)
|
||||
{
|
||||
struct exynos_drm_private *dev_priv = dev->dev_private;
|
||||
struct drm_pending_vblank_event *e, *t;
|
||||
struct drm_crtc *drm_crtc = dev_priv->crtc[crtc];
|
||||
struct drm_crtc *drm_crtc = dev_priv->crtc[pipe];
|
||||
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(drm_crtc);
|
||||
unsigned long flags;
|
||||
|
||||
@ -391,15 +422,71 @@ void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc)
|
||||
list_for_each_entry_safe(e, t, &dev_priv->pageflip_event_list,
|
||||
base.link) {
|
||||
/* if event's pipe isn't same as crtc then ignore it. */
|
||||
if (crtc != e->pipe)
|
||||
if (pipe != e->pipe)
|
||||
continue;
|
||||
|
||||
list_del(&e->base.link);
|
||||
drm_send_vblank_event(dev, -1, e);
|
||||
drm_vblank_put(dev, crtc);
|
||||
drm_vblank_put(dev, pipe);
|
||||
atomic_set(&exynos_crtc->pending_flip, 0);
|
||||
wake_up(&exynos_crtc->pending_flip_queue);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
}
|
||||
|
||||
void exynos_drm_crtc_plane_mode_set(struct drm_crtc *crtc,
|
||||
struct exynos_drm_overlay *overlay)
|
||||
{
|
||||
struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager;
|
||||
|
||||
if (manager->ops->win_mode_set)
|
||||
manager->ops->win_mode_set(manager, overlay);
|
||||
}
|
||||
|
||||
void exynos_drm_crtc_plane_commit(struct drm_crtc *crtc, int zpos)
|
||||
{
|
||||
struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager;
|
||||
|
||||
if (manager->ops->win_commit)
|
||||
manager->ops->win_commit(manager, zpos);
|
||||
}
|
||||
|
||||
void exynos_drm_crtc_plane_enable(struct drm_crtc *crtc, int zpos)
|
||||
{
|
||||
struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager;
|
||||
|
||||
if (manager->ops->win_enable)
|
||||
manager->ops->win_enable(manager, zpos);
|
||||
}
|
||||
|
||||
void exynos_drm_crtc_plane_disable(struct drm_crtc *crtc, int zpos)
|
||||
{
|
||||
struct exynos_drm_manager *manager = to_exynos_crtc(crtc)->manager;
|
||||
|
||||
if (manager->ops->win_disable)
|
||||
manager->ops->win_disable(manager, zpos);
|
||||
}
|
||||
|
||||
void exynos_drm_crtc_complete_scanout(struct drm_framebuffer *fb)
|
||||
{
|
||||
struct exynos_drm_manager *manager;
|
||||
struct drm_device *dev = fb->dev;
|
||||
struct drm_crtc *crtc;
|
||||
|
||||
/*
|
||||
* make sure that overlay data are updated to real hardware
|
||||
* for all encoders.
|
||||
*/
|
||||
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
|
||||
manager = to_exynos_crtc(crtc)->manager;
|
||||
|
||||
/*
|
||||
* wait for vblank interrupt
|
||||
* - this makes sure that overlay data are updated to
|
||||
* real hardware.
|
||||
*/
|
||||
if (manager->ops->wait_for_vblank)
|
||||
manager->ops->wait_for_vblank(manager);
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,21 @@
|
||||
#ifndef _EXYNOS_DRM_CRTC_H_
|
||||
#define _EXYNOS_DRM_CRTC_H_
|
||||
|
||||
int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr);
|
||||
int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc);
|
||||
void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc);
|
||||
void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int crtc);
|
||||
struct drm_device;
|
||||
struct drm_crtc;
|
||||
struct exynos_drm_manager;
|
||||
struct exynos_drm_overlay;
|
||||
|
||||
int exynos_drm_crtc_create(struct exynos_drm_manager *manager);
|
||||
int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int pipe);
|
||||
void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int pipe);
|
||||
void exynos_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe);
|
||||
void exynos_drm_crtc_complete_scanout(struct drm_framebuffer *fb);
|
||||
|
||||
void exynos_drm_crtc_plane_mode_set(struct drm_crtc *crtc,
|
||||
struct exynos_drm_overlay *overlay);
|
||||
void exynos_drm_crtc_plane_commit(struct drm_crtc *crtc, int zpos);
|
||||
void exynos_drm_crtc_plane_enable(struct drm_crtc *crtc, int zpos);
|
||||
void exynos_drm_crtc_plane_disable(struct drm_crtc *crtc, int zpos);
|
||||
|
||||
#endif
|
||||
|
339
drivers/gpu/drm/exynos/exynos_drm_dpi.c
Normal file
339
drivers/gpu/drm/exynos/exynos_drm_dpi.c
Normal file
@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Exynos DRM Parallel output support.
|
||||
*
|
||||
* Copyright (c) 2014 Samsung Electronics Co., Ltd
|
||||
*
|
||||
* Contacts: Andrzej Hajda <a.hajda@samsung.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.
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <video/of_videomode.h>
|
||||
#include <video/videomode.h>
|
||||
|
||||
#include "exynos_drm_drv.h"
|
||||
|
||||
struct exynos_dpi {
|
||||
struct device *dev;
|
||||
struct device_node *panel_node;
|
||||
|
||||
struct drm_panel *panel;
|
||||
struct drm_connector connector;
|
||||
struct drm_encoder *encoder;
|
||||
|
||||
struct videomode *vm;
|
||||
int dpms_mode;
|
||||
};
|
||||
|
||||
#define connector_to_dpi(c) container_of(c, struct exynos_dpi, connector)
|
||||
|
||||
static enum drm_connector_status
|
||||
exynos_dpi_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct exynos_dpi *ctx = connector_to_dpi(connector);
|
||||
|
||||
/* panels supported only by boot-loader are always connected */
|
||||
if (!ctx->panel_node)
|
||||
return connector_status_connected;
|
||||
|
||||
if (!ctx->panel) {
|
||||
ctx->panel = of_drm_find_panel(ctx->panel_node);
|
||||
if (ctx->panel)
|
||||
drm_panel_attach(ctx->panel, &ctx->connector);
|
||||
}
|
||||
|
||||
if (ctx->panel)
|
||||
return connector_status_connected;
|
||||
|
||||
return connector_status_disconnected;
|
||||
}
|
||||
|
||||
static void exynos_dpi_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
drm_sysfs_connector_remove(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
}
|
||||
|
||||
static struct drm_connector_funcs exynos_dpi_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.detect = exynos_dpi_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = exynos_dpi_connector_destroy,
|
||||
};
|
||||
|
||||
static int exynos_dpi_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct exynos_dpi *ctx = connector_to_dpi(connector);
|
||||
|
||||
/* fimd timings gets precedence over panel modes */
|
||||
if (ctx->vm) {
|
||||
struct drm_display_mode *mode;
|
||||
|
||||
mode = drm_mode_create(connector->dev);
|
||||
if (!mode) {
|
||||
DRM_ERROR("failed to create a new display mode\n");
|
||||
return 0;
|
||||
}
|
||||
drm_display_mode_from_videomode(ctx->vm, mode);
|
||||
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
|
||||
drm_mode_probed_add(connector, mode);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ctx->panel)
|
||||
return ctx->panel->funcs->get_modes(ctx->panel);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_dpi_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static struct drm_encoder *
|
||||
exynos_dpi_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct exynos_dpi *ctx = connector_to_dpi(connector);
|
||||
|
||||
return ctx->encoder;
|
||||
}
|
||||
|
||||
static struct drm_connector_helper_funcs exynos_dpi_connector_helper_funcs = {
|
||||
.get_modes = exynos_dpi_get_modes,
|
||||
.mode_valid = exynos_dpi_mode_valid,
|
||||
.best_encoder = exynos_dpi_best_encoder,
|
||||
};
|
||||
|
||||
static int exynos_dpi_create_connector(struct exynos_drm_display *display,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
struct exynos_dpi *ctx = display->ctx;
|
||||
struct drm_connector *connector = &ctx->connector;
|
||||
int ret;
|
||||
|
||||
ctx->encoder = encoder;
|
||||
|
||||
if (ctx->panel_node)
|
||||
connector->polled = DRM_CONNECTOR_POLL_CONNECT;
|
||||
else
|
||||
connector->polled = DRM_CONNECTOR_POLL_HPD;
|
||||
|
||||
ret = drm_connector_init(encoder->dev, connector,
|
||||
&exynos_dpi_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_VGA);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to initialize connector with drm\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(connector, &exynos_dpi_connector_helper_funcs);
|
||||
drm_sysfs_connector_add(connector);
|
||||
drm_mode_connector_attach_encoder(connector, encoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exynos_dpi_poweron(struct exynos_dpi *ctx)
|
||||
{
|
||||
if (ctx->panel)
|
||||
drm_panel_enable(ctx->panel);
|
||||
}
|
||||
|
||||
static void exynos_dpi_poweroff(struct exynos_dpi *ctx)
|
||||
{
|
||||
if (ctx->panel)
|
||||
drm_panel_disable(ctx->panel);
|
||||
}
|
||||
|
||||
static void exynos_dpi_dpms(struct exynos_drm_display *display, int mode)
|
||||
{
|
||||
struct exynos_dpi *ctx = display->ctx;
|
||||
|
||||
switch (mode) {
|
||||
case DRM_MODE_DPMS_ON:
|
||||
if (ctx->dpms_mode != DRM_MODE_DPMS_ON)
|
||||
exynos_dpi_poweron(ctx);
|
||||
break;
|
||||
case DRM_MODE_DPMS_STANDBY:
|
||||
case DRM_MODE_DPMS_SUSPEND:
|
||||
case DRM_MODE_DPMS_OFF:
|
||||
if (ctx->dpms_mode == DRM_MODE_DPMS_ON)
|
||||
exynos_dpi_poweroff(ctx);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
ctx->dpms_mode = mode;
|
||||
}
|
||||
|
||||
static struct exynos_drm_display_ops exynos_dpi_display_ops = {
|
||||
.create_connector = exynos_dpi_create_connector,
|
||||
.dpms = exynos_dpi_dpms
|
||||
};
|
||||
|
||||
static struct exynos_drm_display exynos_dpi_display = {
|
||||
.type = EXYNOS_DISPLAY_TYPE_LCD,
|
||||
.ops = &exynos_dpi_display_ops,
|
||||
};
|
||||
|
||||
/* of_* functions will be removed after merge of of_graph patches */
|
||||
static struct device_node *
|
||||
of_get_child_by_name_reg(struct device_node *parent, const char *name, u32 reg)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
for_each_child_of_node(parent, np) {
|
||||
u32 r;
|
||||
|
||||
if (!np->name || of_node_cmp(np->name, name))
|
||||
continue;
|
||||
|
||||
if (of_property_read_u32(np, "reg", &r) < 0)
|
||||
r = 0;
|
||||
|
||||
if (reg == r)
|
||||
break;
|
||||
}
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
static struct device_node *of_graph_get_port_by_reg(struct device_node *parent,
|
||||
u32 reg)
|
||||
{
|
||||
struct device_node *ports, *port;
|
||||
|
||||
ports = of_get_child_by_name(parent, "ports");
|
||||
if (ports)
|
||||
parent = ports;
|
||||
|
||||
port = of_get_child_by_name_reg(parent, "port", reg);
|
||||
|
||||
of_node_put(ports);
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
static struct device_node *
|
||||
of_graph_get_endpoint_by_reg(struct device_node *port, u32 reg)
|
||||
{
|
||||
return of_get_child_by_name_reg(port, "endpoint", reg);
|
||||
}
|
||||
|
||||
static struct device_node *
|
||||
of_graph_get_remote_port_parent(const struct device_node *node)
|
||||
{
|
||||
struct device_node *np;
|
||||
unsigned int depth;
|
||||
|
||||
np = of_parse_phandle(node, "remote-endpoint", 0);
|
||||
|
||||
/* Walk 3 levels up only if there is 'ports' node. */
|
||||
for (depth = 3; depth && np; depth--) {
|
||||
np = of_get_next_parent(np);
|
||||
if (depth == 2 && of_node_cmp(np->name, "ports"))
|
||||
break;
|
||||
}
|
||||
return np;
|
||||
}
|
||||
|
||||
enum {
|
||||
FIMD_PORT_IN0,
|
||||
FIMD_PORT_IN1,
|
||||
FIMD_PORT_IN2,
|
||||
FIMD_PORT_RGB,
|
||||
FIMD_PORT_WRB,
|
||||
};
|
||||
|
||||
static struct device_node *exynos_dpi_of_find_panel_node(struct device *dev)
|
||||
{
|
||||
struct device_node *np, *ep;
|
||||
|
||||
np = of_graph_get_port_by_reg(dev->of_node, FIMD_PORT_RGB);
|
||||
if (!np)
|
||||
return NULL;
|
||||
|
||||
ep = of_graph_get_endpoint_by_reg(np, 0);
|
||||
of_node_put(np);
|
||||
if (!ep)
|
||||
return NULL;
|
||||
|
||||
np = of_graph_get_remote_port_parent(ep);
|
||||
of_node_put(ep);
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
static int exynos_dpi_parse_dt(struct exynos_dpi *ctx)
|
||||
{
|
||||
struct device *dev = ctx->dev;
|
||||
struct device_node *dn = dev->of_node;
|
||||
struct device_node *np;
|
||||
|
||||
ctx->panel_node = exynos_dpi_of_find_panel_node(dev);
|
||||
|
||||
np = of_get_child_by_name(dn, "display-timings");
|
||||
if (np) {
|
||||
struct videomode *vm;
|
||||
int ret;
|
||||
|
||||
of_node_put(np);
|
||||
|
||||
vm = devm_kzalloc(dev, sizeof(*ctx->vm), GFP_KERNEL);
|
||||
if (!vm)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_get_videomode(dn, vm, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx->vm = vm;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ctx->panel_node)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_dpi_probe(struct device *dev)
|
||||
{
|
||||
struct exynos_dpi *ctx;
|
||||
int ret;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ctx->dev = dev;
|
||||
exynos_dpi_display.ctx = ctx;
|
||||
ctx->dpms_mode = DRM_MODE_DPMS_OFF;
|
||||
|
||||
ret = exynos_dpi_parse_dt(ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
exynos_drm_display_register(&exynos_dpi_display);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_dpi_remove(struct device *dev)
|
||||
{
|
||||
exynos_dpi_dpms(&exynos_dpi_display, DRM_MODE_DPMS_OFF);
|
||||
exynos_drm_display_unregister(&exynos_dpi_display);
|
||||
|
||||
return 0;
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
|
||||
@ -53,6 +54,7 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&private->pageflip_event_list);
|
||||
dev_set_drvdata(dev->dev, dev);
|
||||
dev->dev_private = (void *)private;
|
||||
|
||||
/*
|
||||
@ -64,38 +66,36 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags)
|
||||
ret = drm_create_iommu_mapping(dev);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed to create iommu mapping.\n");
|
||||
goto err_crtc;
|
||||
goto err_free_private;
|
||||
}
|
||||
|
||||
drm_mode_config_init(dev);
|
||||
|
||||
/* init kms poll for handling hpd */
|
||||
drm_kms_helper_poll_init(dev);
|
||||
|
||||
exynos_drm_mode_config_init(dev);
|
||||
|
||||
/*
|
||||
* EXYNOS4 is enough to have two CRTCs and each crtc would be used
|
||||
* without dependency of hardware.
|
||||
*/
|
||||
for (nr = 0; nr < MAX_CRTC; nr++) {
|
||||
ret = exynos_drm_crtc_create(dev, nr);
|
||||
if (ret)
|
||||
goto err_release_iommu_mapping;
|
||||
}
|
||||
ret = exynos_drm_initialize_managers(dev);
|
||||
if (ret)
|
||||
goto err_mode_config_cleanup;
|
||||
|
||||
for (nr = 0; nr < MAX_PLANE; nr++) {
|
||||
struct drm_plane *plane;
|
||||
unsigned int possible_crtcs = (1 << MAX_CRTC) - 1;
|
||||
unsigned long possible_crtcs = (1 << MAX_CRTC) - 1;
|
||||
|
||||
plane = exynos_plane_init(dev, possible_crtcs, false);
|
||||
if (!plane)
|
||||
goto err_release_iommu_mapping;
|
||||
goto err_manager_cleanup;
|
||||
}
|
||||
|
||||
ret = exynos_drm_initialize_displays(dev);
|
||||
if (ret)
|
||||
goto err_manager_cleanup;
|
||||
|
||||
/* init kms poll for handling hpd */
|
||||
drm_kms_helper_poll_init(dev);
|
||||
|
||||
ret = drm_vblank_init(dev, MAX_CRTC);
|
||||
if (ret)
|
||||
goto err_release_iommu_mapping;
|
||||
goto err_display_cleanup;
|
||||
|
||||
/*
|
||||
* probe sub drivers such as display controller and hdmi driver,
|
||||
@ -109,30 +109,25 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags)
|
||||
/* setup possible_clones. */
|
||||
exynos_drm_encoder_setup(dev);
|
||||
|
||||
/*
|
||||
* create and configure fb helper and also exynos specific
|
||||
* fbdev object.
|
||||
*/
|
||||
ret = exynos_drm_fbdev_init(dev);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to initialize drm fbdev\n");
|
||||
goto err_drm_device;
|
||||
}
|
||||
|
||||
drm_vblank_offdelay = VBLANK_OFF_DELAY;
|
||||
|
||||
platform_set_drvdata(dev->platformdev, dev);
|
||||
|
||||
/* force connectors detection */
|
||||
drm_helper_hpd_irq_event(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_drm_device:
|
||||
exynos_drm_device_unregister(dev);
|
||||
err_vblank:
|
||||
drm_vblank_cleanup(dev);
|
||||
err_release_iommu_mapping:
|
||||
drm_release_iommu_mapping(dev);
|
||||
err_crtc:
|
||||
err_display_cleanup:
|
||||
exynos_drm_remove_displays(dev);
|
||||
err_manager_cleanup:
|
||||
exynos_drm_remove_managers(dev);
|
||||
err_mode_config_cleanup:
|
||||
drm_mode_config_cleanup(dev);
|
||||
drm_release_iommu_mapping(dev);
|
||||
err_free_private:
|
||||
kfree(private);
|
||||
|
||||
return ret;
|
||||
@ -144,6 +139,8 @@ static int exynos_drm_unload(struct drm_device *dev)
|
||||
exynos_drm_device_unregister(dev);
|
||||
drm_vblank_cleanup(dev);
|
||||
drm_kms_helper_poll_fini(dev);
|
||||
exynos_drm_remove_displays(dev);
|
||||
exynos_drm_remove_managers(dev);
|
||||
drm_mode_config_cleanup(dev);
|
||||
|
||||
drm_release_iommu_mapping(dev);
|
||||
@ -158,6 +155,41 @@ static const struct file_operations exynos_drm_gem_fops = {
|
||||
.mmap = exynos_drm_gem_mmap_buffer,
|
||||
};
|
||||
|
||||
static int exynos_drm_suspend(struct drm_device *dev, pm_message_t state)
|
||||
{
|
||||
struct drm_connector *connector;
|
||||
|
||||
drm_modeset_lock_all(dev);
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
int old_dpms = connector->dpms;
|
||||
|
||||
if (connector->funcs->dpms)
|
||||
connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF);
|
||||
|
||||
/* Set the old mode back to the connector for resume */
|
||||
connector->dpms = old_dpms;
|
||||
}
|
||||
drm_modeset_unlock_all(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_drm_resume(struct drm_device *dev)
|
||||
{
|
||||
struct drm_connector *connector;
|
||||
|
||||
drm_modeset_lock_all(dev);
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
if (connector->funcs->dpms)
|
||||
connector->funcs->dpms(connector, connector->dpms);
|
||||
}
|
||||
|
||||
drm_helper_resume_force_mode(dev);
|
||||
drm_modeset_unlock_all(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_drm_open(struct drm_device *dev, struct drm_file *file)
|
||||
{
|
||||
struct drm_exynos_file_private *file_priv;
|
||||
@ -295,6 +327,8 @@ static struct drm_driver exynos_drm_driver = {
|
||||
DRIVER_GEM | DRIVER_PRIME,
|
||||
.load = exynos_drm_load,
|
||||
.unload = exynos_drm_unload,
|
||||
.suspend = exynos_drm_suspend,
|
||||
.resume = exynos_drm_resume,
|
||||
.open = exynos_drm_open,
|
||||
.preclose = exynos_drm_preclose,
|
||||
.lastclose = exynos_drm_lastclose,
|
||||
@ -329,6 +363,9 @@ static int exynos_drm_platform_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
|
||||
return drm_platform_init(&exynos_drm_driver, pdev);
|
||||
}
|
||||
|
||||
@ -339,12 +376,67 @@ static int exynos_drm_platform_remove(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int exynos_drm_sys_suspend(struct device *dev)
|
||||
{
|
||||
struct drm_device *drm_dev = dev_get_drvdata(dev);
|
||||
pm_message_t message;
|
||||
|
||||
if (pm_runtime_suspended(dev))
|
||||
return 0;
|
||||
|
||||
message.event = PM_EVENT_SUSPEND;
|
||||
return exynos_drm_suspend(drm_dev, message);
|
||||
}
|
||||
|
||||
static int exynos_drm_sys_resume(struct device *dev)
|
||||
{
|
||||
struct drm_device *drm_dev = dev_get_drvdata(dev);
|
||||
|
||||
if (pm_runtime_suspended(dev))
|
||||
return 0;
|
||||
|
||||
return exynos_drm_resume(drm_dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
static int exynos_drm_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct drm_device *drm_dev = dev_get_drvdata(dev);
|
||||
pm_message_t message;
|
||||
|
||||
if (pm_runtime_suspended(dev))
|
||||
return 0;
|
||||
|
||||
message.event = PM_EVENT_SUSPEND;
|
||||
return exynos_drm_suspend(drm_dev, message);
|
||||
}
|
||||
|
||||
static int exynos_drm_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct drm_device *drm_dev = dev_get_drvdata(dev);
|
||||
|
||||
if (!pm_runtime_suspended(dev))
|
||||
return 0;
|
||||
|
||||
return exynos_drm_resume(drm_dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops exynos_drm_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(exynos_drm_sys_suspend, exynos_drm_sys_resume)
|
||||
SET_RUNTIME_PM_OPS(exynos_drm_runtime_suspend,
|
||||
exynos_drm_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static struct platform_driver exynos_drm_platform_driver = {
|
||||
.probe = exynos_drm_platform_probe,
|
||||
.remove = exynos_drm_platform_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "exynos-drm",
|
||||
.pm = &exynos_drm_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
@ -352,6 +444,18 @@ static int __init exynos_drm_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_DP
|
||||
ret = platform_driver_register(&dp_driver);
|
||||
if (ret < 0)
|
||||
goto out_dp;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_DSI
|
||||
ret = platform_driver_register(&dsi_driver);
|
||||
if (ret < 0)
|
||||
goto out_dsi;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_FIMD
|
||||
ret = platform_driver_register(&fimd_driver);
|
||||
if (ret < 0)
|
||||
@ -365,13 +469,6 @@ static int __init exynos_drm_init(void)
|
||||
ret = platform_driver_register(&mixer_driver);
|
||||
if (ret < 0)
|
||||
goto out_mixer;
|
||||
ret = platform_driver_register(&exynos_drm_common_hdmi_driver);
|
||||
if (ret < 0)
|
||||
goto out_common_hdmi;
|
||||
|
||||
ret = exynos_platform_device_hdmi_register();
|
||||
if (ret < 0)
|
||||
goto out_common_hdmi_dev;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_VIDI
|
||||
@ -464,10 +561,6 @@ out_vidi:
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_HDMI
|
||||
exynos_platform_device_hdmi_unregister();
|
||||
out_common_hdmi_dev:
|
||||
platform_driver_unregister(&exynos_drm_common_hdmi_driver);
|
||||
out_common_hdmi:
|
||||
platform_driver_unregister(&mixer_driver);
|
||||
out_mixer:
|
||||
platform_driver_unregister(&hdmi_driver);
|
||||
@ -477,6 +570,16 @@ out_hdmi:
|
||||
#ifdef CONFIG_DRM_EXYNOS_FIMD
|
||||
platform_driver_unregister(&fimd_driver);
|
||||
out_fimd:
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_DSI
|
||||
platform_driver_unregister(&dsi_driver);
|
||||
out_dsi:
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_DP
|
||||
platform_driver_unregister(&dp_driver);
|
||||
out_dp:
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
@ -509,8 +612,6 @@ static void __exit exynos_drm_exit(void)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_HDMI
|
||||
exynos_platform_device_hdmi_unregister();
|
||||
platform_driver_unregister(&exynos_drm_common_hdmi_driver);
|
||||
platform_driver_unregister(&mixer_driver);
|
||||
platform_driver_unregister(&hdmi_driver);
|
||||
#endif
|
||||
@ -522,6 +623,14 @@ static void __exit exynos_drm_exit(void)
|
||||
#ifdef CONFIG_DRM_EXYNOS_FIMD
|
||||
platform_driver_unregister(&fimd_driver);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_DSI
|
||||
platform_driver_unregister(&dsi_driver);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_DP
|
||||
platform_driver_unregister(&dp_driver);
|
||||
#endif
|
||||
}
|
||||
|
||||
module_init(exynos_drm_init);
|
||||
|
@ -53,22 +53,6 @@ enum exynos_drm_output_type {
|
||||
EXYNOS_DISPLAY_TYPE_VIDI,
|
||||
};
|
||||
|
||||
/*
|
||||
* Exynos drm overlay ops structure.
|
||||
*
|
||||
* @mode_set: copy drm overlay info to hw specific overlay info.
|
||||
* @commit: apply hardware specific overlay data to registers.
|
||||
* @enable: enable hardware specific overlay.
|
||||
* @disable: disable hardware specific overlay.
|
||||
*/
|
||||
struct exynos_drm_overlay_ops {
|
||||
void (*mode_set)(struct device *subdrv_dev,
|
||||
struct exynos_drm_overlay *overlay);
|
||||
void (*commit)(struct device *subdrv_dev, int zpos);
|
||||
void (*enable)(struct device *subdrv_dev, int zpos);
|
||||
void (*disable)(struct device *subdrv_dev, int zpos);
|
||||
};
|
||||
|
||||
/*
|
||||
* Exynos drm common overlay structure.
|
||||
*
|
||||
@ -138,77 +122,110 @@ struct exynos_drm_overlay {
|
||||
* Exynos DRM Display Structure.
|
||||
* - this structure is common to analog tv, digital tv and lcd panel.
|
||||
*
|
||||
* @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI.
|
||||
* @is_connected: check for that display is connected or not.
|
||||
* @get_edid: get edid modes from display driver.
|
||||
* @get_panel: get panel object from display driver.
|
||||
* @initialize: initializes the display with drm_dev
|
||||
* @remove: cleans up the display for removal
|
||||
* @mode_fixup: fix mode data comparing to hw specific display mode.
|
||||
* @mode_set: convert drm_display_mode to hw specific display mode and
|
||||
* would be called by encoder->mode_set().
|
||||
* @check_mode: check if mode is valid or not.
|
||||
* @power_on: display device on or off.
|
||||
* @dpms: display device on or off.
|
||||
* @commit: apply changes to hw
|
||||
*/
|
||||
struct exynos_drm_display;
|
||||
struct exynos_drm_display_ops {
|
||||
int (*initialize)(struct exynos_drm_display *display,
|
||||
struct drm_device *drm_dev);
|
||||
int (*create_connector)(struct exynos_drm_display *display,
|
||||
struct drm_encoder *encoder);
|
||||
void (*remove)(struct exynos_drm_display *display);
|
||||
void (*mode_fixup)(struct exynos_drm_display *display,
|
||||
struct drm_connector *connector,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode);
|
||||
void (*mode_set)(struct exynos_drm_display *display,
|
||||
struct drm_display_mode *mode);
|
||||
int (*check_mode)(struct exynos_drm_display *display,
|
||||
struct drm_display_mode *mode);
|
||||
void (*dpms)(struct exynos_drm_display *display, int mode);
|
||||
void (*commit)(struct exynos_drm_display *display);
|
||||
};
|
||||
|
||||
/*
|
||||
* Exynos drm display structure, maps 1:1 with an encoder/connector
|
||||
*
|
||||
* @list: the list entry for this manager
|
||||
* @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI.
|
||||
* @encoder: encoder object this display maps to
|
||||
* @connector: connector object this display maps to
|
||||
* @ops: pointer to callbacks for exynos drm specific functionality
|
||||
* @ctx: A pointer to the display's implementation specific context
|
||||
*/
|
||||
struct exynos_drm_display {
|
||||
struct list_head list;
|
||||
enum exynos_drm_output_type type;
|
||||
bool (*is_connected)(struct device *dev);
|
||||
struct edid *(*get_edid)(struct device *dev,
|
||||
struct drm_connector *connector);
|
||||
void *(*get_panel)(struct device *dev);
|
||||
int (*check_mode)(struct device *dev, struct drm_display_mode *mode);
|
||||
int (*power_on)(struct device *dev, int mode);
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector *connector;
|
||||
struct exynos_drm_display_ops *ops;
|
||||
void *ctx;
|
||||
};
|
||||
|
||||
/*
|
||||
* Exynos drm manager ops
|
||||
*
|
||||
* @initialize: initializes the manager with drm_dev
|
||||
* @remove: cleans up the manager for removal
|
||||
* @dpms: control device power.
|
||||
* @apply: set timing, vblank and overlay data to registers.
|
||||
* @mode_fixup: fix mode data comparing to hw specific display mode.
|
||||
* @mode_set: convert drm_display_mode to hw specific display mode and
|
||||
* would be called by encoder->mode_set().
|
||||
* @get_max_resol: get maximum resolution to specific hardware.
|
||||
* @mode_fixup: fix mode data before applying it
|
||||
* @mode_set: set the given mode to the manager
|
||||
* @commit: set current hw specific display mode to hw.
|
||||
* @enable_vblank: specific driver callback for enabling vblank interrupt.
|
||||
* @disable_vblank: specific driver callback for disabling vblank interrupt.
|
||||
* @wait_for_vblank: wait for vblank interrupt to make sure that
|
||||
* hardware overlay is updated.
|
||||
* @win_mode_set: copy drm overlay info to hw specific overlay info.
|
||||
* @win_commit: apply hardware specific overlay data to registers.
|
||||
* @win_enable: enable hardware specific overlay.
|
||||
* @win_disable: disable hardware specific overlay.
|
||||
*/
|
||||
struct exynos_drm_manager;
|
||||
struct exynos_drm_manager_ops {
|
||||
void (*dpms)(struct device *subdrv_dev, int mode);
|
||||
void (*apply)(struct device *subdrv_dev);
|
||||
void (*mode_fixup)(struct device *subdrv_dev,
|
||||
struct drm_connector *connector,
|
||||
int (*initialize)(struct exynos_drm_manager *mgr,
|
||||
struct drm_device *drm_dev, int pipe);
|
||||
void (*remove)(struct exynos_drm_manager *mgr);
|
||||
void (*dpms)(struct exynos_drm_manager *mgr, int mode);
|
||||
bool (*mode_fixup)(struct exynos_drm_manager *mgr,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode);
|
||||
void (*mode_set)(struct device *subdrv_dev, void *mode);
|
||||
void (*get_max_resol)(struct device *subdrv_dev, unsigned int *width,
|
||||
unsigned int *height);
|
||||
void (*commit)(struct device *subdrv_dev);
|
||||
int (*enable_vblank)(struct device *subdrv_dev);
|
||||
void (*disable_vblank)(struct device *subdrv_dev);
|
||||
void (*wait_for_vblank)(struct device *subdrv_dev);
|
||||
void (*mode_set)(struct exynos_drm_manager *mgr,
|
||||
const struct drm_display_mode *mode);
|
||||
void (*commit)(struct exynos_drm_manager *mgr);
|
||||
int (*enable_vblank)(struct exynos_drm_manager *mgr);
|
||||
void (*disable_vblank)(struct exynos_drm_manager *mgr);
|
||||
void (*wait_for_vblank)(struct exynos_drm_manager *mgr);
|
||||
void (*win_mode_set)(struct exynos_drm_manager *mgr,
|
||||
struct exynos_drm_overlay *overlay);
|
||||
void (*win_commit)(struct exynos_drm_manager *mgr, int zpos);
|
||||
void (*win_enable)(struct exynos_drm_manager *mgr, int zpos);
|
||||
void (*win_disable)(struct exynos_drm_manager *mgr, int zpos);
|
||||
};
|
||||
|
||||
/*
|
||||
* Exynos drm common manager structure.
|
||||
* Exynos drm common manager structure, maps 1:1 with a crtc
|
||||
*
|
||||
* @dev: pointer to device object for subdrv device driver.
|
||||
* sub drivers such as display controller or hdmi driver,
|
||||
* have their own device object.
|
||||
* @ops: pointer to callbacks for exynos drm specific framebuffer.
|
||||
* these callbacks should be set by specific drivers such fimd
|
||||
* or hdmi driver and are used to control hardware global registers.
|
||||
* @overlay_ops: pointer to callbacks for exynos drm specific framebuffer.
|
||||
* these callbacks should be set by specific drivers such fimd
|
||||
* or hdmi driver and are used to control hardware overlay reigsters.
|
||||
* @display: pointer to callbacks for exynos drm specific framebuffer.
|
||||
* these callbacks should be set by specific drivers such fimd
|
||||
* or hdmi driver and are used to control display devices such as
|
||||
* analog tv, digital tv and lcd panel and also get timing data for them.
|
||||
* @list: the list entry for this manager
|
||||
* @type: one of EXYNOS_DISPLAY_TYPE_LCD and HDMI.
|
||||
* @drm_dev: pointer to the drm device
|
||||
* @pipe: the pipe number for this crtc/manager
|
||||
* @ops: pointer to callbacks for exynos drm specific functionality
|
||||
* @ctx: A pointer to the manager's implementation specific context
|
||||
*/
|
||||
struct exynos_drm_manager {
|
||||
struct device *dev;
|
||||
struct list_head list;
|
||||
enum exynos_drm_output_type type;
|
||||
struct drm_device *drm_dev;
|
||||
int pipe;
|
||||
struct exynos_drm_manager_ops *ops;
|
||||
struct exynos_drm_overlay_ops *overlay_ops;
|
||||
struct exynos_drm_display_ops *display_ops;
|
||||
void *ctx;
|
||||
};
|
||||
|
||||
struct exynos_drm_g2d_private {
|
||||
@ -271,14 +288,11 @@ struct exynos_drm_private {
|
||||
* by probe callback.
|
||||
* @open: this would be called with drm device file open.
|
||||
* @close: this would be called with drm device file close.
|
||||
* @encoder: encoder object owned by this sub driver.
|
||||
* @connector: connector object owned by this sub driver.
|
||||
*/
|
||||
struct exynos_drm_subdrv {
|
||||
struct list_head list;
|
||||
struct device *dev;
|
||||
struct drm_device *drm_dev;
|
||||
struct exynos_drm_manager *manager;
|
||||
|
||||
int (*probe)(struct drm_device *drm_dev, struct device *dev);
|
||||
void (*remove)(struct drm_device *drm_dev, struct device *dev);
|
||||
@ -286,9 +300,6 @@ struct exynos_drm_subdrv {
|
||||
struct drm_file *file);
|
||||
void (*close)(struct drm_device *drm_dev, struct device *dev,
|
||||
struct drm_file *file);
|
||||
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector *connector;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -303,6 +314,16 @@ int exynos_drm_device_register(struct drm_device *dev);
|
||||
*/
|
||||
int exynos_drm_device_unregister(struct drm_device *dev);
|
||||
|
||||
int exynos_drm_initialize_managers(struct drm_device *dev);
|
||||
void exynos_drm_remove_managers(struct drm_device *dev);
|
||||
int exynos_drm_initialize_displays(struct drm_device *dev);
|
||||
void exynos_drm_remove_displays(struct drm_device *dev);
|
||||
|
||||
int exynos_drm_manager_register(struct exynos_drm_manager *manager);
|
||||
int exynos_drm_manager_unregister(struct exynos_drm_manager *manager);
|
||||
int exynos_drm_display_register(struct exynos_drm_display *display);
|
||||
int exynos_drm_display_unregister(struct exynos_drm_display *display);
|
||||
|
||||
/*
|
||||
* this function would be called by sub drivers such as display controller
|
||||
* or hdmi driver to register this sub driver object to exynos drm driver
|
||||
@ -338,6 +359,16 @@ int exynos_platform_device_ipp_register(void);
|
||||
*/
|
||||
void exynos_platform_device_ipp_unregister(void);
|
||||
|
||||
#ifdef CONFIG_DRM_EXYNOS_DPI
|
||||
int exynos_dpi_probe(struct device *dev);
|
||||
int exynos_dpi_remove(struct device *dev);
|
||||
#else
|
||||
static inline int exynos_dpi_probe(struct device *dev) { return 0; }
|
||||
static inline int exynos_dpi_remove(struct device *dev) { return 0; }
|
||||
#endif
|
||||
|
||||
extern struct platform_driver dp_driver;
|
||||
extern struct platform_driver dsi_driver;
|
||||
extern struct platform_driver fimd_driver;
|
||||
extern struct platform_driver hdmi_driver;
|
||||
extern struct platform_driver mixer_driver;
|
||||
|
1524
drivers/gpu/drm/exynos/exynos_drm_dsi.c
Normal file
1524
drivers/gpu/drm/exynos/exynos_drm_dsi.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,6 @@
|
||||
|
||||
#include "exynos_drm_drv.h"
|
||||
#include "exynos_drm_encoder.h"
|
||||
#include "exynos_drm_connector.h"
|
||||
|
||||
#define to_exynos_encoder(x) container_of(x, struct exynos_drm_encoder,\
|
||||
drm_encoder)
|
||||
@ -26,72 +25,22 @@
|
||||
* exynos specific encoder structure.
|
||||
*
|
||||
* @drm_encoder: encoder object.
|
||||
* @manager: specific encoder has its own manager to control a hardware
|
||||
* appropriately and we can access a hardware drawing on this manager.
|
||||
* @dpms: store the encoder dpms value.
|
||||
* @updated: indicate whether overlay data updating is needed or not.
|
||||
* @display: the display structure that maps to this encoder
|
||||
*/
|
||||
struct exynos_drm_encoder {
|
||||
struct drm_crtc *old_crtc;
|
||||
struct drm_encoder drm_encoder;
|
||||
struct exynos_drm_manager *manager;
|
||||
int dpms;
|
||||
bool updated;
|
||||
struct exynos_drm_display *display;
|
||||
};
|
||||
|
||||
static void exynos_drm_connector_power(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
struct drm_device *dev = encoder->dev;
|
||||
struct drm_connector *connector;
|
||||
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
if (exynos_drm_best_encoder(connector) == encoder) {
|
||||
DRM_DEBUG_KMS("connector[%d] dpms[%d]\n",
|
||||
connector->base.id, mode);
|
||||
|
||||
exynos_drm_display_power(connector, mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos_drm_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
struct drm_device *dev = encoder->dev;
|
||||
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder);
|
||||
struct exynos_drm_manager_ops *manager_ops = manager->ops;
|
||||
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
|
||||
struct exynos_drm_display *display = exynos_encoder->display;
|
||||
|
||||
DRM_DEBUG_KMS("encoder dpms: %d\n", mode);
|
||||
|
||||
if (exynos_encoder->dpms == mode) {
|
||||
DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
|
||||
switch (mode) {
|
||||
case DRM_MODE_DPMS_ON:
|
||||
if (manager_ops && manager_ops->apply)
|
||||
if (!exynos_encoder->updated)
|
||||
manager_ops->apply(manager->dev);
|
||||
|
||||
exynos_drm_connector_power(encoder, mode);
|
||||
exynos_encoder->dpms = mode;
|
||||
break;
|
||||
case DRM_MODE_DPMS_STANDBY:
|
||||
case DRM_MODE_DPMS_SUSPEND:
|
||||
case DRM_MODE_DPMS_OFF:
|
||||
exynos_drm_connector_power(encoder, mode);
|
||||
exynos_encoder->dpms = mode;
|
||||
exynos_encoder->updated = false;
|
||||
break;
|
||||
default:
|
||||
DRM_ERROR("unspecified mode %d\n", mode);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
if (display->ops->dpms)
|
||||
display->ops->dpms(display, mode);
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -100,87 +49,31 @@ exynos_drm_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct drm_device *dev = encoder->dev;
|
||||
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
|
||||
struct exynos_drm_display *display = exynos_encoder->display;
|
||||
struct drm_connector *connector;
|
||||
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder);
|
||||
struct exynos_drm_manager_ops *manager_ops = manager->ops;
|
||||
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
if (connector->encoder == encoder)
|
||||
if (manager_ops && manager_ops->mode_fixup)
|
||||
manager_ops->mode_fixup(manager->dev, connector,
|
||||
mode, adjusted_mode);
|
||||
if (connector->encoder != encoder)
|
||||
continue;
|
||||
|
||||
if (display->ops->mode_fixup)
|
||||
display->ops->mode_fixup(display, connector, mode,
|
||||
adjusted_mode);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void disable_plane_to_crtc(struct drm_device *dev,
|
||||
struct drm_crtc *old_crtc,
|
||||
struct drm_crtc *new_crtc)
|
||||
{
|
||||
struct drm_plane *plane;
|
||||
|
||||
/*
|
||||
* if old_crtc isn't same as encoder->crtc then it means that
|
||||
* user changed crtc id to another one so the plane to old_crtc
|
||||
* should be disabled and plane->crtc should be set to new_crtc
|
||||
* (encoder->crtc)
|
||||
*/
|
||||
list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
|
||||
if (plane->crtc == old_crtc) {
|
||||
/*
|
||||
* do not change below call order.
|
||||
*
|
||||
* plane->funcs->disable_plane call checks
|
||||
* if encoder->crtc is same as plane->crtc and if same
|
||||
* then overlay_ops->disable callback will be called
|
||||
* to diasble current hw overlay so plane->crtc should
|
||||
* have new_crtc because new_crtc was set to
|
||||
* encoder->crtc in advance.
|
||||
*/
|
||||
plane->crtc = new_crtc;
|
||||
plane->funcs->disable_plane(plane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos_drm_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct drm_device *dev = encoder->dev;
|
||||
struct drm_connector *connector;
|
||||
struct exynos_drm_manager *manager;
|
||||
struct exynos_drm_manager_ops *manager_ops;
|
||||
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
|
||||
struct exynos_drm_display *display = exynos_encoder->display;
|
||||
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
if (connector->encoder == encoder) {
|
||||
struct exynos_drm_encoder *exynos_encoder;
|
||||
|
||||
exynos_encoder = to_exynos_encoder(encoder);
|
||||
|
||||
if (exynos_encoder->old_crtc != encoder->crtc &&
|
||||
exynos_encoder->old_crtc) {
|
||||
|
||||
/*
|
||||
* disable a plane to old crtc and change
|
||||
* crtc of the plane to new one.
|
||||
*/
|
||||
disable_plane_to_crtc(dev,
|
||||
exynos_encoder->old_crtc,
|
||||
encoder->crtc);
|
||||
}
|
||||
|
||||
manager = exynos_drm_get_manager(encoder);
|
||||
manager_ops = manager->ops;
|
||||
|
||||
if (manager_ops && manager_ops->mode_set)
|
||||
manager_ops->mode_set(manager->dev,
|
||||
adjusted_mode);
|
||||
|
||||
exynos_encoder->old_crtc = encoder->crtc;
|
||||
}
|
||||
}
|
||||
if (display->ops->mode_set)
|
||||
display->ops->mode_set(display, adjusted_mode);
|
||||
}
|
||||
|
||||
static void exynos_drm_encoder_prepare(struct drm_encoder *encoder)
|
||||
@ -191,53 +84,15 @@ static void exynos_drm_encoder_prepare(struct drm_encoder *encoder)
|
||||
static void exynos_drm_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
|
||||
struct exynos_drm_manager *manager = exynos_encoder->manager;
|
||||
struct exynos_drm_manager_ops *manager_ops = manager->ops;
|
||||
struct exynos_drm_display *display = exynos_encoder->display;
|
||||
|
||||
if (manager_ops && manager_ops->commit)
|
||||
manager_ops->commit(manager->dev);
|
||||
if (display->ops->dpms)
|
||||
display->ops->dpms(display, DRM_MODE_DPMS_ON);
|
||||
|
||||
/*
|
||||
* this will avoid one issue that overlay data is updated to
|
||||
* real hardware two times.
|
||||
* And this variable will be used to check if the data was
|
||||
* already updated or not by exynos_drm_encoder_dpms function.
|
||||
*/
|
||||
exynos_encoder->updated = true;
|
||||
|
||||
/*
|
||||
* In case of setcrtc, there is no way to update encoder's dpms
|
||||
* so update it here.
|
||||
*/
|
||||
exynos_encoder->dpms = DRM_MODE_DPMS_ON;
|
||||
if (display->ops->commit)
|
||||
display->ops->commit(display);
|
||||
}
|
||||
|
||||
void exynos_drm_encoder_complete_scanout(struct drm_framebuffer *fb)
|
||||
{
|
||||
struct exynos_drm_encoder *exynos_encoder;
|
||||
struct exynos_drm_manager_ops *ops;
|
||||
struct drm_device *dev = fb->dev;
|
||||
struct drm_encoder *encoder;
|
||||
|
||||
/*
|
||||
* make sure that overlay data are updated to real hardware
|
||||
* for all encoders.
|
||||
*/
|
||||
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
||||
exynos_encoder = to_exynos_encoder(encoder);
|
||||
ops = exynos_encoder->manager->ops;
|
||||
|
||||
/*
|
||||
* wait for vblank interrupt
|
||||
* - this makes sure that overlay data are updated to
|
||||
* real hardware.
|
||||
*/
|
||||
if (ops->wait_for_vblank)
|
||||
ops->wait_for_vblank(exynos_encoder->manager->dev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void exynos_drm_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct drm_plane *plane;
|
||||
@ -246,7 +101,7 @@ static void exynos_drm_encoder_disable(struct drm_encoder *encoder)
|
||||
exynos_drm_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
||||
|
||||
/* all planes connected to this encoder should be also disabled. */
|
||||
list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
|
||||
drm_for_each_legacy_plane(plane, &dev->mode_config.plane_list) {
|
||||
if (plane->crtc == encoder->crtc)
|
||||
plane->funcs->disable_plane(plane);
|
||||
}
|
||||
@ -263,10 +118,7 @@ static struct drm_encoder_helper_funcs exynos_encoder_helper_funcs = {
|
||||
|
||||
static void exynos_drm_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
struct exynos_drm_encoder *exynos_encoder =
|
||||
to_exynos_encoder(encoder);
|
||||
|
||||
exynos_encoder->manager->pipe = -1;
|
||||
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
|
||||
|
||||
drm_encoder_cleanup(encoder);
|
||||
kfree(exynos_encoder);
|
||||
@ -281,13 +133,12 @@ static unsigned int exynos_drm_encoder_clones(struct drm_encoder *encoder)
|
||||
struct drm_encoder *clone;
|
||||
struct drm_device *dev = encoder->dev;
|
||||
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
|
||||
struct exynos_drm_display_ops *display_ops =
|
||||
exynos_encoder->manager->display_ops;
|
||||
struct exynos_drm_display *display = exynos_encoder->display;
|
||||
unsigned int clone_mask = 0;
|
||||
int cnt = 0;
|
||||
|
||||
list_for_each_entry(clone, &dev->mode_config.encoder_list, head) {
|
||||
switch (display_ops->type) {
|
||||
switch (display->type) {
|
||||
case EXYNOS_DISPLAY_TYPE_LCD:
|
||||
case EXYNOS_DISPLAY_TYPE_HDMI:
|
||||
case EXYNOS_DISPLAY_TYPE_VIDI:
|
||||
@ -311,24 +162,20 @@ void exynos_drm_encoder_setup(struct drm_device *dev)
|
||||
|
||||
struct drm_encoder *
|
||||
exynos_drm_encoder_create(struct drm_device *dev,
|
||||
struct exynos_drm_manager *manager,
|
||||
unsigned int possible_crtcs)
|
||||
struct exynos_drm_display *display,
|
||||
unsigned long possible_crtcs)
|
||||
{
|
||||
struct drm_encoder *encoder;
|
||||
struct exynos_drm_encoder *exynos_encoder;
|
||||
|
||||
if (!manager || !possible_crtcs)
|
||||
return NULL;
|
||||
|
||||
if (!manager->dev)
|
||||
if (!possible_crtcs)
|
||||
return NULL;
|
||||
|
||||
exynos_encoder = kzalloc(sizeof(*exynos_encoder), GFP_KERNEL);
|
||||
if (!exynos_encoder)
|
||||
return NULL;
|
||||
|
||||
exynos_encoder->dpms = DRM_MODE_DPMS_OFF;
|
||||
exynos_encoder->manager = manager;
|
||||
exynos_encoder->display = display;
|
||||
encoder = &exynos_encoder->drm_encoder;
|
||||
encoder->possible_crtcs = possible_crtcs;
|
||||
|
||||
@ -344,149 +191,7 @@ exynos_drm_encoder_create(struct drm_device *dev,
|
||||
return encoder;
|
||||
}
|
||||
|
||||
struct exynos_drm_manager *exynos_drm_get_manager(struct drm_encoder *encoder)
|
||||
struct exynos_drm_display *exynos_drm_get_display(struct drm_encoder *encoder)
|
||||
{
|
||||
return to_exynos_encoder(encoder)->manager;
|
||||
}
|
||||
|
||||
void exynos_drm_fn_encoder(struct drm_crtc *crtc, void *data,
|
||||
void (*fn)(struct drm_encoder *, void *))
|
||||
{
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_encoder *encoder;
|
||||
struct exynos_drm_private *private = dev->dev_private;
|
||||
struct exynos_drm_manager *manager;
|
||||
|
||||
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
|
||||
/*
|
||||
* if crtc is detached from encoder, check pipe,
|
||||
* otherwise check crtc attached to encoder
|
||||
*/
|
||||
if (!encoder->crtc) {
|
||||
manager = to_exynos_encoder(encoder)->manager;
|
||||
if (manager->pipe < 0 ||
|
||||
private->crtc[manager->pipe] != crtc)
|
||||
continue;
|
||||
} else {
|
||||
if (encoder->crtc != crtc)
|
||||
continue;
|
||||
}
|
||||
|
||||
fn(encoder, data);
|
||||
}
|
||||
}
|
||||
|
||||
void exynos_drm_enable_vblank(struct drm_encoder *encoder, void *data)
|
||||
{
|
||||
struct exynos_drm_manager *manager =
|
||||
to_exynos_encoder(encoder)->manager;
|
||||
struct exynos_drm_manager_ops *manager_ops = manager->ops;
|
||||
int crtc = *(int *)data;
|
||||
|
||||
if (manager->pipe != crtc)
|
||||
return;
|
||||
|
||||
if (manager_ops->enable_vblank)
|
||||
manager_ops->enable_vblank(manager->dev);
|
||||
}
|
||||
|
||||
void exynos_drm_disable_vblank(struct drm_encoder *encoder, void *data)
|
||||
{
|
||||
struct exynos_drm_manager *manager =
|
||||
to_exynos_encoder(encoder)->manager;
|
||||
struct exynos_drm_manager_ops *manager_ops = manager->ops;
|
||||
int crtc = *(int *)data;
|
||||
|
||||
if (manager->pipe != crtc)
|
||||
return;
|
||||
|
||||
if (manager_ops->disable_vblank)
|
||||
manager_ops->disable_vblank(manager->dev);
|
||||
}
|
||||
|
||||
void exynos_drm_encoder_crtc_dpms(struct drm_encoder *encoder, void *data)
|
||||
{
|
||||
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
|
||||
struct exynos_drm_manager *manager = exynos_encoder->manager;
|
||||
struct exynos_drm_manager_ops *manager_ops = manager->ops;
|
||||
int mode = *(int *)data;
|
||||
|
||||
if (manager_ops && manager_ops->dpms)
|
||||
manager_ops->dpms(manager->dev, mode);
|
||||
|
||||
/*
|
||||
* if this condition is ok then it means that the crtc is already
|
||||
* detached from encoder and last function for detaching is properly
|
||||
* done, so clear pipe from manager to prevent repeated call.
|
||||
*/
|
||||
if (mode > DRM_MODE_DPMS_ON) {
|
||||
if (!encoder->crtc)
|
||||
manager->pipe = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void exynos_drm_encoder_crtc_pipe(struct drm_encoder *encoder, void *data)
|
||||
{
|
||||
struct exynos_drm_manager *manager =
|
||||
to_exynos_encoder(encoder)->manager;
|
||||
int pipe = *(int *)data;
|
||||
|
||||
/*
|
||||
* when crtc is detached from encoder, this pipe is used
|
||||
* to select manager operation
|
||||
*/
|
||||
manager->pipe = pipe;
|
||||
}
|
||||
|
||||
void exynos_drm_encoder_plane_mode_set(struct drm_encoder *encoder, void *data)
|
||||
{
|
||||
struct exynos_drm_manager *manager =
|
||||
to_exynos_encoder(encoder)->manager;
|
||||
struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops;
|
||||
struct exynos_drm_overlay *overlay = data;
|
||||
|
||||
if (overlay_ops && overlay_ops->mode_set)
|
||||
overlay_ops->mode_set(manager->dev, overlay);
|
||||
}
|
||||
|
||||
void exynos_drm_encoder_plane_commit(struct drm_encoder *encoder, void *data)
|
||||
{
|
||||
struct exynos_drm_manager *manager =
|
||||
to_exynos_encoder(encoder)->manager;
|
||||
struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops;
|
||||
int zpos = DEFAULT_ZPOS;
|
||||
|
||||
if (data)
|
||||
zpos = *(int *)data;
|
||||
|
||||
if (overlay_ops && overlay_ops->commit)
|
||||
overlay_ops->commit(manager->dev, zpos);
|
||||
}
|
||||
|
||||
void exynos_drm_encoder_plane_enable(struct drm_encoder *encoder, void *data)
|
||||
{
|
||||
struct exynos_drm_manager *manager =
|
||||
to_exynos_encoder(encoder)->manager;
|
||||
struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops;
|
||||
int zpos = DEFAULT_ZPOS;
|
||||
|
||||
if (data)
|
||||
zpos = *(int *)data;
|
||||
|
||||
if (overlay_ops && overlay_ops->enable)
|
||||
overlay_ops->enable(manager->dev, zpos);
|
||||
}
|
||||
|
||||
void exynos_drm_encoder_plane_disable(struct drm_encoder *encoder, void *data)
|
||||
{
|
||||
struct exynos_drm_manager *manager =
|
||||
to_exynos_encoder(encoder)->manager;
|
||||
struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops;
|
||||
int zpos = DEFAULT_ZPOS;
|
||||
|
||||
if (data)
|
||||
zpos = *(int *)data;
|
||||
|
||||
if (overlay_ops && overlay_ops->disable)
|
||||
overlay_ops->disable(manager->dev, zpos);
|
||||
return to_exynos_encoder(encoder)->display;
|
||||
}
|
||||
|
@ -18,20 +18,8 @@ struct exynos_drm_manager;
|
||||
|
||||
void exynos_drm_encoder_setup(struct drm_device *dev);
|
||||
struct drm_encoder *exynos_drm_encoder_create(struct drm_device *dev,
|
||||
struct exynos_drm_manager *mgr,
|
||||
unsigned int possible_crtcs);
|
||||
struct exynos_drm_manager *
|
||||
exynos_drm_get_manager(struct drm_encoder *encoder);
|
||||
void exynos_drm_fn_encoder(struct drm_crtc *crtc, void *data,
|
||||
void (*fn)(struct drm_encoder *, void *));
|
||||
void exynos_drm_enable_vblank(struct drm_encoder *encoder, void *data);
|
||||
void exynos_drm_disable_vblank(struct drm_encoder *encoder, void *data);
|
||||
void exynos_drm_encoder_crtc_dpms(struct drm_encoder *encoder, void *data);
|
||||
void exynos_drm_encoder_crtc_pipe(struct drm_encoder *encoder, void *data);
|
||||
void exynos_drm_encoder_plane_mode_set(struct drm_encoder *encoder, void *data);
|
||||
void exynos_drm_encoder_plane_commit(struct drm_encoder *encoder, void *data);
|
||||
void exynos_drm_encoder_plane_enable(struct drm_encoder *encoder, void *data);
|
||||
void exynos_drm_encoder_plane_disable(struct drm_encoder *encoder, void *data);
|
||||
void exynos_drm_encoder_complete_scanout(struct drm_framebuffer *fb);
|
||||
struct exynos_drm_display *mgr,
|
||||
unsigned long possible_crtcs);
|
||||
struct exynos_drm_display *exynos_drm_get_display(struct drm_encoder *encoder);
|
||||
|
||||
#endif
|
||||
|
@ -20,9 +20,10 @@
|
||||
|
||||
#include "exynos_drm_drv.h"
|
||||
#include "exynos_drm_fb.h"
|
||||
#include "exynos_drm_fbdev.h"
|
||||
#include "exynos_drm_gem.h"
|
||||
#include "exynos_drm_iommu.h"
|
||||
#include "exynos_drm_encoder.h"
|
||||
#include "exynos_drm_crtc.h"
|
||||
|
||||
#define to_exynos_fb(x) container_of(x, struct exynos_drm_fb, fb)
|
||||
|
||||
@ -71,7 +72,7 @@ static void exynos_drm_fb_destroy(struct drm_framebuffer *fb)
|
||||
unsigned int i;
|
||||
|
||||
/* make sure that overlay data are updated before relesing fb. */
|
||||
exynos_drm_encoder_complete_scanout(fb);
|
||||
exynos_drm_crtc_complete_scanout(fb);
|
||||
|
||||
drm_framebuffer_cleanup(fb);
|
||||
|
||||
@ -300,6 +301,8 @@ static void exynos_drm_output_poll_changed(struct drm_device *dev)
|
||||
|
||||
if (fb_helper)
|
||||
drm_fb_helper_hotplug_event(fb_helper);
|
||||
else
|
||||
exynos_drm_fbdev_init(dev);
|
||||
}
|
||||
|
||||
static const struct drm_mode_config_funcs exynos_drm_mode_config_funcs = {
|
||||
|
@ -90,7 +90,7 @@ static int exynos_drm_fbdev_update(struct drm_fb_helper *helper,
|
||||
/* RGB formats use only one buffer */
|
||||
buffer = exynos_drm_fb_buffer(fb, 0);
|
||||
if (!buffer) {
|
||||
DRM_LOG_KMS("buffer is null.\n");
|
||||
DRM_DEBUG_KMS("buffer is null.\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
@ -237,6 +237,24 @@ static struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = {
|
||||
.fb_probe = exynos_drm_fbdev_create,
|
||||
};
|
||||
|
||||
bool exynos_drm_fbdev_is_anything_connected(struct drm_device *dev)
|
||||
{
|
||||
struct drm_connector *connector;
|
||||
bool ret = false;
|
||||
|
||||
mutex_lock(&dev->mode_config.mutex);
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
if (connector->status != connector_status_connected)
|
||||
continue;
|
||||
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int exynos_drm_fbdev_init(struct drm_device *dev)
|
||||
{
|
||||
struct exynos_drm_fbdev *fbdev;
|
||||
@ -248,6 +266,9 @@ int exynos_drm_fbdev_init(struct drm_device *dev)
|
||||
if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector)
|
||||
return 0;
|
||||
|
||||
if (!exynos_drm_fbdev_is_anything_connected(dev))
|
||||
return 0;
|
||||
|
||||
fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
|
||||
if (!fbdev)
|
||||
return -ENOMEM;
|
||||
|
@ -62,7 +62,7 @@
|
||||
/* FIMD has totally five hardware windows. */
|
||||
#define WINDOWS_NR 5
|
||||
|
||||
#define get_fimd_context(dev) platform_get_drvdata(to_platform_device(dev))
|
||||
#define get_fimd_manager(mgr) platform_get_drvdata(to_platform_device(dev))
|
||||
|
||||
struct fimd_driver_data {
|
||||
unsigned int timing_base;
|
||||
@ -105,20 +105,18 @@ struct fimd_win_data {
|
||||
};
|
||||
|
||||
struct fimd_context {
|
||||
struct exynos_drm_subdrv subdrv;
|
||||
int irq;
|
||||
struct drm_crtc *crtc;
|
||||
struct device *dev;
|
||||
struct drm_device *drm_dev;
|
||||
struct clk *bus_clk;
|
||||
struct clk *lcd_clk;
|
||||
void __iomem *regs;
|
||||
struct drm_display_mode mode;
|
||||
struct fimd_win_data win_data[WINDOWS_NR];
|
||||
unsigned int clkdiv;
|
||||
unsigned int default_win;
|
||||
unsigned long irq_flags;
|
||||
u32 vidcon0;
|
||||
u32 vidcon1;
|
||||
bool suspended;
|
||||
struct mutex lock;
|
||||
int pipe;
|
||||
wait_queue_head_t wait_vsync_queue;
|
||||
atomic_t wait_vsync_event;
|
||||
|
||||
@ -145,153 +143,147 @@ static inline struct fimd_driver_data *drm_fimd_get_driver_data(
|
||||
return (struct fimd_driver_data *)of_id->data;
|
||||
}
|
||||
|
||||
static bool fimd_display_is_connected(struct device *dev)
|
||||
static int fimd_mgr_initialize(struct exynos_drm_manager *mgr,
|
||||
struct drm_device *drm_dev, int pipe)
|
||||
{
|
||||
/* TODO. */
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
|
||||
ctx->drm_dev = drm_dev;
|
||||
ctx->pipe = pipe;
|
||||
|
||||
/*
|
||||
* enable drm irq mode.
|
||||
* - with irq_enabled = true, we can use the vblank feature.
|
||||
*
|
||||
* P.S. note that we wouldn't use drm irq handler but
|
||||
* just specific driver own one instead because
|
||||
* drm framework supports only one irq handler.
|
||||
*/
|
||||
drm_dev->irq_enabled = true;
|
||||
|
||||
/*
|
||||
* with vblank_disable_allowed = true, vblank interrupt will be disabled
|
||||
* by drm timer once a current process gives up ownership of
|
||||
* vblank event.(after drm_vblank_put function is called)
|
||||
*/
|
||||
drm_dev->vblank_disable_allowed = true;
|
||||
|
||||
/* attach this sub driver to iommu mapping if supported. */
|
||||
if (is_drm_iommu_supported(ctx->drm_dev))
|
||||
drm_iommu_attach_device(ctx->drm_dev, ctx->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fimd_mgr_remove(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
|
||||
/* detach this sub driver from iommu mapping if supported. */
|
||||
if (is_drm_iommu_supported(ctx->drm_dev))
|
||||
drm_iommu_detach_device(ctx->drm_dev, ctx->dev);
|
||||
}
|
||||
|
||||
static u32 fimd_calc_clkdiv(struct fimd_context *ctx,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
unsigned long ideal_clk = mode->htotal * mode->vtotal * mode->vrefresh;
|
||||
u32 clkdiv;
|
||||
|
||||
/* Find the clock divider value that gets us closest to ideal_clk */
|
||||
clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->lcd_clk), ideal_clk);
|
||||
|
||||
return (clkdiv < 0x100) ? clkdiv : 0xff;
|
||||
}
|
||||
|
||||
static bool fimd_mode_fixup(struct exynos_drm_manager *mgr,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
if (adjusted_mode->vrefresh == 0)
|
||||
adjusted_mode->vrefresh = FIMD_DEFAULT_FRAMERATE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void *fimd_get_panel(struct device *dev)
|
||||
static void fimd_mode_set(struct exynos_drm_manager *mgr,
|
||||
const struct drm_display_mode *in_mode)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
|
||||
return &ctx->panel;
|
||||
drm_mode_copy(&ctx->mode, in_mode);
|
||||
}
|
||||
|
||||
static int fimd_check_mode(struct device *dev, struct drm_display_mode *mode)
|
||||
static void fimd_commit(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
/* TODO. */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fimd_display_power_on(struct device *dev, int mode)
|
||||
{
|
||||
/* TODO */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct exynos_drm_display_ops fimd_display_ops = {
|
||||
.type = EXYNOS_DISPLAY_TYPE_LCD,
|
||||
.is_connected = fimd_display_is_connected,
|
||||
.get_panel = fimd_get_panel,
|
||||
.check_mode = fimd_check_mode,
|
||||
.power_on = fimd_display_power_on,
|
||||
};
|
||||
|
||||
static void fimd_dpms(struct device *subdrv_dev, int mode)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(subdrv_dev);
|
||||
|
||||
DRM_DEBUG_KMS("%d\n", mode);
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
|
||||
switch (mode) {
|
||||
case DRM_MODE_DPMS_ON:
|
||||
/*
|
||||
* enable fimd hardware only if suspended status.
|
||||
*
|
||||
* P.S. fimd_dpms function would be called at booting time so
|
||||
* clk_enable could be called double time.
|
||||
*/
|
||||
if (ctx->suspended)
|
||||
pm_runtime_get_sync(subdrv_dev);
|
||||
break;
|
||||
case DRM_MODE_DPMS_STANDBY:
|
||||
case DRM_MODE_DPMS_SUSPEND:
|
||||
case DRM_MODE_DPMS_OFF:
|
||||
if (!ctx->suspended)
|
||||
pm_runtime_put_sync(subdrv_dev);
|
||||
break;
|
||||
default:
|
||||
DRM_DEBUG_KMS("unspecified mode %d\n", mode);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
static void fimd_apply(struct device *subdrv_dev)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(subdrv_dev);
|
||||
struct exynos_drm_manager *mgr = ctx->subdrv.manager;
|
||||
struct exynos_drm_manager_ops *mgr_ops = mgr->ops;
|
||||
struct exynos_drm_overlay_ops *ovl_ops = mgr->overlay_ops;
|
||||
struct fimd_win_data *win_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < WINDOWS_NR; i++) {
|
||||
win_data = &ctx->win_data[i];
|
||||
if (win_data->enabled && (ovl_ops && ovl_ops->commit))
|
||||
ovl_ops->commit(subdrv_dev, i);
|
||||
}
|
||||
|
||||
if (mgr_ops && mgr_ops->commit)
|
||||
mgr_ops->commit(subdrv_dev);
|
||||
}
|
||||
|
||||
static void fimd_commit(struct device *dev)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct exynos_drm_panel_info *panel = &ctx->panel;
|
||||
struct videomode *vm = &panel->vm;
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
struct drm_display_mode *mode = &ctx->mode;
|
||||
struct fimd_driver_data *driver_data;
|
||||
u32 val;
|
||||
u32 val, clkdiv, vidcon1;
|
||||
int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd;
|
||||
|
||||
driver_data = ctx->driver_data;
|
||||
if (ctx->suspended)
|
||||
return;
|
||||
|
||||
/* setup polarity values from machine code. */
|
||||
writel(ctx->vidcon1, ctx->regs + driver_data->timing_base + VIDCON1);
|
||||
/* nothing to do if we haven't set the mode yet */
|
||||
if (mode->htotal == 0 || mode->vtotal == 0)
|
||||
return;
|
||||
|
||||
/* setup polarity values */
|
||||
vidcon1 = ctx->vidcon1;
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
vidcon1 |= VIDCON1_INV_VSYNC;
|
||||
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
|
||||
vidcon1 |= VIDCON1_INV_HSYNC;
|
||||
writel(vidcon1, ctx->regs + driver_data->timing_base + VIDCON1);
|
||||
|
||||
/* setup vertical timing values. */
|
||||
val = VIDTCON0_VBPD(vm->vback_porch - 1) |
|
||||
VIDTCON0_VFPD(vm->vfront_porch - 1) |
|
||||
VIDTCON0_VSPW(vm->vsync_len - 1);
|
||||
vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
|
||||
vbpd = mode->crtc_vtotal - mode->crtc_vsync_end;
|
||||
vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay;
|
||||
|
||||
val = VIDTCON0_VBPD(vbpd - 1) |
|
||||
VIDTCON0_VFPD(vfpd - 1) |
|
||||
VIDTCON0_VSPW(vsync_len - 1);
|
||||
writel(val, ctx->regs + driver_data->timing_base + VIDTCON0);
|
||||
|
||||
/* setup horizontal timing values. */
|
||||
val = VIDTCON1_HBPD(vm->hback_porch - 1) |
|
||||
VIDTCON1_HFPD(vm->hfront_porch - 1) |
|
||||
VIDTCON1_HSPW(vm->hsync_len - 1);
|
||||
hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
|
||||
hbpd = mode->crtc_htotal - mode->crtc_hsync_end;
|
||||
hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay;
|
||||
|
||||
val = VIDTCON1_HBPD(hbpd - 1) |
|
||||
VIDTCON1_HFPD(hfpd - 1) |
|
||||
VIDTCON1_HSPW(hsync_len - 1);
|
||||
writel(val, ctx->regs + driver_data->timing_base + VIDTCON1);
|
||||
|
||||
/* setup horizontal and vertical display size. */
|
||||
val = VIDTCON2_LINEVAL(vm->vactive - 1) |
|
||||
VIDTCON2_HOZVAL(vm->hactive - 1) |
|
||||
VIDTCON2_LINEVAL_E(vm->vactive - 1) |
|
||||
VIDTCON2_HOZVAL_E(vm->hactive - 1);
|
||||
val = VIDTCON2_LINEVAL(mode->vdisplay - 1) |
|
||||
VIDTCON2_HOZVAL(mode->hdisplay - 1) |
|
||||
VIDTCON2_LINEVAL_E(mode->vdisplay - 1) |
|
||||
VIDTCON2_HOZVAL_E(mode->hdisplay - 1);
|
||||
writel(val, ctx->regs + driver_data->timing_base + VIDTCON2);
|
||||
|
||||
/* setup clock source, clock divider, enable dma. */
|
||||
val = ctx->vidcon0;
|
||||
val &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR);
|
||||
|
||||
if (ctx->driver_data->has_clksel) {
|
||||
val &= ~VIDCON0_CLKSEL_MASK;
|
||||
val |= VIDCON0_CLKSEL_LCD;
|
||||
}
|
||||
|
||||
if (ctx->clkdiv > 1)
|
||||
val |= VIDCON0_CLKVAL_F(ctx->clkdiv - 1) | VIDCON0_CLKDIR;
|
||||
else
|
||||
val &= ~VIDCON0_CLKDIR; /* 1:1 clock */
|
||||
|
||||
/*
|
||||
* fields of register with prefix '_F' would be updated
|
||||
* at vsync(same as dma start)
|
||||
*/
|
||||
val |= VIDCON0_ENVID | VIDCON0_ENVID_F;
|
||||
val = VIDCON0_ENVID | VIDCON0_ENVID_F;
|
||||
|
||||
if (ctx->driver_data->has_clksel)
|
||||
val |= VIDCON0_CLKSEL_LCD;
|
||||
|
||||
clkdiv = fimd_calc_clkdiv(ctx, mode);
|
||||
if (clkdiv > 1)
|
||||
val |= VIDCON0_CLKVAL_F(clkdiv - 1) | VIDCON0_CLKDIR;
|
||||
|
||||
writel(val, ctx->regs + VIDCON0);
|
||||
}
|
||||
|
||||
static int fimd_enable_vblank(struct device *dev)
|
||||
static int fimd_enable_vblank(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
u32 val;
|
||||
|
||||
if (ctx->suspended)
|
||||
@ -314,9 +306,9 @@ static int fimd_enable_vblank(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fimd_disable_vblank(struct device *dev)
|
||||
static void fimd_disable_vblank(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
u32 val;
|
||||
|
||||
if (ctx->suspended)
|
||||
@ -332,9 +324,9 @@ static void fimd_disable_vblank(struct device *dev)
|
||||
}
|
||||
}
|
||||
|
||||
static void fimd_wait_for_vblank(struct device *dev)
|
||||
static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
|
||||
if (ctx->suspended)
|
||||
return;
|
||||
@ -351,25 +343,16 @@ static void fimd_wait_for_vblank(struct device *dev)
|
||||
DRM_DEBUG_KMS("vblank wait timed out.\n");
|
||||
}
|
||||
|
||||
static struct exynos_drm_manager_ops fimd_manager_ops = {
|
||||
.dpms = fimd_dpms,
|
||||
.apply = fimd_apply,
|
||||
.commit = fimd_commit,
|
||||
.enable_vblank = fimd_enable_vblank,
|
||||
.disable_vblank = fimd_disable_vblank,
|
||||
.wait_for_vblank = fimd_wait_for_vblank,
|
||||
};
|
||||
|
||||
static void fimd_win_mode_set(struct device *dev,
|
||||
struct exynos_drm_overlay *overlay)
|
||||
static void fimd_win_mode_set(struct exynos_drm_manager *mgr,
|
||||
struct exynos_drm_overlay *overlay)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
struct fimd_win_data *win_data;
|
||||
int win;
|
||||
unsigned long offset;
|
||||
|
||||
if (!overlay) {
|
||||
dev_err(dev, "overlay is NULL\n");
|
||||
DRM_ERROR("overlay is NULL\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -409,9 +392,8 @@ static void fimd_win_mode_set(struct device *dev,
|
||||
overlay->fb_width, overlay->crtc_width);
|
||||
}
|
||||
|
||||
static void fimd_win_set_pixfmt(struct device *dev, unsigned int win)
|
||||
static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_win_data *win_data = &ctx->win_data[win];
|
||||
unsigned long val;
|
||||
|
||||
@ -467,9 +449,8 @@ static void fimd_win_set_pixfmt(struct device *dev, unsigned int win)
|
||||
writel(val, ctx->regs + WINCON(win));
|
||||
}
|
||||
|
||||
static void fimd_win_set_colkey(struct device *dev, unsigned int win)
|
||||
static void fimd_win_set_colkey(struct fimd_context *ctx, unsigned int win)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
unsigned int keycon0 = 0, keycon1 = 0;
|
||||
|
||||
keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F |
|
||||
@ -508,9 +489,9 @@ static void fimd_shadow_protect_win(struct fimd_context *ctx,
|
||||
writel(val, ctx->regs + reg);
|
||||
}
|
||||
|
||||
static void fimd_win_commit(struct device *dev, int zpos)
|
||||
static void fimd_win_commit(struct exynos_drm_manager *mgr, int zpos)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
struct fimd_win_data *win_data;
|
||||
int win = zpos;
|
||||
unsigned long val, alpha, size;
|
||||
@ -528,6 +509,12 @@ static void fimd_win_commit(struct device *dev, int zpos)
|
||||
|
||||
win_data = &ctx->win_data[win];
|
||||
|
||||
/* If suspended, enable this on resume */
|
||||
if (ctx->suspended) {
|
||||
win_data->resume = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* SHADOWCON/PRTCON register is used for enabling timing.
|
||||
*
|
||||
@ -605,11 +592,11 @@ static void fimd_win_commit(struct device *dev, int zpos)
|
||||
DRM_DEBUG_KMS("osd size = 0x%x\n", (unsigned int)val);
|
||||
}
|
||||
|
||||
fimd_win_set_pixfmt(dev, win);
|
||||
fimd_win_set_pixfmt(ctx, win);
|
||||
|
||||
/* hardware window 0 doesn't support color key. */
|
||||
if (win != 0)
|
||||
fimd_win_set_colkey(dev, win);
|
||||
fimd_win_set_colkey(ctx, win);
|
||||
|
||||
/* wincon */
|
||||
val = readl(ctx->regs + WINCON(win));
|
||||
@ -628,9 +615,9 @@ static void fimd_win_commit(struct device *dev, int zpos)
|
||||
win_data->enabled = true;
|
||||
}
|
||||
|
||||
static void fimd_win_disable(struct device *dev, int zpos)
|
||||
static void fimd_win_disable(struct exynos_drm_manager *mgr, int zpos)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
struct fimd_win_data *win_data;
|
||||
int win = zpos;
|
||||
u32 val;
|
||||
@ -669,132 +656,6 @@ static void fimd_win_disable(struct device *dev, int zpos)
|
||||
win_data->enabled = false;
|
||||
}
|
||||
|
||||
static struct exynos_drm_overlay_ops fimd_overlay_ops = {
|
||||
.mode_set = fimd_win_mode_set,
|
||||
.commit = fimd_win_commit,
|
||||
.disable = fimd_win_disable,
|
||||
};
|
||||
|
||||
static struct exynos_drm_manager fimd_manager = {
|
||||
.pipe = -1,
|
||||
.ops = &fimd_manager_ops,
|
||||
.overlay_ops = &fimd_overlay_ops,
|
||||
.display_ops = &fimd_display_ops,
|
||||
};
|
||||
|
||||
static irqreturn_t fimd_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct fimd_context *ctx = (struct fimd_context *)dev_id;
|
||||
struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
|
||||
struct drm_device *drm_dev = subdrv->drm_dev;
|
||||
struct exynos_drm_manager *manager = subdrv->manager;
|
||||
u32 val;
|
||||
|
||||
val = readl(ctx->regs + VIDINTCON1);
|
||||
|
||||
if (val & VIDINTCON1_INT_FRAME)
|
||||
/* VSYNC interrupt */
|
||||
writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1);
|
||||
|
||||
/* check the crtc is detached already from encoder */
|
||||
if (manager->pipe < 0)
|
||||
goto out;
|
||||
|
||||
drm_handle_vblank(drm_dev, manager->pipe);
|
||||
exynos_drm_crtc_finish_pageflip(drm_dev, manager->pipe);
|
||||
|
||||
/* set wait vsync event to zero and wake up queue. */
|
||||
if (atomic_read(&ctx->wait_vsync_event)) {
|
||||
atomic_set(&ctx->wait_vsync_event, 0);
|
||||
wake_up(&ctx->wait_vsync_queue);
|
||||
}
|
||||
out:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int fimd_subdrv_probe(struct drm_device *drm_dev, struct device *dev)
|
||||
{
|
||||
/*
|
||||
* enable drm irq mode.
|
||||
* - with irq_enabled = true, we can use the vblank feature.
|
||||
*
|
||||
* P.S. note that we wouldn't use drm irq handler but
|
||||
* just specific driver own one instead because
|
||||
* drm framework supports only one irq handler.
|
||||
*/
|
||||
drm_dev->irq_enabled = true;
|
||||
|
||||
/*
|
||||
* with vblank_disable_allowed = true, vblank interrupt will be disabled
|
||||
* by drm timer once a current process gives up ownership of
|
||||
* vblank event.(after drm_vblank_put function is called)
|
||||
*/
|
||||
drm_dev->vblank_disable_allowed = true;
|
||||
|
||||
/* attach this sub driver to iommu mapping if supported. */
|
||||
if (is_drm_iommu_supported(drm_dev))
|
||||
drm_iommu_attach_device(drm_dev, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fimd_subdrv_remove(struct drm_device *drm_dev, struct device *dev)
|
||||
{
|
||||
/* detach this sub driver from iommu mapping if supported. */
|
||||
if (is_drm_iommu_supported(drm_dev))
|
||||
drm_iommu_detach_device(drm_dev, dev);
|
||||
}
|
||||
|
||||
static int fimd_configure_clocks(struct fimd_context *ctx, struct device *dev)
|
||||
{
|
||||
struct videomode *vm = &ctx->panel.vm;
|
||||
unsigned long clk;
|
||||
|
||||
ctx->bus_clk = devm_clk_get(dev, "fimd");
|
||||
if (IS_ERR(ctx->bus_clk)) {
|
||||
dev_err(dev, "failed to get bus clock\n");
|
||||
return PTR_ERR(ctx->bus_clk);
|
||||
}
|
||||
|
||||
ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd");
|
||||
if (IS_ERR(ctx->lcd_clk)) {
|
||||
dev_err(dev, "failed to get lcd clock\n");
|
||||
return PTR_ERR(ctx->lcd_clk);
|
||||
}
|
||||
|
||||
clk = clk_get_rate(ctx->lcd_clk);
|
||||
if (clk == 0) {
|
||||
dev_err(dev, "error getting sclk_fimd clock rate\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (vm->pixelclock == 0) {
|
||||
unsigned long c;
|
||||
c = vm->hactive + vm->hback_porch + vm->hfront_porch +
|
||||
vm->hsync_len;
|
||||
c *= vm->vactive + vm->vback_porch + vm->vfront_porch +
|
||||
vm->vsync_len;
|
||||
vm->pixelclock = c * FIMD_DEFAULT_FRAMERATE;
|
||||
if (vm->pixelclock == 0) {
|
||||
dev_err(dev, "incorrect display timings\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
dev_warn(dev, "pixel clock recalculated to %luHz (%dHz frame rate)\n",
|
||||
vm->pixelclock, FIMD_DEFAULT_FRAMERATE);
|
||||
}
|
||||
ctx->clkdiv = DIV_ROUND_UP(clk, vm->pixelclock);
|
||||
if (ctx->clkdiv > 256) {
|
||||
dev_warn(dev, "calculated pixel clock divider too high (%u), lowered to 256\n",
|
||||
ctx->clkdiv);
|
||||
ctx->clkdiv = 256;
|
||||
}
|
||||
vm->pixelclock = clk / ctx->clkdiv;
|
||||
DRM_DEBUG_KMS("pixel clock = %lu, clkdiv = %d\n", vm->pixelclock,
|
||||
ctx->clkdiv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fimd_clear_win(struct fimd_context *ctx, int win)
|
||||
{
|
||||
writel(0, ctx->regs + WINCON(win));
|
||||
@ -808,45 +669,24 @@ static void fimd_clear_win(struct fimd_context *ctx, int win)
|
||||
fimd_shadow_protect_win(ctx, win, false);
|
||||
}
|
||||
|
||||
static int fimd_clock(struct fimd_context *ctx, bool enable)
|
||||
static void fimd_window_suspend(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
if (enable) {
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(ctx->bus_clk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(ctx->lcd_clk);
|
||||
if (ret < 0) {
|
||||
clk_disable_unprepare(ctx->bus_clk);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
clk_disable_unprepare(ctx->lcd_clk);
|
||||
clk_disable_unprepare(ctx->bus_clk);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fimd_window_suspend(struct device *dev)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
struct fimd_win_data *win_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < WINDOWS_NR; i++) {
|
||||
win_data = &ctx->win_data[i];
|
||||
win_data->resume = win_data->enabled;
|
||||
fimd_win_disable(dev, i);
|
||||
if (win_data->enabled)
|
||||
fimd_win_disable(mgr, i);
|
||||
}
|
||||
fimd_wait_for_vblank(dev);
|
||||
fimd_wait_for_vblank(mgr);
|
||||
}
|
||||
|
||||
static void fimd_window_resume(struct device *dev)
|
||||
static void fimd_window_resume(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
struct fimd_win_data *win_data;
|
||||
int i;
|
||||
|
||||
@ -857,62 +697,162 @@ static void fimd_window_resume(struct device *dev)
|
||||
}
|
||||
}
|
||||
|
||||
static int fimd_activate(struct fimd_context *ctx, bool enable)
|
||||
static void fimd_apply(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct device *dev = ctx->subdrv.dev;
|
||||
if (enable) {
|
||||
int ret;
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
struct fimd_win_data *win_data;
|
||||
int i;
|
||||
|
||||
ret = fimd_clock(ctx, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx->suspended = false;
|
||||
|
||||
/* if vblank was enabled status, enable it again. */
|
||||
if (test_and_clear_bit(0, &ctx->irq_flags))
|
||||
fimd_enable_vblank(dev);
|
||||
|
||||
fimd_window_resume(dev);
|
||||
} else {
|
||||
fimd_window_suspend(dev);
|
||||
|
||||
fimd_clock(ctx, false);
|
||||
ctx->suspended = true;
|
||||
for (i = 0; i < WINDOWS_NR; i++) {
|
||||
win_data = &ctx->win_data[i];
|
||||
if (win_data->enabled)
|
||||
fimd_win_commit(mgr, i);
|
||||
}
|
||||
|
||||
fimd_commit(mgr);
|
||||
}
|
||||
|
||||
static int fimd_poweron(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
int ret;
|
||||
|
||||
if (!ctx->suspended)
|
||||
return 0;
|
||||
|
||||
ctx->suspended = false;
|
||||
|
||||
pm_runtime_get_sync(ctx->dev);
|
||||
|
||||
ret = clk_prepare_enable(ctx->bus_clk);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("Failed to prepare_enable the bus clk [%d]\n", ret);
|
||||
goto bus_clk_err;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(ctx->lcd_clk);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("Failed to prepare_enable the lcd clk [%d]\n", ret);
|
||||
goto lcd_clk_err;
|
||||
}
|
||||
|
||||
/* if vblank was enabled status, enable it again. */
|
||||
if (test_and_clear_bit(0, &ctx->irq_flags)) {
|
||||
ret = fimd_enable_vblank(mgr);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to re-enable vblank [%d]\n", ret);
|
||||
goto enable_vblank_err;
|
||||
}
|
||||
}
|
||||
|
||||
fimd_window_resume(mgr);
|
||||
|
||||
fimd_apply(mgr);
|
||||
|
||||
return 0;
|
||||
|
||||
enable_vblank_err:
|
||||
clk_disable_unprepare(ctx->lcd_clk);
|
||||
lcd_clk_err:
|
||||
clk_disable_unprepare(ctx->bus_clk);
|
||||
bus_clk_err:
|
||||
ctx->suspended = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fimd_poweroff(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct fimd_context *ctx = mgr->ctx;
|
||||
|
||||
if (ctx->suspended)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* We need to make sure that all windows are disabled before we
|
||||
* suspend that connector. Otherwise we might try to scan from
|
||||
* a destroyed buffer later.
|
||||
*/
|
||||
fimd_window_suspend(mgr);
|
||||
|
||||
clk_disable_unprepare(ctx->lcd_clk);
|
||||
clk_disable_unprepare(ctx->bus_clk);
|
||||
|
||||
pm_runtime_put_sync(ctx->dev);
|
||||
|
||||
ctx->suspended = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fimd_get_platform_data(struct fimd_context *ctx, struct device *dev)
|
||||
static void fimd_dpms(struct exynos_drm_manager *mgr, int mode)
|
||||
{
|
||||
struct videomode *vm;
|
||||
int ret;
|
||||
DRM_DEBUG_KMS("%s, %d\n", __FILE__, mode);
|
||||
|
||||
vm = &ctx->panel.vm;
|
||||
ret = of_get_videomode(dev->of_node, vm, OF_USE_NATIVE_MODE);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed: of_get_videomode() : %d\n", ret);
|
||||
return ret;
|
||||
switch (mode) {
|
||||
case DRM_MODE_DPMS_ON:
|
||||
fimd_poweron(mgr);
|
||||
break;
|
||||
case DRM_MODE_DPMS_STANDBY:
|
||||
case DRM_MODE_DPMS_SUSPEND:
|
||||
case DRM_MODE_DPMS_OFF:
|
||||
fimd_poweroff(mgr);
|
||||
break;
|
||||
default:
|
||||
DRM_DEBUG_KMS("unspecified mode %d\n", mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vm->flags & DISPLAY_FLAGS_VSYNC_LOW)
|
||||
ctx->vidcon1 |= VIDCON1_INV_VSYNC;
|
||||
if (vm->flags & DISPLAY_FLAGS_HSYNC_LOW)
|
||||
ctx->vidcon1 |= VIDCON1_INV_HSYNC;
|
||||
if (vm->flags & DISPLAY_FLAGS_DE_LOW)
|
||||
ctx->vidcon1 |= VIDCON1_INV_VDEN;
|
||||
if (vm->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
|
||||
ctx->vidcon1 |= VIDCON1_INV_VCLK;
|
||||
static struct exynos_drm_manager_ops fimd_manager_ops = {
|
||||
.initialize = fimd_mgr_initialize,
|
||||
.remove = fimd_mgr_remove,
|
||||
.dpms = fimd_dpms,
|
||||
.mode_fixup = fimd_mode_fixup,
|
||||
.mode_set = fimd_mode_set,
|
||||
.commit = fimd_commit,
|
||||
.enable_vblank = fimd_enable_vblank,
|
||||
.disable_vblank = fimd_disable_vblank,
|
||||
.wait_for_vblank = fimd_wait_for_vblank,
|
||||
.win_mode_set = fimd_win_mode_set,
|
||||
.win_commit = fimd_win_commit,
|
||||
.win_disable = fimd_win_disable,
|
||||
};
|
||||
|
||||
return 0;
|
||||
static struct exynos_drm_manager fimd_manager = {
|
||||
.type = EXYNOS_DISPLAY_TYPE_LCD,
|
||||
.ops = &fimd_manager_ops,
|
||||
};
|
||||
|
||||
static irqreturn_t fimd_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct fimd_context *ctx = (struct fimd_context *)dev_id;
|
||||
u32 val;
|
||||
|
||||
val = readl(ctx->regs + VIDINTCON1);
|
||||
|
||||
if (val & VIDINTCON1_INT_FRAME)
|
||||
/* VSYNC interrupt */
|
||||
writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1);
|
||||
|
||||
/* check the crtc is detached already from encoder */
|
||||
if (ctx->pipe < 0 || !ctx->drm_dev)
|
||||
goto out;
|
||||
|
||||
drm_handle_vblank(ctx->drm_dev, ctx->pipe);
|
||||
exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe);
|
||||
|
||||
/* set wait vsync event to zero and wake up queue. */
|
||||
if (atomic_read(&ctx->wait_vsync_event)) {
|
||||
atomic_set(&ctx->wait_vsync_event, 0);
|
||||
wake_up(&ctx->wait_vsync_queue);
|
||||
}
|
||||
out:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int fimd_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct fimd_context *ctx;
|
||||
struct exynos_drm_subdrv *subdrv;
|
||||
struct resource *res;
|
||||
int win;
|
||||
int ret = -EINVAL;
|
||||
@ -924,13 +864,25 @@ static int fimd_probe(struct platform_device *pdev)
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = fimd_get_platform_data(ctx, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
ctx->dev = dev;
|
||||
ctx->suspended = true;
|
||||
|
||||
ret = fimd_configure_clocks(ctx, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (of_property_read_bool(dev->of_node, "samsung,invert-vden"))
|
||||
ctx->vidcon1 |= VIDCON1_INV_VDEN;
|
||||
if (of_property_read_bool(dev->of_node, "samsung,invert-vclk"))
|
||||
ctx->vidcon1 |= VIDCON1_INV_VCLK;
|
||||
|
||||
ctx->bus_clk = devm_clk_get(dev, "fimd");
|
||||
if (IS_ERR(ctx->bus_clk)) {
|
||||
dev_err(dev, "failed to get bus clock\n");
|
||||
return PTR_ERR(ctx->bus_clk);
|
||||
}
|
||||
|
||||
ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd");
|
||||
if (IS_ERR(ctx->lcd_clk)) {
|
||||
dev_err(dev, "failed to get lcd clock\n");
|
||||
return PTR_ERR(ctx->lcd_clk);
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
@ -944,9 +896,7 @@ static int fimd_probe(struct platform_device *pdev)
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
ctx->irq = res->start;
|
||||
|
||||
ret = devm_request_irq(dev, ctx->irq, fimd_irq_handler,
|
||||
ret = devm_request_irq(dev, res->start, fimd_irq_handler,
|
||||
0, "drm_fimd", ctx);
|
||||
if (ret) {
|
||||
dev_err(dev, "irq request failed.\n");
|
||||
@ -957,120 +907,42 @@ static int fimd_probe(struct platform_device *pdev)
|
||||
init_waitqueue_head(&ctx->wait_vsync_queue);
|
||||
atomic_set(&ctx->wait_vsync_event, 0);
|
||||
|
||||
subdrv = &ctx->subdrv;
|
||||
platform_set_drvdata(pdev, &fimd_manager);
|
||||
|
||||
subdrv->dev = dev;
|
||||
subdrv->manager = &fimd_manager;
|
||||
subdrv->probe = fimd_subdrv_probe;
|
||||
subdrv->remove = fimd_subdrv_remove;
|
||||
fimd_manager.ctx = ctx;
|
||||
exynos_drm_manager_register(&fimd_manager);
|
||||
|
||||
mutex_init(&ctx->lock);
|
||||
|
||||
platform_set_drvdata(pdev, ctx);
|
||||
exynos_dpi_probe(ctx->dev);
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
pm_runtime_get_sync(dev);
|
||||
|
||||
for (win = 0; win < WINDOWS_NR; win++)
|
||||
fimd_clear_win(ctx, win);
|
||||
|
||||
exynos_drm_subdrv_register(subdrv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fimd_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct fimd_context *ctx = platform_get_drvdata(pdev);
|
||||
struct exynos_drm_manager *mgr = platform_get_drvdata(pdev);
|
||||
|
||||
exynos_drm_subdrv_unregister(&ctx->subdrv);
|
||||
exynos_dpi_remove(&pdev->dev);
|
||||
|
||||
if (ctx->suspended)
|
||||
goto out;
|
||||
exynos_drm_manager_unregister(&fimd_manager);
|
||||
|
||||
pm_runtime_set_suspended(dev);
|
||||
pm_runtime_put_sync(dev);
|
||||
fimd_dpms(mgr, DRM_MODE_DPMS_OFF);
|
||||
|
||||
out:
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int fimd_suspend(struct device *dev)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
|
||||
/*
|
||||
* do not use pm_runtime_suspend(). if pm_runtime_suspend() is
|
||||
* called here, an error would be returned by that interface
|
||||
* because the usage_count of pm runtime is more than 1.
|
||||
*/
|
||||
if (!pm_runtime_suspended(dev))
|
||||
return fimd_activate(ctx, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fimd_resume(struct device *dev)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
|
||||
/*
|
||||
* if entered to sleep when lcd panel was on, the usage_count
|
||||
* of pm runtime would still be 1 so in this case, fimd driver
|
||||
* should be on directly not drawing on pm runtime interface.
|
||||
*/
|
||||
if (!pm_runtime_suspended(dev)) {
|
||||
int ret;
|
||||
|
||||
ret = fimd_activate(ctx, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* in case of dpms on(standby), fimd_apply function will
|
||||
* be called by encoder's dpms callback to update fimd's
|
||||
* registers but in case of sleep wakeup, it's not.
|
||||
* so fimd_apply function should be called at here.
|
||||
*/
|
||||
fimd_apply(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
static int fimd_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
|
||||
return fimd_activate(ctx, false);
|
||||
}
|
||||
|
||||
static int fimd_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct fimd_context *ctx = get_fimd_context(dev);
|
||||
|
||||
return fimd_activate(ctx, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops fimd_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(fimd_suspend, fimd_resume)
|
||||
SET_RUNTIME_PM_OPS(fimd_runtime_suspend, fimd_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
struct platform_driver fimd_driver = {
|
||||
.probe = fimd_probe,
|
||||
.remove = fimd_remove,
|
||||
.driver = {
|
||||
.name = "exynos4-fb",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &fimd_pm_ops,
|
||||
.of_match_table = fimd_driver_dt_match,
|
||||
},
|
||||
};
|
||||
|
@ -1,439 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Samsung Electronics Co.Ltd
|
||||
* Authors:
|
||||
* Inki Dae <inki.dae@samsung.com>
|
||||
* Seung-Woo Kim <sw0312.kim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include <drm/exynos_drm.h>
|
||||
|
||||
#include "exynos_drm_drv.h"
|
||||
#include "exynos_drm_hdmi.h"
|
||||
|
||||
#define to_context(dev) platform_get_drvdata(to_platform_device(dev))
|
||||
#define to_subdrv(dev) to_context(dev)
|
||||
#define get_ctx_from_subdrv(subdrv) container_of(subdrv,\
|
||||
struct drm_hdmi_context, subdrv);
|
||||
|
||||
/* platform device pointer for common drm hdmi device. */
|
||||
static struct platform_device *exynos_drm_hdmi_pdev;
|
||||
|
||||
/* Common hdmi subdrv needs to access the hdmi and mixer though context.
|
||||
* These should be initialied by the repective drivers */
|
||||
static struct exynos_drm_hdmi_context *hdmi_ctx;
|
||||
static struct exynos_drm_hdmi_context *mixer_ctx;
|
||||
|
||||
/* these callback points shoud be set by specific drivers. */
|
||||
static struct exynos_hdmi_ops *hdmi_ops;
|
||||
static struct exynos_mixer_ops *mixer_ops;
|
||||
|
||||
struct drm_hdmi_context {
|
||||
struct exynos_drm_subdrv subdrv;
|
||||
struct exynos_drm_hdmi_context *hdmi_ctx;
|
||||
struct exynos_drm_hdmi_context *mixer_ctx;
|
||||
|
||||
bool enabled[MIXER_WIN_NR];
|
||||
};
|
||||
|
||||
int exynos_platform_device_hdmi_register(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
|
||||
if (exynos_drm_hdmi_pdev)
|
||||
return -EEXIST;
|
||||
|
||||
pdev = platform_device_register_simple(
|
||||
"exynos-drm-hdmi", -1, NULL, 0);
|
||||
if (IS_ERR(pdev))
|
||||
return PTR_ERR(pdev);
|
||||
|
||||
exynos_drm_hdmi_pdev = pdev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void exynos_platform_device_hdmi_unregister(void)
|
||||
{
|
||||
if (exynos_drm_hdmi_pdev) {
|
||||
platform_device_unregister(exynos_drm_hdmi_pdev);
|
||||
exynos_drm_hdmi_pdev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void exynos_hdmi_drv_attach(struct exynos_drm_hdmi_context *ctx)
|
||||
{
|
||||
if (ctx)
|
||||
hdmi_ctx = ctx;
|
||||
}
|
||||
|
||||
void exynos_mixer_drv_attach(struct exynos_drm_hdmi_context *ctx)
|
||||
{
|
||||
if (ctx)
|
||||
mixer_ctx = ctx;
|
||||
}
|
||||
|
||||
void exynos_hdmi_ops_register(struct exynos_hdmi_ops *ops)
|
||||
{
|
||||
if (ops)
|
||||
hdmi_ops = ops;
|
||||
}
|
||||
|
||||
void exynos_mixer_ops_register(struct exynos_mixer_ops *ops)
|
||||
{
|
||||
if (ops)
|
||||
mixer_ops = ops;
|
||||
}
|
||||
|
||||
static bool drm_hdmi_is_connected(struct device *dev)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(dev);
|
||||
|
||||
if (hdmi_ops && hdmi_ops->is_connected)
|
||||
return hdmi_ops->is_connected(ctx->hdmi_ctx->ctx);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct edid *drm_hdmi_get_edid(struct device *dev,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(dev);
|
||||
|
||||
if (hdmi_ops && hdmi_ops->get_edid)
|
||||
return hdmi_ops->get_edid(ctx->hdmi_ctx->ctx, connector);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int drm_hdmi_check_mode(struct device *dev,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(dev);
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Both, mixer and hdmi should be able to handle the requested mode.
|
||||
* If any of the two fails, return mode as BAD.
|
||||
*/
|
||||
|
||||
if (mixer_ops && mixer_ops->check_mode)
|
||||
ret = mixer_ops->check_mode(ctx->mixer_ctx->ctx, mode);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (hdmi_ops && hdmi_ops->check_mode)
|
||||
return hdmi_ops->check_mode(ctx->hdmi_ctx->ctx, mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int drm_hdmi_power_on(struct device *dev, int mode)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(dev);
|
||||
|
||||
if (hdmi_ops && hdmi_ops->power_on)
|
||||
return hdmi_ops->power_on(ctx->hdmi_ctx->ctx, mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct exynos_drm_display_ops drm_hdmi_display_ops = {
|
||||
.type = EXYNOS_DISPLAY_TYPE_HDMI,
|
||||
.is_connected = drm_hdmi_is_connected,
|
||||
.get_edid = drm_hdmi_get_edid,
|
||||
.check_mode = drm_hdmi_check_mode,
|
||||
.power_on = drm_hdmi_power_on,
|
||||
};
|
||||
|
||||
static int drm_hdmi_enable_vblank(struct device *subdrv_dev)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
|
||||
struct exynos_drm_manager *manager = subdrv->manager;
|
||||
|
||||
if (mixer_ops && mixer_ops->enable_vblank)
|
||||
return mixer_ops->enable_vblank(ctx->mixer_ctx->ctx,
|
||||
manager->pipe);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void drm_hdmi_disable_vblank(struct device *subdrv_dev)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
|
||||
if (mixer_ops && mixer_ops->disable_vblank)
|
||||
return mixer_ops->disable_vblank(ctx->mixer_ctx->ctx);
|
||||
}
|
||||
|
||||
static void drm_hdmi_wait_for_vblank(struct device *subdrv_dev)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
|
||||
if (mixer_ops && mixer_ops->wait_for_vblank)
|
||||
mixer_ops->wait_for_vblank(ctx->mixer_ctx->ctx);
|
||||
}
|
||||
|
||||
static void drm_hdmi_mode_fixup(struct device *subdrv_dev,
|
||||
struct drm_connector *connector,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct drm_display_mode *m;
|
||||
int mode_ok;
|
||||
|
||||
drm_mode_set_crtcinfo(adjusted_mode, 0);
|
||||
|
||||
mode_ok = drm_hdmi_check_mode(subdrv_dev, adjusted_mode);
|
||||
|
||||
/* just return if user desired mode exists. */
|
||||
if (mode_ok == 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* otherwise, find the most suitable mode among modes and change it
|
||||
* to adjusted_mode.
|
||||
*/
|
||||
list_for_each_entry(m, &connector->modes, head) {
|
||||
mode_ok = drm_hdmi_check_mode(subdrv_dev, m);
|
||||
|
||||
if (mode_ok == 0) {
|
||||
struct drm_mode_object base;
|
||||
struct list_head head;
|
||||
|
||||
DRM_INFO("desired mode doesn't exist so\n");
|
||||
DRM_INFO("use the most suitable mode among modes.\n");
|
||||
|
||||
DRM_DEBUG_KMS("Adjusted Mode: [%d]x[%d] [%d]Hz\n",
|
||||
m->hdisplay, m->vdisplay, m->vrefresh);
|
||||
|
||||
/* preserve display mode header while copying. */
|
||||
head = adjusted_mode->head;
|
||||
base = adjusted_mode->base;
|
||||
memcpy(adjusted_mode, m, sizeof(*m));
|
||||
adjusted_mode->head = head;
|
||||
adjusted_mode->base = base;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drm_hdmi_mode_set(struct device *subdrv_dev, void *mode)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
|
||||
if (hdmi_ops && hdmi_ops->mode_set)
|
||||
hdmi_ops->mode_set(ctx->hdmi_ctx->ctx, mode);
|
||||
}
|
||||
|
||||
static void drm_hdmi_get_max_resol(struct device *subdrv_dev,
|
||||
unsigned int *width, unsigned int *height)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
|
||||
if (hdmi_ops && hdmi_ops->get_max_resol)
|
||||
hdmi_ops->get_max_resol(ctx->hdmi_ctx->ctx, width, height);
|
||||
}
|
||||
|
||||
static void drm_hdmi_commit(struct device *subdrv_dev)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
|
||||
if (hdmi_ops && hdmi_ops->commit)
|
||||
hdmi_ops->commit(ctx->hdmi_ctx->ctx);
|
||||
}
|
||||
|
||||
static void drm_hdmi_dpms(struct device *subdrv_dev, int mode)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
|
||||
if (mixer_ops && mixer_ops->dpms)
|
||||
mixer_ops->dpms(ctx->mixer_ctx->ctx, mode);
|
||||
|
||||
if (hdmi_ops && hdmi_ops->dpms)
|
||||
hdmi_ops->dpms(ctx->hdmi_ctx->ctx, mode);
|
||||
}
|
||||
|
||||
static void drm_hdmi_apply(struct device *subdrv_dev)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MIXER_WIN_NR; i++) {
|
||||
if (!ctx->enabled[i])
|
||||
continue;
|
||||
if (mixer_ops && mixer_ops->win_commit)
|
||||
mixer_ops->win_commit(ctx->mixer_ctx->ctx, i);
|
||||
}
|
||||
|
||||
if (hdmi_ops && hdmi_ops->commit)
|
||||
hdmi_ops->commit(ctx->hdmi_ctx->ctx);
|
||||
}
|
||||
|
||||
static struct exynos_drm_manager_ops drm_hdmi_manager_ops = {
|
||||
.dpms = drm_hdmi_dpms,
|
||||
.apply = drm_hdmi_apply,
|
||||
.enable_vblank = drm_hdmi_enable_vblank,
|
||||
.disable_vblank = drm_hdmi_disable_vblank,
|
||||
.wait_for_vblank = drm_hdmi_wait_for_vblank,
|
||||
.mode_fixup = drm_hdmi_mode_fixup,
|
||||
.mode_set = drm_hdmi_mode_set,
|
||||
.get_max_resol = drm_hdmi_get_max_resol,
|
||||
.commit = drm_hdmi_commit,
|
||||
};
|
||||
|
||||
static void drm_mixer_mode_set(struct device *subdrv_dev,
|
||||
struct exynos_drm_overlay *overlay)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
|
||||
if (mixer_ops && mixer_ops->win_mode_set)
|
||||
mixer_ops->win_mode_set(ctx->mixer_ctx->ctx, overlay);
|
||||
}
|
||||
|
||||
static void drm_mixer_commit(struct device *subdrv_dev, int zpos)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos;
|
||||
|
||||
if (win < 0 || win >= MIXER_WIN_NR) {
|
||||
DRM_ERROR("mixer window[%d] is wrong\n", win);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mixer_ops && mixer_ops->win_commit)
|
||||
mixer_ops->win_commit(ctx->mixer_ctx->ctx, win);
|
||||
|
||||
ctx->enabled[win] = true;
|
||||
}
|
||||
|
||||
static void drm_mixer_disable(struct device *subdrv_dev, int zpos)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = to_context(subdrv_dev);
|
||||
int win = (zpos == DEFAULT_ZPOS) ? MIXER_DEFAULT_WIN : zpos;
|
||||
|
||||
if (win < 0 || win >= MIXER_WIN_NR) {
|
||||
DRM_ERROR("mixer window[%d] is wrong\n", win);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mixer_ops && mixer_ops->win_disable)
|
||||
mixer_ops->win_disable(ctx->mixer_ctx->ctx, win);
|
||||
|
||||
ctx->enabled[win] = false;
|
||||
}
|
||||
|
||||
static struct exynos_drm_overlay_ops drm_hdmi_overlay_ops = {
|
||||
.mode_set = drm_mixer_mode_set,
|
||||
.commit = drm_mixer_commit,
|
||||
.disable = drm_mixer_disable,
|
||||
};
|
||||
|
||||
static struct exynos_drm_manager hdmi_manager = {
|
||||
.pipe = -1,
|
||||
.ops = &drm_hdmi_manager_ops,
|
||||
.overlay_ops = &drm_hdmi_overlay_ops,
|
||||
.display_ops = &drm_hdmi_display_ops,
|
||||
};
|
||||
|
||||
static int hdmi_subdrv_probe(struct drm_device *drm_dev,
|
||||
struct device *dev)
|
||||
{
|
||||
struct exynos_drm_subdrv *subdrv = to_subdrv(dev);
|
||||
struct drm_hdmi_context *ctx;
|
||||
|
||||
if (!hdmi_ctx) {
|
||||
DRM_ERROR("hdmi context not initialized.\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (!mixer_ctx) {
|
||||
DRM_ERROR("mixer context not initialized.\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
ctx = get_ctx_from_subdrv(subdrv);
|
||||
|
||||
if (!ctx) {
|
||||
DRM_ERROR("no drm hdmi context.\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
ctx->hdmi_ctx = hdmi_ctx;
|
||||
ctx->mixer_ctx = mixer_ctx;
|
||||
|
||||
ctx->hdmi_ctx->drm_dev = drm_dev;
|
||||
ctx->mixer_ctx->drm_dev = drm_dev;
|
||||
|
||||
if (mixer_ops->iommu_on)
|
||||
mixer_ops->iommu_on(ctx->mixer_ctx->ctx, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hdmi_subdrv_remove(struct drm_device *drm_dev, struct device *dev)
|
||||
{
|
||||
struct drm_hdmi_context *ctx;
|
||||
struct exynos_drm_subdrv *subdrv = to_subdrv(dev);
|
||||
|
||||
ctx = get_ctx_from_subdrv(subdrv);
|
||||
|
||||
if (mixer_ops->iommu_on)
|
||||
mixer_ops->iommu_on(ctx->mixer_ctx->ctx, false);
|
||||
}
|
||||
|
||||
static int exynos_drm_hdmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct exynos_drm_subdrv *subdrv;
|
||||
struct drm_hdmi_context *ctx;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
subdrv = &ctx->subdrv;
|
||||
|
||||
subdrv->dev = dev;
|
||||
subdrv->manager = &hdmi_manager;
|
||||
subdrv->probe = hdmi_subdrv_probe;
|
||||
subdrv->remove = hdmi_subdrv_remove;
|
||||
|
||||
platform_set_drvdata(pdev, subdrv);
|
||||
|
||||
exynos_drm_subdrv_register(subdrv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_drm_hdmi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct drm_hdmi_context *ctx = platform_get_drvdata(pdev);
|
||||
|
||||
exynos_drm_subdrv_unregister(&ctx->subdrv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct platform_driver exynos_drm_common_hdmi_driver = {
|
||||
.probe = exynos_drm_hdmi_probe,
|
||||
.remove = exynos_drm_hdmi_remove,
|
||||
.driver = {
|
||||
.name = "exynos-drm-hdmi",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
@ -1,67 +0,0 @@
|
||||
/* exynos_drm_hdmi.h
|
||||
*
|
||||
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
|
||||
* Authoer: Inki Dae <inki.dae@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef _EXYNOS_DRM_HDMI_H_
|
||||
#define _EXYNOS_DRM_HDMI_H_
|
||||
|
||||
#define MIXER_WIN_NR 3
|
||||
#define MIXER_DEFAULT_WIN 0
|
||||
|
||||
/*
|
||||
* exynos hdmi common context structure.
|
||||
*
|
||||
* @drm_dev: pointer to drm_device.
|
||||
* @ctx: pointer to the context of specific device driver.
|
||||
* this context should be hdmi_context or mixer_context.
|
||||
*/
|
||||
struct exynos_drm_hdmi_context {
|
||||
struct drm_device *drm_dev;
|
||||
void *ctx;
|
||||
};
|
||||
|
||||
struct exynos_hdmi_ops {
|
||||
/* display */
|
||||
bool (*is_connected)(void *ctx);
|
||||
struct edid *(*get_edid)(void *ctx,
|
||||
struct drm_connector *connector);
|
||||
int (*check_mode)(void *ctx, struct drm_display_mode *mode);
|
||||
int (*power_on)(void *ctx, int mode);
|
||||
|
||||
/* manager */
|
||||
void (*mode_set)(void *ctx, struct drm_display_mode *mode);
|
||||
void (*get_max_resol)(void *ctx, unsigned int *width,
|
||||
unsigned int *height);
|
||||
void (*commit)(void *ctx);
|
||||
void (*dpms)(void *ctx, int mode);
|
||||
};
|
||||
|
||||
struct exynos_mixer_ops {
|
||||
/* manager */
|
||||
int (*iommu_on)(void *ctx, bool enable);
|
||||
int (*enable_vblank)(void *ctx, int pipe);
|
||||
void (*disable_vblank)(void *ctx);
|
||||
void (*wait_for_vblank)(void *ctx);
|
||||
void (*dpms)(void *ctx, int mode);
|
||||
|
||||
/* overlay */
|
||||
void (*win_mode_set)(void *ctx, struct exynos_drm_overlay *overlay);
|
||||
void (*win_commit)(void *ctx, int zpos);
|
||||
void (*win_disable)(void *ctx, int zpos);
|
||||
|
||||
/* display */
|
||||
int (*check_mode)(void *ctx, struct drm_display_mode *mode);
|
||||
};
|
||||
|
||||
void exynos_hdmi_drv_attach(struct exynos_drm_hdmi_context *ctx);
|
||||
void exynos_mixer_drv_attach(struct exynos_drm_hdmi_context *ctx);
|
||||
void exynos_hdmi_ops_register(struct exynos_hdmi_ops *ops);
|
||||
void exynos_mixer_ops_register(struct exynos_mixer_ops *ops);
|
||||
#endif
|
@ -13,7 +13,7 @@
|
||||
|
||||
#include <drm/exynos_drm.h>
|
||||
#include "exynos_drm_drv.h"
|
||||
#include "exynos_drm_encoder.h"
|
||||
#include "exynos_drm_crtc.h"
|
||||
#include "exynos_drm_fb.h"
|
||||
#include "exynos_drm_gem.h"
|
||||
#include "exynos_drm_plane.h"
|
||||
@ -87,7 +87,7 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc,
|
||||
struct exynos_drm_gem_buf *buffer = exynos_drm_fb_buffer(fb, i);
|
||||
|
||||
if (!buffer) {
|
||||
DRM_LOG_KMS("buffer is null\n");
|
||||
DRM_DEBUG_KMS("buffer is null\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc,
|
||||
overlay->crtc_x, overlay->crtc_y,
|
||||
overlay->crtc_width, overlay->crtc_height);
|
||||
|
||||
exynos_drm_fn_encoder(crtc, overlay, exynos_drm_encoder_plane_mode_set);
|
||||
exynos_drm_crtc_plane_mode_set(crtc, overlay);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -149,8 +149,7 @@ void exynos_plane_commit(struct drm_plane *plane)
|
||||
struct exynos_plane *exynos_plane = to_exynos_plane(plane);
|
||||
struct exynos_drm_overlay *overlay = &exynos_plane->overlay;
|
||||
|
||||
exynos_drm_fn_encoder(plane->crtc, &overlay->zpos,
|
||||
exynos_drm_encoder_plane_commit);
|
||||
exynos_drm_crtc_plane_commit(plane->crtc, overlay->zpos);
|
||||
}
|
||||
|
||||
void exynos_plane_dpms(struct drm_plane *plane, int mode)
|
||||
@ -162,17 +161,13 @@ void exynos_plane_dpms(struct drm_plane *plane, int mode)
|
||||
if (exynos_plane->enabled)
|
||||
return;
|
||||
|
||||
exynos_drm_fn_encoder(plane->crtc, &overlay->zpos,
|
||||
exynos_drm_encoder_plane_enable);
|
||||
|
||||
exynos_drm_crtc_plane_enable(plane->crtc, overlay->zpos);
|
||||
exynos_plane->enabled = true;
|
||||
} else {
|
||||
if (!exynos_plane->enabled)
|
||||
return;
|
||||
|
||||
exynos_drm_fn_encoder(plane->crtc, &overlay->zpos,
|
||||
exynos_drm_encoder_plane_disable);
|
||||
|
||||
exynos_drm_crtc_plane_disable(plane->crtc, overlay->zpos);
|
||||
exynos_plane->enabled = false;
|
||||
}
|
||||
}
|
||||
@ -259,7 +254,7 @@ static void exynos_plane_attach_zpos_property(struct drm_plane *plane)
|
||||
}
|
||||
|
||||
struct drm_plane *exynos_plane_init(struct drm_device *dev,
|
||||
unsigned int possible_crtcs, bool priv)
|
||||
unsigned long possible_crtcs, bool priv)
|
||||
{
|
||||
struct exynos_plane *exynos_plane;
|
||||
int err;
|
||||
|
@ -17,4 +17,4 @@ int exynos_plane_mode_set(struct drm_plane *plane, struct drm_crtc *crtc,
|
||||
void exynos_plane_commit(struct drm_plane *plane);
|
||||
void exynos_plane_dpms(struct drm_plane *plane, int mode);
|
||||
struct drm_plane *exynos_plane_init(struct drm_device *dev,
|
||||
unsigned int possible_crtcs, bool priv);
|
||||
unsigned long possible_crtcs, bool priv);
|
||||
|
@ -28,7 +28,9 @@
|
||||
/* vidi has totally three virtual windows. */
|
||||
#define WINDOWS_NR 3
|
||||
|
||||
#define get_vidi_context(dev) platform_get_drvdata(to_platform_device(dev))
|
||||
#define get_vidi_mgr(dev) platform_get_drvdata(to_platform_device(dev))
|
||||
#define ctx_from_connector(c) container_of(c, struct vidi_context, \
|
||||
connector)
|
||||
|
||||
struct vidi_win_data {
|
||||
unsigned int offset_x;
|
||||
@ -45,8 +47,10 @@ struct vidi_win_data {
|
||||
};
|
||||
|
||||
struct vidi_context {
|
||||
struct exynos_drm_subdrv subdrv;
|
||||
struct drm_device *drm_dev;
|
||||
struct drm_crtc *crtc;
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector connector;
|
||||
struct vidi_win_data win_data[WINDOWS_NR];
|
||||
struct edid *raw_edid;
|
||||
unsigned int clkdiv;
|
||||
@ -58,6 +62,7 @@ struct vidi_context {
|
||||
bool direct_vblank;
|
||||
struct work_struct work;
|
||||
struct mutex lock;
|
||||
int pipe;
|
||||
};
|
||||
|
||||
static const char fake_edid_info[] = {
|
||||
@ -85,126 +90,34 @@ static const char fake_edid_info[] = {
|
||||
0x00, 0x00, 0x00, 0x06
|
||||
};
|
||||
|
||||
static bool vidi_display_is_connected(struct device *dev)
|
||||
static void vidi_apply(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
|
||||
/*
|
||||
* connection request would come from user side
|
||||
* to do hotplug through specific ioctl.
|
||||
*/
|
||||
return ctx->connected ? true : false;
|
||||
}
|
||||
|
||||
static struct edid *vidi_get_edid(struct device *dev,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
struct edid *edid;
|
||||
|
||||
/*
|
||||
* the edid data comes from user side and it would be set
|
||||
* to ctx->raw_edid through specific ioctl.
|
||||
*/
|
||||
if (!ctx->raw_edid) {
|
||||
DRM_DEBUG_KMS("raw_edid is null.\n");
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
|
||||
edid = drm_edid_duplicate(ctx->raw_edid);
|
||||
if (!edid) {
|
||||
DRM_DEBUG_KMS("failed to allocate edid\n");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
return edid;
|
||||
}
|
||||
|
||||
static void *vidi_get_panel(struct device *dev)
|
||||
{
|
||||
/* TODO. */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int vidi_check_mode(struct device *dev, struct drm_display_mode *mode)
|
||||
{
|
||||
/* TODO. */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vidi_display_power_on(struct device *dev, int mode)
|
||||
{
|
||||
/* TODO */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct exynos_drm_display_ops vidi_display_ops = {
|
||||
.type = EXYNOS_DISPLAY_TYPE_VIDI,
|
||||
.is_connected = vidi_display_is_connected,
|
||||
.get_edid = vidi_get_edid,
|
||||
.get_panel = vidi_get_panel,
|
||||
.check_mode = vidi_check_mode,
|
||||
.power_on = vidi_display_power_on,
|
||||
};
|
||||
|
||||
static void vidi_dpms(struct device *subdrv_dev, int mode)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(subdrv_dev);
|
||||
|
||||
DRM_DEBUG_KMS("%d\n", mode);
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
|
||||
switch (mode) {
|
||||
case DRM_MODE_DPMS_ON:
|
||||
/* TODO. */
|
||||
break;
|
||||
case DRM_MODE_DPMS_STANDBY:
|
||||
case DRM_MODE_DPMS_SUSPEND:
|
||||
case DRM_MODE_DPMS_OFF:
|
||||
/* TODO. */
|
||||
break;
|
||||
default:
|
||||
DRM_DEBUG_KMS("unspecified mode %d\n", mode);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
static void vidi_apply(struct device *subdrv_dev)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(subdrv_dev);
|
||||
struct exynos_drm_manager *mgr = ctx->subdrv.manager;
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
struct exynos_drm_manager_ops *mgr_ops = mgr->ops;
|
||||
struct exynos_drm_overlay_ops *ovl_ops = mgr->overlay_ops;
|
||||
struct vidi_win_data *win_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < WINDOWS_NR; i++) {
|
||||
win_data = &ctx->win_data[i];
|
||||
if (win_data->enabled && (ovl_ops && ovl_ops->commit))
|
||||
ovl_ops->commit(subdrv_dev, i);
|
||||
if (win_data->enabled && (mgr_ops && mgr_ops->win_commit))
|
||||
mgr_ops->win_commit(mgr, i);
|
||||
}
|
||||
|
||||
if (mgr_ops && mgr_ops->commit)
|
||||
mgr_ops->commit(subdrv_dev);
|
||||
mgr_ops->commit(mgr);
|
||||
}
|
||||
|
||||
static void vidi_commit(struct device *dev)
|
||||
static void vidi_commit(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
|
||||
if (ctx->suspended)
|
||||
return;
|
||||
}
|
||||
|
||||
static int vidi_enable_vblank(struct device *dev)
|
||||
static int vidi_enable_vblank(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
|
||||
if (ctx->suspended)
|
||||
return -EPERM;
|
||||
@ -217,16 +130,16 @@ static int vidi_enable_vblank(struct device *dev)
|
||||
/*
|
||||
* in case of page flip request, vidi_finish_pageflip function
|
||||
* will not be called because direct_vblank is true and then
|
||||
* that function will be called by overlay_ops->commit callback
|
||||
* that function will be called by manager_ops->win_commit callback
|
||||
*/
|
||||
schedule_work(&ctx->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vidi_disable_vblank(struct device *dev)
|
||||
static void vidi_disable_vblank(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
|
||||
if (ctx->suspended)
|
||||
return;
|
||||
@ -235,24 +148,16 @@ static void vidi_disable_vblank(struct device *dev)
|
||||
ctx->vblank_on = false;
|
||||
}
|
||||
|
||||
static struct exynos_drm_manager_ops vidi_manager_ops = {
|
||||
.dpms = vidi_dpms,
|
||||
.apply = vidi_apply,
|
||||
.commit = vidi_commit,
|
||||
.enable_vblank = vidi_enable_vblank,
|
||||
.disable_vblank = vidi_disable_vblank,
|
||||
};
|
||||
|
||||
static void vidi_win_mode_set(struct device *dev,
|
||||
struct exynos_drm_overlay *overlay)
|
||||
static void vidi_win_mode_set(struct exynos_drm_manager *mgr,
|
||||
struct exynos_drm_overlay *overlay)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
struct vidi_win_data *win_data;
|
||||
int win;
|
||||
unsigned long offset;
|
||||
|
||||
if (!overlay) {
|
||||
dev_err(dev, "overlay is NULL\n");
|
||||
DRM_ERROR("overlay is NULL\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -296,9 +201,9 @@ static void vidi_win_mode_set(struct device *dev,
|
||||
overlay->fb_width, overlay->crtc_width);
|
||||
}
|
||||
|
||||
static void vidi_win_commit(struct device *dev, int zpos)
|
||||
static void vidi_win_commit(struct exynos_drm_manager *mgr, int zpos)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
struct vidi_win_data *win_data;
|
||||
int win = zpos;
|
||||
|
||||
@ -321,9 +226,9 @@ static void vidi_win_commit(struct device *dev, int zpos)
|
||||
schedule_work(&ctx->work);
|
||||
}
|
||||
|
||||
static void vidi_win_disable(struct device *dev, int zpos)
|
||||
static void vidi_win_disable(struct exynos_drm_manager *mgr, int zpos)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
struct vidi_win_data *win_data;
|
||||
int win = zpos;
|
||||
|
||||
@ -339,27 +244,107 @@ static void vidi_win_disable(struct device *dev, int zpos)
|
||||
/* TODO. */
|
||||
}
|
||||
|
||||
static struct exynos_drm_overlay_ops vidi_overlay_ops = {
|
||||
.mode_set = vidi_win_mode_set,
|
||||
.commit = vidi_win_commit,
|
||||
.disable = vidi_win_disable,
|
||||
static int vidi_power_on(struct exynos_drm_manager *mgr, bool enable)
|
||||
{
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
|
||||
DRM_DEBUG_KMS("%s\n", __FILE__);
|
||||
|
||||
if (enable != false && enable != true)
|
||||
return -EINVAL;
|
||||
|
||||
if (enable) {
|
||||
ctx->suspended = false;
|
||||
|
||||
/* if vblank was enabled status, enable it again. */
|
||||
if (test_and_clear_bit(0, &ctx->irq_flags))
|
||||
vidi_enable_vblank(mgr);
|
||||
|
||||
vidi_apply(mgr);
|
||||
} else {
|
||||
ctx->suspended = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vidi_dpms(struct exynos_drm_manager *mgr, int mode)
|
||||
{
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
|
||||
DRM_DEBUG_KMS("%d\n", mode);
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
|
||||
switch (mode) {
|
||||
case DRM_MODE_DPMS_ON:
|
||||
vidi_power_on(mgr, true);
|
||||
break;
|
||||
case DRM_MODE_DPMS_STANDBY:
|
||||
case DRM_MODE_DPMS_SUSPEND:
|
||||
case DRM_MODE_DPMS_OFF:
|
||||
vidi_power_on(mgr, false);
|
||||
break;
|
||||
default:
|
||||
DRM_DEBUG_KMS("unspecified mode %d\n", mode);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
static int vidi_mgr_initialize(struct exynos_drm_manager *mgr,
|
||||
struct drm_device *drm_dev, int pipe)
|
||||
{
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
|
||||
DRM_ERROR("vidi initialize ct=%p dev=%p pipe=%d\n", ctx, drm_dev, pipe);
|
||||
|
||||
ctx->drm_dev = drm_dev;
|
||||
ctx->pipe = pipe;
|
||||
|
||||
/*
|
||||
* enable drm irq mode.
|
||||
* - with irq_enabled = 1, we can use the vblank feature.
|
||||
*
|
||||
* P.S. note that we wouldn't use drm irq handler but
|
||||
* just specific driver own one instead because
|
||||
* drm framework supports only one irq handler.
|
||||
*/
|
||||
drm_dev->irq_enabled = 1;
|
||||
|
||||
/*
|
||||
* with vblank_disable_allowed = 1, vblank interrupt will be disabled
|
||||
* by drm timer once a current process gives up ownership of
|
||||
* vblank event.(after drm_vblank_put function is called)
|
||||
*/
|
||||
drm_dev->vblank_disable_allowed = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct exynos_drm_manager_ops vidi_manager_ops = {
|
||||
.initialize = vidi_mgr_initialize,
|
||||
.dpms = vidi_dpms,
|
||||
.commit = vidi_commit,
|
||||
.enable_vblank = vidi_enable_vblank,
|
||||
.disable_vblank = vidi_disable_vblank,
|
||||
.win_mode_set = vidi_win_mode_set,
|
||||
.win_commit = vidi_win_commit,
|
||||
.win_disable = vidi_win_disable,
|
||||
};
|
||||
|
||||
static struct exynos_drm_manager vidi_manager = {
|
||||
.pipe = -1,
|
||||
.ops = &vidi_manager_ops,
|
||||
.overlay_ops = &vidi_overlay_ops,
|
||||
.display_ops = &vidi_display_ops,
|
||||
.type = EXYNOS_DISPLAY_TYPE_VIDI,
|
||||
.ops = &vidi_manager_ops,
|
||||
};
|
||||
|
||||
static void vidi_fake_vblank_handler(struct work_struct *work)
|
||||
{
|
||||
struct vidi_context *ctx = container_of(work, struct vidi_context,
|
||||
work);
|
||||
struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
|
||||
struct exynos_drm_manager *manager = subdrv->manager;
|
||||
|
||||
if (manager->pipe < 0)
|
||||
if (ctx->pipe < 0)
|
||||
return;
|
||||
|
||||
/* refresh rate is about 50Hz. */
|
||||
@ -368,7 +353,7 @@ static void vidi_fake_vblank_handler(struct work_struct *work)
|
||||
mutex_lock(&ctx->lock);
|
||||
|
||||
if (ctx->direct_vblank) {
|
||||
drm_handle_vblank(subdrv->drm_dev, manager->pipe);
|
||||
drm_handle_vblank(ctx->drm_dev, ctx->pipe);
|
||||
ctx->direct_vblank = false;
|
||||
mutex_unlock(&ctx->lock);
|
||||
return;
|
||||
@ -376,61 +361,15 @@ static void vidi_fake_vblank_handler(struct work_struct *work)
|
||||
|
||||
mutex_unlock(&ctx->lock);
|
||||
|
||||
exynos_drm_crtc_finish_pageflip(subdrv->drm_dev, manager->pipe);
|
||||
}
|
||||
|
||||
static int vidi_subdrv_probe(struct drm_device *drm_dev, struct device *dev)
|
||||
{
|
||||
/*
|
||||
* enable drm irq mode.
|
||||
* - with irq_enabled = true, we can use the vblank feature.
|
||||
*
|
||||
* P.S. note that we wouldn't use drm irq handler but
|
||||
* just specific driver own one instead because
|
||||
* drm framework supports only one irq handler.
|
||||
*/
|
||||
drm_dev->irq_enabled = true;
|
||||
|
||||
/*
|
||||
* with vblank_disable_allowed = true, vblank interrupt will be disabled
|
||||
* by drm timer once a current process gives up ownership of
|
||||
* vblank event.(after drm_vblank_put function is called)
|
||||
*/
|
||||
drm_dev->vblank_disable_allowed = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vidi_subdrv_remove(struct drm_device *drm_dev, struct device *dev)
|
||||
{
|
||||
/* TODO. */
|
||||
}
|
||||
|
||||
static int vidi_power_on(struct vidi_context *ctx, bool enable)
|
||||
{
|
||||
struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
|
||||
struct device *dev = subdrv->dev;
|
||||
|
||||
if (enable) {
|
||||
ctx->suspended = false;
|
||||
|
||||
/* if vblank was enabled status, enable it again. */
|
||||
if (test_and_clear_bit(0, &ctx->irq_flags))
|
||||
vidi_enable_vblank(dev);
|
||||
|
||||
vidi_apply(dev);
|
||||
} else {
|
||||
ctx->suspended = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe);
|
||||
}
|
||||
|
||||
static int vidi_show_connection(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int rc;
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
struct exynos_drm_manager *mgr = get_vidi_mgr(dev);
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
|
||||
@ -445,7 +384,8 @@ static int vidi_store_connection(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
struct exynos_drm_manager *mgr = get_vidi_mgr(dev);
|
||||
struct vidi_context *ctx = mgr->ctx;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoint(buf, 0, &ctx->connected);
|
||||
@ -467,7 +407,7 @@ static int vidi_store_connection(struct device *dev,
|
||||
|
||||
DRM_DEBUG_KMS("requested connection.\n");
|
||||
|
||||
drm_helper_hpd_irq_event(ctx->subdrv.drm_dev);
|
||||
drm_helper_hpd_irq_event(ctx->drm_dev);
|
||||
|
||||
return len;
|
||||
}
|
||||
@ -480,8 +420,7 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data,
|
||||
{
|
||||
struct vidi_context *ctx = NULL;
|
||||
struct drm_encoder *encoder;
|
||||
struct exynos_drm_manager *manager;
|
||||
struct exynos_drm_display_ops *display_ops;
|
||||
struct exynos_drm_display *display;
|
||||
struct drm_exynos_vidi_connection *vidi = data;
|
||||
|
||||
if (!vidi) {
|
||||
@ -496,11 +435,10 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data,
|
||||
|
||||
list_for_each_entry(encoder, &drm_dev->mode_config.encoder_list,
|
||||
head) {
|
||||
manager = exynos_drm_get_manager(encoder);
|
||||
display_ops = manager->display_ops;
|
||||
display = exynos_drm_get_display(encoder);
|
||||
|
||||
if (display_ops->type == EXYNOS_DISPLAY_TYPE_VIDI) {
|
||||
ctx = get_vidi_context(manager->dev);
|
||||
if (display->type == EXYNOS_DISPLAY_TYPE_VIDI) {
|
||||
ctx = display->ctx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -539,16 +477,119 @@ int vidi_connection_ioctl(struct drm_device *drm_dev, void *data,
|
||||
}
|
||||
|
||||
ctx->connected = vidi->connection;
|
||||
drm_helper_hpd_irq_event(ctx->subdrv.drm_dev);
|
||||
drm_helper_hpd_irq_event(ctx->drm_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum drm_connector_status vidi_detect(struct drm_connector *connector,
|
||||
bool force)
|
||||
{
|
||||
struct vidi_context *ctx = ctx_from_connector(connector);
|
||||
|
||||
/*
|
||||
* connection request would come from user side
|
||||
* to do hotplug through specific ioctl.
|
||||
*/
|
||||
return ctx->connected ? connector_status_connected :
|
||||
connector_status_disconnected;
|
||||
}
|
||||
|
||||
static void vidi_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
}
|
||||
|
||||
static struct drm_connector_funcs vidi_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.detect = vidi_detect,
|
||||
.destroy = vidi_connector_destroy,
|
||||
};
|
||||
|
||||
static int vidi_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct vidi_context *ctx = ctx_from_connector(connector);
|
||||
struct edid *edid;
|
||||
int edid_len;
|
||||
|
||||
/*
|
||||
* the edid data comes from user side and it would be set
|
||||
* to ctx->raw_edid through specific ioctl.
|
||||
*/
|
||||
if (!ctx->raw_edid) {
|
||||
DRM_DEBUG_KMS("raw_edid is null.\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
edid_len = (1 + ctx->raw_edid->extensions) * EDID_LENGTH;
|
||||
edid = kmemdup(ctx->raw_edid, edid_len, GFP_KERNEL);
|
||||
if (!edid) {
|
||||
DRM_DEBUG_KMS("failed to allocate edid\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
drm_mode_connector_update_edid_property(connector, edid);
|
||||
|
||||
return drm_add_edid_modes(connector, edid);
|
||||
}
|
||||
|
||||
static int vidi_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static struct drm_encoder *vidi_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct vidi_context *ctx = ctx_from_connector(connector);
|
||||
|
||||
return ctx->encoder;
|
||||
}
|
||||
|
||||
static struct drm_connector_helper_funcs vidi_connector_helper_funcs = {
|
||||
.get_modes = vidi_get_modes,
|
||||
.mode_valid = vidi_mode_valid,
|
||||
.best_encoder = vidi_best_encoder,
|
||||
};
|
||||
|
||||
static int vidi_create_connector(struct exynos_drm_display *display,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
struct vidi_context *ctx = display->ctx;
|
||||
struct drm_connector *connector = &ctx->connector;
|
||||
int ret;
|
||||
|
||||
ctx->encoder = encoder;
|
||||
connector->polled = DRM_CONNECTOR_POLL_HPD;
|
||||
|
||||
ret = drm_connector_init(ctx->drm_dev, connector,
|
||||
&vidi_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to initialize connector with drm\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(connector, &vidi_connector_helper_funcs);
|
||||
drm_sysfs_connector_add(connector);
|
||||
drm_mode_connector_attach_encoder(connector, encoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct exynos_drm_display_ops vidi_display_ops = {
|
||||
.create_connector = vidi_create_connector,
|
||||
};
|
||||
|
||||
static struct exynos_drm_display vidi_display = {
|
||||
.type = EXYNOS_DISPLAY_TYPE_VIDI,
|
||||
.ops = &vidi_display_ops,
|
||||
};
|
||||
|
||||
static int vidi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct vidi_context *ctx;
|
||||
struct exynos_drm_subdrv *subdrv;
|
||||
int ret;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
@ -559,21 +600,19 @@ static int vidi_probe(struct platform_device *pdev)
|
||||
|
||||
INIT_WORK(&ctx->work, vidi_fake_vblank_handler);
|
||||
|
||||
subdrv = &ctx->subdrv;
|
||||
subdrv->dev = dev;
|
||||
subdrv->manager = &vidi_manager;
|
||||
subdrv->probe = vidi_subdrv_probe;
|
||||
subdrv->remove = vidi_subdrv_remove;
|
||||
vidi_manager.ctx = ctx;
|
||||
vidi_display.ctx = ctx;
|
||||
|
||||
mutex_init(&ctx->lock);
|
||||
|
||||
platform_set_drvdata(pdev, ctx);
|
||||
platform_set_drvdata(pdev, &vidi_manager);
|
||||
|
||||
ret = device_create_file(dev, &dev_attr_connection);
|
||||
if (ret < 0)
|
||||
DRM_INFO("failed to create connection sysfs.\n");
|
||||
|
||||
exynos_drm_subdrv_register(subdrv);
|
||||
exynos_drm_manager_register(&vidi_manager);
|
||||
exynos_drm_display_register(&vidi_display);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -582,7 +621,8 @@ static int vidi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct vidi_context *ctx = platform_get_drvdata(pdev);
|
||||
|
||||
exynos_drm_subdrv_unregister(&ctx->subdrv);
|
||||
exynos_drm_display_unregister(&vidi_display);
|
||||
exynos_drm_manager_unregister(&vidi_manager);
|
||||
|
||||
if (ctx->raw_edid != (struct edid *)fake_edid_info) {
|
||||
kfree(ctx->raw_edid);
|
||||
@ -592,32 +632,11 @@ static int vidi_remove(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int vidi_suspend(struct device *dev)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
|
||||
return vidi_power_on(ctx, false);
|
||||
}
|
||||
|
||||
static int vidi_resume(struct device *dev)
|
||||
{
|
||||
struct vidi_context *ctx = get_vidi_context(dev);
|
||||
|
||||
return vidi_power_on(ctx, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops vidi_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(vidi_suspend, vidi_resume)
|
||||
};
|
||||
|
||||
struct platform_driver vidi_driver = {
|
||||
.probe = vidi_probe,
|
||||
.remove = vidi_remove,
|
||||
.driver = {
|
||||
.name = "exynos-drm-vidi",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &vidi_pm_ops,
|
||||
},
|
||||
};
|
||||
|
@ -33,38 +33,42 @@
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/hdmi.h>
|
||||
|
||||
#include <drm/exynos_drm.h>
|
||||
|
||||
#include "exynos_drm_drv.h"
|
||||
#include "exynos_drm_hdmi.h"
|
||||
|
||||
#include "exynos_hdmi.h"
|
||||
#include "exynos_mixer.h"
|
||||
|
||||
#include <linux/gpio.h>
|
||||
#include <media/s5p_hdmi.h>
|
||||
|
||||
#define MAX_WIDTH 1920
|
||||
#define MAX_HEIGHT 1080
|
||||
#define get_hdmi_context(dev) platform_get_drvdata(to_platform_device(dev))
|
||||
#define get_hdmi_display(dev) platform_get_drvdata(to_platform_device(dev))
|
||||
#define ctx_from_connector(c) container_of(c, struct hdmi_context, connector)
|
||||
|
||||
/* AVI header and aspect ratio */
|
||||
#define HDMI_AVI_VERSION 0x02
|
||||
#define HDMI_AVI_LENGTH 0x0D
|
||||
#define AVI_PIC_ASPECT_RATIO_16_9 (2 << 4)
|
||||
#define AVI_SAME_AS_PIC_ASPECT_RATIO 8
|
||||
|
||||
/* AUI header info */
|
||||
#define HDMI_AUI_VERSION 0x01
|
||||
#define HDMI_AUI_LENGTH 0x0A
|
||||
#define AVI_SAME_AS_PIC_ASPECT_RATIO 0x8
|
||||
#define AVI_4_3_CENTER_RATIO 0x9
|
||||
#define AVI_16_9_CENTER_RATIO 0xa
|
||||
|
||||
enum hdmi_type {
|
||||
HDMI_TYPE13,
|
||||
HDMI_TYPE14,
|
||||
};
|
||||
|
||||
struct hdmi_driver_data {
|
||||
unsigned int type;
|
||||
unsigned int is_apb_phy:1;
|
||||
};
|
||||
|
||||
struct hdmi_resources {
|
||||
struct clk *hdmi;
|
||||
struct clk *sclk_hdmi;
|
||||
@ -162,6 +166,7 @@ struct hdmi_v14_conf {
|
||||
struct hdmi_conf_regs {
|
||||
int pixel_clock;
|
||||
int cea_video_id;
|
||||
enum hdmi_picture_aspect aspect_ratio;
|
||||
union {
|
||||
struct hdmi_v13_conf v13_conf;
|
||||
struct hdmi_v14_conf v14_conf;
|
||||
@ -171,16 +176,17 @@ struct hdmi_conf_regs {
|
||||
struct hdmi_context {
|
||||
struct device *dev;
|
||||
struct drm_device *drm_dev;
|
||||
struct drm_connector connector;
|
||||
struct drm_encoder *encoder;
|
||||
bool hpd;
|
||||
bool powered;
|
||||
bool dvi_mode;
|
||||
struct mutex hdmi_mutex;
|
||||
|
||||
void __iomem *regs;
|
||||
void *parent_ctx;
|
||||
int irq;
|
||||
|
||||
struct i2c_client *ddc_port;
|
||||
struct i2c_adapter *ddc_adpt;
|
||||
struct i2c_client *hdmiphy_port;
|
||||
|
||||
/* current hdmiphy conf regs */
|
||||
@ -198,6 +204,14 @@ struct hdmiphy_config {
|
||||
u8 conf[32];
|
||||
};
|
||||
|
||||
struct hdmi_driver_data exynos4212_hdmi_driver_data = {
|
||||
.type = HDMI_TYPE14,
|
||||
};
|
||||
|
||||
struct hdmi_driver_data exynos5_hdmi_driver_data = {
|
||||
.type = HDMI_TYPE14,
|
||||
};
|
||||
|
||||
/* list of phy config settings */
|
||||
static const struct hdmiphy_config hdmiphy_v13_configs[] = {
|
||||
{
|
||||
@ -302,6 +316,24 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = {
|
||||
0x54, 0xbd, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
|
||||
},
|
||||
},
|
||||
{
|
||||
.pixel_clock = 71000000,
|
||||
.conf = {
|
||||
0x01, 0x91, 0x1e, 0x15, 0x40, 0x3c, 0xce, 0x08,
|
||||
0x04, 0x20, 0xb2, 0xd8, 0x45, 0xa0, 0xac, 0x80,
|
||||
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
|
||||
0x54, 0xad, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
|
||||
},
|
||||
},
|
||||
{
|
||||
.pixel_clock = 73250000,
|
||||
.conf = {
|
||||
0x01, 0xd1, 0x1f, 0x15, 0x40, 0x18, 0xe9, 0x08,
|
||||
0x02, 0xa0, 0xb7, 0xd8, 0x45, 0xa0, 0xac, 0x80,
|
||||
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
|
||||
0x54, 0xa8, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
|
||||
},
|
||||
},
|
||||
{
|
||||
.pixel_clock = 74176000,
|
||||
.conf = {
|
||||
@ -329,6 +361,15 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = {
|
||||
0x54, 0x93, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
|
||||
},
|
||||
},
|
||||
{
|
||||
.pixel_clock = 88750000,
|
||||
.conf = {
|
||||
0x01, 0x91, 0x25, 0x17, 0x40, 0x30, 0xfe, 0x08,
|
||||
0x06, 0x20, 0xde, 0xd8, 0x45, 0xa0, 0xac, 0x80,
|
||||
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
|
||||
0x54, 0x8a, 0x24, 0x01, 0x00, 0x00, 0x01, 0x80,
|
||||
},
|
||||
},
|
||||
{
|
||||
.pixel_clock = 106500000,
|
||||
.conf = {
|
||||
@ -347,6 +388,24 @@ static const struct hdmiphy_config hdmiphy_v14_configs[] = {
|
||||
0x54, 0xc7, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
|
||||
},
|
||||
},
|
||||
{
|
||||
.pixel_clock = 115500000,
|
||||
.conf = {
|
||||
0x01, 0xd1, 0x30, 0x1a, 0x40, 0x40, 0x10, 0x04,
|
||||
0x04, 0xa0, 0x21, 0xd9, 0x45, 0xa0, 0xac, 0x80,
|
||||
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
|
||||
0x54, 0xaa, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
|
||||
},
|
||||
},
|
||||
{
|
||||
.pixel_clock = 119000000,
|
||||
.conf = {
|
||||
0x01, 0x91, 0x32, 0x14, 0x40, 0x60, 0xd8, 0x08,
|
||||
0x06, 0x20, 0x2a, 0xd9, 0x45, 0xa0, 0xac, 0x80,
|
||||
0x06, 0x80, 0x11, 0x04, 0x02, 0x22, 0x44, 0x86,
|
||||
0x54, 0x9d, 0x25, 0x03, 0x00, 0x00, 0x01, 0x80,
|
||||
},
|
||||
},
|
||||
{
|
||||
.pixel_clock = 146250000,
|
||||
.conf = {
|
||||
@ -668,7 +727,6 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata,
|
||||
{
|
||||
u32 hdr_sum;
|
||||
u8 chksum;
|
||||
u32 aspect_ratio;
|
||||
u32 mod;
|
||||
u32 vic;
|
||||
|
||||
@ -697,10 +755,28 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata,
|
||||
AVI_ACTIVE_FORMAT_VALID |
|
||||
AVI_UNDERSCANNED_DISPLAY_VALID);
|
||||
|
||||
aspect_ratio = AVI_PIC_ASPECT_RATIO_16_9;
|
||||
|
||||
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2), aspect_ratio |
|
||||
AVI_SAME_AS_PIC_ASPECT_RATIO);
|
||||
/*
|
||||
* Set the aspect ratio as per the mode, mentioned in
|
||||
* Table 9 AVI InfoFrame Data Byte 2 of CEA-861-D Standard
|
||||
*/
|
||||
switch (hdata->mode_conf.aspect_ratio) {
|
||||
case HDMI_PICTURE_ASPECT_4_3:
|
||||
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2),
|
||||
hdata->mode_conf.aspect_ratio |
|
||||
AVI_4_3_CENTER_RATIO);
|
||||
break;
|
||||
case HDMI_PICTURE_ASPECT_16_9:
|
||||
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2),
|
||||
hdata->mode_conf.aspect_ratio |
|
||||
AVI_16_9_CENTER_RATIO);
|
||||
break;
|
||||
case HDMI_PICTURE_ASPECT_NONE:
|
||||
default:
|
||||
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(2),
|
||||
hdata->mode_conf.aspect_ratio |
|
||||
AVI_SAME_AS_PIC_ASPECT_RATIO);
|
||||
break;
|
||||
}
|
||||
|
||||
vic = hdata->mode_conf.cea_video_id;
|
||||
hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(4), vic);
|
||||
@ -728,31 +804,46 @@ static void hdmi_reg_infoframe(struct hdmi_context *hdata,
|
||||
}
|
||||
}
|
||||
|
||||
static bool hdmi_is_connected(void *ctx)
|
||||
static enum drm_connector_status hdmi_detect(struct drm_connector *connector,
|
||||
bool force)
|
||||
{
|
||||
struct hdmi_context *hdata = ctx;
|
||||
struct hdmi_context *hdata = ctx_from_connector(connector);
|
||||
|
||||
return hdata->hpd;
|
||||
return hdata->hpd ? connector_status_connected :
|
||||
connector_status_disconnected;
|
||||
}
|
||||
|
||||
static struct edid *hdmi_get_edid(void *ctx, struct drm_connector *connector)
|
||||
static void hdmi_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct edid *raw_edid;
|
||||
struct hdmi_context *hdata = ctx;
|
||||
}
|
||||
|
||||
if (!hdata->ddc_port)
|
||||
return ERR_PTR(-ENODEV);
|
||||
static struct drm_connector_funcs hdmi_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.detect = hdmi_detect,
|
||||
.destroy = hdmi_connector_destroy,
|
||||
};
|
||||
|
||||
raw_edid = drm_get_edid(connector, hdata->ddc_port->adapter);
|
||||
if (!raw_edid)
|
||||
return ERR_PTR(-ENODEV);
|
||||
static int hdmi_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct hdmi_context *hdata = ctx_from_connector(connector);
|
||||
struct edid *edid;
|
||||
|
||||
hdata->dvi_mode = !drm_detect_hdmi_monitor(raw_edid);
|
||||
if (!hdata->ddc_adpt)
|
||||
return -ENODEV;
|
||||
|
||||
edid = drm_get_edid(connector, hdata->ddc_adpt);
|
||||
if (!edid)
|
||||
return -ENODEV;
|
||||
|
||||
hdata->dvi_mode = !drm_detect_hdmi_monitor(edid);
|
||||
DRM_DEBUG_KMS("%s : width[%d] x height[%d]\n",
|
||||
(hdata->dvi_mode ? "dvi monitor" : "hdmi monitor"),
|
||||
raw_edid->width_cm, raw_edid->height_cm);
|
||||
edid->width_cm, edid->height_cm);
|
||||
|
||||
return raw_edid;
|
||||
drm_mode_connector_update_edid_property(connector, edid);
|
||||
|
||||
return drm_add_edid_modes(connector, edid);
|
||||
}
|
||||
|
||||
static int hdmi_find_phy_conf(struct hdmi_context *hdata, u32 pixel_clock)
|
||||
@ -777,9 +868,10 @@ static int hdmi_find_phy_conf(struct hdmi_context *hdata, u32 pixel_clock)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int hdmi_check_mode(void *ctx, struct drm_display_mode *mode)
|
||||
static int hdmi_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct hdmi_context *hdata = ctx;
|
||||
struct hdmi_context *hdata = ctx_from_connector(connector);
|
||||
int ret;
|
||||
|
||||
DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d clock=%d\n",
|
||||
@ -787,12 +879,103 @@ static int hdmi_check_mode(void *ctx, struct drm_display_mode *mode)
|
||||
(mode->flags & DRM_MODE_FLAG_INTERLACE) ? true :
|
||||
false, mode->clock * 1000);
|
||||
|
||||
ret = mixer_check_mode(mode);
|
||||
if (ret)
|
||||
return MODE_BAD;
|
||||
|
||||
ret = hdmi_find_phy_conf(hdata, mode->clock * 1000);
|
||||
if (ret < 0)
|
||||
return MODE_BAD;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static struct drm_encoder *hdmi_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct hdmi_context *hdata = ctx_from_connector(connector);
|
||||
|
||||
return hdata->encoder;
|
||||
}
|
||||
|
||||
static struct drm_connector_helper_funcs hdmi_connector_helper_funcs = {
|
||||
.get_modes = hdmi_get_modes,
|
||||
.mode_valid = hdmi_mode_valid,
|
||||
.best_encoder = hdmi_best_encoder,
|
||||
};
|
||||
|
||||
static int hdmi_create_connector(struct exynos_drm_display *display,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
struct hdmi_context *hdata = display->ctx;
|
||||
struct drm_connector *connector = &hdata->connector;
|
||||
int ret;
|
||||
|
||||
hdata->encoder = encoder;
|
||||
connector->interlace_allowed = true;
|
||||
connector->polled = DRM_CONNECTOR_POLL_HPD;
|
||||
|
||||
ret = drm_connector_init(hdata->drm_dev, connector,
|
||||
&hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to initialize connector with drm\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(connector, &hdmi_connector_helper_funcs);
|
||||
drm_sysfs_connector_add(connector);
|
||||
drm_mode_connector_attach_encoder(connector, encoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hdmi_initialize(struct exynos_drm_display *display,
|
||||
struct drm_device *drm_dev)
|
||||
{
|
||||
struct hdmi_context *hdata = display->ctx;
|
||||
|
||||
hdata->drm_dev = drm_dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hdmi_mode_fixup(struct exynos_drm_display *display,
|
||||
struct drm_connector *connector,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct drm_display_mode *m;
|
||||
int mode_ok;
|
||||
|
||||
DRM_DEBUG_KMS("%s\n", __FILE__);
|
||||
|
||||
drm_mode_set_crtcinfo(adjusted_mode, 0);
|
||||
|
||||
mode_ok = hdmi_mode_valid(connector, adjusted_mode);
|
||||
|
||||
/* just return if user desired mode exists. */
|
||||
if (mode_ok == MODE_OK)
|
||||
return;
|
||||
|
||||
/*
|
||||
* otherwise, find the most suitable mode among modes and change it
|
||||
* to adjusted_mode.
|
||||
*/
|
||||
list_for_each_entry(m, &connector->modes, head) {
|
||||
mode_ok = hdmi_mode_valid(connector, m);
|
||||
|
||||
if (mode_ok == MODE_OK) {
|
||||
DRM_INFO("desired mode doesn't exist so\n");
|
||||
DRM_INFO("use the most suitable mode among modes.\n");
|
||||
|
||||
DRM_DEBUG_KMS("Adjusted Mode: [%d]x[%d] [%d]Hz\n",
|
||||
m->hdisplay, m->vdisplay, m->vrefresh);
|
||||
|
||||
drm_mode_copy(adjusted_mode, m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void hdmi_set_acr(u32 freq, u8 *acr)
|
||||
{
|
||||
u32 n, cts;
|
||||
@ -1421,6 +1604,7 @@ static void hdmi_v13_mode_set(struct hdmi_context *hdata,
|
||||
hdata->mode_conf.cea_video_id =
|
||||
drm_match_cea_mode((struct drm_display_mode *)m);
|
||||
hdata->mode_conf.pixel_clock = m->clock * 1000;
|
||||
hdata->mode_conf.aspect_ratio = m->picture_aspect_ratio;
|
||||
|
||||
hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay);
|
||||
hdmi_set_reg(core->h_v_line, 3, (m->htotal << 12) | m->vtotal);
|
||||
@ -1517,6 +1701,7 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata,
|
||||
hdata->mode_conf.cea_video_id =
|
||||
drm_match_cea_mode((struct drm_display_mode *)m);
|
||||
hdata->mode_conf.pixel_clock = m->clock * 1000;
|
||||
hdata->mode_conf.aspect_ratio = m->picture_aspect_ratio;
|
||||
|
||||
hdmi_set_reg(core->h_blank, 2, m->htotal - m->hdisplay);
|
||||
hdmi_set_reg(core->v_line, 2, m->vtotal);
|
||||
@ -1618,9 +1803,10 @@ static void hdmi_v14_mode_set(struct hdmi_context *hdata,
|
||||
hdmi_set_reg(tg->tg_3d, 1, 0x0);
|
||||
}
|
||||
|
||||
static void hdmi_mode_set(void *ctx, struct drm_display_mode *mode)
|
||||
static void hdmi_mode_set(struct exynos_drm_display *display,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct hdmi_context *hdata = ctx;
|
||||
struct hdmi_context *hdata = display->ctx;
|
||||
struct drm_display_mode *m = mode;
|
||||
|
||||
DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%s\n",
|
||||
@ -1634,16 +1820,9 @@ static void hdmi_mode_set(void *ctx, struct drm_display_mode *mode)
|
||||
hdmi_v14_mode_set(hdata, mode);
|
||||
}
|
||||
|
||||
static void hdmi_get_max_resol(void *ctx, unsigned int *width,
|
||||
unsigned int *height)
|
||||
static void hdmi_commit(struct exynos_drm_display *display)
|
||||
{
|
||||
*width = MAX_WIDTH;
|
||||
*height = MAX_HEIGHT;
|
||||
}
|
||||
|
||||
static void hdmi_commit(void *ctx)
|
||||
{
|
||||
struct hdmi_context *hdata = ctx;
|
||||
struct hdmi_context *hdata = display->ctx;
|
||||
|
||||
mutex_lock(&hdata->hdmi_mutex);
|
||||
if (!hdata->powered) {
|
||||
@ -1655,8 +1834,9 @@ static void hdmi_commit(void *ctx)
|
||||
hdmi_conf_apply(hdata);
|
||||
}
|
||||
|
||||
static void hdmi_poweron(struct hdmi_context *hdata)
|
||||
static void hdmi_poweron(struct exynos_drm_display *display)
|
||||
{
|
||||
struct hdmi_context *hdata = display->ctx;
|
||||
struct hdmi_resources *res = &hdata->res;
|
||||
|
||||
mutex_lock(&hdata->hdmi_mutex);
|
||||
@ -1669,6 +1849,8 @@ static void hdmi_poweron(struct hdmi_context *hdata)
|
||||
|
||||
mutex_unlock(&hdata->hdmi_mutex);
|
||||
|
||||
pm_runtime_get_sync(hdata->dev);
|
||||
|
||||
if (regulator_bulk_enable(res->regul_count, res->regul_bulk))
|
||||
DRM_DEBUG_KMS("failed to enable regulator bulk\n");
|
||||
|
||||
@ -1677,10 +1859,12 @@ static void hdmi_poweron(struct hdmi_context *hdata)
|
||||
clk_prepare_enable(res->sclk_hdmi);
|
||||
|
||||
hdmiphy_poweron(hdata);
|
||||
hdmi_commit(display);
|
||||
}
|
||||
|
||||
static void hdmi_poweroff(struct hdmi_context *hdata)
|
||||
static void hdmi_poweroff(struct exynos_drm_display *display)
|
||||
{
|
||||
struct hdmi_context *hdata = display->ctx;
|
||||
struct hdmi_resources *res = &hdata->res;
|
||||
|
||||
mutex_lock(&hdata->hdmi_mutex);
|
||||
@ -1700,30 +1884,27 @@ static void hdmi_poweroff(struct hdmi_context *hdata)
|
||||
clk_disable_unprepare(res->hdmiphy);
|
||||
regulator_bulk_disable(res->regul_count, res->regul_bulk);
|
||||
|
||||
mutex_lock(&hdata->hdmi_mutex);
|
||||
pm_runtime_put_sync(hdata->dev);
|
||||
|
||||
mutex_lock(&hdata->hdmi_mutex);
|
||||
hdata->powered = false;
|
||||
|
||||
out:
|
||||
mutex_unlock(&hdata->hdmi_mutex);
|
||||
}
|
||||
|
||||
static void hdmi_dpms(void *ctx, int mode)
|
||||
static void hdmi_dpms(struct exynos_drm_display *display, int mode)
|
||||
{
|
||||
struct hdmi_context *hdata = ctx;
|
||||
|
||||
DRM_DEBUG_KMS("mode %d\n", mode);
|
||||
|
||||
switch (mode) {
|
||||
case DRM_MODE_DPMS_ON:
|
||||
if (pm_runtime_suspended(hdata->dev))
|
||||
pm_runtime_get_sync(hdata->dev);
|
||||
hdmi_poweron(display);
|
||||
break;
|
||||
case DRM_MODE_DPMS_STANDBY:
|
||||
case DRM_MODE_DPMS_SUSPEND:
|
||||
case DRM_MODE_DPMS_OFF:
|
||||
if (!pm_runtime_suspended(hdata->dev))
|
||||
pm_runtime_put_sync(hdata->dev);
|
||||
hdmi_poweroff(display);
|
||||
break;
|
||||
default:
|
||||
DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode);
|
||||
@ -1731,30 +1912,30 @@ static void hdmi_dpms(void *ctx, int mode)
|
||||
}
|
||||
}
|
||||
|
||||
static struct exynos_hdmi_ops hdmi_ops = {
|
||||
/* display */
|
||||
.is_connected = hdmi_is_connected,
|
||||
.get_edid = hdmi_get_edid,
|
||||
.check_mode = hdmi_check_mode,
|
||||
|
||||
/* manager */
|
||||
static struct exynos_drm_display_ops hdmi_display_ops = {
|
||||
.initialize = hdmi_initialize,
|
||||
.create_connector = hdmi_create_connector,
|
||||
.mode_fixup = hdmi_mode_fixup,
|
||||
.mode_set = hdmi_mode_set,
|
||||
.get_max_resol = hdmi_get_max_resol,
|
||||
.commit = hdmi_commit,
|
||||
.dpms = hdmi_dpms,
|
||||
.commit = hdmi_commit,
|
||||
};
|
||||
|
||||
static struct exynos_drm_display hdmi_display = {
|
||||
.type = EXYNOS_DISPLAY_TYPE_HDMI,
|
||||
.ops = &hdmi_display_ops,
|
||||
};
|
||||
|
||||
static irqreturn_t hdmi_irq_thread(int irq, void *arg)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *ctx = arg;
|
||||
struct hdmi_context *hdata = ctx->ctx;
|
||||
struct hdmi_context *hdata = arg;
|
||||
|
||||
mutex_lock(&hdata->hdmi_mutex);
|
||||
hdata->hpd = gpio_get_value(hdata->hpd_gpio);
|
||||
mutex_unlock(&hdata->hdmi_mutex);
|
||||
|
||||
if (ctx->drm_dev)
|
||||
drm_helper_hpd_irq_event(ctx->drm_dev);
|
||||
if (hdata->drm_dev)
|
||||
drm_helper_hpd_irq_event(hdata->drm_dev);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@ -1830,20 +2011,6 @@ fail:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static struct i2c_client *hdmi_ddc, *hdmi_hdmiphy;
|
||||
|
||||
void hdmi_attach_ddc_client(struct i2c_client *ddc)
|
||||
{
|
||||
if (ddc)
|
||||
hdmi_ddc = ddc;
|
||||
}
|
||||
|
||||
void hdmi_attach_hdmiphy_client(struct i2c_client *hdmiphy)
|
||||
{
|
||||
if (hdmiphy)
|
||||
hdmi_hdmiphy = hdmiphy;
|
||||
}
|
||||
|
||||
static struct s5p_hdmi_platform_data *drm_hdmi_dt_parse_pdata
|
||||
(struct device *dev)
|
||||
{
|
||||
@ -1871,10 +2038,10 @@ err_data:
|
||||
static struct of_device_id hdmi_match_types[] = {
|
||||
{
|
||||
.compatible = "samsung,exynos5-hdmi",
|
||||
.data = (void *)HDMI_TYPE14,
|
||||
.data = &exynos5_hdmi_driver_data,
|
||||
}, {
|
||||
.compatible = "samsung,exynos4212-hdmi",
|
||||
.data = (void *)HDMI_TYPE14,
|
||||
.data = &exynos4212_hdmi_driver_data,
|
||||
}, {
|
||||
/* end node */
|
||||
}
|
||||
@ -1883,11 +2050,12 @@ static struct of_device_id hdmi_match_types[] = {
|
||||
static int hdmi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct exynos_drm_hdmi_context *drm_hdmi_ctx;
|
||||
struct hdmi_context *hdata;
|
||||
struct s5p_hdmi_platform_data *pdata;
|
||||
struct resource *res;
|
||||
const struct of_device_id *match;
|
||||
struct device_node *ddc_node, *phy_node;
|
||||
struct hdmi_driver_data *drv_data;
|
||||
int ret;
|
||||
|
||||
if (!dev->of_node)
|
||||
@ -1897,25 +2065,20 @@ static int hdmi_probe(struct platform_device *pdev)
|
||||
if (!pdata)
|
||||
return -EINVAL;
|
||||
|
||||
drm_hdmi_ctx = devm_kzalloc(dev, sizeof(*drm_hdmi_ctx), GFP_KERNEL);
|
||||
if (!drm_hdmi_ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
hdata = devm_kzalloc(dev, sizeof(struct hdmi_context), GFP_KERNEL);
|
||||
if (!hdata)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&hdata->hdmi_mutex);
|
||||
|
||||
drm_hdmi_ctx->ctx = (void *)hdata;
|
||||
hdata->parent_ctx = (void *)drm_hdmi_ctx;
|
||||
|
||||
platform_set_drvdata(pdev, drm_hdmi_ctx);
|
||||
platform_set_drvdata(pdev, &hdmi_display);
|
||||
|
||||
match = of_match_node(hdmi_match_types, dev->of_node);
|
||||
if (!match)
|
||||
return -ENODEV;
|
||||
hdata->type = (enum hdmi_type)match->data;
|
||||
|
||||
drv_data = (struct hdmi_driver_data *)match->data;
|
||||
hdata->type = drv_data->type;
|
||||
|
||||
hdata->hpd_gpio = pdata->hpd_gpio;
|
||||
hdata->dev = dev;
|
||||
@ -1938,21 +2101,34 @@ static int hdmi_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
/* DDC i2c driver */
|
||||
if (i2c_add_driver(&ddc_driver)) {
|
||||
DRM_ERROR("failed to register ddc i2c driver\n");
|
||||
return -ENOENT;
|
||||
ddc_node = of_parse_phandle(dev->of_node, "ddc", 0);
|
||||
if (!ddc_node) {
|
||||
DRM_ERROR("Failed to find ddc node in device tree\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
hdata->ddc_adpt = of_find_i2c_adapter_by_node(ddc_node);
|
||||
if (!hdata->ddc_adpt) {
|
||||
DRM_ERROR("Failed to get ddc i2c adapter by node\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hdata->ddc_port = hdmi_ddc;
|
||||
/* Not support APB PHY yet. */
|
||||
if (drv_data->is_apb_phy)
|
||||
return -EPERM;
|
||||
|
||||
/* hdmiphy i2c driver */
|
||||
if (i2c_add_driver(&hdmiphy_driver)) {
|
||||
DRM_ERROR("failed to register hdmiphy i2c driver\n");
|
||||
ret = -ENOENT;
|
||||
phy_node = of_parse_phandle(dev->of_node, "phy", 0);
|
||||
if (!phy_node) {
|
||||
DRM_ERROR("Failed to find hdmiphy node in device tree\n");
|
||||
ret = -ENODEV;
|
||||
goto err_ddc;
|
||||
}
|
||||
hdata->hdmiphy_port = of_find_i2c_device_by_node(phy_node);
|
||||
if (!hdata->hdmiphy_port) {
|
||||
DRM_ERROR("Failed to get hdmi phy i2c client from node\n");
|
||||
ret = -ENODEV;
|
||||
goto err_ddc;
|
||||
}
|
||||
|
||||
hdata->hdmiphy_port = hdmi_hdmiphy;
|
||||
|
||||
hdata->irq = gpio_to_irq(hdata->hpd_gpio);
|
||||
if (hdata->irq < 0) {
|
||||
@ -1966,119 +2142,45 @@ static int hdmi_probe(struct platform_device *pdev)
|
||||
ret = devm_request_threaded_irq(dev, hdata->irq, NULL,
|
||||
hdmi_irq_thread, IRQF_TRIGGER_RISING |
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
"hdmi", drm_hdmi_ctx);
|
||||
"hdmi", hdata);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to register hdmi interrupt\n");
|
||||
goto err_hdmiphy;
|
||||
}
|
||||
|
||||
/* Attach HDMI Driver to common hdmi. */
|
||||
exynos_hdmi_drv_attach(drm_hdmi_ctx);
|
||||
|
||||
/* register specific callbacks to common hdmi. */
|
||||
exynos_hdmi_ops_register(&hdmi_ops);
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
hdmi_display.ctx = hdata;
|
||||
exynos_drm_display_register(&hdmi_display);
|
||||
|
||||
return 0;
|
||||
|
||||
err_hdmiphy:
|
||||
i2c_del_driver(&hdmiphy_driver);
|
||||
put_device(&hdata->hdmiphy_port->dev);
|
||||
err_ddc:
|
||||
i2c_del_driver(&ddc_driver);
|
||||
put_device(&hdata->ddc_adpt->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hdmi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct exynos_drm_display *display = get_hdmi_display(dev);
|
||||
struct hdmi_context *hdata = display->ctx;
|
||||
|
||||
pm_runtime_disable(dev);
|
||||
|
||||
/* hdmiphy i2c driver */
|
||||
i2c_del_driver(&hdmiphy_driver);
|
||||
/* DDC i2c driver */
|
||||
i2c_del_driver(&ddc_driver);
|
||||
put_device(&hdata->hdmiphy_port->dev);
|
||||
put_device(&hdata->ddc_adpt->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int hdmi_suspend(struct device *dev)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev);
|
||||
struct hdmi_context *hdata = ctx->ctx;
|
||||
|
||||
disable_irq(hdata->irq);
|
||||
|
||||
hdata->hpd = false;
|
||||
if (ctx->drm_dev)
|
||||
drm_helper_hpd_irq_event(ctx->drm_dev);
|
||||
|
||||
if (pm_runtime_suspended(dev)) {
|
||||
DRM_DEBUG_KMS("Already suspended\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
hdmi_poweroff(hdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hdmi_resume(struct device *dev)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev);
|
||||
struct hdmi_context *hdata = ctx->ctx;
|
||||
|
||||
hdata->hpd = gpio_get_value(hdata->hpd_gpio);
|
||||
|
||||
enable_irq(hdata->irq);
|
||||
|
||||
if (!pm_runtime_suspended(dev)) {
|
||||
DRM_DEBUG_KMS("Already resumed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
hdmi_poweron(hdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
static int hdmi_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev);
|
||||
struct hdmi_context *hdata = ctx->ctx;
|
||||
|
||||
hdmi_poweroff(hdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hdmi_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev);
|
||||
struct hdmi_context *hdata = ctx->ctx;
|
||||
|
||||
hdmi_poweron(hdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops hdmi_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(hdmi_suspend, hdmi_resume)
|
||||
SET_RUNTIME_PM_OPS(hdmi_runtime_suspend, hdmi_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
struct platform_driver hdmi_driver = {
|
||||
.probe = hdmi_probe,
|
||||
.remove = hdmi_remove,
|
||||
.driver = {
|
||||
.name = "exynos-hdmi",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &hdmi_pm_ops,
|
||||
.of_match_table = hdmi_match_types,
|
||||
},
|
||||
};
|
||||
|
@ -36,10 +36,13 @@
|
||||
|
||||
#include "exynos_drm_drv.h"
|
||||
#include "exynos_drm_crtc.h"
|
||||
#include "exynos_drm_hdmi.h"
|
||||
#include "exynos_drm_iommu.h"
|
||||
#include "exynos_mixer.h"
|
||||
|
||||
#define get_mixer_context(dev) platform_get_drvdata(to_platform_device(dev))
|
||||
#define get_mixer_manager(dev) platform_get_drvdata(to_platform_device(dev))
|
||||
|
||||
#define MIXER_WIN_NR 3
|
||||
#define MIXER_DEFAULT_WIN 0
|
||||
|
||||
struct hdmi_win_data {
|
||||
dma_addr_t dma_addr;
|
||||
@ -82,6 +85,7 @@ enum mixer_version_id {
|
||||
};
|
||||
|
||||
struct mixer_context {
|
||||
struct platform_device *pdev;
|
||||
struct device *dev;
|
||||
struct drm_device *drm_dev;
|
||||
int pipe;
|
||||
@ -94,7 +98,6 @@ struct mixer_context {
|
||||
struct mixer_resources mixer_res;
|
||||
struct hdmi_win_data win_data[MIXER_WIN_NR];
|
||||
enum mixer_version_id mxr_ver;
|
||||
void *parent_ctx;
|
||||
wait_queue_head_t wait_vsync_queue;
|
||||
atomic_t wait_vsync_event;
|
||||
};
|
||||
@ -685,30 +688,195 @@ static void mixer_win_reset(struct mixer_context *ctx)
|
||||
spin_unlock_irqrestore(&res->reg_slock, flags);
|
||||
}
|
||||
|
||||
static int mixer_iommu_on(void *ctx, bool enable)
|
||||
static irqreturn_t mixer_irq_handler(int irq, void *arg)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *drm_hdmi_ctx;
|
||||
struct mixer_context *mdata = ctx;
|
||||
struct drm_device *drm_dev;
|
||||
struct mixer_context *ctx = arg;
|
||||
struct mixer_resources *res = &ctx->mixer_res;
|
||||
u32 val, base, shadow;
|
||||
|
||||
drm_hdmi_ctx = mdata->parent_ctx;
|
||||
drm_dev = drm_hdmi_ctx->drm_dev;
|
||||
spin_lock(&res->reg_slock);
|
||||
|
||||
if (is_drm_iommu_supported(drm_dev)) {
|
||||
if (enable)
|
||||
return drm_iommu_attach_device(drm_dev, mdata->dev);
|
||||
/* read interrupt status for handling and clearing flags for VSYNC */
|
||||
val = mixer_reg_read(res, MXR_INT_STATUS);
|
||||
|
||||
drm_iommu_detach_device(drm_dev, mdata->dev);
|
||||
/* handling VSYNC */
|
||||
if (val & MXR_INT_STATUS_VSYNC) {
|
||||
/* interlace scan need to check shadow register */
|
||||
if (ctx->interlace) {
|
||||
base = mixer_reg_read(res, MXR_GRAPHIC_BASE(0));
|
||||
shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(0));
|
||||
if (base != shadow)
|
||||
goto out;
|
||||
|
||||
base = mixer_reg_read(res, MXR_GRAPHIC_BASE(1));
|
||||
shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(1));
|
||||
if (base != shadow)
|
||||
goto out;
|
||||
}
|
||||
|
||||
drm_handle_vblank(ctx->drm_dev, ctx->pipe);
|
||||
exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe);
|
||||
|
||||
/* set wait vsync event to zero and wake up queue. */
|
||||
if (atomic_read(&ctx->wait_vsync_event)) {
|
||||
atomic_set(&ctx->wait_vsync_event, 0);
|
||||
wake_up(&ctx->wait_vsync_queue);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
/* clear interrupts */
|
||||
if (~val & MXR_INT_EN_VSYNC) {
|
||||
/* vsync interrupt use different bit for read and clear */
|
||||
val &= ~MXR_INT_EN_VSYNC;
|
||||
val |= MXR_INT_CLEAR_VSYNC;
|
||||
}
|
||||
mixer_reg_write(res, MXR_INT_STATUS, val);
|
||||
|
||||
spin_unlock(&res->reg_slock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int mixer_resources_init(struct mixer_context *mixer_ctx)
|
||||
{
|
||||
struct device *dev = &mixer_ctx->pdev->dev;
|
||||
struct mixer_resources *mixer_res = &mixer_ctx->mixer_res;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
spin_lock_init(&mixer_res->reg_slock);
|
||||
|
||||
mixer_res->mixer = devm_clk_get(dev, "mixer");
|
||||
if (IS_ERR(mixer_res->mixer)) {
|
||||
dev_err(dev, "failed to get clock 'mixer'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mixer_res->sclk_hdmi = devm_clk_get(dev, "sclk_hdmi");
|
||||
if (IS_ERR(mixer_res->sclk_hdmi)) {
|
||||
dev_err(dev, "failed to get clock 'sclk_hdmi'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_MEM, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(dev, "get memory resource failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
mixer_res->mixer_regs = devm_ioremap(dev, res->start,
|
||||
resource_size(res));
|
||||
if (mixer_res->mixer_regs == NULL) {
|
||||
dev_err(dev, "register mapping failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_IRQ, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(dev, "get interrupt resource failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, res->start, mixer_irq_handler,
|
||||
0, "drm_mixer", mixer_ctx);
|
||||
if (ret) {
|
||||
dev_err(dev, "request interrupt failed.\n");
|
||||
return ret;
|
||||
}
|
||||
mixer_res->irq = res->start;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_enable_vblank(void *ctx, int pipe)
|
||||
static int vp_resources_init(struct mixer_context *mixer_ctx)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx;
|
||||
struct device *dev = &mixer_ctx->pdev->dev;
|
||||
struct mixer_resources *mixer_res = &mixer_ctx->mixer_res;
|
||||
struct resource *res;
|
||||
|
||||
mixer_res->vp = devm_clk_get(dev, "vp");
|
||||
if (IS_ERR(mixer_res->vp)) {
|
||||
dev_err(dev, "failed to get clock 'vp'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
mixer_res->sclk_mixer = devm_clk_get(dev, "sclk_mixer");
|
||||
if (IS_ERR(mixer_res->sclk_mixer)) {
|
||||
dev_err(dev, "failed to get clock 'sclk_mixer'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
mixer_res->sclk_dac = devm_clk_get(dev, "sclk_dac");
|
||||
if (IS_ERR(mixer_res->sclk_dac)) {
|
||||
dev_err(dev, "failed to get clock 'sclk_dac'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (mixer_res->sclk_hdmi)
|
||||
clk_set_parent(mixer_res->sclk_mixer, mixer_res->sclk_hdmi);
|
||||
|
||||
res = platform_get_resource(mixer_ctx->pdev, IORESOURCE_MEM, 1);
|
||||
if (res == NULL) {
|
||||
dev_err(dev, "get memory resource failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
mixer_res->vp_regs = devm_ioremap(dev, res->start,
|
||||
resource_size(res));
|
||||
if (mixer_res->vp_regs == NULL) {
|
||||
dev_err(dev, "register mapping failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_initialize(struct exynos_drm_manager *mgr,
|
||||
struct drm_device *drm_dev, int pipe)
|
||||
{
|
||||
int ret;
|
||||
struct mixer_context *mixer_ctx = mgr->ctx;
|
||||
|
||||
mixer_ctx->drm_dev = drm_dev;
|
||||
mixer_ctx->pipe = pipe;
|
||||
|
||||
/* acquire resources: regs, irqs, clocks */
|
||||
ret = mixer_resources_init(mixer_ctx);
|
||||
if (ret) {
|
||||
DRM_ERROR("mixer_resources_init failed ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mixer_ctx->vp_enabled) {
|
||||
/* acquire vp resources: regs, irqs, clocks */
|
||||
ret = vp_resources_init(mixer_ctx);
|
||||
if (ret) {
|
||||
DRM_ERROR("vp_resources_init failed ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_drm_iommu_supported(mixer_ctx->drm_dev))
|
||||
return 0;
|
||||
|
||||
return drm_iommu_attach_device(mixer_ctx->drm_dev, mixer_ctx->dev);
|
||||
}
|
||||
|
||||
static void mixer_mgr_remove(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = mgr->ctx;
|
||||
|
||||
if (is_drm_iommu_supported(mixer_ctx->drm_dev))
|
||||
drm_iommu_detach_device(mixer_ctx->drm_dev, mixer_ctx->dev);
|
||||
}
|
||||
|
||||
static int mixer_enable_vblank(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = mgr->ctx;
|
||||
struct mixer_resources *res = &mixer_ctx->mixer_res;
|
||||
|
||||
mixer_ctx->pipe = pipe;
|
||||
if (!mixer_ctx->powered) {
|
||||
mixer_ctx->int_en |= MXR_INT_EN_VSYNC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* enable vsync interrupt */
|
||||
mixer_reg_writemask(res, MXR_INT_EN, MXR_INT_EN_VSYNC,
|
||||
@ -717,19 +885,19 @@ static int mixer_enable_vblank(void *ctx, int pipe)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mixer_disable_vblank(void *ctx)
|
||||
static void mixer_disable_vblank(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx;
|
||||
struct mixer_context *mixer_ctx = mgr->ctx;
|
||||
struct mixer_resources *res = &mixer_ctx->mixer_res;
|
||||
|
||||
/* disable vsync interrupt */
|
||||
mixer_reg_writemask(res, MXR_INT_EN, 0, MXR_INT_EN_VSYNC);
|
||||
}
|
||||
|
||||
static void mixer_win_mode_set(void *ctx,
|
||||
struct exynos_drm_overlay *overlay)
|
||||
static void mixer_win_mode_set(struct exynos_drm_manager *mgr,
|
||||
struct exynos_drm_overlay *overlay)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx;
|
||||
struct mixer_context *mixer_ctx = mgr->ctx;
|
||||
struct hdmi_win_data *win_data;
|
||||
int win;
|
||||
|
||||
@ -778,9 +946,10 @@ static void mixer_win_mode_set(void *ctx,
|
||||
win_data->scan_flags = overlay->scan_flag;
|
||||
}
|
||||
|
||||
static void mixer_win_commit(void *ctx, int win)
|
||||
static void mixer_win_commit(struct exynos_drm_manager *mgr, int zpos)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx;
|
||||
struct mixer_context *mixer_ctx = mgr->ctx;
|
||||
int win = zpos == DEFAULT_ZPOS ? MIXER_DEFAULT_WIN : zpos;
|
||||
|
||||
DRM_DEBUG_KMS("win: %d\n", win);
|
||||
|
||||
@ -799,10 +968,11 @@ static void mixer_win_commit(void *ctx, int win)
|
||||
mixer_ctx->win_data[win].enabled = true;
|
||||
}
|
||||
|
||||
static void mixer_win_disable(void *ctx, int win)
|
||||
static void mixer_win_disable(struct exynos_drm_manager *mgr, int zpos)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx;
|
||||
struct mixer_context *mixer_ctx = mgr->ctx;
|
||||
struct mixer_resources *res = &mixer_ctx->mixer_res;
|
||||
int win = zpos == DEFAULT_ZPOS ? MIXER_DEFAULT_WIN : zpos;
|
||||
unsigned long flags;
|
||||
|
||||
DRM_DEBUG_KMS("win: %d\n", win);
|
||||
@ -826,32 +996,9 @@ static void mixer_win_disable(void *ctx, int win)
|
||||
mixer_ctx->win_data[win].enabled = false;
|
||||
}
|
||||
|
||||
static int mixer_check_mode(void *ctx, struct drm_display_mode *mode)
|
||||
static void mixer_wait_for_vblank(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx;
|
||||
u32 w, h;
|
||||
|
||||
w = mode->hdisplay;
|
||||
h = mode->vdisplay;
|
||||
|
||||
DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d\n",
|
||||
mode->hdisplay, mode->vdisplay, mode->vrefresh,
|
||||
(mode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0);
|
||||
|
||||
if (mixer_ctx->mxr_ver == MXR_VER_0_0_0_16 ||
|
||||
mixer_ctx->mxr_ver == MXR_VER_128_0_0_184)
|
||||
return 0;
|
||||
|
||||
if ((w >= 464 && w <= 720 && h >= 261 && h <= 576) ||
|
||||
(w >= 1024 && w <= 1280 && h >= 576 && h <= 720) ||
|
||||
(w >= 1664 && w <= 1920 && h >= 936 && h <= 1080))
|
||||
return 0;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
static void mixer_wait_for_vblank(void *ctx)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx;
|
||||
struct mixer_context *mixer_ctx = mgr->ctx;
|
||||
|
||||
mutex_lock(&mixer_ctx->mixer_mutex);
|
||||
if (!mixer_ctx->powered) {
|
||||
@ -872,21 +1019,23 @@ static void mixer_wait_for_vblank(void *ctx)
|
||||
DRM_DEBUG_KMS("vblank wait timed out.\n");
|
||||
}
|
||||
|
||||
static void mixer_window_suspend(struct mixer_context *ctx)
|
||||
static void mixer_window_suspend(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct mixer_context *ctx = mgr->ctx;
|
||||
struct hdmi_win_data *win_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MIXER_WIN_NR; i++) {
|
||||
win_data = &ctx->win_data[i];
|
||||
win_data->resume = win_data->enabled;
|
||||
mixer_win_disable(ctx, i);
|
||||
mixer_win_disable(mgr, i);
|
||||
}
|
||||
mixer_wait_for_vblank(ctx);
|
||||
mixer_wait_for_vblank(mgr);
|
||||
}
|
||||
|
||||
static void mixer_window_resume(struct mixer_context *ctx)
|
||||
static void mixer_window_resume(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct mixer_context *ctx = mgr->ctx;
|
||||
struct hdmi_win_data *win_data;
|
||||
int i;
|
||||
|
||||
@ -894,11 +1043,14 @@ static void mixer_window_resume(struct mixer_context *ctx)
|
||||
win_data = &ctx->win_data[i];
|
||||
win_data->enabled = win_data->resume;
|
||||
win_data->resume = false;
|
||||
if (win_data->enabled)
|
||||
mixer_win_commit(mgr, i);
|
||||
}
|
||||
}
|
||||
|
||||
static void mixer_poweron(struct mixer_context *ctx)
|
||||
static void mixer_poweron(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct mixer_context *ctx = mgr->ctx;
|
||||
struct mixer_resources *res = &ctx->mixer_res;
|
||||
|
||||
mutex_lock(&ctx->mixer_mutex);
|
||||
@ -909,6 +1061,8 @@ static void mixer_poweron(struct mixer_context *ctx)
|
||||
ctx->powered = true;
|
||||
mutex_unlock(&ctx->mixer_mutex);
|
||||
|
||||
pm_runtime_get_sync(ctx->dev);
|
||||
|
||||
clk_prepare_enable(res->mixer);
|
||||
if (ctx->vp_enabled) {
|
||||
clk_prepare_enable(res->vp);
|
||||
@ -918,11 +1072,12 @@ static void mixer_poweron(struct mixer_context *ctx)
|
||||
mixer_reg_write(res, MXR_INT_EN, ctx->int_en);
|
||||
mixer_win_reset(ctx);
|
||||
|
||||
mixer_window_resume(ctx);
|
||||
mixer_window_resume(mgr);
|
||||
}
|
||||
|
||||
static void mixer_poweroff(struct mixer_context *ctx)
|
||||
static void mixer_poweroff(struct exynos_drm_manager *mgr)
|
||||
{
|
||||
struct mixer_context *ctx = mgr->ctx;
|
||||
struct mixer_resources *res = &ctx->mixer_res;
|
||||
|
||||
mutex_lock(&ctx->mixer_mutex);
|
||||
@ -930,7 +1085,7 @@ static void mixer_poweroff(struct mixer_context *ctx)
|
||||
goto out;
|
||||
mutex_unlock(&ctx->mixer_mutex);
|
||||
|
||||
mixer_window_suspend(ctx);
|
||||
mixer_window_suspend(mgr);
|
||||
|
||||
ctx->int_en = mixer_reg_read(res, MXR_INT_EN);
|
||||
|
||||
@ -940,6 +1095,8 @@ static void mixer_poweroff(struct mixer_context *ctx)
|
||||
clk_disable_unprepare(res->sclk_mixer);
|
||||
}
|
||||
|
||||
pm_runtime_put_sync(ctx->dev);
|
||||
|
||||
mutex_lock(&ctx->mixer_mutex);
|
||||
ctx->powered = false;
|
||||
|
||||
@ -947,20 +1104,16 @@ out:
|
||||
mutex_unlock(&ctx->mixer_mutex);
|
||||
}
|
||||
|
||||
static void mixer_dpms(void *ctx, int mode)
|
||||
static void mixer_dpms(struct exynos_drm_manager *mgr, int mode)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx;
|
||||
|
||||
switch (mode) {
|
||||
case DRM_MODE_DPMS_ON:
|
||||
if (pm_runtime_suspended(mixer_ctx->dev))
|
||||
pm_runtime_get_sync(mixer_ctx->dev);
|
||||
mixer_poweron(mgr);
|
||||
break;
|
||||
case DRM_MODE_DPMS_STANDBY:
|
||||
case DRM_MODE_DPMS_SUSPEND:
|
||||
case DRM_MODE_DPMS_OFF:
|
||||
if (!pm_runtime_suspended(mixer_ctx->dev))
|
||||
pm_runtime_put_sync(mixer_ctx->dev);
|
||||
mixer_poweroff(mgr);
|
||||
break;
|
||||
default:
|
||||
DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode);
|
||||
@ -968,169 +1121,42 @@ static void mixer_dpms(void *ctx, int mode)
|
||||
}
|
||||
}
|
||||
|
||||
static struct exynos_mixer_ops mixer_ops = {
|
||||
/* manager */
|
||||
.iommu_on = mixer_iommu_on,
|
||||
/* Only valid for Mixer version 16.0.33.0 */
|
||||
int mixer_check_mode(struct drm_display_mode *mode)
|
||||
{
|
||||
u32 w, h;
|
||||
|
||||
w = mode->hdisplay;
|
||||
h = mode->vdisplay;
|
||||
|
||||
DRM_DEBUG_KMS("xres=%d, yres=%d, refresh=%d, intl=%d\n",
|
||||
mode->hdisplay, mode->vdisplay, mode->vrefresh,
|
||||
(mode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0);
|
||||
|
||||
if ((w >= 464 && w <= 720 && h >= 261 && h <= 576) ||
|
||||
(w >= 1024 && w <= 1280 && h >= 576 && h <= 720) ||
|
||||
(w >= 1664 && w <= 1920 && h >= 936 && h <= 1080))
|
||||
return 0;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct exynos_drm_manager_ops mixer_manager_ops = {
|
||||
.initialize = mixer_initialize,
|
||||
.remove = mixer_mgr_remove,
|
||||
.dpms = mixer_dpms,
|
||||
.enable_vblank = mixer_enable_vblank,
|
||||
.disable_vblank = mixer_disable_vblank,
|
||||
.wait_for_vblank = mixer_wait_for_vblank,
|
||||
.dpms = mixer_dpms,
|
||||
|
||||
/* overlay */
|
||||
.win_mode_set = mixer_win_mode_set,
|
||||
.win_commit = mixer_win_commit,
|
||||
.win_disable = mixer_win_disable,
|
||||
|
||||
/* display */
|
||||
.check_mode = mixer_check_mode,
|
||||
};
|
||||
|
||||
static irqreturn_t mixer_irq_handler(int irq, void *arg)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *drm_hdmi_ctx = arg;
|
||||
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
|
||||
struct mixer_resources *res = &ctx->mixer_res;
|
||||
u32 val, base, shadow;
|
||||
|
||||
spin_lock(&res->reg_slock);
|
||||
|
||||
/* read interrupt status for handling and clearing flags for VSYNC */
|
||||
val = mixer_reg_read(res, MXR_INT_STATUS);
|
||||
|
||||
/* handling VSYNC */
|
||||
if (val & MXR_INT_STATUS_VSYNC) {
|
||||
/* interlace scan need to check shadow register */
|
||||
if (ctx->interlace) {
|
||||
base = mixer_reg_read(res, MXR_GRAPHIC_BASE(0));
|
||||
shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(0));
|
||||
if (base != shadow)
|
||||
goto out;
|
||||
|
||||
base = mixer_reg_read(res, MXR_GRAPHIC_BASE(1));
|
||||
shadow = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(1));
|
||||
if (base != shadow)
|
||||
goto out;
|
||||
}
|
||||
|
||||
drm_handle_vblank(drm_hdmi_ctx->drm_dev, ctx->pipe);
|
||||
exynos_drm_crtc_finish_pageflip(drm_hdmi_ctx->drm_dev,
|
||||
ctx->pipe);
|
||||
|
||||
/* set wait vsync event to zero and wake up queue. */
|
||||
if (atomic_read(&ctx->wait_vsync_event)) {
|
||||
atomic_set(&ctx->wait_vsync_event, 0);
|
||||
wake_up(&ctx->wait_vsync_queue);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
/* clear interrupts */
|
||||
if (~val & MXR_INT_EN_VSYNC) {
|
||||
/* vsync interrupt use different bit for read and clear */
|
||||
val &= ~MXR_INT_EN_VSYNC;
|
||||
val |= MXR_INT_CLEAR_VSYNC;
|
||||
}
|
||||
mixer_reg_write(res, MXR_INT_STATUS, val);
|
||||
|
||||
spin_unlock(&res->reg_slock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int mixer_resources_init(struct exynos_drm_hdmi_context *ctx,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx->ctx;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mixer_resources *mixer_res = &mixer_ctx->mixer_res;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
spin_lock_init(&mixer_res->reg_slock);
|
||||
|
||||
mixer_res->mixer = devm_clk_get(dev, "mixer");
|
||||
if (IS_ERR(mixer_res->mixer)) {
|
||||
dev_err(dev, "failed to get clock 'mixer'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mixer_res->sclk_hdmi = devm_clk_get(dev, "sclk_hdmi");
|
||||
if (IS_ERR(mixer_res->sclk_hdmi)) {
|
||||
dev_err(dev, "failed to get clock 'sclk_hdmi'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(dev, "get memory resource failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
mixer_res->mixer_regs = devm_ioremap(dev, res->start,
|
||||
resource_size(res));
|
||||
if (mixer_res->mixer_regs == NULL) {
|
||||
dev_err(dev, "register mapping failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(dev, "get interrupt resource failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, res->start, mixer_irq_handler,
|
||||
0, "drm_mixer", ctx);
|
||||
if (ret) {
|
||||
dev_err(dev, "request interrupt failed.\n");
|
||||
return ret;
|
||||
}
|
||||
mixer_res->irq = res->start;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vp_resources_init(struct exynos_drm_hdmi_context *ctx,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct mixer_context *mixer_ctx = ctx->ctx;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mixer_resources *mixer_res = &mixer_ctx->mixer_res;
|
||||
struct resource *res;
|
||||
|
||||
mixer_res->vp = devm_clk_get(dev, "vp");
|
||||
if (IS_ERR(mixer_res->vp)) {
|
||||
dev_err(dev, "failed to get clock 'vp'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
mixer_res->sclk_mixer = devm_clk_get(dev, "sclk_mixer");
|
||||
if (IS_ERR(mixer_res->sclk_mixer)) {
|
||||
dev_err(dev, "failed to get clock 'sclk_mixer'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
mixer_res->sclk_dac = devm_clk_get(dev, "sclk_dac");
|
||||
if (IS_ERR(mixer_res->sclk_dac)) {
|
||||
dev_err(dev, "failed to get clock 'sclk_dac'\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (mixer_res->sclk_hdmi)
|
||||
clk_set_parent(mixer_res->sclk_mixer, mixer_res->sclk_hdmi);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
if (res == NULL) {
|
||||
dev_err(dev, "get memory resource failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
mixer_res->vp_regs = devm_ioremap(dev, res->start,
|
||||
resource_size(res));
|
||||
if (mixer_res->vp_regs == NULL) {
|
||||
dev_err(dev, "register mapping failed.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
static struct exynos_drm_manager mixer_manager = {
|
||||
.type = EXYNOS_DISPLAY_TYPE_HDMI,
|
||||
.ops = &mixer_manager_ops,
|
||||
};
|
||||
|
||||
static struct mixer_drv_data exynos5420_mxr_drv_data = {
|
||||
.version = MXR_VER_128_0_0_184,
|
||||
@ -1177,21 +1203,16 @@ static struct of_device_id mixer_match_types[] = {
|
||||
static int mixer_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct exynos_drm_hdmi_context *drm_hdmi_ctx;
|
||||
struct mixer_context *ctx;
|
||||
struct mixer_drv_data *drv;
|
||||
int ret;
|
||||
|
||||
dev_info(dev, "probe start\n");
|
||||
|
||||
drm_hdmi_ctx = devm_kzalloc(dev, sizeof(*drm_hdmi_ctx),
|
||||
GFP_KERNEL);
|
||||
if (!drm_hdmi_ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx) {
|
||||
DRM_ERROR("failed to alloc mixer context.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
mutex_init(&ctx->mixer_mutex);
|
||||
|
||||
@ -1204,46 +1225,20 @@ static int mixer_probe(struct platform_device *pdev)
|
||||
platform_get_device_id(pdev)->driver_data;
|
||||
}
|
||||
|
||||
ctx->pdev = pdev;
|
||||
ctx->dev = dev;
|
||||
ctx->parent_ctx = (void *)drm_hdmi_ctx;
|
||||
drm_hdmi_ctx->ctx = (void *)ctx;
|
||||
ctx->vp_enabled = drv->is_vp_enabled;
|
||||
ctx->mxr_ver = drv->version;
|
||||
init_waitqueue_head(&ctx->wait_vsync_queue);
|
||||
atomic_set(&ctx->wait_vsync_event, 0);
|
||||
|
||||
platform_set_drvdata(pdev, drm_hdmi_ctx);
|
||||
|
||||
/* acquire resources: regs, irqs, clocks */
|
||||
ret = mixer_resources_init(drm_hdmi_ctx, pdev);
|
||||
if (ret) {
|
||||
DRM_ERROR("mixer_resources_init failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ctx->vp_enabled) {
|
||||
/* acquire vp resources: regs, irqs, clocks */
|
||||
ret = vp_resources_init(drm_hdmi_ctx, pdev);
|
||||
if (ret) {
|
||||
DRM_ERROR("vp_resources_init failed\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* attach mixer driver to common hdmi. */
|
||||
exynos_mixer_drv_attach(drm_hdmi_ctx);
|
||||
|
||||
/* register specific callback point to common hdmi. */
|
||||
exynos_mixer_ops_register(&mixer_ops);
|
||||
mixer_manager.ctx = ctx;
|
||||
platform_set_drvdata(pdev, &mixer_manager);
|
||||
exynos_drm_manager_register(&mixer_manager);
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
fail:
|
||||
dev_info(dev, "probe failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mixer_remove(struct platform_device *pdev)
|
||||
@ -1255,70 +1250,10 @@ static int mixer_remove(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int mixer_suspend(struct device *dev)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev);
|
||||
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
|
||||
|
||||
if (pm_runtime_suspended(dev)) {
|
||||
DRM_DEBUG_KMS("Already suspended\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mixer_poweroff(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_resume(struct device *dev)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev);
|
||||
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
|
||||
|
||||
if (!pm_runtime_suspended(dev)) {
|
||||
DRM_DEBUG_KMS("Already resumed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mixer_poweron(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
static int mixer_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev);
|
||||
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
|
||||
|
||||
mixer_poweroff(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct exynos_drm_hdmi_context *drm_hdmi_ctx = get_mixer_context(dev);
|
||||
struct mixer_context *ctx = drm_hdmi_ctx->ctx;
|
||||
|
||||
mixer_poweron(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops mixer_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(mixer_suspend, mixer_resume)
|
||||
SET_RUNTIME_PM_OPS(mixer_runtime_suspend, mixer_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
struct platform_driver mixer_driver = {
|
||||
.driver = {
|
||||
.name = "exynos-mixer",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &mixer_pm_ops,
|
||||
.of_match_table = mixer_match_types,
|
||||
},
|
||||
.probe = mixer_probe,
|
||||
|
20
drivers/gpu/drm/exynos/exynos_mixer.h
Normal file
20
drivers/gpu/drm/exynos/exynos_mixer.h
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _EXYNOS_MIXER_H_
|
||||
#define _EXYNOS_MIXER_H_
|
||||
|
||||
/* This function returns 0 if the given timing is valid for the mixer */
|
||||
int mixer_check_mode(struct drm_display_mode *mode);
|
||||
|
||||
#endif
|
@ -13,9 +13,11 @@ gma500_gfx-y += \
|
||||
intel_i2c.o \
|
||||
intel_gmbus.o \
|
||||
mmu.o \
|
||||
blitter.o \
|
||||
power.o \
|
||||
psb_drv.o \
|
||||
gma_display.o \
|
||||
gma_device.o \
|
||||
psb_intel_display.o \
|
||||
psb_intel_lvds.o \
|
||||
psb_intel_modes.o \
|
||||
|
51
drivers/gpu/drm/gma500/blitter.c
Normal file
51
drivers/gpu/drm/gma500/blitter.c
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Patrik Jakobsson
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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.
|
||||
*
|
||||
* Authors: Patrik Jakobsson <patrik.r.jakobsson@gmail.com>
|
||||
*/
|
||||
|
||||
#include "psb_drv.h"
|
||||
|
||||
#include "blitter.h"
|
||||
#include "psb_reg.h"
|
||||
|
||||
/* Wait for the blitter to be completely idle */
|
||||
int gma_blt_wait_idle(struct drm_psb_private *dev_priv)
|
||||
{
|
||||
unsigned long stop = jiffies + HZ;
|
||||
int busy = 1;
|
||||
|
||||
/* NOP for Cedarview */
|
||||
if (IS_CDV(dev_priv->dev))
|
||||
return 0;
|
||||
|
||||
/* First do a quick check */
|
||||
if ((PSB_RSGX32(PSB_CR_2D_SOCIF) == _PSB_C2_SOCIF_EMPTY) &&
|
||||
((PSB_RSGX32(PSB_CR_2D_BLIT_STATUS) & _PSB_C2B_STATUS_BUSY) == 0))
|
||||
return 0;
|
||||
|
||||
do {
|
||||
busy = (PSB_RSGX32(PSB_CR_2D_SOCIF) != _PSB_C2_SOCIF_EMPTY);
|
||||
} while (busy && !time_after_eq(jiffies, stop));
|
||||
|
||||
if (busy)
|
||||
return -EBUSY;
|
||||
|
||||
do {
|
||||
busy = ((PSB_RSGX32(PSB_CR_2D_BLIT_STATUS) &
|
||||
_PSB_C2B_STATUS_BUSY) != 0);
|
||||
} while (busy && !time_after_eq(jiffies, stop));
|
||||
|
||||
/* If still busy, we probably have a hang */
|
||||
return (busy) ? -EBUSY : 0;
|
||||
}
|
22
drivers/gpu/drm/gma500/blitter.h
Normal file
22
drivers/gpu/drm/gma500/blitter.h
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Patrik Jakobsson
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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.
|
||||
*
|
||||
* Authors: Patrik Jakobsson <patrik.r.jakobsson@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef __BLITTER_H
|
||||
#define __BLITTER_H
|
||||
|
||||
extern int gma_blt_wait_idle(struct drm_psb_private *dev_priv);
|
||||
|
||||
#endif
|
@ -26,6 +26,7 @@
|
||||
#include "psb_intel_reg.h"
|
||||
#include "intel_bios.h"
|
||||
#include "cdv_device.h"
|
||||
#include "gma_device.h"
|
||||
|
||||
#define VGA_SR_INDEX 0x3c4
|
||||
#define VGA_SR_DATA 0x3c5
|
||||
@ -426,43 +427,6 @@ static int cdv_power_up(struct drm_device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FIXME ? - shared with Poulsbo */
|
||||
static void cdv_get_core_freq(struct drm_device *dev)
|
||||
{
|
||||
uint32_t clock;
|
||||
struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0);
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
|
||||
pci_write_config_dword(pci_root, 0xD0, 0xD0050300);
|
||||
pci_read_config_dword(pci_root, 0xD4, &clock);
|
||||
pci_dev_put(pci_root);
|
||||
|
||||
switch (clock & 0x07) {
|
||||
case 0:
|
||||
dev_priv->core_freq = 100;
|
||||
break;
|
||||
case 1:
|
||||
dev_priv->core_freq = 133;
|
||||
break;
|
||||
case 2:
|
||||
dev_priv->core_freq = 150;
|
||||
break;
|
||||
case 3:
|
||||
dev_priv->core_freq = 178;
|
||||
break;
|
||||
case 4:
|
||||
dev_priv->core_freq = 200;
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
dev_priv->core_freq = 266;
|
||||
break;
|
||||
default:
|
||||
dev_priv->core_freq = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void cdv_hotplug_work_func(struct work_struct *work)
|
||||
{
|
||||
struct drm_psb_private *dev_priv = container_of(work, struct drm_psb_private,
|
||||
@ -618,7 +582,7 @@ static int cdv_chip_setup(struct drm_device *dev)
|
||||
if (pci_enable_msi(dev->pdev))
|
||||
dev_warn(dev->dev, "Enabling MSI failed!\n");
|
||||
dev_priv->regmap = cdv_regmap;
|
||||
cdv_get_core_freq(dev);
|
||||
gma_get_core_freq(dev);
|
||||
psb_intel_opregion_init(dev);
|
||||
psb_intel_init_bios(dev);
|
||||
cdv_hotplug_enable(dev, false);
|
||||
|
@ -81,13 +81,6 @@ static int cdv_intel_crt_mode_valid(struct drm_connector *connector,
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static bool cdv_intel_crt_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cdv_intel_crt_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
@ -224,7 +217,7 @@ static int cdv_intel_crt_set_property(struct drm_connector *connector,
|
||||
|
||||
static const struct drm_encoder_helper_funcs cdv_intel_crt_helper_funcs = {
|
||||
.dpms = cdv_intel_crt_dpms,
|
||||
.mode_fixup = cdv_intel_crt_mode_fixup,
|
||||
.mode_fixup = gma_encoder_mode_fixup,
|
||||
.prepare = gma_encoder_prepare,
|
||||
.commit = gma_encoder_commit,
|
||||
.mode_set = cdv_intel_crt_mode_set,
|
||||
|
@ -412,8 +412,11 @@ static bool cdv_intel_find_dp_pll(const struct gma_limit_t *limit,
|
||||
int refclk,
|
||||
struct gma_clock_t *best_clock)
|
||||
{
|
||||
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
|
||||
struct gma_clock_t clock;
|
||||
if (refclk == 27000) {
|
||||
|
||||
switch (refclk) {
|
||||
case 27000:
|
||||
if (target < 200000) {
|
||||
clock.p1 = 2;
|
||||
clock.p2 = 10;
|
||||
@ -427,7 +430,9 @@ static bool cdv_intel_find_dp_pll(const struct gma_limit_t *limit,
|
||||
clock.m1 = 0;
|
||||
clock.m2 = 98;
|
||||
}
|
||||
} else if (refclk == 100000) {
|
||||
break;
|
||||
|
||||
case 100000:
|
||||
if (target < 200000) {
|
||||
clock.p1 = 2;
|
||||
clock.p2 = 10;
|
||||
@ -441,12 +446,13 @@ static bool cdv_intel_find_dp_pll(const struct gma_limit_t *limit,
|
||||
clock.m1 = 0;
|
||||
clock.m2 = 133;
|
||||
}
|
||||
} else
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
clock.m = clock.m2 + 2;
|
||||
clock.p = clock.p1 * clock.p2;
|
||||
clock.vco = (refclk * clock.m) / clock.n;
|
||||
clock.dot = clock.vco / clock.p;
|
||||
}
|
||||
|
||||
gma_crtc->clock_funcs->clock(refclk, &clock);
|
||||
memcpy(best_clock, &clock, sizeof(struct gma_clock_t));
|
||||
return true;
|
||||
}
|
||||
@ -463,54 +469,11 @@ static bool cdv_intel_pipe_enabled(struct drm_device *dev, int pipe)
|
||||
crtc = dev_priv->pipe_to_crtc_mapping[pipe];
|
||||
gma_crtc = to_gma_crtc(crtc);
|
||||
|
||||
if (crtc->fb == NULL || !gma_crtc->active)
|
||||
if (crtc->primary->fb == NULL || !gma_crtc->active)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cdv_intel_single_pipe_active (struct drm_device *dev)
|
||||
{
|
||||
uint32_t pipe_enabled = 0;
|
||||
|
||||
if (cdv_intel_pipe_enabled(dev, 0))
|
||||
pipe_enabled |= FIFO_PIPEA;
|
||||
|
||||
if (cdv_intel_pipe_enabled(dev, 1))
|
||||
pipe_enabled |= FIFO_PIPEB;
|
||||
|
||||
|
||||
DRM_DEBUG_KMS("pipe enabled %x\n", pipe_enabled);
|
||||
|
||||
if (pipe_enabled == FIFO_PIPEA || pipe_enabled == FIFO_PIPEB)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_pipeb_lvds(struct drm_device *dev, struct drm_crtc *crtc)
|
||||
{
|
||||
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
|
||||
struct drm_mode_config *mode_config = &dev->mode_config;
|
||||
struct drm_connector *connector;
|
||||
|
||||
if (gma_crtc->pipe != 1)
|
||||
return false;
|
||||
|
||||
list_for_each_entry(connector, &mode_config->connector_list, head) {
|
||||
struct gma_encoder *gma_encoder =
|
||||
gma_attached_encoder(connector);
|
||||
|
||||
if (!connector->encoder
|
||||
|| connector->encoder->crtc != crtc)
|
||||
continue;
|
||||
|
||||
if (gma_encoder->type == INTEL_OUTPUT_LVDS)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void cdv_disable_sr(struct drm_device *dev)
|
||||
{
|
||||
if (REG_READ(FW_BLC_SELF) & FW_BLC_SELF_EN) {
|
||||
@ -535,8 +498,10 @@ void cdv_disable_sr(struct drm_device *dev)
|
||||
void cdv_update_wm(struct drm_device *dev, struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
|
||||
|
||||
if (cdv_intel_single_pipe_active(dev)) {
|
||||
/* Is only one pipe enabled? */
|
||||
if (cdv_intel_pipe_enabled(dev, 0) ^ cdv_intel_pipe_enabled(dev, 1)) {
|
||||
u32 fw;
|
||||
|
||||
fw = REG_READ(DSPFW1);
|
||||
@ -557,7 +522,9 @@ void cdv_update_wm(struct drm_device *dev, struct drm_crtc *crtc)
|
||||
|
||||
/* ignore FW4 */
|
||||
|
||||
if (is_pipeb_lvds(dev, crtc)) {
|
||||
/* Is pipe b lvds ? */
|
||||
if (gma_crtc->pipe == 1 &&
|
||||
gma_pipe_has_type(crtc, INTEL_OUTPUT_LVDS)) {
|
||||
REG_WRITE(DSPFW5, 0x00040330);
|
||||
} else {
|
||||
fw = (3 << DSP_PLANE_B_FIFO_WM1_SHIFT) |
|
||||
|
@ -1693,7 +1693,7 @@ done:
|
||||
struct drm_crtc *crtc = encoder->base.crtc;
|
||||
drm_crtc_helper_set_mode(crtc, &crtc->mode,
|
||||
crtc->x, crtc->y,
|
||||
crtc->fb);
|
||||
crtc->primary->fb);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -89,13 +89,6 @@ static void cdv_hdmi_mode_set(struct drm_encoder *encoder,
|
||||
REG_READ(hdmi_priv->hdmi_reg);
|
||||
}
|
||||
|
||||
static bool cdv_hdmi_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cdv_hdmi_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
struct drm_device *dev = encoder->dev;
|
||||
@ -199,7 +192,7 @@ static int cdv_hdmi_set_property(struct drm_connector *connector,
|
||||
crtc->saved_mode.vdisplay != 0) {
|
||||
if (centre) {
|
||||
if (!drm_crtc_helper_set_mode(encoder->crtc, &crtc->saved_mode,
|
||||
encoder->crtc->x, encoder->crtc->y, encoder->crtc->fb))
|
||||
encoder->crtc->x, encoder->crtc->y, encoder->crtc->primary->fb))
|
||||
return -1;
|
||||
} else {
|
||||
struct drm_encoder_helper_funcs *helpers
|
||||
@ -262,7 +255,7 @@ static void cdv_hdmi_destroy(struct drm_connector *connector)
|
||||
|
||||
static const struct drm_encoder_helper_funcs cdv_hdmi_helper_funcs = {
|
||||
.dpms = cdv_hdmi_dpms,
|
||||
.mode_fixup = cdv_hdmi_mode_fixup,
|
||||
.mode_fixup = gma_encoder_mode_fixup,
|
||||
.prepare = gma_encoder_prepare,
|
||||
.mode_set = cdv_hdmi_mode_set,
|
||||
.commit = gma_encoder_commit,
|
||||
|
@ -494,7 +494,7 @@ static int cdv_intel_lvds_set_property(struct drm_connector *connector,
|
||||
&crtc->saved_mode,
|
||||
encoder->crtc->x,
|
||||
encoder->crtc->y,
|
||||
encoder->crtc->fb))
|
||||
encoder->crtc->primary->fb))
|
||||
return -1;
|
||||
}
|
||||
} else if (!strcmp(property->name, "backlight") && encoder) {
|
||||
@ -712,6 +712,7 @@ void cdv_intel_lvds_init(struct drm_device *dev,
|
||||
* Attempt to get the fixed panel mode from DDC. Assume that the
|
||||
* preferred mode is the right one.
|
||||
*/
|
||||
mutex_lock(&dev->mode_config.mutex);
|
||||
psb_intel_ddc_get_modes(connector,
|
||||
&gma_encoder->ddc_bus->adapter);
|
||||
list_for_each_entry(scan, &connector->probed_modes, head) {
|
||||
@ -772,10 +773,12 @@ void cdv_intel_lvds_init(struct drm_device *dev,
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
drm_sysfs_connector_add(connector);
|
||||
return;
|
||||
|
||||
failed_find:
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
printk(KERN_ERR "Failed find\n");
|
||||
if (gma_encoder->ddc_bus)
|
||||
psb_intel_i2c_destroy(gma_encoder->ddc_bus);
|
||||
|
@ -319,7 +319,7 @@ static struct gtt_range *psbfb_alloc(struct drm_device *dev, int aligned_size)
|
||||
{
|
||||
struct gtt_range *backing;
|
||||
/* Begin by trying to use stolen memory backing */
|
||||
backing = psb_gtt_alloc_range(dev, aligned_size, "fb", 1);
|
||||
backing = psb_gtt_alloc_range(dev, aligned_size, "fb", 1, PAGE_SIZE);
|
||||
if (backing) {
|
||||
drm_gem_private_object_init(dev, &backing->gem, aligned_size);
|
||||
return backing;
|
||||
|
@ -62,9 +62,6 @@ int psb_gem_dumb_map_gtt(struct drm_file *file, struct drm_device *dev,
|
||||
int ret = 0;
|
||||
struct drm_gem_object *obj;
|
||||
|
||||
if (!(dev->driver->driver_features & DRIVER_GEM))
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
|
||||
/* GEM does all our handle to object mapping */
|
||||
@ -98,8 +95,8 @@ unlock:
|
||||
* it so that userspace can speak about it. This does the core work
|
||||
* for the various methods that do/will create GEM objects for things
|
||||
*/
|
||||
static int psb_gem_create(struct drm_file *file,
|
||||
struct drm_device *dev, uint64_t size, uint32_t *handlep)
|
||||
int psb_gem_create(struct drm_file *file, struct drm_device *dev, u64 size,
|
||||
u32 *handlep, int stolen, u32 align)
|
||||
{
|
||||
struct gtt_range *r;
|
||||
int ret;
|
||||
@ -109,7 +106,7 @@ static int psb_gem_create(struct drm_file *file,
|
||||
|
||||
/* Allocate our object - for now a direct gtt range which is not
|
||||
stolen memory backed */
|
||||
r = psb_gtt_alloc_range(dev, size, "gem", 0);
|
||||
r = psb_gtt_alloc_range(dev, size, "gem", 0, PAGE_SIZE);
|
||||
if (r == NULL) {
|
||||
dev_err(dev->dev, "no memory for %lld byte GEM object\n", size);
|
||||
return -ENOSPC;
|
||||
@ -153,7 +150,8 @@ int psb_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
|
||||
{
|
||||
args->pitch = ALIGN(args->width * ((args->bpp + 7) / 8), 64);
|
||||
args->size = args->pitch * args->height;
|
||||
return psb_gem_create(file, dev, args->size, &args->handle);
|
||||
return psb_gem_create(file, dev, args->size, &args->handle, 0,
|
||||
PAGE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -229,47 +227,3 @@ fail:
|
||||
return VM_FAULT_SIGBUS;
|
||||
}
|
||||
}
|
||||
|
||||
static int psb_gem_create_stolen(struct drm_file *file, struct drm_device *dev,
|
||||
int size, u32 *handle)
|
||||
{
|
||||
struct gtt_range *gtt = psb_gtt_alloc_range(dev, size, "gem", 1);
|
||||
if (gtt == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
drm_gem_private_object_init(dev, >t->gem, size);
|
||||
if (drm_gem_handle_create(file, >t->gem, handle) == 0)
|
||||
return 0;
|
||||
|
||||
drm_gem_object_release(>t->gem);
|
||||
psb_gtt_free_range(dev, gtt);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* GEM interfaces for our specific client
|
||||
*/
|
||||
int psb_gem_create_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file)
|
||||
{
|
||||
struct drm_psb_gem_create *args = data;
|
||||
int ret;
|
||||
if (args->flags & GMA_GEM_CREATE_STOLEN) {
|
||||
ret = psb_gem_create_stolen(file, dev, args->size,
|
||||
&args->handle);
|
||||
if (ret == 0)
|
||||
return 0;
|
||||
/* Fall throguh */
|
||||
args->flags &= ~GMA_GEM_CREATE_STOLEN;
|
||||
}
|
||||
return psb_gem_create(file, dev, args->size, &args->handle);
|
||||
}
|
||||
|
||||
int psb_gem_mmap_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file)
|
||||
{
|
||||
struct drm_psb_gem_mmap *args = data;
|
||||
return dev->driver->dumb_map_offset(file, dev,
|
||||
args->handle, &args->offset);
|
||||
}
|
||||
|
||||
|
21
drivers/gpu/drm/gma500/gem.h
Normal file
21
drivers/gpu/drm/gma500/gem.h
Normal file
@ -0,0 +1,21 @@
|
||||
/**************************************************************************
|
||||
* Copyright (c) 2014 Patrik Jakobsson
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef _GEM_H
|
||||
#define _GEM_H
|
||||
|
||||
extern int psb_gem_create(struct drm_file *file, struct drm_device *dev,
|
||||
u64 size, u32 *handlep, int stolen, u32 align);
|
||||
#endif
|
56
drivers/gpu/drm/gma500/gma_device.c
Normal file
56
drivers/gpu/drm/gma500/gma_device.c
Normal file
@ -0,0 +1,56 @@
|
||||
/**************************************************************************
|
||||
* Copyright (c) 2011, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include "psb_drv.h"
|
||||
|
||||
void gma_get_core_freq(struct drm_device *dev)
|
||||
{
|
||||
uint32_t clock;
|
||||
struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0);
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
|
||||
/*pci_write_config_dword(pci_root, 0xD4, 0x00C32004);*/
|
||||
/*pci_write_config_dword(pci_root, 0xD0, 0xE0033000);*/
|
||||
|
||||
pci_write_config_dword(pci_root, 0xD0, 0xD0050300);
|
||||
pci_read_config_dword(pci_root, 0xD4, &clock);
|
||||
pci_dev_put(pci_root);
|
||||
|
||||
switch (clock & 0x07) {
|
||||
case 0:
|
||||
dev_priv->core_freq = 100;
|
||||
break;
|
||||
case 1:
|
||||
dev_priv->core_freq = 133;
|
||||
break;
|
||||
case 2:
|
||||
dev_priv->core_freq = 150;
|
||||
break;
|
||||
case 3:
|
||||
dev_priv->core_freq = 178;
|
||||
break;
|
||||
case 4:
|
||||
dev_priv->core_freq = 200;
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
dev_priv->core_freq = 266;
|
||||
break;
|
||||
default:
|
||||
dev_priv->core_freq = 0;
|
||||
}
|
||||
}
|
21
drivers/gpu/drm/gma500/gma_device.h
Normal file
21
drivers/gpu/drm/gma500/gma_device.h
Normal file
@ -0,0 +1,21 @@
|
||||
/**************************************************************************
|
||||
* Copyright (c) 2011, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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.
|
||||
*
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef _GMA_DEVICE_H
|
||||
#define _GMA_DEVICE_H
|
||||
|
||||
extern void gma_get_core_freq(struct drm_device *dev);
|
||||
|
||||
#endif
|
@ -59,7 +59,7 @@ int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
|
||||
struct psb_framebuffer *psbfb = to_psb_fb(crtc->fb);
|
||||
struct psb_framebuffer *psbfb = to_psb_fb(crtc->primary->fb);
|
||||
int pipe = gma_crtc->pipe;
|
||||
const struct psb_offset *map = &dev_priv->regmap[pipe];
|
||||
unsigned long start, offset;
|
||||
@ -70,7 +70,7 @@ int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
return 0;
|
||||
|
||||
/* no fb bound */
|
||||
if (!crtc->fb) {
|
||||
if (!crtc->primary->fb) {
|
||||
dev_err(dev->dev, "No FB bound\n");
|
||||
goto gma_pipe_cleaner;
|
||||
}
|
||||
@ -81,19 +81,19 @@ int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
if (ret < 0)
|
||||
goto gma_pipe_set_base_exit;
|
||||
start = psbfb->gtt->offset;
|
||||
offset = y * crtc->fb->pitches[0] + x * (crtc->fb->bits_per_pixel / 8);
|
||||
offset = y * crtc->primary->fb->pitches[0] + x * (crtc->primary->fb->bits_per_pixel / 8);
|
||||
|
||||
REG_WRITE(map->stride, crtc->fb->pitches[0]);
|
||||
REG_WRITE(map->stride, crtc->primary->fb->pitches[0]);
|
||||
|
||||
dspcntr = REG_READ(map->cntr);
|
||||
dspcntr &= ~DISPPLANE_PIXFORMAT_MASK;
|
||||
|
||||
switch (crtc->fb->bits_per_pixel) {
|
||||
switch (crtc->primary->fb->bits_per_pixel) {
|
||||
case 8:
|
||||
dspcntr |= DISPPLANE_8BPP;
|
||||
break;
|
||||
case 16:
|
||||
if (crtc->fb->depth == 15)
|
||||
if (crtc->primary->fb->depth == 15)
|
||||
dspcntr |= DISPPLANE_15_16BPP;
|
||||
else
|
||||
dspcntr |= DISPPLANE_16BPP;
|
||||
@ -485,6 +485,13 @@ int gma_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool gma_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gma_crtc_mode_fixup(struct drm_crtc *crtc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
@ -511,8 +518,8 @@ void gma_crtc_disable(struct drm_crtc *crtc)
|
||||
|
||||
crtc_funcs->dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
|
||||
if (crtc->fb) {
|
||||
gt = to_psb_fb(crtc->fb)->gtt;
|
||||
if (crtc->primary->fb) {
|
||||
gt = to_psb_fb(crtc->primary->fb)->gtt;
|
||||
psb_gtt_unpin(gt);
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,9 @@ extern void gma_crtc_restore(struct drm_crtc *crtc);
|
||||
extern void gma_encoder_prepare(struct drm_encoder *encoder);
|
||||
extern void gma_encoder_commit(struct drm_encoder *encoder);
|
||||
extern void gma_encoder_destroy(struct drm_encoder *encoder);
|
||||
extern bool gma_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode);
|
||||
|
||||
/* Common clock related functions */
|
||||
extern const struct gma_limit_t *gma_limit(struct drm_crtc *crtc, int refclk);
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <drm/drmP.h>
|
||||
#include <linux/shmem_fs.h>
|
||||
#include "psb_drv.h"
|
||||
#include "blitter.h"
|
||||
|
||||
|
||||
/*
|
||||
@ -105,11 +106,13 @@ static int psb_gtt_insert(struct drm_device *dev, struct gtt_range *r,
|
||||
|
||||
/* Write our page entries into the GTT itself */
|
||||
for (i = r->roll; i < r->npage; i++) {
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]), 0);
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]),
|
||||
PSB_MMU_CACHED_MEMORY);
|
||||
iowrite32(pte, gtt_slot++);
|
||||
}
|
||||
for (i = 0; i < r->roll; i++) {
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]), 0);
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]),
|
||||
PSB_MMU_CACHED_MEMORY);
|
||||
iowrite32(pte, gtt_slot++);
|
||||
}
|
||||
/* Make sure all the entries are set before we return */
|
||||
@ -127,7 +130,7 @@ static int psb_gtt_insert(struct drm_device *dev, struct gtt_range *r,
|
||||
* page table entries with the dummy page. This is protected via the gtt
|
||||
* mutex which the caller must hold.
|
||||
*/
|
||||
static void psb_gtt_remove(struct drm_device *dev, struct gtt_range *r)
|
||||
void psb_gtt_remove(struct drm_device *dev, struct gtt_range *r)
|
||||
{
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
u32 __iomem *gtt_slot;
|
||||
@ -137,7 +140,8 @@ static void psb_gtt_remove(struct drm_device *dev, struct gtt_range *r)
|
||||
WARN_ON(r->stolen);
|
||||
|
||||
gtt_slot = psb_gtt_entry(dev, r);
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(dev_priv->scratch_page), 0);
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(dev_priv->scratch_page),
|
||||
PSB_MMU_CACHED_MEMORY);
|
||||
|
||||
for (i = 0; i < r->npage; i++)
|
||||
iowrite32(pte, gtt_slot++);
|
||||
@ -176,11 +180,13 @@ void psb_gtt_roll(struct drm_device *dev, struct gtt_range *r, int roll)
|
||||
gtt_slot = psb_gtt_entry(dev, r);
|
||||
|
||||
for (i = r->roll; i < r->npage; i++) {
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]), 0);
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]),
|
||||
PSB_MMU_CACHED_MEMORY);
|
||||
iowrite32(pte, gtt_slot++);
|
||||
}
|
||||
for (i = 0; i < r->roll; i++) {
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]), 0);
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]),
|
||||
PSB_MMU_CACHED_MEMORY);
|
||||
iowrite32(pte, gtt_slot++);
|
||||
}
|
||||
ioread32(gtt_slot - 1);
|
||||
@ -240,6 +246,7 @@ int psb_gtt_pin(struct gtt_range *gt)
|
||||
int ret = 0;
|
||||
struct drm_device *dev = gt->gem.dev;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
u32 gpu_base = dev_priv->gtt.gatt_start;
|
||||
|
||||
mutex_lock(&dev_priv->gtt_mutex);
|
||||
|
||||
@ -252,6 +259,9 @@ int psb_gtt_pin(struct gtt_range *gt)
|
||||
psb_gtt_detach_pages(gt);
|
||||
goto out;
|
||||
}
|
||||
psb_mmu_insert_pages(psb_mmu_get_default_pd(dev_priv->mmu),
|
||||
gt->pages, (gpu_base + gt->offset),
|
||||
gt->npage, 0, 0, PSB_MMU_CACHED_MEMORY);
|
||||
}
|
||||
gt->in_gart++;
|
||||
out:
|
||||
@ -274,16 +284,30 @@ void psb_gtt_unpin(struct gtt_range *gt)
|
||||
{
|
||||
struct drm_device *dev = gt->gem.dev;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
u32 gpu_base = dev_priv->gtt.gatt_start;
|
||||
int ret;
|
||||
|
||||
/* While holding the gtt_mutex no new blits can be initiated */
|
||||
mutex_lock(&dev_priv->gtt_mutex);
|
||||
|
||||
/* Wait for any possible usage of the memory to be finished */
|
||||
ret = gma_blt_wait_idle(dev_priv);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to idle the blitter, unpin failed!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
WARN_ON(!gt->in_gart);
|
||||
|
||||
gt->in_gart--;
|
||||
if (gt->in_gart == 0 && gt->stolen == 0) {
|
||||
psb_mmu_remove_pages(psb_mmu_get_default_pd(dev_priv->mmu),
|
||||
(gpu_base + gt->offset), gt->npage, 0, 0);
|
||||
psb_gtt_remove(dev, gt);
|
||||
psb_gtt_detach_pages(gt);
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&dev_priv->gtt_mutex);
|
||||
}
|
||||
|
||||
@ -306,7 +330,7 @@ void psb_gtt_unpin(struct gtt_range *gt)
|
||||
* as in use.
|
||||
*/
|
||||
struct gtt_range *psb_gtt_alloc_range(struct drm_device *dev, int len,
|
||||
const char *name, int backed)
|
||||
const char *name, int backed, u32 align)
|
||||
{
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
struct gtt_range *gt;
|
||||
@ -334,7 +358,7 @@ struct gtt_range *psb_gtt_alloc_range(struct drm_device *dev, int len,
|
||||
/* Ensure this is set for non GEM objects */
|
||||
gt->gem.dev = dev;
|
||||
ret = allocate_resource(dev_priv->gtt_mem, >->resource,
|
||||
len, start, end, PAGE_SIZE, NULL, NULL);
|
||||
len, start, end, align, NULL, NULL);
|
||||
if (ret == 0) {
|
||||
gt->offset = gt->resource.start - r->start;
|
||||
return gt;
|
||||
@ -497,6 +521,7 @@ int psb_gtt_init(struct drm_device *dev, int resume)
|
||||
if (!resume)
|
||||
dev_priv->vram_addr = ioremap_wc(dev_priv->stolen_base,
|
||||
stolen_size);
|
||||
|
||||
if (!dev_priv->vram_addr) {
|
||||
dev_err(dev->dev, "Failure to map stolen base.\n");
|
||||
ret = -ENOMEM;
|
||||
@ -512,7 +537,7 @@ int psb_gtt_init(struct drm_device *dev, int resume)
|
||||
dev_dbg(dev->dev, "Set up %d stolen pages starting at 0x%08x, GTT offset %dK\n",
|
||||
num_pages, pfn_base << PAGE_SHIFT, 0);
|
||||
for (i = 0; i < num_pages; ++i) {
|
||||
pte = psb_gtt_mask_pte(pfn_base + i, 0);
|
||||
pte = psb_gtt_mask_pte(pfn_base + i, PSB_MMU_CACHED_MEMORY);
|
||||
iowrite32(pte, dev_priv->gtt_map + i);
|
||||
}
|
||||
|
||||
@ -521,7 +546,7 @@ int psb_gtt_init(struct drm_device *dev, int resume)
|
||||
*/
|
||||
|
||||
pfn_base = page_to_pfn(dev_priv->scratch_page);
|
||||
pte = psb_gtt_mask_pte(pfn_base, 0);
|
||||
pte = psb_gtt_mask_pte(pfn_base, PSB_MMU_CACHED_MEMORY);
|
||||
for (; i < gtt_pages; ++i)
|
||||
iowrite32(pte, dev_priv->gtt_map + i);
|
||||
|
||||
|
@ -53,7 +53,8 @@ struct gtt_range {
|
||||
};
|
||||
|
||||
extern struct gtt_range *psb_gtt_alloc_range(struct drm_device *dev, int len,
|
||||
const char *name, int backed);
|
||||
const char *name, int backed,
|
||||
u32 align);
|
||||
extern void psb_gtt_kref_put(struct gtt_range *gt);
|
||||
extern void psb_gtt_free_range(struct drm_device *dev, struct gtt_range *gt);
|
||||
extern int psb_gtt_pin(struct gtt_range *gt);
|
||||
|
@ -287,7 +287,7 @@ static int mdfld_dsi_connector_set_property(struct drm_connector *connector,
|
||||
&gma_crtc->saved_mode,
|
||||
encoder->crtc->x,
|
||||
encoder->crtc->y,
|
||||
encoder->crtc->fb))
|
||||
encoder->crtc->primary->fb))
|
||||
goto set_prop_error;
|
||||
} else {
|
||||
struct drm_encoder_helper_funcs *funcs =
|
||||
|
@ -166,7 +166,7 @@ static int mdfld__intel_pipe_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
|
||||
struct psb_framebuffer *psbfb = to_psb_fb(crtc->fb);
|
||||
struct psb_framebuffer *psbfb = to_psb_fb(crtc->primary->fb);
|
||||
int pipe = gma_crtc->pipe;
|
||||
const struct psb_offset *map = &dev_priv->regmap[pipe];
|
||||
unsigned long start, offset;
|
||||
@ -178,12 +178,12 @@ static int mdfld__intel_pipe_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
dev_dbg(dev->dev, "pipe = 0x%x.\n", pipe);
|
||||
|
||||
/* no fb bound */
|
||||
if (!crtc->fb) {
|
||||
if (!crtc->primary->fb) {
|
||||
dev_dbg(dev->dev, "No FB bound\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = check_fb(crtc->fb);
|
||||
ret = check_fb(crtc->primary->fb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -196,18 +196,18 @@ static int mdfld__intel_pipe_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
return 0;
|
||||
|
||||
start = psbfb->gtt->offset;
|
||||
offset = y * crtc->fb->pitches[0] + x * (crtc->fb->bits_per_pixel / 8);
|
||||
offset = y * crtc->primary->fb->pitches[0] + x * (crtc->primary->fb->bits_per_pixel / 8);
|
||||
|
||||
REG_WRITE(map->stride, crtc->fb->pitches[0]);
|
||||
REG_WRITE(map->stride, crtc->primary->fb->pitches[0]);
|
||||
dspcntr = REG_READ(map->cntr);
|
||||
dspcntr &= ~DISPPLANE_PIXFORMAT_MASK;
|
||||
|
||||
switch (crtc->fb->bits_per_pixel) {
|
||||
switch (crtc->primary->fb->bits_per_pixel) {
|
||||
case 8:
|
||||
dspcntr |= DISPPLANE_8BPP;
|
||||
break;
|
||||
case 16:
|
||||
if (crtc->fb->depth == 15)
|
||||
if (crtc->primary->fb->depth == 15)
|
||||
dspcntr |= DISPPLANE_15_16BPP;
|
||||
else
|
||||
dspcntr |= DISPPLANE_16BPP;
|
||||
@ -700,7 +700,7 @@ static int mdfld_crtc_mode_set(struct drm_crtc *crtc,
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = check_fb(crtc->fb);
|
||||
ret = check_fb(crtc->primary->fb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <drm/drmP.h>
|
||||
#include "psb_drv.h"
|
||||
#include "psb_reg.h"
|
||||
#include "mmu.h"
|
||||
|
||||
/*
|
||||
* Code for the SGX MMU:
|
||||
@ -47,51 +48,6 @@
|
||||
* but on average it should be fast.
|
||||
*/
|
||||
|
||||
struct psb_mmu_driver {
|
||||
/* protects driver- and pd structures. Always take in read mode
|
||||
* before taking the page table spinlock.
|
||||
*/
|
||||
struct rw_semaphore sem;
|
||||
|
||||
/* protects page tables, directory tables and pt tables.
|
||||
* and pt structures.
|
||||
*/
|
||||
spinlock_t lock;
|
||||
|
||||
atomic_t needs_tlbflush;
|
||||
|
||||
uint8_t __iomem *register_map;
|
||||
struct psb_mmu_pd *default_pd;
|
||||
/*uint32_t bif_ctrl;*/
|
||||
int has_clflush;
|
||||
int clflush_add;
|
||||
unsigned long clflush_mask;
|
||||
|
||||
struct drm_psb_private *dev_priv;
|
||||
};
|
||||
|
||||
struct psb_mmu_pd;
|
||||
|
||||
struct psb_mmu_pt {
|
||||
struct psb_mmu_pd *pd;
|
||||
uint32_t index;
|
||||
uint32_t count;
|
||||
struct page *p;
|
||||
uint32_t *v;
|
||||
};
|
||||
|
||||
struct psb_mmu_pd {
|
||||
struct psb_mmu_driver *driver;
|
||||
int hw_context;
|
||||
struct psb_mmu_pt **tables;
|
||||
struct page *p;
|
||||
struct page *dummy_pt;
|
||||
struct page *dummy_page;
|
||||
uint32_t pd_mask;
|
||||
uint32_t invalid_pde;
|
||||
uint32_t invalid_pte;
|
||||
};
|
||||
|
||||
static inline uint32_t psb_mmu_pt_index(uint32_t offset)
|
||||
{
|
||||
return (offset >> PSB_PTE_SHIFT) & 0x3FF;
|
||||
@ -102,13 +58,13 @@ static inline uint32_t psb_mmu_pd_index(uint32_t offset)
|
||||
return offset >> PSB_PDE_SHIFT;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_X86)
|
||||
static inline void psb_clflush(void *addr)
|
||||
{
|
||||
__asm__ __volatile__("clflush (%0)\n" : : "r"(addr) : "memory");
|
||||
}
|
||||
|
||||
static inline void psb_mmu_clflush(struct psb_mmu_driver *driver,
|
||||
void *addr)
|
||||
static inline void psb_mmu_clflush(struct psb_mmu_driver *driver, void *addr)
|
||||
{
|
||||
if (!driver->has_clflush)
|
||||
return;
|
||||
@ -117,62 +73,77 @@ static inline void psb_mmu_clflush(struct psb_mmu_driver *driver,
|
||||
psb_clflush(addr);
|
||||
mb();
|
||||
}
|
||||
#else
|
||||
|
||||
static void psb_page_clflush(struct psb_mmu_driver *driver, struct page* page)
|
||||
static inline void psb_mmu_clflush(struct psb_mmu_driver *driver, void *addr)
|
||||
{;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void psb_mmu_flush_pd_locked(struct psb_mmu_driver *driver, int force)
|
||||
{
|
||||
uint32_t clflush_add = driver->clflush_add >> PAGE_SHIFT;
|
||||
uint32_t clflush_count = PAGE_SIZE / clflush_add;
|
||||
int i;
|
||||
uint8_t *clf;
|
||||
struct drm_device *dev = driver->dev;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
|
||||
clf = kmap_atomic(page);
|
||||
mb();
|
||||
for (i = 0; i < clflush_count; ++i) {
|
||||
psb_clflush(clf);
|
||||
clf += clflush_add;
|
||||
if (atomic_read(&driver->needs_tlbflush) || force) {
|
||||
uint32_t val = PSB_RSGX32(PSB_CR_BIF_CTRL);
|
||||
PSB_WSGX32(val | _PSB_CB_CTRL_INVALDC, PSB_CR_BIF_CTRL);
|
||||
|
||||
/* Make sure data cache is turned off before enabling it */
|
||||
wmb();
|
||||
PSB_WSGX32(val & ~_PSB_CB_CTRL_INVALDC, PSB_CR_BIF_CTRL);
|
||||
(void)PSB_RSGX32(PSB_CR_BIF_CTRL);
|
||||
if (driver->msvdx_mmu_invaldc)
|
||||
atomic_set(driver->msvdx_mmu_invaldc, 1);
|
||||
}
|
||||
mb();
|
||||
kunmap_atomic(clf);
|
||||
}
|
||||
|
||||
static void psb_pages_clflush(struct psb_mmu_driver *driver,
|
||||
struct page *page[], unsigned long num_pages)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!driver->has_clflush)
|
||||
return ;
|
||||
|
||||
for (i = 0; i < num_pages; i++)
|
||||
psb_page_clflush(driver, *page++);
|
||||
}
|
||||
|
||||
static void psb_mmu_flush_pd_locked(struct psb_mmu_driver *driver,
|
||||
int force)
|
||||
{
|
||||
atomic_set(&driver->needs_tlbflush, 0);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void psb_mmu_flush_pd(struct psb_mmu_driver *driver, int force)
|
||||
{
|
||||
down_write(&driver->sem);
|
||||
psb_mmu_flush_pd_locked(driver, force);
|
||||
up_write(&driver->sem);
|
||||
}
|
||||
#endif
|
||||
|
||||
void psb_mmu_flush(struct psb_mmu_driver *driver, int rc_prot)
|
||||
void psb_mmu_flush(struct psb_mmu_driver *driver)
|
||||
{
|
||||
if (rc_prot)
|
||||
down_write(&driver->sem);
|
||||
if (rc_prot)
|
||||
up_write(&driver->sem);
|
||||
struct drm_device *dev = driver->dev;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
uint32_t val;
|
||||
|
||||
down_write(&driver->sem);
|
||||
val = PSB_RSGX32(PSB_CR_BIF_CTRL);
|
||||
if (atomic_read(&driver->needs_tlbflush))
|
||||
PSB_WSGX32(val | _PSB_CB_CTRL_INVALDC, PSB_CR_BIF_CTRL);
|
||||
else
|
||||
PSB_WSGX32(val | _PSB_CB_CTRL_FLUSH, PSB_CR_BIF_CTRL);
|
||||
|
||||
/* Make sure data cache is turned off and MMU is flushed before
|
||||
restoring bank interface control register */
|
||||
wmb();
|
||||
PSB_WSGX32(val & ~(_PSB_CB_CTRL_FLUSH | _PSB_CB_CTRL_INVALDC),
|
||||
PSB_CR_BIF_CTRL);
|
||||
(void)PSB_RSGX32(PSB_CR_BIF_CTRL);
|
||||
|
||||
atomic_set(&driver->needs_tlbflush, 0);
|
||||
if (driver->msvdx_mmu_invaldc)
|
||||
atomic_set(driver->msvdx_mmu_invaldc, 1);
|
||||
up_write(&driver->sem);
|
||||
}
|
||||
|
||||
void psb_mmu_set_pd_context(struct psb_mmu_pd *pd, int hw_context)
|
||||
{
|
||||
/*ttm_tt_cache_flush(&pd->p, 1);*/
|
||||
psb_pages_clflush(pd->driver, &pd->p, 1);
|
||||
struct drm_device *dev = pd->driver->dev;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
uint32_t offset = (hw_context == 0) ? PSB_CR_BIF_DIR_LIST_BASE0 :
|
||||
PSB_CR_BIF_DIR_LIST_BASE1 + hw_context * 4;
|
||||
|
||||
down_write(&pd->driver->sem);
|
||||
PSB_WSGX32(page_to_pfn(pd->p) << PAGE_SHIFT, offset);
|
||||
wmb();
|
||||
psb_mmu_flush_pd_locked(pd->driver, 1);
|
||||
pd->hw_context = hw_context;
|
||||
@ -183,7 +154,6 @@ void psb_mmu_set_pd_context(struct psb_mmu_pd *pd, int hw_context)
|
||||
static inline unsigned long psb_pd_addr_end(unsigned long addr,
|
||||
unsigned long end)
|
||||
{
|
||||
|
||||
addr = (addr + PSB_PDE_MASK + 1) & ~PSB_PDE_MASK;
|
||||
return (addr < end) ? addr : end;
|
||||
}
|
||||
@ -223,12 +193,10 @@ struct psb_mmu_pd *psb_mmu_alloc_pd(struct psb_mmu_driver *driver,
|
||||
goto out_err3;
|
||||
|
||||
if (!trap_pagefaults) {
|
||||
pd->invalid_pde =
|
||||
psb_mmu_mask_pte(page_to_pfn(pd->dummy_pt),
|
||||
invalid_type);
|
||||
pd->invalid_pte =
|
||||
psb_mmu_mask_pte(page_to_pfn(pd->dummy_page),
|
||||
invalid_type);
|
||||
pd->invalid_pde = psb_mmu_mask_pte(page_to_pfn(pd->dummy_pt),
|
||||
invalid_type);
|
||||
pd->invalid_pte = psb_mmu_mask_pte(page_to_pfn(pd->dummy_page),
|
||||
invalid_type);
|
||||
} else {
|
||||
pd->invalid_pde = 0;
|
||||
pd->invalid_pte = 0;
|
||||
@ -279,12 +247,16 @@ static void psb_mmu_free_pt(struct psb_mmu_pt *pt)
|
||||
void psb_mmu_free_pagedir(struct psb_mmu_pd *pd)
|
||||
{
|
||||
struct psb_mmu_driver *driver = pd->driver;
|
||||
struct drm_device *dev = driver->dev;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
struct psb_mmu_pt *pt;
|
||||
int i;
|
||||
|
||||
down_write(&driver->sem);
|
||||
if (pd->hw_context != -1)
|
||||
if (pd->hw_context != -1) {
|
||||
PSB_WSGX32(0, PSB_CR_BIF_DIR_LIST_BASE0 + pd->hw_context * 4);
|
||||
psb_mmu_flush_pd_locked(driver, 1);
|
||||
}
|
||||
|
||||
/* Should take the spinlock here, but we don't need to do that
|
||||
since we have the semaphore in write mode. */
|
||||
@ -331,7 +303,7 @@ static struct psb_mmu_pt *psb_mmu_alloc_pt(struct psb_mmu_pd *pd)
|
||||
for (i = 0; i < (PAGE_SIZE / sizeof(uint32_t)); ++i)
|
||||
*ptes++ = pd->invalid_pte;
|
||||
|
||||
|
||||
#if defined(CONFIG_X86)
|
||||
if (pd->driver->has_clflush && pd->hw_context != -1) {
|
||||
mb();
|
||||
for (i = 0; i < clflush_count; ++i) {
|
||||
@ -340,7 +312,7 @@ static struct psb_mmu_pt *psb_mmu_alloc_pt(struct psb_mmu_pd *pd)
|
||||
}
|
||||
mb();
|
||||
}
|
||||
|
||||
#endif
|
||||
kunmap_atomic(v);
|
||||
spin_unlock(lock);
|
||||
|
||||
@ -351,7 +323,7 @@ static struct psb_mmu_pt *psb_mmu_alloc_pt(struct psb_mmu_pd *pd)
|
||||
return pt;
|
||||
}
|
||||
|
||||
static struct psb_mmu_pt *psb_mmu_pt_alloc_map_lock(struct psb_mmu_pd *pd,
|
||||
struct psb_mmu_pt *psb_mmu_pt_alloc_map_lock(struct psb_mmu_pd *pd,
|
||||
unsigned long addr)
|
||||
{
|
||||
uint32_t index = psb_mmu_pd_index(addr);
|
||||
@ -383,7 +355,7 @@ static struct psb_mmu_pt *psb_mmu_pt_alloc_map_lock(struct psb_mmu_pd *pd,
|
||||
kunmap_atomic((void *) v);
|
||||
|
||||
if (pd->hw_context != -1) {
|
||||
psb_mmu_clflush(pd->driver, (void *) &v[index]);
|
||||
psb_mmu_clflush(pd->driver, (void *)&v[index]);
|
||||
atomic_set(&pd->driver->needs_tlbflush, 1);
|
||||
}
|
||||
}
|
||||
@ -420,8 +392,7 @@ static void psb_mmu_pt_unmap_unlock(struct psb_mmu_pt *pt)
|
||||
pd->tables[pt->index] = NULL;
|
||||
|
||||
if (pd->hw_context != -1) {
|
||||
psb_mmu_clflush(pd->driver,
|
||||
(void *) &v[pt->index]);
|
||||
psb_mmu_clflush(pd->driver, (void *)&v[pt->index]);
|
||||
atomic_set(&pd->driver->needs_tlbflush, 1);
|
||||
}
|
||||
kunmap_atomic(pt->v);
|
||||
@ -432,8 +403,8 @@ static void psb_mmu_pt_unmap_unlock(struct psb_mmu_pt *pt)
|
||||
spin_unlock(&pd->driver->lock);
|
||||
}
|
||||
|
||||
static inline void psb_mmu_set_pte(struct psb_mmu_pt *pt,
|
||||
unsigned long addr, uint32_t pte)
|
||||
static inline void psb_mmu_set_pte(struct psb_mmu_pt *pt, unsigned long addr,
|
||||
uint32_t pte)
|
||||
{
|
||||
pt->v[psb_mmu_pt_index(addr)] = pte;
|
||||
}
|
||||
@ -444,69 +415,50 @@ static inline void psb_mmu_invalidate_pte(struct psb_mmu_pt *pt,
|
||||
pt->v[psb_mmu_pt_index(addr)] = pt->pd->invalid_pte;
|
||||
}
|
||||
|
||||
|
||||
void psb_mmu_mirror_gtt(struct psb_mmu_pd *pd,
|
||||
uint32_t mmu_offset, uint32_t gtt_start,
|
||||
uint32_t gtt_pages)
|
||||
{
|
||||
uint32_t *v;
|
||||
uint32_t start = psb_mmu_pd_index(mmu_offset);
|
||||
struct psb_mmu_driver *driver = pd->driver;
|
||||
int num_pages = gtt_pages;
|
||||
|
||||
down_read(&driver->sem);
|
||||
spin_lock(&driver->lock);
|
||||
|
||||
v = kmap_atomic(pd->p);
|
||||
v += start;
|
||||
|
||||
while (gtt_pages--) {
|
||||
*v++ = gtt_start | pd->pd_mask;
|
||||
gtt_start += PAGE_SIZE;
|
||||
}
|
||||
|
||||
/*ttm_tt_cache_flush(&pd->p, num_pages);*/
|
||||
psb_pages_clflush(pd->driver, &pd->p, num_pages);
|
||||
kunmap_atomic(v);
|
||||
spin_unlock(&driver->lock);
|
||||
|
||||
if (pd->hw_context != -1)
|
||||
atomic_set(&pd->driver->needs_tlbflush, 1);
|
||||
|
||||
up_read(&pd->driver->sem);
|
||||
psb_mmu_flush_pd(pd->driver, 0);
|
||||
}
|
||||
|
||||
struct psb_mmu_pd *psb_mmu_get_default_pd(struct psb_mmu_driver *driver)
|
||||
{
|
||||
struct psb_mmu_pd *pd;
|
||||
|
||||
/* down_read(&driver->sem); */
|
||||
down_read(&driver->sem);
|
||||
pd = driver->default_pd;
|
||||
/* up_read(&driver->sem); */
|
||||
up_read(&driver->sem);
|
||||
|
||||
return pd;
|
||||
}
|
||||
|
||||
/* Returns the physical address of the PD shared by sgx/msvdx */
|
||||
uint32_t psb_get_default_pd_addr(struct psb_mmu_driver *driver)
|
||||
{
|
||||
struct psb_mmu_pd *pd;
|
||||
|
||||
pd = psb_mmu_get_default_pd(driver);
|
||||
return page_to_pfn(pd->p) << PAGE_SHIFT;
|
||||
}
|
||||
|
||||
void psb_mmu_driver_takedown(struct psb_mmu_driver *driver)
|
||||
{
|
||||
struct drm_device *dev = driver->dev;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
|
||||
PSB_WSGX32(driver->bif_ctrl, PSB_CR_BIF_CTRL);
|
||||
psb_mmu_free_pagedir(driver->default_pd);
|
||||
kfree(driver);
|
||||
}
|
||||
|
||||
struct psb_mmu_driver *psb_mmu_driver_init(uint8_t __iomem * registers,
|
||||
int trap_pagefaults,
|
||||
int invalid_type,
|
||||
struct drm_psb_private *dev_priv)
|
||||
struct psb_mmu_driver *psb_mmu_driver_init(struct drm_device *dev,
|
||||
int trap_pagefaults,
|
||||
int invalid_type,
|
||||
atomic_t *msvdx_mmu_invaldc)
|
||||
{
|
||||
struct psb_mmu_driver *driver;
|
||||
struct drm_psb_private *dev_priv = dev->dev_private;
|
||||
|
||||
driver = kmalloc(sizeof(*driver), GFP_KERNEL);
|
||||
|
||||
if (!driver)
|
||||
return NULL;
|
||||
driver->dev_priv = dev_priv;
|
||||
|
||||
driver->dev = dev;
|
||||
driver->default_pd = psb_mmu_alloc_pd(driver, trap_pagefaults,
|
||||
invalid_type);
|
||||
if (!driver->default_pd)
|
||||
@ -515,17 +467,24 @@ struct psb_mmu_driver *psb_mmu_driver_init(uint8_t __iomem * registers,
|
||||
spin_lock_init(&driver->lock);
|
||||
init_rwsem(&driver->sem);
|
||||
down_write(&driver->sem);
|
||||
driver->register_map = registers;
|
||||
atomic_set(&driver->needs_tlbflush, 1);
|
||||
driver->msvdx_mmu_invaldc = msvdx_mmu_invaldc;
|
||||
|
||||
driver->bif_ctrl = PSB_RSGX32(PSB_CR_BIF_CTRL);
|
||||
PSB_WSGX32(driver->bif_ctrl | _PSB_CB_CTRL_CLEAR_FAULT,
|
||||
PSB_CR_BIF_CTRL);
|
||||
PSB_WSGX32(driver->bif_ctrl & ~_PSB_CB_CTRL_CLEAR_FAULT,
|
||||
PSB_CR_BIF_CTRL);
|
||||
|
||||
driver->has_clflush = 0;
|
||||
|
||||
#if defined(CONFIG_X86)
|
||||
if (boot_cpu_has(X86_FEATURE_CLFLUSH)) {
|
||||
uint32_t tfms, misc, cap0, cap4, clflush_size;
|
||||
|
||||
/*
|
||||
* clflush size is determined at kernel setup for x86_64
|
||||
* but not for i386. We have to do it here.
|
||||
* clflush size is determined at kernel setup for x86_64 but not
|
||||
* for i386. We have to do it here.
|
||||
*/
|
||||
|
||||
cpuid(0x00000001, &tfms, &misc, &cap0, &cap4);
|
||||
@ -536,6 +495,7 @@ struct psb_mmu_driver *psb_mmu_driver_init(uint8_t __iomem * registers,
|
||||
driver->clflush_mask = driver->clflush_add - 1;
|
||||
driver->clflush_mask = ~driver->clflush_mask;
|
||||
}
|
||||
#endif
|
||||
|
||||
up_write(&driver->sem);
|
||||
return driver;
|
||||
@ -545,9 +505,9 @@ out_err1:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void psb_mmu_flush_ptes(struct psb_mmu_pd *pd,
|
||||
unsigned long address, uint32_t num_pages,
|
||||
uint32_t desired_tile_stride,
|
||||
#if defined(CONFIG_X86)
|
||||
static void psb_mmu_flush_ptes(struct psb_mmu_pd *pd, unsigned long address,
|
||||
uint32_t num_pages, uint32_t desired_tile_stride,
|
||||
uint32_t hw_tile_stride)
|
||||
{
|
||||
struct psb_mmu_pt *pt;
|
||||
@ -561,11 +521,8 @@ static void psb_mmu_flush_ptes(struct psb_mmu_pd *pd,
|
||||
unsigned long clflush_add = pd->driver->clflush_add;
|
||||
unsigned long clflush_mask = pd->driver->clflush_mask;
|
||||
|
||||
if (!pd->driver->has_clflush) {
|
||||
/*ttm_tt_cache_flush(&pd->p, num_pages);*/
|
||||
psb_pages_clflush(pd->driver, &pd->p, num_pages);
|
||||
if (!pd->driver->has_clflush)
|
||||
return;
|
||||
}
|
||||
|
||||
if (hw_tile_stride)
|
||||
rows = num_pages / desired_tile_stride;
|
||||
@ -586,10 +543,8 @@ static void psb_mmu_flush_ptes(struct psb_mmu_pd *pd,
|
||||
if (!pt)
|
||||
continue;
|
||||
do {
|
||||
psb_clflush(&pt->v
|
||||
[psb_mmu_pt_index(addr)]);
|
||||
} while (addr +=
|
||||
clflush_add,
|
||||
psb_clflush(&pt->v[psb_mmu_pt_index(addr)]);
|
||||
} while (addr += clflush_add,
|
||||
(addr & clflush_mask) < next);
|
||||
|
||||
psb_mmu_pt_unmap_unlock(pt);
|
||||
@ -598,6 +553,14 @@ static void psb_mmu_flush_ptes(struct psb_mmu_pd *pd,
|
||||
}
|
||||
mb();
|
||||
}
|
||||
#else
|
||||
static void psb_mmu_flush_ptes(struct psb_mmu_pd *pd, unsigned long address,
|
||||
uint32_t num_pages, uint32_t desired_tile_stride,
|
||||
uint32_t hw_tile_stride)
|
||||
{
|
||||
drm_ttm_cache_flush();
|
||||
}
|
||||
#endif
|
||||
|
||||
void psb_mmu_remove_pfn_sequence(struct psb_mmu_pd *pd,
|
||||
unsigned long address, uint32_t num_pages)
|
||||
@ -633,7 +596,7 @@ out:
|
||||
up_read(&pd->driver->sem);
|
||||
|
||||
if (pd->hw_context != -1)
|
||||
psb_mmu_flush(pd->driver, 0);
|
||||
psb_mmu_flush(pd->driver);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -660,7 +623,7 @@ void psb_mmu_remove_pages(struct psb_mmu_pd *pd, unsigned long address,
|
||||
add = desired_tile_stride << PAGE_SHIFT;
|
||||
row_add = hw_tile_stride << PAGE_SHIFT;
|
||||
|
||||
/* down_read(&pd->driver->sem); */
|
||||
down_read(&pd->driver->sem);
|
||||
|
||||
/* Make sure we only need to flush this processor's cache */
|
||||
|
||||
@ -688,10 +651,10 @@ void psb_mmu_remove_pages(struct psb_mmu_pd *pd, unsigned long address,
|
||||
psb_mmu_flush_ptes(pd, f_address, num_pages,
|
||||
desired_tile_stride, hw_tile_stride);
|
||||
|
||||
/* up_read(&pd->driver->sem); */
|
||||
up_read(&pd->driver->sem);
|
||||
|
||||
if (pd->hw_context != -1)
|
||||
psb_mmu_flush(pd->driver, 0);
|
||||
psb_mmu_flush(pd->driver);
|
||||
}
|
||||
|
||||
int psb_mmu_insert_pfn_sequence(struct psb_mmu_pd *pd, uint32_t start_pfn,
|
||||
@ -704,7 +667,7 @@ int psb_mmu_insert_pfn_sequence(struct psb_mmu_pd *pd, uint32_t start_pfn,
|
||||
unsigned long end;
|
||||
unsigned long next;
|
||||
unsigned long f_address = address;
|
||||
int ret = 0;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
down_read(&pd->driver->sem);
|
||||
|
||||
@ -726,6 +689,7 @@ int psb_mmu_insert_pfn_sequence(struct psb_mmu_pd *pd, uint32_t start_pfn,
|
||||
psb_mmu_pt_unmap_unlock(pt);
|
||||
|
||||
} while (addr = next, next != end);
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
if (pd->hw_context != -1)
|
||||
@ -734,15 +698,15 @@ out:
|
||||
up_read(&pd->driver->sem);
|
||||
|
||||
if (pd->hw_context != -1)
|
||||
psb_mmu_flush(pd->driver, 1);
|
||||
psb_mmu_flush(pd->driver);
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int psb_mmu_insert_pages(struct psb_mmu_pd *pd, struct page **pages,
|
||||
unsigned long address, uint32_t num_pages,
|
||||
uint32_t desired_tile_stride,
|
||||
uint32_t hw_tile_stride, int type)
|
||||
uint32_t desired_tile_stride, uint32_t hw_tile_stride,
|
||||
int type)
|
||||
{
|
||||
struct psb_mmu_pt *pt;
|
||||
uint32_t rows = 1;
|
||||
@ -754,7 +718,7 @@ int psb_mmu_insert_pages(struct psb_mmu_pd *pd, struct page **pages,
|
||||
unsigned long add;
|
||||
unsigned long row_add;
|
||||
unsigned long f_address = address;
|
||||
int ret = 0;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
if (hw_tile_stride) {
|
||||
if (num_pages % desired_tile_stride != 0)
|
||||
@ -777,14 +741,11 @@ int psb_mmu_insert_pages(struct psb_mmu_pd *pd, struct page **pages,
|
||||
do {
|
||||
next = psb_pd_addr_end(addr, end);
|
||||
pt = psb_mmu_pt_alloc_map_lock(pd, addr);
|
||||
if (!pt) {
|
||||
ret = -ENOMEM;
|
||||
if (!pt)
|
||||
goto out;
|
||||
}
|
||||
do {
|
||||
pte =
|
||||
psb_mmu_mask_pte(page_to_pfn(*pages++),
|
||||
type);
|
||||
pte = psb_mmu_mask_pte(page_to_pfn(*pages++),
|
||||
type);
|
||||
psb_mmu_set_pte(pt, addr, pte);
|
||||
pt->count++;
|
||||
} while (addr += PAGE_SIZE, addr < next);
|
||||
@ -794,6 +755,8 @@ int psb_mmu_insert_pages(struct psb_mmu_pd *pd, struct page **pages,
|
||||
|
||||
address += row_add;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
if (pd->hw_context != -1)
|
||||
psb_mmu_flush_ptes(pd, f_address, num_pages,
|
||||
@ -802,7 +765,7 @@ out:
|
||||
up_read(&pd->driver->sem);
|
||||
|
||||
if (pd->hw_context != -1)
|
||||
psb_mmu_flush(pd->driver, 1);
|
||||
psb_mmu_flush(pd->driver);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
93
drivers/gpu/drm/gma500/mmu.h
Normal file
93
drivers/gpu/drm/gma500/mmu.h
Normal file
@ -0,0 +1,93 @@
|
||||
/**************************************************************************
|
||||
* Copyright (c) 2007-2011, Intel Corporation.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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.
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef __MMU_H
|
||||
#define __MMU_H
|
||||
|
||||
struct psb_mmu_driver {
|
||||
/* protects driver- and pd structures. Always take in read mode
|
||||
* before taking the page table spinlock.
|
||||
*/
|
||||
struct rw_semaphore sem;
|
||||
|
||||
/* protects page tables, directory tables and pt tables.
|
||||
* and pt structures.
|
||||
*/
|
||||
spinlock_t lock;
|
||||
|
||||
atomic_t needs_tlbflush;
|
||||
atomic_t *msvdx_mmu_invaldc;
|
||||
struct psb_mmu_pd *default_pd;
|
||||
uint32_t bif_ctrl;
|
||||
int has_clflush;
|
||||
int clflush_add;
|
||||
unsigned long clflush_mask;
|
||||
|
||||
struct drm_device *dev;
|
||||
};
|
||||
|
||||
struct psb_mmu_pd;
|
||||
|
||||
struct psb_mmu_pt {
|
||||
struct psb_mmu_pd *pd;
|
||||
uint32_t index;
|
||||
uint32_t count;
|
||||
struct page *p;
|
||||
uint32_t *v;
|
||||
};
|
||||
|
||||
struct psb_mmu_pd {
|
||||
struct psb_mmu_driver *driver;
|
||||
int hw_context;
|
||||
struct psb_mmu_pt **tables;
|
||||
struct page *p;
|
||||
struct page *dummy_pt;
|
||||
struct page *dummy_page;
|
||||
uint32_t pd_mask;
|
||||
uint32_t invalid_pde;
|
||||
uint32_t invalid_pte;
|
||||
};
|
||||
|
||||
extern struct psb_mmu_driver *psb_mmu_driver_init(struct drm_device *dev,
|
||||
int trap_pagefaults,
|
||||
int invalid_type,
|
||||
atomic_t *msvdx_mmu_invaldc);
|
||||
extern void psb_mmu_driver_takedown(struct psb_mmu_driver *driver);
|
||||
extern struct psb_mmu_pd *psb_mmu_get_default_pd(struct psb_mmu_driver
|
||||
*driver);
|
||||
extern struct psb_mmu_pd *psb_mmu_alloc_pd(struct psb_mmu_driver *driver,
|
||||
int trap_pagefaults,
|
||||
int invalid_type);
|
||||
extern void psb_mmu_free_pagedir(struct psb_mmu_pd *pd);
|
||||
extern void psb_mmu_flush(struct psb_mmu_driver *driver);
|
||||
extern void psb_mmu_remove_pfn_sequence(struct psb_mmu_pd *pd,
|
||||
unsigned long address,
|
||||
uint32_t num_pages);
|
||||
extern int psb_mmu_insert_pfn_sequence(struct psb_mmu_pd *pd,
|
||||
uint32_t start_pfn,
|
||||
unsigned long address,
|
||||
uint32_t num_pages, int type);
|
||||
extern int psb_mmu_virtual_to_pfn(struct psb_mmu_pd *pd, uint32_t virtual,
|
||||
unsigned long *pfn);
|
||||
extern void psb_mmu_set_pd_context(struct psb_mmu_pd *pd, int hw_context);
|
||||
extern int psb_mmu_insert_pages(struct psb_mmu_pd *pd, struct page **pages,
|
||||
unsigned long address, uint32_t num_pages,
|
||||
uint32_t desired_tile_stride,
|
||||
uint32_t hw_tile_stride, int type);
|
||||
extern void psb_mmu_remove_pages(struct psb_mmu_pd *pd,
|
||||
unsigned long address, uint32_t num_pages,
|
||||
uint32_t desired_tile_stride,
|
||||
uint32_t hw_tile_stride);
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user