2
0
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:
Linus Torvalds 2014-04-08 09:52:16 -07:00
commit e9f37d3a8d
483 changed files with 37548 additions and 16000 deletions

View File

@ -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 &amp; 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 &amp; 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&lt;num&gt;</term>. Additionally, a currently
unused control node, called <term>controlD&lt;num&gt;</term> is also
and called card&lt;num&gt;. Additionally, a currently
unused control node, called controlD&lt;num&gt; 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&lt;num&gt;</term>. There will
separate render node called renderD&lt;num&gt;. 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>

View 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>;
};

View 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";
};

View File

@ -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:
/ {

View File

@ -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.

View File

@ -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.

View 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.

View 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>;
};
};
};

View 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>;
};
};
};

View File

@ -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>;
};
};
};

View 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>;
};
};
};
};

View File

@ -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>;
};

View File

@ -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:

View File

@ -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>

View File

@ -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";

View File

@ -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 = <>;

View File

@ -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";

View File

@ -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";

View File

@ -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"

View File

@ -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/

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -0,0 +1,5 @@
config DRM_PTN3460
tristate "PTN3460 DP/LVDS bridge"
depends on DRM
select DRM_KMS_HELPER
---help---

View File

@ -0,0 +1,3 @@
ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_PTN3460) += ptn3460.o

View 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);

View File

@ -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 */

View File

@ -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

View File

@ -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;

View 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);

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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.
*/

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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)

View File

@ -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);

View 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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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 */

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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

View 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;
}

View File

@ -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);

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -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

View File

@ -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 = {

View File

@ -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;

View File

@ -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,
},
};

View File

@ -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,
},
};

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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,
},
};

View File

@ -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,
},
};

View File

@ -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,

View 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

View File

@ -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 \

View 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;
}

View 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

View File

@ -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);

View File

@ -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,

View File

@ -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) |

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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, &gtt->gem, size);
if (drm_gem_handle_create(file, &gtt->gem, handle) == 0)
return 0;
drm_gem_object_release(&gtt->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);
}

View 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

View 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;
}
}

View 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

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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, &gt->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);

View File

@ -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);

View File

@ -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 =

View File

@ -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;

View File

@ -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;
}

View 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