Hi everybody,
Here's the third RFC of the Common Display Framework. This is a resent, the series I've sent earlier seems not to have made it to the vger mailing lists, possibly due to a too long list of CCs (the other explanation being that CDF has been delayed for so long that vger considers it as spam, which I really hope isn't the case :-)). I've thus dropped the CCs, sorry about that.
I won't repeat all the background information from the versions one and two here, you can read it at http://lwn.net/Articles/512363/ and http://lwn.net/Articles/526965/.
This RFC isn't final. Given the high interest in CDF and the urgent tasks that kept delaying the next version of the patch set, I've decided to release v3 before completing all parts of the implementation. Known missing items are
- documentation: kerneldoc and this cover letter should provide basic information, more extensive documentation will likely make it to v4.
- pipeline configuration and control: generic code to configure and control display pipelines (in a nutshell, translating high-level mode setting and DPMS calls to low-level entity operations) is missing. Video and stream control operations have been carried over from v2, but will need to be revised for v4.
- DSI support: I still have no DSI hardware I can easily test the code on.
Special thanks go to
- Renesas for inviting me to LinuxCon Japan 2013 where I had the opportunity to validate the CDF v3 concepts with Alexandre Courbot (NVidia) and Tomasz Figa (Samsung).
- Tomi Valkeinen (TI) for taking the time to deeply brainstorm v3 with me.
- Linaro for inviting me to Linaro Connect Europe 2013, the discussions we had there greatly helped moving CDF forward.
- And of course all the developers who showed interest in CDF and spent time sharing ideas, reviewing patches and testing code.
I have to confess I was a bit lost and discouraged after all the CDF-related meetings during which we have discussed how to move from v2 to v3. With every meeting I was hoping to run the implementation through use cases of various interesting parties and narrow down the scope of the huge fuzzy beast that CDF was. With every meeting the scope actually broadened, with no clear path at sight anywhere.
Earlier this year I was about to drop one of the requirements on which I had based CDF v2: sharing drivers between DRM/KMS and V4L2. With only two HDMI transmitters as use cases for that feature (with only out-of-tree drivers so far), I just thought the involved completely wasn't worth it and that I should implement CDF v3 as a DRM/KMS-only helper framework. However, a seemingly unrelated discussion with Xilinx developers showed me that hybrid SoC-FPGA platforms such as the Xilinx Zynq 7000 have a larger library of IP cores that can be used in camera capture pipelines and in display pipelines. The two use cases suddenly became tens or even hundreds of use cases that I couldn't ignore anymore.
CDF v3 is thus userspace API agnostic. It isn't tied to DRM/KMS or V4L2 and can be used by any kernel subsystem, potentially including FBDEV (although I won't personally wrote FBDEV support code, as I've already advocated for FBDEV to be deprecated).
The code you are about to read is based on the concept of display entities introduced in v2. Diagrams related to the explanations below are available at http://ideasonboard.org/media/cdf/20130709-lce-cdf.pdf.
Display Entities ----------------
A display entity abstracts any hardware block that sources, processes or sinks display-related video streams. It offers an abstract API, implemented by display entity drivers, that is used by master drivers (such as the main display driver) to query, configure and control display pipelines.
Display entities are connected to at least one video data bus, and optionally to a control bus. The video data busses carry display-related video data out of sources (such as a CRTC in a display controller) to sinks (such as a panel or a monitor), optionally going through transmitters, encoders, decoders, bridges or other similar devices. A CRTC or a panel will usually be connected to a single data bus, while an encoder or a transmitter will be connected to two data busses.
The simple linear display pipelines we find in most embedded platforms at the moment are expected to grow more complex with time. CDF needs to accomodate those needs from the start to be, if not future-proof, at least present-proof at the time it will get merged in to mainline. For this reason display entities have data ports through which video streams flow in or out, with link objects representing the connections between those ports. A typical entity in a linear display pipeline will have one (for video source and video sink entities such as CRTCs or panels) or two ports (for video processing entities such as encoders), but more ports are allowed, and entities can be linked in complex non-linear pipelines.
Readers might think that this model if extremely similar to the media controller graph model. They would be right, and given my background this is most probably not a coincidence. The CDF v3 implementation uses the in-kernel media controller framework to model the graph of display entities, with the display entity data structure inheriting from the media entity structure. The display pipeline graph topology will be automatically exposed to userspace through the media controller API as an added bonus. However, ussage of the media controller userspace API in applications is *not* mandatory, and the current CDF implementation doesn't use the media controller link setup userspace API to configure the display pipelines.
While some display entities don't require any configuration (DPI panels are a good example), many of them are connected to a control bus accessible to the CPU. Control requests can be sent on a dedicated control bus (such as I2C or SPI) or multiplexed on a mixed control and data bus (such as DBI or DSI). To support both options the CDF display entity model separates the control and data busses in different APIs.
Display entities are abstract object that must be implemented by a real device. The device sits on its control bus and is registered with the Linux device core and matched with his driver using the control bus specific API. The CDF doesn't create a display entity class or bus, display entity drivers thus standard Linux kernel drivers using existing busses. A DBI bus is added as part of this patch set, but strictly speaking this isn't part of CDF.
When a display entity driver probes a device it must create an instance of the display_entity structure, initialize it and add it to the CDF core entities pool. The display entity exposes abstract operations through function pointers, and the entity driver must implement those operations. Those operations can act on either the whole entity or on a given port, depending on the operation. They are divided in two groups, control operations and video operations.
Control Operations ------------------
Control operations are called by upper-level drivers, usually in response to a request originating from userspace. They query or control the display entity state and operation. Currently defined control operations are
- get_size(), to retrieve the entity physical size (applicable to panels only) - get_modes(), to retrieve the video modes supported at an entity port - get_params(), to retrieve the data bus parameters at an entity port
- set_state(), to control the state of the entity (off, standby or on) - update(), to trigger a display update (for entities that implement manual update, such as manual-update panels that store frames in their internal frame buffer)
The last two operations have been carried from v2 and will be reworked.
Pipeline Control ----------------
The figure on page 4 shows the control model for a linear pipeline. This differs significantly from CDF v2 where calls where forwarded from entity to entity using a Russian dolls model. v3 removes the need of neighbour awareness from entity drivers, simplifying the entity drivers. The complexity of pipeline configuration is moved to a central location called a pipeline controller instead of being spread out to all drivers.
Pipeline controllers provide library functions that display drivers can use to control a pipeline. Several controllers can be implemented to accomodate the needs of various pipeline topologies and complexities, and display drivers can even implement their own pipeline control algorithm if needed. I'm working on a linear pipeline controller for the next version of the patch set.
If pipeline controllers are responsible for propagating a pipeline configuration on all entity ports in the pipeline, entity drivers are responsible for propagating the configuration inside entities, from sink (input) to source (output) ports as illustrated on page 5. The rationale behind this is that knowledge of the entity internals is located in the entity driver, while knowledge of the pipeline belongs to the pipeline controller. The controller will thus configure the pipeline by performing the following steps:
- applying a configuration on sink ports of an entity - read the configuration that has been propagated by the entity driver on its source ports - optionally, modify the source port configuration (to configure custom timings, scaling or other parameters, if supported by the entity) - propagate the source port configuration to the sink ports of the next entities in the pipeline and start over
Beside modifying the active configuration, the entities API will allow trying configurations without applying them to the hardware. As configuration of a port possibly depend on the configurations of the other ports, trying a configuration must be done at the entity level instead of the port level. The implementation will be based on the concept of configuration store objects that will store the configuration of all ports for a given entity. Each entity will have a single active configuration store, and test configuration stores will be created dynamically to try a configuration on an entity. The get and set operations implemented by the entity will receive a configuration store pointer, and active and test code paths in entity drivers will be identical, except for applying the configuration to the hardware for the active code path.
Video Operations ----------------
Video operations control the video stream state on entity ports. The only currently defined video operation is
- set_stream(), to start (in continuous or single-shot mode) the video stream on an entity port
The call model for video operations differ from the control operations model described above. The set_stream() operation is called directly by downstream entities on upstream entities (from a video data bus point of view). Terminating entities in a pipeline (such as panels) will usually call the set_stream() operation in their set_state() handler, and intermediate entities will forward the set_stream() call upstream.
Integration -----------
The figure on page 8 describes how a panel driver, implemented using CDF as a display entity, interacts with the other components in the system. The use case is a simple pipeline made of a display controller and a panel.
The display controller driver receives control request from userspace through DRM (or FBDEV) API calls. It processes the request and calls the panel driver through the CDF control operations API. The panel driver will then issue requests on its control bus (several possible control busses are shown on the figure, panel drivers typically use one of them only) and call video operations of the display controller on its left side to control the video stream.
Registration and Notification -----------------------------
Due to possibly complex dependencies between entities we can't guarantee that all entities part of the display pipeline will have been successfully probed when the master display controller driver is probed. For instance a panel can be a child of the DBI or DSI bus controlled by the display device, or use a clock provided by that device. We can't defer the display device probe until the panel is probed and also defer the panel device probe until the display device is probed. For this reason we need a notification system that allows entities to register themselves with the CDF core, and display controller drivers to get notified when entities they need are available.
The notification system has been completely redesigned in v3. This version is based on the V4L2 asynchronous probing notification code, with large parts of the code shamelessly copied. This is an interim solution to let me play with the notification code as needed by CDF. I'm not a fan of code duplication, and will work on merging the CDF and V4L2 implementations in a later stage when CDF will reach a mature enough state.
CDF manages a pool of entities and a list of notifiers. Notifiers are registered by master display drivers with an array of entities match descriptors. When an entity is added to the CDF entities pool, all notifiers are searched for a match. If a match is found, the corresponding notifier is called to notify the master display driver.
The two currently supported match methods are platform match, which uses device names, and DT match, which uses DT node pointers. More match method might be added later if needed. Two helper functions exist to build a notifier from a list of platform device names (in the non-DT case) or a DT representation of the display pipeline topology.
Once all required entities have been successfully found, the master display driver is responsible for creating media controller links between all entities in the pipeline. Two helper functions are also available to automate that process, one for the non-DT case and one for the DT case. Once again some DT-related code has been copied from the V4L2 DT code, I will work on merging both in a future version.
Note that notification brings a different issue after registration, as display controller and display entity drivers would take a reference to each other. Those circular references would make driver unloading impossible. One possible solution to this problem would be to simulate an unplug event for the display entity, to force the display driver to release the dislay entities it uses. We would need a userspace API for that though. Better solutions would of course be welcome.
Device Tree Bindings --------------------
CDF entities device tree bindings are not documented yet. They describe both the graph topology and entity-specific information. The graph description uses the V4L2 DT bindings (which are actually not V4L2-specific) specified at Documentation/devicetree/bindings/media/video-interfaces.txt. Entity-specific information will be described in individual DT bindings documentation. The DPI panel driver uses the display timing bindings documented in Documentation/devicetree/bindings/video/display-timing.txt.
Please note that most of the display entities on devices I own are just dumb panels with no control bus, and are thus not the best candidates to design a framework that needs to take complex panels' needs into account. This is why I hope to see you using the CDF with your display device and tell me what needs to be modified/improved/redesigned.
This patch set is split as follows:
- The first patch fixes a Kconfig namespace issue with the OMAP DSS panels. It could be applied already independently of this series. - Patches 02/19 to 07/19 add the CDF core, including the notification system and the graph and OF helpers. - Patch 08/19 adds a MIPI DBI bus. This isn't part of CDF strictly speaking, but is needed for the DBI panel drivers. - Patches 09/19 to 13/19 add panel drivers, a VGA DAC driver and a VGA connector driver. - Patches 14/19 to 18/19 add CDF-compliant reference board code and DT for the Renesas Marzen and Lager boards. - Patch 19/19 port the Renesas R-Car Display Unit driver to CDF.
The patches are available in my git tree at
git://linuxtv.org/pinchartl/fbdev.git cdf/v3 http://git.linuxtv.org/pinchartl/fbdev.git/shortlog/refs/heads/cdf/v3
For convenience I've included modifications to the Renesas R-Car Display Unit driver to use the CDF. You can read the code to see how the driver uses CDF to interface panels. Please note that the rcar-du-drm implementation is still work in progress, its set_stream operation implementation doesn't enable and disable the video stream yet as it should.
As already mentioned in v2, I will appreciate all reviews, comments, criticisms, ideas, remarks, ... If you can find a clever way to solve the cyclic references issue described above I'll buy you a beer at the next conference we will both attend. If you think the proposed solution is too complex, or too simple, I'm all ears, but I'll have more arguments this time than I had with v2
Laurent Pinchart (19): OMAPDSS: panels: Rename Kconfig options to OMAP2_DISPLAY_* video: Add Common Display Framework core video: display: Add video and stream control operations video: display: Add display entity notifier video: display: Graph helpers video: display: OF support video: display: Add pixel coding definitions video: display: Add MIPI DBI bus support video: panel: Add DPI panel support video: panel: Add R61505 panel support video: panel: Add R61517 panel support video: display: Add VGA Digital to Analog Converter support video: display: Add VGA connector support ARM: shmobile: r8a7790: Add DU clocks for DT ARM: shmobile: r8a7790: Add DU device node to device tree ARM: shmobile: marzen: Port DU platform data to CDF ARM: shmobile: lager: Port DU platform data to CDF ARM: shmobile: lager-reference: Add display device nodes to device tree drm/rcar-du: Port to the Common Display Framework
arch/arm/boot/dts/r8a7790-lager-reference.dts | 92 ++++ arch/arm/boot/dts/r8a7790.dtsi | 33 ++ arch/arm/mach-shmobile/board-lager.c | 76 ++- arch/arm/mach-shmobile/board-marzen.c | 77 ++- arch/arm/mach-shmobile/clock-r8a7790.c | 5 + drivers/gpu/drm/rcar-du/Kconfig | 3 +- drivers/gpu/drm/rcar-du/Makefile | 7 +- drivers/gpu/drm/rcar-du/rcar_du_connector.c | 164 ++++++ drivers/gpu/drm/rcar-du/rcar_du_connector.h | 36 ++ drivers/gpu/drm/rcar-du/rcar_du_crtc.h | 2 +- drivers/gpu/drm/rcar-du/rcar_du_drv.c | 279 ++++++++-- drivers/gpu/drm/rcar-du/rcar_du_drv.h | 28 +- drivers/gpu/drm/rcar-du/rcar_du_encoder.c | 87 ++- drivers/gpu/drm/rcar-du/rcar_du_encoder.h | 22 +- drivers/gpu/drm/rcar-du/rcar_du_kms.c | 116 +++- drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c | 131 ----- drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h | 25 - drivers/gpu/drm/rcar-du/rcar_du_vgacon.c | 96 ---- drivers/gpu/drm/rcar-du/rcar_du_vgacon.h | 23 - drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/display/Kconfig | 62 +++ drivers/video/display/Makefile | 9 + drivers/video/display/con-vga.c | 148 +++++ drivers/video/display/display-core.c | 759 ++++++++++++++++++++++++++ drivers/video/display/display-notifier.c | 542 ++++++++++++++++++ drivers/video/display/mipi-dbi-bus.c | 234 ++++++++ drivers/video/display/panel-dpi.c | 207 +++++++ drivers/video/display/panel-r61505.c | 567 +++++++++++++++++++ drivers/video/display/panel-r61517.c | 460 ++++++++++++++++ drivers/video/display/vga-dac.c | 152 ++++++ drivers/video/omap2/displays-new/Kconfig | 24 +- drivers/video/omap2/displays-new/Makefile | 24 +- include/linux/platform_data/rcar-du.h | 55 +- include/video/display.h | 398 ++++++++++++++ include/video/mipi-dbi-bus.h | 125 +++++ include/video/panel-dpi.h | 24 + include/video/panel-r61505.h | 27 + include/video/panel-r61517.h | 28 + 39 files changed, 4615 insertions(+), 534 deletions(-) create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_connector.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_connector.h delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vgacon.c delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vgacon.h create mode 100644 drivers/video/display/Kconfig create mode 100644 drivers/video/display/Makefile create mode 100644 drivers/video/display/con-vga.c create mode 100644 drivers/video/display/display-core.c create mode 100644 drivers/video/display/display-notifier.c create mode 100644 drivers/video/display/mipi-dbi-bus.c create mode 100644 drivers/video/display/panel-dpi.c create mode 100644 drivers/video/display/panel-r61505.c create mode 100644 drivers/video/display/panel-r61517.c create mode 100644 drivers/video/display/vga-dac.c create mode 100644 include/video/display.h create mode 100644 include/video/mipi-dbi-bus.h create mode 100644 include/video/panel-dpi.h create mode 100644 include/video/panel-r61505.h create mode 100644 include/video/panel-r61517.h
The DISPLAY_ prefix will clash with the Common Display Framework, rename it.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/video/omap2/displays-new/Kconfig | 24 ++++++++++++------------ drivers/video/omap2/displays-new/Makefile | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/drivers/video/omap2/displays-new/Kconfig b/drivers/video/omap2/displays-new/Kconfig index 6c90885..2a44b41 100644 --- a/drivers/video/omap2/displays-new/Kconfig +++ b/drivers/video/omap2/displays-new/Kconfig @@ -1,68 +1,68 @@ menu "OMAP Display Device Drivers (new device model)" depends on OMAP2_DSS
-config DISPLAY_ENCODER_TFP410 +config OMAP2_DISPLAY_ENCODER_TFP410 tristate "TFP410 DPI to DVI Encoder" help Driver for TFP410 DPI to DVI encoder.
-config DISPLAY_ENCODER_TPD12S015 +config OMAP2_DISPLAY_ENCODER_TPD12S015 tristate "TPD12S015 HDMI ESD protection and level shifter" help Driver for TPD12S015, which offers HDMI ESD protection and level shifting.
-config DISPLAY_CONNECTOR_DVI +config OMAP2_DISPLAY_CONNECTOR_DVI tristate "DVI Connector" depends on I2C help Driver for a generic DVI connector.
-config DISPLAY_CONNECTOR_HDMI +config OMAP2_DISPLAY_CONNECTOR_HDMI tristate "HDMI Connector" help Driver for a generic HDMI connector.
-config DISPLAY_CONNECTOR_ANALOG_TV +config OMAP2_DISPLAY_CONNECTOR_ANALOG_TV tristate "Analog TV Connector" help Driver for a generic analog TV connector.
-config DISPLAY_PANEL_DPI +config OMAP2_DISPLAY_PANEL_DPI tristate "Generic DPI panel" help Driver for generic DPI panels.
-config DISPLAY_PANEL_DSI_CM +config OMAP2_DISPLAY_PANEL_DSI_CM tristate "Generic DSI Command Mode Panel" help Driver for generic DSI command mode panels.
-config DISPLAY_PANEL_SONY_ACX565AKM +config OMAP2_DISPLAY_PANEL_SONY_ACX565AKM tristate "ACX565AKM Panel" depends on SPI && BACKLIGHT_CLASS_DEVICE help This is the LCD panel used on Nokia N900
-config DISPLAY_PANEL_LGPHILIPS_LB035Q02 +config OMAP2_DISPLAY_PANEL_LGPHILIPS_LB035Q02 tristate "LG.Philips LB035Q02 LCD Panel" depends on SPI help LCD Panel used on the Gumstix Overo Palo35
-config DISPLAY_PANEL_SHARP_LS037V7DW01 +config OMAP2_DISPLAY_PANEL_SHARP_LS037V7DW01 tristate "Sharp LS037V7DW01 LCD Panel" depends on BACKLIGHT_CLASS_DEVICE help LCD Panel used in TI's SDP3430 and EVM boards
-config DISPLAY_PANEL_TPO_TD043MTEA1 +config OMAP2_DISPLAY_PANEL_TPO_TD043MTEA1 tristate "TPO TD043MTEA1 LCD Panel" depends on SPI help LCD Panel used in OMAP3 Pandora
-config DISPLAY_PANEL_NEC_NL8048HL11 +config OMAP2_DISPLAY_PANEL_NEC_NL8048HL11 tristate "NEC NL8048HL11 Panel" depends on SPI depends on BACKLIGHT_CLASS_DEVICE diff --git a/drivers/video/omap2/displays-new/Makefile b/drivers/video/omap2/displays-new/Makefile index 5aeb11b..768ad94 100644 --- a/drivers/video/omap2/displays-new/Makefile +++ b/drivers/video/omap2/displays-new/Makefile @@ -1,12 +1,12 @@ -obj-$(CONFIG_DISPLAY_ENCODER_TFP410) += encoder-tfp410.o -obj-$(CONFIG_DISPLAY_ENCODER_TPD12S015) += encoder-tpd12s015.o -obj-$(CONFIG_DISPLAY_CONNECTOR_DVI) += connector-dvi.o -obj-$(CONFIG_DISPLAY_CONNECTOR_HDMI) += connector-hdmi.o -obj-$(CONFIG_DISPLAY_CONNECTOR_ANALOG_TV) += connector-analog-tv.o -obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o -obj-$(CONFIG_DISPLAY_PANEL_DSI_CM) += panel-dsi-cm.o -obj-$(CONFIG_DISPLAY_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o -obj-$(CONFIG_DISPLAY_PANEL_LGPHILIPS_LB035Q02) += panel-lgphilips-lb035q02.o -obj-$(CONFIG_DISPLAY_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o -obj-$(CONFIG_DISPLAY_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o -obj-$(CONFIG_DISPLAY_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o +obj-$(CONFIG_OMAP2_DISPLAY_ENCODER_TFP410) += encoder-tfp410.o +obj-$(CONFIG_OMAP2_DISPLAY_ENCODER_TPD12S015) += encoder-tpd12s015.o +obj-$(CONFIG_OMAP2_DISPLAY_CONNECTOR_DVI) += connector-dvi.o +obj-$(CONFIG_OMAP2_DISPLAY_CONNECTOR_HDMI) += connector-hdmi.o +obj-$(CONFIG_OMAP2_DISPLAY_CONNECTOR_ANALOG_TV) += connector-analog-tv.o +obj-$(CONFIG_OMAP2_DISPLAY_PANEL_DPI) += panel-dpi.o +obj-$(CONFIG_OMAP2_DISPLAY_PANEL_DSI_CM) += panel-dsi-cm.o +obj-$(CONFIG_OMAP2_DISPLAY_PANEL_SONY_ACX565AKM) += panel-sony-acx565akm.o +obj-$(CONFIG_OMAP2_DISPLAY_PANEL_LGPHILIPS_LB035Q02) += panel-lgphilips-lb035q02.o +obj-$(CONFIG_OMAP2_DISPLAY_PANEL_SHARP_LS037V7DW01) += panel-sharp-ls037v7dw01.o +obj-$(CONFIG_OMAP2_DISPLAY_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o +obj-$(CONFIG_OMAP2_DISPLAY_PANEL_NEC_NL8048HL11) += panel-nec-nl8048hl11.o
The Common Display Framework (CDF) splits display devices in entities that interact through an abstract API. Each entity is managed by its own driver independently of the other entities, with the framework orchestrating interactions.
This commit introduces the CDF core with entity (un)registration and core control operations support.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/display/Kconfig | 4 + drivers/video/display/Makefile | 2 + drivers/video/display/display-core.c | 236 +++++++++++++++++++++++++++++++++++ include/video/display.h | 94 ++++++++++++++ 6 files changed, 338 insertions(+) create mode 100644 drivers/video/display/Kconfig create mode 100644 drivers/video/display/Makefile create mode 100644 drivers/video/display/display-core.c create mode 100644 include/video/display.h
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 4cf1e1d..c9ca1d5 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2477,6 +2477,7 @@ source "drivers/video/omap2/Kconfig" source "drivers/video/exynos/Kconfig" source "drivers/video/mmp/Kconfig" source "drivers/video/backlight/Kconfig" +source "drivers/video/display/Kconfig"
if VT source "drivers/video/console/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index e8bae8d..d7fd4a2 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -15,6 +15,7 @@ fb-objs := $(fb-y) obj-$(CONFIG_VT) += console/ obj-$(CONFIG_LOGO) += logo/ obj-y += backlight/ +obj-y += display/
obj-$(CONFIG_EXYNOS_VIDEO) += exynos/
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig new file mode 100644 index 0000000..1d533e7 --- /dev/null +++ b/drivers/video/display/Kconfig @@ -0,0 +1,4 @@ +menuconfig DISPLAY_CORE + tristate "Display Core" + ---help--- + Support common display framework for graphics devices. diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile new file mode 100644 index 0000000..3054adc --- /dev/null +++ b/drivers/video/display/Makefile @@ -0,0 +1,2 @@ +display-y := display-core.o +obj-$(CONFIG_DISPLAY_CORE) += display.o diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c new file mode 100644 index 0000000..82fc58b --- /dev/null +++ b/drivers/video/display/display-core.c @@ -0,0 +1,236 @@ +/* + * Display Core + * + * Copyright (C) 2013 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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 <linux/export.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <media/media-device.h> + +#include <video/display.h> +#include <video/videomode.h> + +/* ----------------------------------------------------------------------------- + * Control operations + */ + +/** + * display_entity_get_modes - Get video modes supported by the display entity + * @entity: The display entity + * @port: The display entity port + * @modes: Pointer to an array of modes + * + * Fill the modes argument with a pointer to an array of video modes. The array + * is owned by the display entity. + * + * Return the number of supported modes on success (including 0 if no mode is + * supported) or a negative error code otherwise. + */ +int display_entity_get_modes(struct display_entity *entity, unsigned int port, + const struct videomode **modes) +{ + if (port >= entity->entity.num_pads) + return -EINVAL; + + if (!entity->ops->ctrl || !entity->ops->ctrl->get_modes) + return 0; + + return entity->ops->ctrl->get_modes(entity, port, modes); +} +EXPORT_SYMBOL_GPL(display_entity_get_modes); + +/** + * display_entity_get_size - Get display entity physical size + * @entity: The display entity + * @width: Physical width in millimeters + * @height: Physical height in millimeters + * + * When applicable, for instance for display panels, retrieve the display + * physical size in millimeters. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_get_size(struct display_entity *entity, + unsigned int *width, unsigned int *height) +{ + if (!entity->ops->ctrl || !entity->ops->ctrl->get_size) + return -EOPNOTSUPP; + + return entity->ops->ctrl->get_size(entity, width, height); +} +EXPORT_SYMBOL_GPL(display_entity_get_size); + +/** + * display_entity_get_params - Get display entity interface parameters + * @entity: The display entity + * @port: The display entity port + * @params: Pointer to interface parameters + * + * Fill the parameters structure pointed to by the params argument with display + * entity interface parameters. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_get_params(struct display_entity *entity, unsigned int port, + struct display_entity_interface_params *params) +{ + if (port >= entity->entity.num_pads) + return -EINVAL; + + if (!entity->ops->ctrl || !entity->ops->ctrl->get_params) + return -EOPNOTSUPP; + + return entity->ops->ctrl->get_params(entity, port, params); +} +EXPORT_SYMBOL_GPL(display_entity_get_params); + +/* ----------------------------------------------------------------------------- + * Connections + */ + +/** + * display_entity_connect - Connect two entities through a video stream + * @source: The video stream source + * @sink: The video stream sink + * + * Set the sink entity source field to the source entity. + */ + +/** + * display_entity_disconnect - Disconnect two previously connected entities + * @source: The video stream source + * @sink: The video stream sink + * + * Break a connection between two previously connected entities. The source + * entity source field is reset to NULL. + */ + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +static void display_entity_release(struct kref *ref) +{ + struct display_entity *entity = + container_of(ref, struct display_entity, ref); + + if (entity->release) + entity->release(entity); +} + +/** + * display_entity_get - get a reference to a display entity + * @display_entity: the display entity + * + * Return the display entity pointer. + */ +struct display_entity *display_entity_get(struct display_entity *entity) +{ + if (entity == NULL) + return NULL; + + kref_get(&entity->ref); + return entity; +} +EXPORT_SYMBOL_GPL(display_entity_get); + +/** + * display_entity_put - release a reference to a display entity + * @entity: the display entity + * + * Releasing the last reference to a display entity releases the display entity + * itself. + */ +void display_entity_put(struct display_entity *entity) +{ + kref_put(&entity->ref, display_entity_release); +} +EXPORT_SYMBOL_GPL(display_entity_put); + +/** + * display_entity_init - Initialize a display entity + * @entity: display entity to be registered + * @num_sinks: number of sink ports + * @num_sources: number of source ports + * + * Initialize the display entity with the given number of sink and source ports. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_init(struct display_entity *entity, unsigned int num_sinks, + unsigned int num_sources) +{ + struct media_entity *ment = &entity->entity; + struct media_pad *pads; + unsigned int num_pads; + unsigned int i; + int ret; + + kref_init(&entity->ref); + + num_pads = num_sinks + num_sources; + pads = kzalloc(sizeof(*pads) * num_pads, GFP_KERNEL); + if (pads == NULL) + return -ENOMEM; + + for (i = 0; i < num_sinks; ++i) + pads[i].flags = MEDIA_PAD_FL_SINK; + for (; i < num_pads; ++i) + pads[i].flags = MEDIA_PAD_FL_SOURCE; + + ment->name = entity->name; + + ret = media_entity_init(ment, num_pads, pads, 0); + if (ret < 0) { + kfree(pads); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(display_entity_init); + +/** + * display_entity_cleanup - Clean a display entity up + * @entity: display entity to be cleaned up + * + * Clean the entity up and free all resources allocated by by + * display_entity_init(). + */ +void display_entity_cleanup(struct display_entity *entity) +{ + struct media_entity *ment = &entity->entity; + + kfree(ment->pads); + media_entity_cleanup(ment); + + display_entity_put(entity); +} +EXPORT_SYMBOL_GPL(display_entity_cleanup); + +int display_entity_register(struct media_device *mdev, + struct display_entity *entity) +{ + return media_device_register_entity(mdev, &entity->entity); +} +EXPORT_SYMBOL_GPL(display_entity_register); + +void display_entity_unregister(struct display_entity *entity) +{ + media_device_unregister_entity(&entity->entity); +} +EXPORT_SYMBOL_GPL(display_entity_unregister); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("Display Core"); +MODULE_LICENSE("GPL"); diff --git a/include/video/display.h b/include/video/display.h new file mode 100644 index 0000000..74b227d --- /dev/null +++ b/include/video/display.h @@ -0,0 +1,94 @@ +/* + * Display Core + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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. + */ + +#ifndef __DISPLAY_H__ +#define __DISPLAY_H__ + +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/module.h> +#include <media/media-entity.h> + +/* ----------------------------------------------------------------------------- + * Display Entity + */ + +struct display_entity; +struct videomode; + +enum display_entity_interface_type { + DISPLAY_ENTITY_INTERFACE_DPI, + DISPLAY_ENTITY_INTERFACE_DBI, + DISPLAY_ENTITY_INTERFACE_LVDS, + DISPLAY_ENTITY_INTERFACE_VGA, +}; + +struct display_entity_interface_params { + enum display_entity_interface_type type; +}; + +struct display_entity_control_ops { + int (*get_size)(struct display_entity *ent, + unsigned int *width, unsigned int *height); + + /* Port operations */ + int (*get_modes)(struct display_entity *entity, unsigned int port, + const struct videomode **modes); + int (*get_params)(struct display_entity *entity, unsigned int port, + struct display_entity_interface_params *params); +}; + +struct display_entity_ops { + const struct display_entity_control_ops *ctrl; +}; + +struct display_entity { + struct list_head list; + struct device *dev; + struct module *owner; + struct kref ref; + + char name[32]; + struct media_entity entity; + + const struct display_entity_ops *ops; + + void(*release)(struct display_entity *ent); +}; + +static inline struct display_entity * +to_display_entity(struct media_entity *entity) +{ + return container_of(entity, struct display_entity, entity); +} + +int __must_check display_entity_init(struct display_entity *entity, + unsigned int num_sinks, + unsigned int num_sources); +void display_entity_cleanup(struct display_entity *entity); + +int display_entity_register(struct media_device *mdev, + struct display_entity *entity); +void display_entity_unregister(struct display_entity *entity); + +/* Operations */ +struct display_entity *display_entity_get(struct display_entity *entity); +void display_entity_put(struct display_entity *entity); + +int display_entity_get_size(struct display_entity *entity, + unsigned int *width, unsigned int *height); +int display_entity_get_modes(struct display_entity *entity, unsigned int port, + const struct videomode **modes); +int display_entity_get_params(struct display_entity *entity, unsigned int port, + struct display_entity_interface_params *params); + +#endif /* __DISPLAY_H__ */
The video and stream control operations handle video stream management, both from the control point of view (called in response to userspace requests) and the video stream point of view (called by entities to control the video stream they receive on their input(s)).
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/video/display/display-core.c | 82 ++++++++++++++++++++++++++++++++++++ include/video/display.h | 49 +++++++++++++++++++++ 2 files changed, 131 insertions(+)
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index 82fc58b..bb18723 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c @@ -25,6 +25,57 @@ */
/** + * display_entity_set_state - Set the display entity operation state + * @entity: The display entity + * @state: Display entity operation state + * + * See &enum display_entity_state for information regarding the entity states. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_set_state(struct display_entity *entity, + enum display_entity_state state) +{ + int ret; + + if (entity->state == state) + return 0; + + if (!entity->ops->ctrl || !entity->ops->ctrl->set_state) + return 0; + + ret = entity->ops->ctrl->set_state(entity, state); + if (ret < 0) + return ret; + + entity->state = state; + return 0; +} +EXPORT_SYMBOL_GPL(display_entity_set_state); + +/** + * display_entity_update - Update the display + * @entity: The display entity + * + * Make the display entity ready to receive pixel data and start frame transfer. + * This operation can only be called if the display entity is in STANDBY or ON + * state. + * + * The display entity will call the upstream entity in the video chain to start + * the video stream. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_update(struct display_entity *entity) +{ + if (!entity->ops->ctrl || !entity->ops->ctrl->update) + return 0; + + return entity->ops->ctrl->update(entity); +} +EXPORT_SYMBOL_GPL(display_entity_update); + +/** * display_entity_get_modes - Get video modes supported by the display entity * @entity: The display entity * @port: The display entity port @@ -95,6 +146,36 @@ int display_entity_get_params(struct display_entity *entity, unsigned int port, EXPORT_SYMBOL_GPL(display_entity_get_params);
/* ----------------------------------------------------------------------------- + * Video operations + */ + +/** + * display_entity_set_stream - Control the video stream state + * @entity: The display entity + * @port: The display entity port + * @state: Display video stream state + * + * Control the video stream state at the entity video output. + * + * See &enum display_entity_stream_state for information regarding the stream + * states. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_set_stream(struct display_entity *entity, unsigned int port, + enum display_entity_stream_state state) +{ + if (port >= entity->entity.num_pads) + return -EINVAL; + + if (!entity->ops->video || !entity->ops->video->set_stream) + return 0; + + return entity->ops->video->set_stream(entity, port, state); +} +EXPORT_SYMBOL_GPL(display_entity_set_stream); + +/* ----------------------------------------------------------------------------- * Connections */
@@ -177,6 +258,7 @@ int display_entity_init(struct display_entity *entity, unsigned int num_sinks, int ret;
kref_init(&entity->ref); + entity->state = DISPLAY_ENTITY_STATE_OFF;
num_pads = num_sinks + num_sources; pads = kzalloc(sizeof(*pads) * num_pads, GFP_KERNEL); diff --git a/include/video/display.h b/include/video/display.h index 74b227d..fef05a68 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -25,6 +25,38 @@ struct display_entity; struct videomode;
+/** + * enum display_entity_state - State of a display entity + * @DISPLAY_ENTITY_STATE_OFF: The entity is turned off completely, possibly + * including its power supplies. Communication with a display entity in + * that state is not possible. + * @DISPLAY_ENTITY_STATE_STANDBY: The entity is in a low-power state. Full + * communication with the display entity is supported, including pixel data + * transfer, but the output is kept blanked. + * @DISPLAY_ENTITY_STATE_ON: The entity is fully operational. + */ +enum display_entity_state { + DISPLAY_ENTITY_STATE_OFF, + DISPLAY_ENTITY_STATE_STANDBY, + DISPLAY_ENTITY_STATE_ON, +}; + +/** + * enum display_entity_stream_state - State of a video stream + * @DISPLAY_ENTITY_STREAM_STOPPED: The video stream is stopped, no frames are + * transferred. + * @DISPLAY_ENTITY_STREAM_SINGLE_SHOT: The video stream has been started for + * single shot operation. The source entity will transfer a single frame + * and then stop. + * @DISPLAY_ENTITY_STREAM_CONTINUOUS: The video stream is running, frames are + * transferred continuously by the source entity. + */ +enum display_entity_stream_state { + DISPLAY_ENTITY_STREAM_STOPPED, + DISPLAY_ENTITY_STREAM_SINGLE_SHOT, + DISPLAY_ENTITY_STREAM_CONTINUOUS, +}; + enum display_entity_interface_type { DISPLAY_ENTITY_INTERFACE_DPI, DISPLAY_ENTITY_INTERFACE_DBI, @@ -39,6 +71,9 @@ struct display_entity_interface_params { struct display_entity_control_ops { int (*get_size)(struct display_entity *ent, unsigned int *width, unsigned int *height); + int (*set_state)(struct display_entity *ent, + enum display_entity_state state); + int (*update)(struct display_entity *ent);
/* Port operations */ int (*get_modes)(struct display_entity *entity, unsigned int port, @@ -47,8 +82,14 @@ struct display_entity_control_ops { struct display_entity_interface_params *params); };
+struct display_entity_video_ops { + int (*set_stream)(struct display_entity *ent, unsigned int port, + enum display_entity_stream_state state); +}; + struct display_entity_ops { const struct display_entity_control_ops *ctrl; + const struct display_entity_video_ops *video; };
struct display_entity { @@ -63,6 +104,8 @@ struct display_entity { const struct display_entity_ops *ops;
void(*release)(struct display_entity *ent); + + enum display_entity_state state; };
static inline struct display_entity * @@ -86,9 +129,15 @@ void display_entity_put(struct display_entity *entity);
int display_entity_get_size(struct display_entity *entity, unsigned int *width, unsigned int *height); +int display_entity_set_state(struct display_entity *entity, + enum display_entity_state state); +int display_entity_update(struct display_entity *entity); int display_entity_get_modes(struct display_entity *entity, unsigned int port, const struct videomode **modes); int display_entity_get_params(struct display_entity *entity, unsigned int port, struct display_entity_interface_params *params);
+int display_entity_set_stream(struct display_entity *entity, unsigned int port, + enum display_entity_stream_state state); + #endif /* __DISPLAY_H__ */
Display entities are initialized by they respective drivers asynchronously with the master display driver. The notifier infrastructure allows display drivers to create a list of entities they need (based on platform data) and be notified when those entities are added to or removed from the system.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/video/display/Makefile | 3 +- drivers/video/display/display-notifier.c | 304 +++++++++++++++++++++++++++++++ include/video/display.h | 66 +++++++ 3 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 drivers/video/display/display-notifier.c
diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index 3054adc..b907aad 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -1,2 +1,3 @@ -display-y := display-core.o +display-y := display-core.o \ + display-notifier.o obj-$(CONFIG_DISPLAY_CORE) += display.o diff --git a/drivers/video/display/display-notifier.c b/drivers/video/display/display-notifier.c new file mode 100644 index 0000000..c9210ec --- /dev/null +++ b/drivers/video/display/display-notifier.c @@ -0,0 +1,304 @@ +/* + * Display Notifier + * + * Copyright (C) 2013 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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 <linux/device.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/of.h> + +#include <video/display.h> + +static LIST_HEAD(display_entity_list); +static LIST_HEAD(display_entity_notifiers); +static DEFINE_MUTEX(display_entity_mutex); + +/* ----------------------------------------------------------------------------- + * Notifiers + */ + +static bool match_platform(struct device *dev, + struct display_entity_match *match) +{ + pr_debug("%s: matching device '%s' with name '%s'\n", __func__, + dev_name(dev), match->match.platform.name); + + return !strcmp(match->match.platform.name, dev_name(dev)); +} + +static struct display_entity_match * +display_entity_notifier_match(struct display_entity_notifier *notifier, + struct display_entity *entity) +{ + bool (*match_func)(struct device *, struct display_entity_match *); + struct display_entity_match *match; + + pr_debug("%s: matching entity '%s' (ptr 0x%p dev '%s')\n", __func__, + entity->name, entity, dev_name(entity->dev)); + + list_for_each_entry(match, ¬ifier->waiting, list) { + switch (match->type) { + default: + case DISPLAY_ENTITY_BUS_PLATFORM: + match_func = match_platform; + break; + } + + if (match_func(entity->dev, match)) + return match; + } + + return NULL; +} + +static void display_entity_notifier_cleanup(struct display_entity *entity) +{ + entity->match = NULL; + entity->notifier = NULL; +} + +static int +display_entity_notifier_notify(struct display_entity_notifier *notifier, + struct display_entity *entity, + struct display_entity_match *match) +{ + int ret; + + pr_debug("%s: notifying device '%s' for entity '%s' (ptr 0x%p dev '%s')\n", + __func__, dev_name(notifier->dev), entity->name, entity, + dev_name(entity->dev)); + + /* Remove the match from waiting list. */ + list_del(&match->list); + entity->match = match; + entity->notifier = notifier; + + if (notifier->bound) { + ret = notifier->bound(notifier, entity, match); + if (ret < 0) + goto error_bound; + } + + /* Move the entity from the global list to the notifier's done list. */ + list_move(&entity->list, ¬ifier->done); + + if (list_empty(¬ifier->waiting) && notifier->complete) { + pr_debug("%s: notifying device '%s' of completion\n", __func__, + dev_name(notifier->dev)); + ret = notifier->complete(notifier); + if (ret < 0) + goto error_complete; + } + + return 0; + +error_complete: + /* Move the entity back to the global list. */ + list_move(&entity->list, &display_entity_list); + if (notifier->unbind) + notifier->unbind(notifier, entity, match); +error_bound: + /* Put the match back to the waiting list. */ + list_add_tail(&match->list, ¬ifier->waiting); + display_entity_notifier_cleanup(entity); + + return ret; +} + +/** + * display_entity_register_notifier - register a display entity notifier + * @notifier: display entity notifier structure we want to register + * + * Display entity notifiers are called to notify drivers of display + * entity-related events for matching display_entitys. + * + * Notifiers and display_entitys are matched through the device they correspond + * to. If the notifier dev field is equal to the display entity dev field the + * notifier will be called when an event is reported. Notifiers with a NULL dev + * field act as catch-all and will be called for all display_entitys. + * + * Supported events are + * + * - DISPLAY_ENTITY_NOTIFIER_CONNECT reports display entity connection and is + * sent at display entity or notifier registration time + * - DISPLAY_ENTITY_NOTIFIER_DISCONNECT reports display entity disconnection and + * is sent at display entity unregistration time + * + * Registering a notifier sends DISPLAY_ENTITY_NOTIFIER_CONNECT events for all + * previously registered display_entitys that match the notifiers. + * + * Return 0 on success. + */ +int display_entity_register_notifier(struct display_entity_notifier *notifier) +{ + struct display_entity_match *match; + struct display_entity *entity; + struct display_entity *next; + unsigned int i; + int ret = 0; + + if (notifier->num_entities == 0) + return -EINVAL; + + INIT_LIST_HEAD(¬ifier->waiting); + INIT_LIST_HEAD(¬ifier->done); + + for (i = 0; i < notifier->num_entities; i++) { + match = ¬ifier->entities[i]; + + switch (match->type) { + case DISPLAY_ENTITY_BUS_PLATFORM: + break; + default: + dev_err(notifier->dev, + "%s: Invalid bus type %u on %p\n", __func__, + match->type, match); + return -EINVAL; + } + + list_add_tail(&match->list, ¬ifier->waiting); + } + + mutex_lock(&display_entity_mutex); + + list_add_tail(¬ifier->list, &display_entity_notifiers); + + list_for_each_entry_safe(entity, next, &display_entity_list, list) { + match = display_entity_notifier_match(notifier, entity); + if (!match) + continue; + + ret = display_entity_notifier_notify(notifier, entity, match); + if (ret) + break; + } + + mutex_unlock(&display_entity_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(display_entity_register_notifier); + +/** + * display_entity_unregister_notifier - unregister a display entity notifier + * @notifier: display entity notifier to be unregistered + * + * Unregistration guarantees that the notifier will never be called upon return + * of this function. + */ +void display_entity_unregister_notifier(struct display_entity_notifier *notifier) +{ + struct display_entity *entity; + struct display_entity *next; + + if (notifier->num_entities == 0) + return; + + mutex_lock(&display_entity_mutex); + + list_del(¬ifier->list); + + list_for_each_entry_safe(entity, next, ¬ifier->done, list) { + if (notifier->unbind) + notifier->unbind(notifier, entity, entity->match); + + /* Move the entity back to the global list. */ + display_entity_notifier_cleanup(entity); + list_move(&entity->list, &display_entity_list); + } + mutex_unlock(&display_entity_mutex); +} +EXPORT_SYMBOL_GPL(display_entity_unregister_notifier); + +/* ----------------------------------------------------------------------------- + * Entity Registration + */ + +/** + * display_entity_add - add a display entity to the list of available entities + * @entity: display entity to be added + * + * Add the display entity to the list of available entities and send the + * DISPLAY_ENTITY_NOTIFIER_CONNECT event to all matching registered notifiers. + * + * Return 0 on success. + */ +int display_entity_add(struct display_entity *entity) +{ + struct display_entity_notifier *notifier; + struct display_entity_match *match = NULL; + + pr_debug("%s: adding entity '%s' (ptr 0x%p dev '%s')\n", __func__, + entity->name, entity, dev_name(entity->dev)); + + mutex_lock(&display_entity_mutex); + + /* Add the entity to the global unbound entities list. It will later be + * moved to the notifier done list by display_entity_notifier_notify(). + */ + list_add_tail(&entity->list, &display_entity_list); + + list_for_each_entry(notifier, &display_entity_notifiers, list) { + match = display_entity_notifier_match(notifier, entity); + if (match) + break; + } + + if (match) { + /* A match has been found, notify the notifier. */ + display_entity_notifier_notify(notifier, entity, match); + } + + mutex_unlock(&display_entity_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(display_entity_add); + +/** + * display_entity_remove - removea display entity + * @entity: display entity to be removed + * + * Remove the display entity from the list of available entities and send the + * DISPLAY_ENTITY_NOTIFIER_DISCONNECT event to all matching registered + * notifiers. + */ +void display_entity_remove(struct display_entity *entity) +{ + struct display_entity_notifier *notifier = entity->notifier; + struct display_entity_match *match = entity->match; + + pr_debug("%s: removing entity '%s' (ptr 0x%p dev '%s')\n", __func__, + entity->name, entity, dev_name(entity->dev)); + + if (!notifier) { + /* Remove the entity from the global list. */ + list_del(&entity->list); + return; + } + + mutex_lock(&display_entity_mutex); + + if (notifier->unbind) + notifier->unbind(notifier, entity, match); + + /* Remove the entity from the notifier's done list. */ + display_entity_notifier_cleanup(entity); + list_del(&entity->list); + + /* Move the match back to the waiting list. */ + list_add_tail(&match->list, ¬ifier->waiting); + + mutex_unlock(&display_entity_mutex); +} +EXPORT_SYMBOL_GPL(display_entity_remove); diff --git a/include/video/display.h b/include/video/display.h index fef05a68..2063694 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -23,6 +23,8 @@ */
struct display_entity; +struct display_entity_match; +struct display_entity_notify; struct videomode;
/** @@ -101,6 +103,9 @@ struct display_entity { char name[32]; struct media_entity entity;
+ struct display_entity_match *match; + struct display_entity_notifier *notifier; + const struct display_entity_ops *ops;
void(*release)(struct display_entity *ent); @@ -140,4 +145,65 @@ int display_entity_get_params(struct display_entity *entity, unsigned int port, int display_entity_set_stream(struct display_entity *entity, unsigned int port, enum display_entity_stream_state state);
+/* ----------------------------------------------------------------------------- + * Notifier + */ + +enum display_entity_bus_type { + DISPLAY_ENTITY_BUS_PLATFORM, +}; + +/** + * struct display_entity_match - Display entity description + * @type: display entity bus type + * @match.platform.name: platform device name + * @match.dt.node: DT node + * @list: link match objects waiting to be matched + */ +struct display_entity_match { + enum display_entity_bus_type type; + union { + struct { + const char *name; + } platform; + } match; + + struct list_head list; +}; + +/** + * display_entity_notifier - display entity notifier + * @num_entities: number of display entities + * @entities: array of pointers to subdevice descriptors + * @waiting: list of struct v4l2_async_subdev, waiting for their drivers + * @done: list of struct v4l2_async_subdev_list, already probed + * @list: member in a global list of notifiers + * @bound: a display entity has been registered + * @complete: all display entities have been registered + * @unbind: a display entity is being unregistered + */ +struct display_entity_notifier { + struct device *dev; + + unsigned int num_entities; + struct display_entity_match *entities; + struct list_head waiting; + struct list_head done; + struct list_head list; + + int (*bound)(struct display_entity_notifier *notifier, + struct display_entity *entity, + struct display_entity_match *match); + int (*complete)(struct display_entity_notifier *notifier); + void (*unbind)(struct display_entity_notifier *notifier, + struct display_entity *entity, + struct display_entity_match *match); +}; + +int display_entity_register_notifier(struct display_entity_notifier *notifier); +void display_entity_unregister_notifier(struct display_entity_notifier *notifier); + +int display_entity_add(struct display_entity *entity); +void display_entity_remove(struct display_entity *entity); + #endif /* __DISPLAY_H__ */
Add two graph helper functions. display_entity_build_notifier() builds an entity notifier from an entities graph represented as a flat array, typically passed from platform data. display_entity_link_graph() can then be used to create media controller links between all entities in the graph.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/video/display/display-core.c | 107 +++++++++++++++++++++++++++++++ drivers/video/display/display-notifier.c | 51 +++++++++++++++ include/video/display.h | 20 ++++++ 3 files changed, 178 insertions(+)
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index bb18723..c3b47d3 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c @@ -10,6 +10,7 @@ * published by the Free Software Foundation. */
+#include <linux/device.h> #include <linux/export.h> #include <linux/kernel.h> #include <linux/module.h> @@ -313,6 +314,112 @@ void display_entity_unregister(struct display_entity *entity) } EXPORT_SYMBOL_GPL(display_entity_unregister);
+/* ----------------------------------------------------------------------------- + * Graph Helpers + */ + +static int display_entity_link_entity(struct device *dev, + struct display_entity *entity, + struct list_head *entities) +{ + const struct display_entity_graph_data *graph = entity->match->data; + u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED; + struct media_entity *local = &entity->entity; + unsigned int i; + int ret = 0; + + dev_dbg(dev, "creating links for entity %s\n", local->name); + + for (i = 0; i < entity->entity.num_pads; ++i) { + const struct display_entity_source_data *source; + struct media_pad *local_pad = &local->pads[i]; + struct media_entity *remote = NULL; + struct media_pad *remote_pad; + struct display_entity *ent; + + dev_dbg(dev, "processing pad %u\n", i); + + /* Skip source pads, they will be processed from the other end + * of the link. + */ + if (local_pad->flags & MEDIA_PAD_FL_SOURCE) { + dev_dbg(dev, "skipping source pad %s:%u\n", + local->name, i); + continue; + } + + /* Find the remote entity. If not found, just skip the link as + * it goes out of scope of the entities handled by the notifier. + */ + source = &graph->sources[i]; + list_for_each_entry(ent, entities, list) { + if (strcmp(source->name, dev_name(ent->dev)) == 0) { + remote = &ent->entity; + break; + } + } + + if (remote == NULL) { + dev_dbg(dev, "no entity found for %s\n", source->name); + continue; + } + + if (source->port >= remote->num_pads) { + dev_err(dev, "invalid port number %u on %s\n", + source->port, source->name); + ret = -EINVAL; + break; + } + + remote_pad = &remote->pads[source->port]; + + /* Create the media link. */ + dev_dbg(dev, "creating %s:%u -> %s:%u link\n", + remote->name, remote_pad->index, + local->name, local_pad->index); + + ret = media_entity_create_link(remote, remote_pad->index, + local, local_pad->index, + link_flags); + if (ret < 0) { + dev_err(dev, "failed to create %s:%u -> %s:%u link\n", + remote->name, remote_pad->index, + local->name, local_pad->index); + break; + } + } + + return ret; +} + +/** + * display_entity_link_graph - Link all entities in a graph + * @dev: device used to print debugging and error messages + * @entities: list of display entities in the graph + * + * This function creates media controller links for all entities in a graph + * based on graph link data. It relies on the entities match data pointers + * having been initialized by the display_entity_build_notifier() function when + * building the notifier and thus can't be used when the notifier is built in a + * different way. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_entity_link_graph(struct device *dev, struct list_head *entities) +{ + struct display_entity *entity; + int ret; + + list_for_each_entry(entity, entities, list) { + ret = display_entity_link_entity(dev, entity, entities); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(display_entity_link_graph); + MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); MODULE_DESCRIPTION("Display Core"); MODULE_LICENSE("GPL"); diff --git a/drivers/video/display/display-notifier.c b/drivers/video/display/display-notifier.c index c9210ec..2d752b3 100644 --- a/drivers/video/display/display-notifier.c +++ b/drivers/video/display/display-notifier.c @@ -220,6 +220,57 @@ void display_entity_unregister_notifier(struct display_entity_notifier *notifier } EXPORT_SYMBOL_GPL(display_entity_unregister_notifier);
+/** + * display_entity_build_notifier - build a notifier from graph data + * @notifier: display entity notifier to be built + * @graph: graph data + * + * Before registering a notifier drivers must initialize the notifier's list of + * entities. This helper function simplifies building the list of entities for + * drivers that use an array of struct display_entity_graph_data to describe the + * entities graph. + * + * The function allocates an array of struct display_entity_match, initialize it + * from graph data, and sets the notifier entities and num_entities fields. + * + * The entities array is allocated using the managed memory allocation API on + * the notifier device, which must be initialized before calling this function. + * + * Return 0 on success or a negative error code on error. + */ +int display_entity_build_notifier(struct display_entity_notifier *notifier, + const struct display_entity_graph_data *graph) +{ + struct display_entity_match *entities; + unsigned int num_entities; + unsigned int i; + + for (num_entities = 0; graph[num_entities].name; ++num_entities) { + } + + if (num_entities == 0) + return -EINVAL; + + entities = devm_kzalloc(notifier->dev, sizeof(*notifier->entities) * + num_entities, GFP_KERNEL); + if (entities == NULL) + return -ENOMEM; + + for (i = 0; i < num_entities; ++i) { + struct display_entity_match *match = &entities[i]; + + match->type = DISPLAY_ENTITY_BUS_PLATFORM; + match->match.platform.name = graph[i].name; + match->data = &graph[i]; + } + + notifier->num_entities = num_entities; + notifier->entities = entities; + + return 0; +} +EXPORT_SYMBOL_GPL(display_entity_build_notifier); + /* ----------------------------------------------------------------------------- * Entity Registration */ diff --git a/include/video/display.h b/include/video/display.h index 2063694..58ff0d1 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -159,6 +159,7 @@ enum display_entity_bus_type { * @match.platform.name: platform device name * @match.dt.node: DT node * @list: link match objects waiting to be matched + * @data: driver private data, not touched by the core */ struct display_entity_match { enum display_entity_bus_type type; @@ -169,6 +170,7 @@ struct display_entity_match { } match;
struct list_head list; + const void *data; };
/** @@ -206,4 +208,22 @@ void display_entity_unregister_notifier(struct display_entity_notifier *notifier int display_entity_add(struct display_entity *entity); void display_entity_remove(struct display_entity *entity);
+/* ----------------------------------------------------------------------------- + * Graph Helpers + */ + +struct display_entity_source_data { + const char *name; + unsigned int port; +}; + +struct display_entity_graph_data { + const char *name; + const struct display_entity_source_data *sources; +}; + +int display_entity_build_notifier(struct display_entity_notifier *notifier, + const struct display_entity_graph_data *graph); +int display_entity_link_graph(struct device *dev, struct list_head *entities); + #endif /* __DISPLAY_H__ */
Extend the notifier with DT node matching support, and add helper functions to build the notifier and link entities based on a graph representation in DT.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/video/display/display-core.c | 334 +++++++++++++++++++++++++++++++ drivers/video/display/display-notifier.c | 187 +++++++++++++++++ include/video/display.h | 45 +++++ 3 files changed, 566 insertions(+)
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index c3b47d3..328ead7 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c @@ -14,6 +14,7 @@ #include <linux/export.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/slab.h>
#include <media/media-device.h> @@ -315,6 +316,184 @@ void display_entity_unregister(struct display_entity *entity) EXPORT_SYMBOL_GPL(display_entity_unregister);
/* ----------------------------------------------------------------------------- + * OF Helpers + */ + +#ifdef CONFIG_OF + +/** + * display_of_get_next_endpoint() - get next endpoint node + * @parent: pointer to the parent device node + * @prev: previous endpoint node, or NULL to get first + * + * Return: An 'endpoint' node pointer with refcount incremented. Refcount + * of the passed @prev node is not decremented, the caller have to use + * of_node_put() on it when done. + */ +struct device_node * +display_of_get_next_endpoint(const struct device_node *parent, + struct device_node *prev) +{ + struct device_node *endpoint; + struct device_node *port = NULL; + + if (!parent) + return NULL; + + if (!prev) { + struct device_node *node; + /* + * It's the first call, we have to find a port subnode + * within this node or within an optional 'ports' node. + */ + node = of_get_child_by_name(parent, "ports"); + if (node) + parent = node; + + port = of_get_child_by_name(parent, "port"); + + if (port) { + /* Found a port, get an endpoint. */ + endpoint = of_get_next_child(port, NULL); + of_node_put(port); + } else { + endpoint = NULL; + } + + if (!endpoint) + pr_err("%s(): no endpoint nodes specified for %s\n", + __func__, parent->full_name); + of_node_put(node); + } else { + port = of_get_parent(prev); + if (!port) + /* Hm, has someone given us the root node ?... */ + return NULL; + + /* Avoid dropping prev node refcount to 0. */ + of_node_get(prev); + endpoint = of_get_next_child(port, prev); + if (endpoint) { + of_node_put(port); + return endpoint; + } + + /* No more endpoints under this port, try the next one. */ + do { + port = of_get_next_child(parent, port); + if (!port) + return NULL; + } while (of_node_cmp(port->name, "port")); + + /* Pick up the first endpoint in this port. */ + endpoint = of_get_next_child(port, NULL); + of_node_put(port); + } + + return endpoint; +} + +/** + * display_of_get_remote_port_parent() - get remote port's parent node + * @node: pointer to a local endpoint device_node + * + * Return: Remote device node associated with remote endpoint node linked + * to @node. Use of_node_put() on it when done. + */ +struct device_node * +display_of_get_remote_port_parent(const struct device_node *node) +{ + struct device_node *np; + unsigned int depth; + + /* Get remote endpoint node. */ + 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; +} + +/** + * struct display_of_link - a link between two endpoints + * @local_node: pointer to device_node of this endpoint + * @local_port: identifier of the port this endpoint belongs to + * @remote_node: pointer to device_node of the remote endpoint + * @remote_port: identifier of the port the remote endpoint belongs to + */ +struct display_of_link { + struct device_node *local_node; + unsigned int local_port; + struct device_node *remote_node; + unsigned int remote_port; +}; + +/** + * display_of_parse_link() - parse a link between two endpoints + * @node: pointer to the endpoint at the local end of the link + * @link: pointer to the display OF link data structure + * + * Fill the link structure with the local and remote nodes and port numbers. + * The local_node and remote_node fields are set to point to the local and + * remote port parent nodes respectively (the port parent node being the parent + * node of the port node if that node isn't a 'ports' node, or the grand-parent + * node of the port node otherwise). + * + * A reference is taken to both the local and remote nodes, the caller must use + * display_of_put_link() to drop the references when done with the link. + * + * Return: 0 on success, or -ENOLINK if the remote endpoint can't be found. + */ +static int display_of_parse_link(const struct device_node *node, + struct display_of_link *link) +{ + struct device_node *np; + + memset(link, 0, sizeof(*link)); + + np = of_get_parent(node); + of_property_read_u32(np, "reg", &link->local_port); + np = of_get_next_parent(np); + if (of_node_cmp(np->name, "ports") == 0) + np = of_get_next_parent(np); + link->local_node = np; + + np = of_parse_phandle(node, "remote-endpoint", 0); + if (!np) { + of_node_put(link->local_node); + return -ENOLINK; + } + + np = of_get_parent(np); + of_property_read_u32(np, "reg", &link->remote_port); + np = of_get_next_parent(np); + if (of_node_cmp(np->name, "ports") == 0) + np = of_get_next_parent(np); + link->remote_node = np; + + return 0; +} + +/** + * display_of_put_link() - drop references to nodes in a link + * @link: pointer to the display OF link data structure + * + * Drop references to the local and remote nodes in the link. This function must + * be called on every link parsed with display_of_parse_link(). + */ +static void display_of_put_link(struct display_of_link *link) +{ + of_node_put(link->local_node); + of_node_put(link->remote_node); +} + +#endif /* CONFIG_OF */ + +/* ----------------------------------------------------------------------------- * Graph Helpers */
@@ -420,6 +599,161 @@ int display_entity_link_graph(struct device *dev, struct list_head *entities) } EXPORT_SYMBOL_GPL(display_entity_link_graph);
+#ifdef CONFIG_OF + +static int display_of_entity_link_entity(struct device *dev, + struct display_entity *entity, + struct list_head *entities, + struct display_entity *root) +{ + u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED; + const struct device_node *node = entity->dev->of_node; + struct media_entity *local = &entity->entity; + struct device_node *ep = NULL; + int ret = 0; + + dev_dbg(dev, "creating links for entity %s\n", local->name); + + while (1) { + struct media_entity *remote = NULL; + struct media_pad *remote_pad; + struct media_pad *local_pad; + struct display_of_link link; + struct display_entity *ent; + struct device_node *next; + + /* Get the next endpoint and parse its link. */ + next = display_of_get_next_endpoint(node, ep); + if (next == NULL) + break; + + of_node_put(ep); + ep = next; + + dev_dbg(dev, "processing endpoint %s\n", ep->full_name); + + ret = display_of_parse_link(ep, &link); + if (ret < 0) { + dev_err(dev, "failed to parse link for %s\n", + ep->full_name); + continue; + } + + /* Skip source pads, they will be processed from the other end of + * the link. + */ + if (link.local_port >= local->num_pads) { + dev_err(dev, "invalid port number %u on %s\n", + link.local_port, link.local_node->full_name); + display_of_put_link(&link); + ret = -EINVAL; + break; + } + + local_pad = &local->pads[link.local_port]; + + if (local_pad->flags & MEDIA_PAD_FL_SOURCE) { + dev_dbg(dev, "skipping source port %s:%u\n", + link.local_node->full_name, link.local_port); + display_of_put_link(&link); + continue; + } + + /* Find the remote entity. If not found, just skip the link as + * it goes out of scope of the entities handled by the notifier. + */ + list_for_each_entry(ent, entities, list) { + if (ent->dev->of_node == link.remote_node) { + remote = &ent->entity; + break; + } + } + + if (root->dev->of_node == link.remote_node) + remote = &root->entity; + + if (remote == NULL) { + dev_dbg(dev, "no entity found for %s\n", + link.remote_node->full_name); + display_of_put_link(&link); + continue; + } + + if (link.remote_port >= remote->num_pads) { + dev_err(dev, "invalid port number %u on %s\n", + link.remote_port, link.remote_node->full_name); + display_of_put_link(&link); + ret = -EINVAL; + break; + } + + remote_pad = &remote->pads[link.remote_port]; + + display_of_put_link(&link); + + /* Create the media link. */ + dev_dbg(dev, "creating %s:%u -> %s:%u link\n", + remote->name, remote_pad->index, + local->name, local_pad->index); + + ret = media_entity_create_link(remote, remote_pad->index, + local, local_pad->index, + link_flags); + if (ret < 0) { + dev_err(dev, + "failed to create %s:%u -> %s:%u link\n", + remote->name, remote_pad->index, + local->name, local_pad->index); + break; + } + } + + of_node_put(ep); + return ret; +} + +/** + * display_of_entity_link_graph - Link all entities in a graph + * @dev: device used to print debugging and error messages + * @root: optional root display entity + * @entities: list of display entities in the graph + * + * This function creates media controller links for all entities in a graph + * based on the device tree graph representation. It relies on all entities + * having been instantiated from the device tree. + * + * The list of entities is typically taken directly from a display notifier + * done list. It will thus not include any display entity not handled by the + * notifier, such as entities directly accessible by the caller without going + * through the notification process. The optional root entity parameter can be + * used to pass such a display entity and include it in the graph. For all + * practical purpose the root entity is handled is if it was part of the + * entities list. + * + * Return 0 on success or a negative error code otherwise. + */ +int display_of_entity_link_graph(struct device *dev, struct list_head *entities, + struct display_entity *root) +{ + struct display_entity *entity; + int ret; + + list_for_each_entry(entity, entities, list) { + if (WARN_ON(entity->match->type != DISPLAY_ENTITY_BUS_DT)) + return -EINVAL; + + ret = display_of_entity_link_entity(dev, entity, entities, + root); + if (ret < 0) + return ret; + } + + return display_of_entity_link_entity(dev, root, entities, root); +} +EXPORT_SYMBOL_GPL(display_of_entity_link_graph); + +#endif + MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); MODULE_DESCRIPTION("Display Core"); MODULE_LICENSE("GPL"); diff --git a/drivers/video/display/display-notifier.c b/drivers/video/display/display-notifier.c index 2d752b3..6bede03 100644 --- a/drivers/video/display/display-notifier.c +++ b/drivers/video/display/display-notifier.c @@ -16,6 +16,7 @@ #include <linux/list.h> #include <linux/mutex.h> #include <linux/of.h> +#include <linux/slab.h>
#include <video/display.h>
@@ -36,6 +37,14 @@ static bool match_platform(struct device *dev, return !strcmp(match->match.platform.name, dev_name(dev)); }
+static bool match_dt(struct device *dev, struct display_entity_match *match) +{ + pr_debug("%s: matching device node '%s' with node '%s'\n", __func__, + dev->of_node->full_name, match->match.dt.node->full_name); + + return match->match.dt.node == dev->of_node; +} + static struct display_entity_match * display_entity_notifier_match(struct display_entity_notifier *notifier, struct display_entity *entity) @@ -52,6 +61,9 @@ display_entity_notifier_match(struct display_entity_notifier *notifier, case DISPLAY_ENTITY_BUS_PLATFORM: match_func = match_platform; break; + case DISPLAY_ENTITY_BUS_DT: + match_func = match_dt; + break; }
if (match_func(entity->dev, match)) @@ -158,6 +170,7 @@ int display_entity_register_notifier(struct display_entity_notifier *notifier)
switch (match->type) { case DISPLAY_ENTITY_BUS_PLATFORM: + case DISPLAY_ENTITY_BUS_DT: break; default: dev_err(notifier->dev, @@ -272,6 +285,180 @@ int display_entity_build_notifier(struct display_entity_notifier *notifier, EXPORT_SYMBOL_GPL(display_entity_build_notifier);
/* ----------------------------------------------------------------------------- + * OF Support + */ + +#ifdef CONFIG_OF + +struct display_entity_of { + struct list_head list; + struct device_node *node; +}; + +static struct display_entity_of * +display_of_find_entity(struct list_head *entities, + const struct device_node *node) +{ + struct display_entity_of *entity; + + list_for_each_entry(entity, entities, list) { + if (entity->node == node) + return entity; + } + + return NULL; +} + +static int display_of_parse_dt(struct display_entity_notifier *notifier, + struct list_head *entities, + struct device_node *node) +{ + struct display_entity_of *entity; + struct device_node *remote; + struct device_node *ep = NULL; + struct device_node *next; + unsigned int num_entities = 0; + int ret = 0; + + /* Walk the device tree and build a list of nodes. */ + dev_dbg(notifier->dev, "parsing node %s\n", node->full_name); + + while (1) { + next = display_of_get_next_endpoint(node, ep); + if (next == NULL) + break; + + of_node_put(ep); + ep = next; + + dev_dbg(notifier->dev, "handling endpoint %s\n", ep->full_name); + + remote = display_of_get_remote_port_parent(ep); + if (remote == NULL) + continue; + + /* Skip entities that we have already processed. */ + if (display_of_find_entity(entities, remote) || remote == node) { + dev_dbg(notifier->dev, + "entity %s already in list, skipping\n", + remote->full_name); + continue; + } + + entity = kzalloc(sizeof(*entity), GFP_KERNEL); + if (entity == NULL) { + of_node_put(remote); + ret = -ENOMEM; + break; + } + + dev_dbg(notifier->dev, "adding remote entity %s to list\n", + remote->full_name); + + entity->node = remote; + list_add_tail(&entity->list, entities); + num_entities++; + } + + of_node_put(ep); + + if (ret < 0) + return ret; + + return num_entities; +} + +/** + * display_of_entity_build_notifier - build a notifier from device tree + * @notifier: display entity notifier to be built + * @node: device tree node + * + * Before registering a notifier drivers must initialize the notifier's list of + * entities. This helper function simplifies building the list of entities for + * drivers that use a device tree representation of the graph. + * + * The function allocates an array of struct display_entity_match, initialize it + * from the device tree, and sets the notifier entities and num_entities fields. + * + * The entities array is allocated using the managed memory allocation API on + * the notifier device, which must be initialized before calling this function. + * + * Return 0 on success or a negative error code on error. + */ +int display_of_entity_build_notifier(struct display_entity_notifier *notifier, + struct device_node *node) +{ + struct display_entity_match *matches; + struct display_entity_of *entity; + struct display_entity_of *next; + unsigned int num_entities = 0; + LIST_HEAD(entities); + unsigned int i; + int ret; + + /* Add an initial entity that stores the device tree node pointer to the + * list. + */ + entity = kzalloc(sizeof(*entity), GFP_KERNEL); + if (entity == NULL) + return -ENOMEM; + + entity->node = node; + list_add_tail(&entity->list, &entities); + + /* Parse all entities in the list. New entities will be added at the + * tail when parsing the device tree and will just be processed by the + * next iterations. + */ + list_for_each_entry(entity, &entities, list) { + ret = display_of_parse_dt(notifier, &entities, entity->node); + if (ret < 0) + goto error; + + num_entities += ret; + } + + /* Allocate the entity matches array and fill it. */ + matches = devm_kzalloc(notifier->dev, sizeof(*notifier->entities) * + num_entities, GFP_KERNEL); + if (matches == NULL) { + ret = -ENOMEM; + goto error; + } + + i = 0; + list_for_each_entry_safe(entity, next, &entities, list) { + struct display_entity_match *match; + + /* Don't add the initial node to the matches array. */ + if (entity->node != node) { + match = &matches[i++]; + match->type = DISPLAY_ENTITY_BUS_DT; + match->match.dt.node = entity->node; + } + + list_del(&entity->list); + kfree(entity); + } + + notifier->num_entities = num_entities; + notifier->entities = matches; + + return 0; + +error: + list_for_each_entry_safe(entity, next, &entities, list) { + list_del(&entity->list); + kfree(entity); + } + + return ret; +} +EXPORT_SYMBOL_GPL(display_of_entity_build_notifier); + +#endif /* CONFIG_OF */ + +/* ----------------------------------------------------------------------------- * Entity Registration */
diff --git a/include/video/display.h b/include/video/display.h index 58ff0d1..36ff637 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -22,6 +22,7 @@ * Display Entity */
+struct device_node; struct display_entity; struct display_entity_match; struct display_entity_notify; @@ -145,12 +146,33 @@ int display_entity_get_params(struct display_entity *entity, unsigned int port, int display_entity_set_stream(struct display_entity *entity, unsigned int port, enum display_entity_stream_state state);
+#ifdef CONFIG_OF +struct device_node * +display_of_get_next_endpoint(const struct device_node *parent, + struct device_node *prev); +struct device_node * +display_of_get_remote_port_parent(const struct device_node *node); +#else +static inline struct device_node * +display_of_get_next_endpoint(const struct device_node *parent, + struct device_node *prev) +{ + return NULL; +} +static inline struct device_node * +display_of_get_remote_port_parent(const struct device_node *node) +{ + return NULL; +} +#endif + /* ----------------------------------------------------------------------------- * Notifier */
enum display_entity_bus_type { DISPLAY_ENTITY_BUS_PLATFORM, + DISPLAY_ENTITY_BUS_DT, };
/** @@ -167,6 +189,9 @@ struct display_entity_match { struct { const char *name; } platform; + struct { + const struct device_node *node; + } dt; } match;
struct list_head list; @@ -226,4 +251,24 @@ int display_entity_build_notifier(struct display_entity_notifier *notifier, const struct display_entity_graph_data *graph); int display_entity_link_graph(struct device *dev, struct list_head *entities);
+#ifdef CONFIG_OF +int display_of_entity_build_notifier(struct display_entity_notifier *notifier, + struct device_node *node); +int display_of_entity_link_graph(struct device *dev, struct list_head *entities, + struct display_entity *root); +#else +static inline int +display_of_entity_build_notifier(struct display_entity_notifier *notifier, + struct device_node *node) +{ + return -ENOSYS; +} +static inline int +display_of_entity_link_graph(struct device *dev,struct list_head *entities, + struct display_entity *root) +{ + return -ENOSYS; +} +#endif + #endif /* __DISPLAY_H__ */
Hi Laurent,
I have another small issue with the graph helpers below:
Am Samstag, den 10.08.2013, 01:03 +0200 schrieb Laurent Pinchart: [...]
+/* -----------------------------------------------------------------------------
- Graph Helpers
*/
@@ -420,6 +599,161 @@ int display_entity_link_graph(struct device *dev, struct list_head *entities) } EXPORT_SYMBOL_GPL(display_entity_link_graph);
+#ifdef CONFIG_OF
+static int display_of_entity_link_entity(struct device *dev,
struct display_entity *entity,
struct list_head *entities,
struct display_entity *root)
+{
- u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
- const struct device_node *node = entity->dev->of_node;
- struct media_entity *local = &entity->entity;
- struct device_node *ep = NULL;
- int ret = 0;
- dev_dbg(dev, "creating links for entity %s\n", local->name);
- while (1) {
struct media_entity *remote = NULL;
struct media_pad *remote_pad;
struct media_pad *local_pad;
struct display_of_link link;
struct display_entity *ent;
struct device_node *next;
/* Get the next endpoint and parse its link. */
next = display_of_get_next_endpoint(node, ep);
if (next == NULL)
break;
of_node_put(ep);
ep = next;
dev_dbg(dev, "processing endpoint %s\n", ep->full_name);
ret = display_of_parse_link(ep, &link);
if (ret < 0) {
dev_err(dev, "failed to parse link for %s\n",
ep->full_name);
continue;
}
/* Skip source pads, they will be processed from the other end of
* the link.
*/
if (link.local_port >= local->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.local_port, link.local_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
local_pad = &local->pads[link.local_port];
if (local_pad->flags & MEDIA_PAD_FL_SOURCE) {
dev_dbg(dev, "skipping source port %s:%u\n",
link.local_node->full_name, link.local_port);
display_of_put_link(&link);
continue;
}
/* Find the remote entity. If not found, just skip the link as
* it goes out of scope of the entities handled by the notifier.
*/
list_for_each_entry(ent, entities, list) {
if (ent->dev->of_node == link.remote_node) {
remote = &ent->entity;
break;
}
}
if (root->dev->of_node == link.remote_node)
remote = &root->entity;
if (remote == NULL) {
dev_dbg(dev, "no entity found for %s\n",
link.remote_node->full_name);
display_of_put_link(&link);
continue;
}
if (link.remote_port >= remote->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.remote_port, link.remote_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
remote_pad = &remote->pads[link.remote_port];
display_of_put_link(&link);
/* Create the media link. */
dev_dbg(dev, "creating %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
ret = media_entity_create_link(remote, remote_pad->index,
local, local_pad->index,
link_flags);
if (ret < 0) {
dev_err(dev,
"failed to create %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
break;
}
- }
- of_node_put(ep);
- return ret;
+}
+/**
- display_of_entity_link_graph - Link all entities in a graph
- @dev: device used to print debugging and error messages
- @root: optional root display entity
- @entities: list of display entities in the graph
- This function creates media controller links for all entities in a graph
- based on the device tree graph representation. It relies on all entities
- having been instantiated from the device tree.
- The list of entities is typically taken directly from a display notifier
- done list. It will thus not include any display entity not handled by the
- notifier, such as entities directly accessible by the caller without going
- through the notification process. The optional root entity parameter can be
- used to pass such a display entity and include it in the graph. For all
- practical purpose the root entity is handled is if it was part of the
- entities list.
- Return 0 on success or a negative error code otherwise.
- */
+int display_of_entity_link_graph(struct device *dev, struct list_head *entities,
struct display_entity *root)
+{
- struct display_entity *entity;
- int ret;
- list_for_each_entry(entity, entities, list) {
if (WARN_ON(entity->match->type != DISPLAY_ENTITY_BUS_DT))
return -EINVAL;
ret = display_of_entity_link_entity(dev, entity, entities,
root);
if (ret < 0)
return ret;
- }
- return display_of_entity_link_entity(dev, root, entities, root);
+} +EXPORT_SYMBOL_GPL(display_of_entity_link_graph);
The root display entity given to display_of_entity_link_graph is documented to be optional. Therefore, do not try to dereference root if it is NULL:
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index 328ead7..6c8094f 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c @@ -669,7 +669,7 @@ static int display_of_entity_link_entity(struct device *dev, } }
- if (root->dev->of_node == link.remote_node) + if (root && root->dev->of_node == link.remote_node) remote = &root->entity;
if (remote == NULL) { @@ -748,6 +748,9 @@ int display_of_entity_link_graph(struct device *dev, struct list_head *entities, return ret; }
+ if (!root) + return 0; + return display_of_entity_link_entity(dev, root, entities, root); } EXPORT_SYMBOL_GPL(display_of_entity_link_graph);
Hi Philipp,
On Tuesday 27 August 2013 11:30:51 Philipp Zabel wrote:
Hi Laurent,
I have another small issue with the graph helpers below:
Am Samstag, den 10.08.2013, 01:03 +0200 schrieb Laurent Pinchart: [...]
+/*
---->
- Graph Helpers
*/
@@ -420,6 +599,161 @@ int display_entity_link_graph(struct device *dev, struct list_head *entities)> } EXPORT_SYMBOL_GPL(display_entity_link_graph);
+#ifdef CONFIG_OF
+static int display_of_entity_link_entity(struct device *dev,
struct display_entity *entity,
struct list_head *entities,
struct display_entity *root)
+{
- u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
- const struct device_node *node = entity->dev->of_node;
- struct media_entity *local = &entity->entity;
- struct device_node *ep = NULL;
- int ret = 0;
- dev_dbg(dev, "creating links for entity %s\n", local->name);
- while (1) {
struct media_entity *remote = NULL;
struct media_pad *remote_pad;
struct media_pad *local_pad;
struct display_of_link link;
struct display_entity *ent;
struct device_node *next;
/* Get the next endpoint and parse its link. */
next = display_of_get_next_endpoint(node, ep);
if (next == NULL)
break;
of_node_put(ep);
ep = next;
dev_dbg(dev, "processing endpoint %s\n", ep->full_name);
ret = display_of_parse_link(ep, &link);
if (ret < 0) {
dev_err(dev, "failed to parse link for %s\n",
ep->full_name);
continue;
}
/* Skip source pads, they will be processed from the other end of
* the link.
*/
if (link.local_port >= local->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.local_port, link.local_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
local_pad = &local->pads[link.local_port];
if (local_pad->flags & MEDIA_PAD_FL_SOURCE) {
dev_dbg(dev, "skipping source port %s:%u\n",
link.local_node->full_name, link.local_port);
display_of_put_link(&link);
continue;
}
/* Find the remote entity. If not found, just skip the link as
* it goes out of scope of the entities handled by the notifier.
*/
list_for_each_entry(ent, entities, list) {
if (ent->dev->of_node == link.remote_node) {
remote = &ent->entity;
break;
}
}
if (root->dev->of_node == link.remote_node)
remote = &root->entity;
if (remote == NULL) {
dev_dbg(dev, "no entity found for %s\n",
link.remote_node->full_name);
display_of_put_link(&link);
continue;
}
if (link.remote_port >= remote->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.remote_port, link.remote_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
remote_pad = &remote->pads[link.remote_port];
display_of_put_link(&link);
/* Create the media link. */
dev_dbg(dev, "creating %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
ret = media_entity_create_link(remote, remote_pad->index,
local, local_pad->index,
link_flags);
if (ret < 0) {
dev_err(dev,
"failed to create %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
break;
}
- }
- of_node_put(ep);
- return ret;
+}
+/**
- display_of_entity_link_graph - Link all entities in a graph
- @dev: device used to print debugging and error messages
- @root: optional root display entity
- @entities: list of display entities in the graph
- This function creates media controller links for all entities in a
graph + * based on the device tree graph representation. It relies on all entities + * having been instantiated from the device tree.
- The list of entities is typically taken directly from a display
notifier + * done list. It will thus not include any display entity not handled by the + * notifier, such as entities directly accessible by the caller without going + * through the notification process. The optional root entity parameter can be + * used to pass such a display entity and include it in the graph. For all + * practical purpose the root entity is handled is if it was part of the + * entities list.
- Return 0 on success or a negative error code otherwise.
- */
+int display_of_entity_link_graph(struct device *dev, struct list_head *entities, + struct display_entity *root) +{
- struct display_entity *entity;
- int ret;
- list_for_each_entry(entity, entities, list) {
if (WARN_ON(entity->match->type != DISPLAY_ENTITY_BUS_DT))
return -EINVAL;
ret = display_of_entity_link_entity(dev, entity, entities,
root);
if (ret < 0)
return ret;
- }
- return display_of_entity_link_entity(dev, root, entities, root);
+} +EXPORT_SYMBOL_GPL(display_of_entity_link_graph);
The root display entity given to display_of_entity_link_graph is documented to be optional. Therefore, do not try to dereference root if it is NULL:
Good catch ! I'll fix it in v4, thank you.
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index 328ead7..6c8094f 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c @@ -669,7 +669,7 @@ static int display_of_entity_link_entity(struct device *dev, } }
if (root->dev->of_node == link.remote_node)
if (root && root->dev->of_node == link.remote_node) remote = &root->entity;
if (remote == NULL) {
@@ -748,6 +748,9 @@ int display_of_entity_link_graph(struct device *dev, struct list_head *entities, return ret; }
- if (!root)
return 0;
- return display_of_entity_link_entity(dev, root, entities, root);
} EXPORT_SYMBOL_GPL(display_of_entity_link_graph);
Hi Laurent,
Am Samstag, den 10.08.2013, 01:03 +0200 schrieb Laurent Pinchart:
Extend the notifier with DT node matching support, and add helper functions to build the notifier and link entities based on a graph representation in DT.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com
drivers/video/display/display-core.c | 334 +++++++++++++++++++++++++++++++ drivers/video/display/display-notifier.c | 187 +++++++++++++++++ include/video/display.h | 45 +++++ 3 files changed, 566 insertions(+)
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index c3b47d3..328ead7 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c
[...]
@@ -420,6 +599,161 @@ int display_entity_link_graph(struct device *dev, struct list_head *entities) } EXPORT_SYMBOL_GPL(display_entity_link_graph);
+#ifdef CONFIG_OF
+static int display_of_entity_link_entity(struct device *dev,
struct display_entity *entity,
struct list_head *entities,
struct display_entity *root)
+{
- u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
- const struct device_node *node = entity->dev->of_node;
the current device tree matching implementation only allows one display entity per linux device. How about adding an of_node pointer to struct display_entity directly and allow multiple display entity nodes below in a single device node in the device tree?
lvds-encoder { channel@0 { port@0 { lvds0_input: endpoint { }; }; port@1 { lvds0_output: endpoint { }; }; }; channel@1 { port@0 { lvds1_input: endpoint { }; }; lvds1: port@1 { lvds1_output: endpoint { }; }; }; };
- struct media_entity *local = &entity->entity;
- struct device_node *ep = NULL;
- int ret = 0;
- dev_dbg(dev, "creating links for entity %s\n", local->name);
- while (1) {
struct media_entity *remote = NULL;
struct media_pad *remote_pad;
struct media_pad *local_pad;
struct display_of_link link;
struct display_entity *ent;
struct device_node *next;
/* Get the next endpoint and parse its link. */
next = display_of_get_next_endpoint(node, ep);
if (next == NULL)
break;
of_node_put(ep);
ep = next;
dev_dbg(dev, "processing endpoint %s\n", ep->full_name);
ret = display_of_parse_link(ep, &link);
if (ret < 0) {
dev_err(dev, "failed to parse link for %s\n",
ep->full_name);
continue;
}
/* Skip source pads, they will be processed from the other end of
* the link.
*/
if (link.local_port >= local->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.local_port, link.local_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
local_pad = &local->pads[link.local_port];
if (local_pad->flags & MEDIA_PAD_FL_SOURCE) {
dev_dbg(dev, "skipping source port %s:%u\n",
link.local_node->full_name, link.local_port);
display_of_put_link(&link);
continue;
}
/* Find the remote entity. If not found, just skip the link as
* it goes out of scope of the entities handled by the notifier.
*/
list_for_each_entry(ent, entities, list) {
if (ent->dev->of_node == link.remote_node) {
remote = &ent->entity;
break;
}
}
if (root->dev->of_node == link.remote_node)
remote = &root->entity;
if (remote == NULL) {
dev_dbg(dev, "no entity found for %s\n",
link.remote_node->full_name);
display_of_put_link(&link);
continue;
}
if (link.remote_port >= remote->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.remote_port, link.remote_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
remote_pad = &remote->pads[link.remote_port];
display_of_put_link(&link);
/* Create the media link. */
dev_dbg(dev, "creating %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
ret = media_entity_create_link(remote, remote_pad->index,
local, local_pad->index,
link_flags);
if (ret < 0) {
dev_err(dev,
"failed to create %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
break;
}
- }
- of_node_put(ep);
- return ret;
+}
[...]
For example like this:
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index 7910c23..a04feed 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c @@ -302,6 +302,9 @@ int display_entity_init(struct display_entity *entity, unsigned int num_sinks, kref_init(&entity->ref); entity->state = DISPLAY_ENTITY_STATE_OFF;
+ if (!entity->of_node && entity->dev) + entity->of_node = entity->dev->of_node; + num_pads = num_sinks + num_sources; pads = kzalloc(sizeof(*pads) * num_pads, GFP_KERNEL); if (pads == NULL) @@ -665,7 +668,7 @@ static int display_of_entity_link_entity(struct device *dev, struct display_entity *root) { u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED; - const struct device_node *node = entity->dev->of_node; + const struct device_node *node = entity->of_node; struct media_entity *local = &entity->entity; struct device_node *ep = NULL; int num_sink, ret = 0; @@ -727,13 +730,13 @@ static int display_of_entity_link_entity(struct device *dev, * it goes out of scope of the entities handled by the notifier. */ list_for_each_entry(ent, entities, list) { - if (ent->dev->of_node == link.remote_node) { + if (ent->of_node == link.remote_node) { remote = &ent->entity; break; } }
- if (root && root->dev->of_node == link.remote_node) + if (root && root->of_node == link.remote_node) remote = &root->entity;
if (remote == NULL) { diff --git a/drivers/video/display/display-notifier.c b/drivers/video/display/display-notifier.c index a3998c7..d0da6e5 100644 --- a/drivers/video/display/display-notifier.c +++ b/drivers/video/display/display-notifier.c @@ -28,28 +28,30 @@ static DEFINE_MUTEX(display_entity_mutex); * Notifiers */
-static bool match_platform(struct device *dev, +static bool match_platform(struct display_entity *entity, struct display_entity_match *match) { pr_debug("%s: matching device '%s' with name '%s'\n", __func__, - dev_name(dev), match->match.platform.name); + dev_name(entity->dev), match->match.platform.name);
- return !strcmp(match->match.platform.name, dev_name(dev)); + return !strcmp(match->match.platform.name, dev_name(entity->dev)); }
-static bool match_dt(struct device *dev, struct display_entity_match *match) +static bool match_dt(struct display_entity *entity, + struct display_entity_match *match) { pr_debug("%s: matching device node '%s' with node '%s'\n", __func__, - dev->of_node->full_name, match->match.dt.node->full_name); + entity->of_node->full_name, match->match.dt.node->full_name);
- return match->match.dt.node == dev->of_node; + return match->match.dt.node == entity->of_node; }
static struct display_entity_match * display_entity_notifier_match(struct display_entity_notifier *notifier, struct display_entity *entity) { - bool (*match_func)(struct device *, struct display_entity_match *); + bool (*match_func)(struct display_entity *, + struct display_entity_match *); struct display_entity_match *match;
pr_debug("%s: matching entity '%s' (ptr 0x%p dev '%s')\n", __func__, @@ -66,7 +68,7 @@ display_entity_notifier_match(struct display_entity_notifier *notifier, break; }
- if (match_func(entity->dev, match)) + if (match_func(entity, match)) return match; }
diff --git a/include/video/display.h b/include/video/display.h index 4c402bee..d1f8833 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -228,6 +228,7 @@ struct display_entity { struct list_head list; struct device *dev; struct module *owner; + struct device_node *of_node; struct kref ref;
char name[32];
Hi Philipp,
On Wednesday 04 September 2013 16:21:38 Philipp Zabel wrote:
Am Samstag, den 10.08.2013, 01:03 +0200 schrieb Laurent Pinchart:
Extend the notifier with DT node matching support, and add helper functions to build the notifier and link entities based on a graph representation in DT.
Signed-off-by: Laurent Pinchart
laurent.pinchart+renesas@ideasonboard.com
drivers/video/display/display-core.c | 334 ++++++++++++++++++++++++++ drivers/video/display/display-notifier.c | 187 +++++++++++++++++ include/video/display.h | 45 +++++ 3 files changed, 566 insertions(+)
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index c3b47d3..328ead7 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c
[...]
@@ -420,6 +599,161 @@ int display_entity_link_graph(struct device *dev, struct list_head *entities)> } EXPORT_SYMBOL_GPL(display_entity_link_graph);
+#ifdef CONFIG_OF
+static int display_of_entity_link_entity(struct device *dev,
struct display_entity *entity,
struct list_head *entities,
struct display_entity *root)
+{
- u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
- const struct device_node *node = entity->dev->of_node;
the current device tree matching implementation only allows one display entity per linux device. How about adding an of_node pointer to struct display_entity directly and allow multiple display entity nodes below in a single device node in the device tree?
That's a very good point. We had a similar issues in V4L2, with sensors that would create several entities. However, in those cases, the sensors would be connected to the rest of the pipeline through a single entity :
Sensor Entity 1 -> ... -> Sensor Entity N -> V4L2 pipeline ...
The core code thus had to care about a single sensor entity when building the pipeline. We could solve the problem in a similar way for panels, but encoders need a more elaborate solution.
I see (at least) two possibilities here, either explicitly describing all entities that make the device in DT (as you have proposed below), or creating a hierarchy of entities, with parent entities that can contain several child entities. I've CC'ed Guennadi, Hans, Sylwester and Sakari to get their opinion on the matter.
lvds-encoder { channel@0 {
If I understand this correctly, your LVDS encoder has two independent channels. In the general case a device made of multiple entities might have those entities chained, so "channel" might not be the best term. "entity" might be a better choice.
port@0 { lvds0_input: endpoint { }; }; port@1 { lvds0_output: endpoint { }; };
}; channel@1 { port@0 { lvds1_input: endpoint { }; }; lvds1: port@1 { lvds1_output: endpoint { }; }; }; };
- struct media_entity *local = &entity->entity;
- struct device_node *ep = NULL;
- int ret = 0;
- dev_dbg(dev, "creating links for entity %s\n", local->name);
- while (1) {
struct media_entity *remote = NULL;
struct media_pad *remote_pad;
struct media_pad *local_pad;
struct display_of_link link;
struct display_entity *ent;
struct device_node *next;
/* Get the next endpoint and parse its link. */
next = display_of_get_next_endpoint(node, ep);
if (next == NULL)
break;
of_node_put(ep);
ep = next;
dev_dbg(dev, "processing endpoint %s\n", ep->full_name);
ret = display_of_parse_link(ep, &link);
if (ret < 0) {
dev_err(dev, "failed to parse link for %s\n",
ep->full_name);
continue;
}
/* Skip source pads, they will be processed from the other end of
* the link.
*/
if (link.local_port >= local->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.local_port, link.local_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
local_pad = &local->pads[link.local_port];
if (local_pad->flags & MEDIA_PAD_FL_SOURCE) {
dev_dbg(dev, "skipping source port %s:%u\n",
link.local_node->full_name, link.local_port);
display_of_put_link(&link);
continue;
}
/* Find the remote entity. If not found, just skip the link as
* it goes out of scope of the entities handled by the notifier.
*/
list_for_each_entry(ent, entities, list) {
if (ent->dev->of_node == link.remote_node) {
remote = &ent->entity;
break;
}
}
if (root->dev->of_node == link.remote_node)
remote = &root->entity;
if (remote == NULL) {
dev_dbg(dev, "no entity found for %s\n",
link.remote_node->full_name);
display_of_put_link(&link);
continue;
}
if (link.remote_port >= remote->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.remote_port, link.remote_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
remote_pad = &remote->pads[link.remote_port];
display_of_put_link(&link);
/* Create the media link. */
dev_dbg(dev, "creating %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
ret = media_entity_create_link(remote, remote_pad->index,
local, local_pad->index,
link_flags);
if (ret < 0) {
dev_err(dev,
"failed to create %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
break;
}
- }
- of_node_put(ep);
- return ret;
+}
[...]
For example like this:
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index 7910c23..a04feed 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c @@ -302,6 +302,9 @@ int display_entity_init(struct display_entity *entity, unsigned int num_sinks, kref_init(&entity->ref); entity->state = DISPLAY_ENTITY_STATE_OFF;
- if (!entity->of_node && entity->dev)
entity->of_node = entity->dev->of_node;
- num_pads = num_sinks + num_sources; pads = kzalloc(sizeof(*pads) * num_pads, GFP_KERNEL); if (pads == NULL)
@@ -665,7 +668,7 @@ static int display_of_entity_link_entity(struct device *dev, struct display_entity *root) { u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
- const struct device_node *node = entity->dev->of_node;
- const struct device_node *node = entity->of_node; struct media_entity *local = &entity->entity; struct device_node *ep = NULL; int num_sink, ret = 0;
@@ -727,13 +730,13 @@ static int display_of_entity_link_entity(struct device *dev, * it goes out of scope of the entities handled by the notifier. */ list_for_each_entry(ent, entities, list) {
if (ent->dev->of_node == link.remote_node) {
}if (ent->of_node == link.remote_node) { remote = &ent->entity; break; }
if (root && root->dev->of_node == link.remote_node)
if (root && root->of_node == link.remote_node) remote = &root->entity;
if (remote == NULL) {
diff --git a/drivers/video/display/display-notifier.c b/drivers/video/display/display-notifier.c index a3998c7..d0da6e5 100644 --- a/drivers/video/display/display-notifier.c +++ b/drivers/video/display/display-notifier.c @@ -28,28 +28,30 @@ static DEFINE_MUTEX(display_entity_mutex);
- Notifiers
*/
-static bool match_platform(struct device *dev, +static bool match_platform(struct display_entity *entity, struct display_entity_match *match) { pr_debug("%s: matching device '%s' with name '%s'\n", __func__,
dev_name(dev), match->match.platform.name);
dev_name(entity->dev), match->match.platform.name);
- return !strcmp(match->match.platform.name, dev_name(dev));
- return !strcmp(match->match.platform.name, dev_name(entity->dev));
}
-static bool match_dt(struct device *dev, struct display_entity_match *match) +static bool match_dt(struct display_entity *entity,
struct display_entity_match *match)
{ pr_debug("%s: matching device node '%s' with node '%s'\n", __func__,
dev->of_node->full_name, match->match.dt.node->full_name);
entity->of_node->full_name, match->match.dt.node->full_name);
- return match->match.dt.node == dev->of_node;
- return match->match.dt.node == entity->of_node;
}
static struct display_entity_match * display_entity_notifier_match(struct display_entity_notifier *notifier, struct display_entity *entity) {
- bool (*match_func)(struct device *, struct display_entity_match *);
bool (*match_func)(struct display_entity *,
struct display_entity_match *);
struct display_entity_match *match;
pr_debug("%s: matching entity '%s' (ptr 0x%p dev '%s')\n", __func__,
@@ -66,7 +68,7 @@ display_entity_notifier_match(struct display_entity_notifier *notifier, break; }
if (match_func(entity->dev, match))
}if (match_func(entity, match)) return match;
diff --git a/include/video/display.h b/include/video/display.h index 4c402bee..d1f8833 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -228,6 +228,7 @@ struct display_entity { struct list_head list; struct device *dev; struct module *owner;
struct device_node *of_node; struct kref ref;
char name[32];
Am Mittwoch, den 11.09.2013, 13:33 +0200 schrieb Laurent Pinchart:
Hi Philipp,
On Wednesday 04 September 2013 16:21:38 Philipp Zabel wrote:
Am Samstag, den 10.08.2013, 01:03 +0200 schrieb Laurent Pinchart:
Extend the notifier with DT node matching support, and add helper functions to build the notifier and link entities based on a graph representation in DT.
Signed-off-by: Laurent Pinchart
laurent.pinchart+renesas@ideasonboard.com
drivers/video/display/display-core.c | 334 ++++++++++++++++++++++++++ drivers/video/display/display-notifier.c | 187 +++++++++++++++++ include/video/display.h | 45 +++++ 3 files changed, 566 insertions(+)
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index c3b47d3..328ead7 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c
[...]
@@ -420,6 +599,161 @@ int display_entity_link_graph(struct device *dev, struct list_head *entities)> } EXPORT_SYMBOL_GPL(display_entity_link_graph);
+#ifdef CONFIG_OF
+static int display_of_entity_link_entity(struct device *dev,
struct display_entity *entity,
struct list_head *entities,
struct display_entity *root)
+{
- u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
- const struct device_node *node = entity->dev->of_node;
the current device tree matching implementation only allows one display entity per linux device. How about adding an of_node pointer to struct display_entity directly and allow multiple display entity nodes below in a single device node in the device tree?
That's a very good point. We had a similar issues in V4L2, with sensors that would create several entities. However, in those cases, the sensors would be connected to the rest of the pipeline through a single entity :
Sensor Entity 1 -> ... -> Sensor Entity N -> V4L2 pipeline ...
The core code thus had to care about a single sensor entity when building the pipeline. We could solve the problem in a similar way for panels, but encoders need a more elaborate solution.
I see (at least) two possibilities here, either explicitly describing all entities that make the device in DT (as you have proposed below), or creating a hierarchy of entities, with parent entities that can contain several child entities. I've CC'ed Guennadi, Hans, Sylwester and Sakari to get their opinion on the matter.
When you say hierarchy of entities, I imagine something like GStreamer bins? I suspect hierarchically encapsulated entities would complicate the pipeline/graph traversal code quite a bit, although it would probably help to organise the graph and reduce the amount of boilerplate needed in the device tree.
lvds-encoder { channel@0 {
If I understand this correctly, your LVDS encoder has two independent channels.
In this example, yes. In reality the i.MX LDB has a mux in each path, so both inputs can be routed to both outputs. With an entity hierarchy this could be described as a single entity with two inputs and two outputs, containing two multiplexer entites and two encoder entities.
LDB entity with two input pads, four internal entities, and two output pads: ,----------------------------------------. |-----. LDB ,------. ,------. ,-----| --| pad |--------| mux0 |--| enc0 |--| pad |-- | 0 |--. ,--| | | | | 2 | |-----´ / `------´ `------´ `-----| |-----. /\ ,------. ,------. .-----| --| pad |--´ `--| mux1 | | enc1 |--| pad |-- | 1 |--------| |--| | | 3 | |-----´ `------´ `------´ `-----| `----------------------------------------´ (In guess the mux and enc entities could each be combined into one)
In the general case a device made of multiple entities might have those entities chained, so "channel" might not be the best term. "entity" might be a better choice.
On the other hand, when describing subdevice entities in the device tree, maybe the generic type of entity (sensor, scaler, encoder, mux, etc.) would be useful information?
Another module where I'd like to describe the (outward facing) contained entities in the device tree is the i.MX Image Processing Unit, which has two capture interfaces and two display interfaces (all parallel). Those can't be combined into a single entity because there are other internal entities connected to them, and because the capture interfaces are v4l2 subdevices, whereas the display interfaces will be display entites. Alternatively, this could also be described as a single entity containing an internal structure.
IPU entity with two input pads, two internal capture entities (csi), two display entities (di), and two output pads: ,----------------------------------------. |-----. ,------. IPU ,------. ,-----| --| pad |--| csi0 | | di0 |--| pad |-- | 0 | | |... | | | 2 | |-----´ `------´ `------´ `-----| |-----. ,------. ,------. .-----| --| pad |--| csi1 | | di1 |--| pad |-- | 1 | | |... | | | 3 | |-----´ `------´ `------´ `-----| `----------------------------------------´
regards Philipp
On 09/11/2013 01:33 PM, Laurent Pinchart wrote:
Hi Philipp,
On Wednesday 04 September 2013 16:21:38 Philipp Zabel wrote:
Am Samstag, den 10.08.2013, 01:03 +0200 schrieb Laurent Pinchart:
Extend the notifier with DT node matching support, and add helper functions to build the notifier and link entities based on a graph representation in DT.
Signed-off-by: Laurent Pinchart
laurent.pinchart+renesas@ideasonboard.com
drivers/video/display/display-core.c | 334 ++++++++++++++++++++++++++ drivers/video/display/display-notifier.c | 187 +++++++++++++++++ include/video/display.h | 45 +++++ 3 files changed, 566 insertions(+)
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index c3b47d3..328ead7 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c
[...]
@@ -420,6 +599,161 @@ int display_entity_link_graph(struct device *dev, struct list_head *entities)> } EXPORT_SYMBOL_GPL(display_entity_link_graph);
+#ifdef CONFIG_OF
+static int display_of_entity_link_entity(struct device *dev,
struct display_entity *entity,
struct list_head *entities,
struct display_entity *root)
+{
- u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
- const struct device_node *node = entity->dev->of_node;
the current device tree matching implementation only allows one display entity per linux device. How about adding an of_node pointer to struct display_entity directly and allow multiple display entity nodes below in a single device node in the device tree?
That's a very good point. We had a similar issues in V4L2, with sensors that would create several entities. However, in those cases, the sensors would be connected to the rest of the pipeline through a single entity :
Sensor Entity 1 -> ... -> Sensor Entity N -> V4L2 pipeline ...
The core code thus had to care about a single sensor entity when building the pipeline. We could solve the problem in a similar way for panels, but encoders need a more elaborate solution.
Why? Sorry, I don't see why an encoder is different in this respect than a panel. I'm sure I'm missing something here.
I see (at least) two possibilities here, either explicitly describing all entities that make the device in DT (as you have proposed below), or creating a hierarchy of entities, with parent entities that can contain several child entities. I've CC'ed Guennadi, Hans, Sylwester and Sakari to get their opinion on the matter.
I think the way this is done today in complex devices is that the driver just exposes itself as a single sub-device, but internally it has its own pipeline of sub-devices. The only one that I know of (platform/s5p-tv/hdmi_drv) doesn't expose them to a media controller, they are completely hidden inside the hdmi driver.
The ability to support hierarchies of entities would be very nice. However, I don't know how much work that would be to implement and if it is worth the effort.
Regards,
Hans
lvds-encoder { channel@0 {
If I understand this correctly, your LVDS encoder has two independent channels. In the general case a device made of multiple entities might have those entities chained, so "channel" might not be the best term. "entity" might be a better choice.
port@0 { lvds0_input: endpoint { }; }; port@1 { lvds0_output: endpoint { }; };
}; channel@1 { port@0 { lvds1_input: endpoint { }; }; lvds1: port@1 { lvds1_output: endpoint { }; }; }; };
- struct media_entity *local = &entity->entity;
- struct device_node *ep = NULL;
- int ret = 0;
- dev_dbg(dev, "creating links for entity %s\n", local->name);
- while (1) {
struct media_entity *remote = NULL;
struct media_pad *remote_pad;
struct media_pad *local_pad;
struct display_of_link link;
struct display_entity *ent;
struct device_node *next;
/* Get the next endpoint and parse its link. */
next = display_of_get_next_endpoint(node, ep);
if (next == NULL)
break;
of_node_put(ep);
ep = next;
dev_dbg(dev, "processing endpoint %s\n", ep->full_name);
ret = display_of_parse_link(ep, &link);
if (ret < 0) {
dev_err(dev, "failed to parse link for %s\n",
ep->full_name);
continue;
}
/* Skip source pads, they will be processed from the other end of
* the link.
*/
if (link.local_port >= local->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.local_port, link.local_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
local_pad = &local->pads[link.local_port];
if (local_pad->flags & MEDIA_PAD_FL_SOURCE) {
dev_dbg(dev, "skipping source port %s:%u\n",
link.local_node->full_name, link.local_port);
display_of_put_link(&link);
continue;
}
/* Find the remote entity. If not found, just skip the link as
* it goes out of scope of the entities handled by the notifier.
*/
list_for_each_entry(ent, entities, list) {
if (ent->dev->of_node == link.remote_node) {
remote = &ent->entity;
break;
}
}
if (root->dev->of_node == link.remote_node)
remote = &root->entity;
if (remote == NULL) {
dev_dbg(dev, "no entity found for %s\n",
link.remote_node->full_name);
display_of_put_link(&link);
continue;
}
if (link.remote_port >= remote->num_pads) {
dev_err(dev, "invalid port number %u on %s\n",
link.remote_port, link.remote_node->full_name);
display_of_put_link(&link);
ret = -EINVAL;
break;
}
remote_pad = &remote->pads[link.remote_port];
display_of_put_link(&link);
/* Create the media link. */
dev_dbg(dev, "creating %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
ret = media_entity_create_link(remote, remote_pad->index,
local, local_pad->index,
link_flags);
if (ret < 0) {
dev_err(dev,
"failed to create %s:%u -> %s:%u link\n",
remote->name, remote_pad->index,
local->name, local_pad->index);
break;
}
- }
- of_node_put(ep);
- return ret;
+}
[...]
For example like this:
diff --git a/drivers/video/display/display-core.c b/drivers/video/display/display-core.c index 7910c23..a04feed 100644 --- a/drivers/video/display/display-core.c +++ b/drivers/video/display/display-core.c @@ -302,6 +302,9 @@ int display_entity_init(struct display_entity *entity, unsigned int num_sinks, kref_init(&entity->ref); entity->state = DISPLAY_ENTITY_STATE_OFF;
- if (!entity->of_node && entity->dev)
entity->of_node = entity->dev->of_node;
- num_pads = num_sinks + num_sources; pads = kzalloc(sizeof(*pads) * num_pads, GFP_KERNEL); if (pads == NULL)
@@ -665,7 +668,7 @@ static int display_of_entity_link_entity(struct device *dev, struct display_entity *root) { u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
- const struct device_node *node = entity->dev->of_node;
- const struct device_node *node = entity->of_node; struct media_entity *local = &entity->entity; struct device_node *ep = NULL; int num_sink, ret = 0;
@@ -727,13 +730,13 @@ static int display_of_entity_link_entity(struct device *dev, * it goes out of scope of the entities handled by the notifier. */ list_for_each_entry(ent, entities, list) {
if (ent->dev->of_node == link.remote_node) {
}if (ent->of_node == link.remote_node) { remote = &ent->entity; break; }
if (root && root->dev->of_node == link.remote_node)
if (root && root->of_node == link.remote_node) remote = &root->entity;
if (remote == NULL) {
diff --git a/drivers/video/display/display-notifier.c b/drivers/video/display/display-notifier.c index a3998c7..d0da6e5 100644 --- a/drivers/video/display/display-notifier.c +++ b/drivers/video/display/display-notifier.c @@ -28,28 +28,30 @@ static DEFINE_MUTEX(display_entity_mutex);
- Notifiers
*/
-static bool match_platform(struct device *dev, +static bool match_platform(struct display_entity *entity, struct display_entity_match *match) { pr_debug("%s: matching device '%s' with name '%s'\n", __func__,
dev_name(dev), match->match.platform.name);
dev_name(entity->dev), match->match.platform.name);
- return !strcmp(match->match.platform.name, dev_name(dev));
- return !strcmp(match->match.platform.name, dev_name(entity->dev));
}
-static bool match_dt(struct device *dev, struct display_entity_match *match) +static bool match_dt(struct display_entity *entity,
struct display_entity_match *match)
{ pr_debug("%s: matching device node '%s' with node '%s'\n", __func__,
dev->of_node->full_name, match->match.dt.node->full_name);
entity->of_node->full_name, match->match.dt.node->full_name);
- return match->match.dt.node == dev->of_node;
- return match->match.dt.node == entity->of_node;
}
static struct display_entity_match * display_entity_notifier_match(struct display_entity_notifier *notifier, struct display_entity *entity) {
- bool (*match_func)(struct device *, struct display_entity_match *);
bool (*match_func)(struct display_entity *,
struct display_entity_match *);
struct display_entity_match *match;
pr_debug("%s: matching entity '%s' (ptr 0x%p dev '%s')\n", __func__,
@@ -66,7 +68,7 @@ display_entity_notifier_match(struct display_entity_notifier *notifier, break; }
if (match_func(entity->dev, match))
}if (match_func(entity, match)) return match;
diff --git a/include/video/display.h b/include/video/display.h index 4c402bee..d1f8833 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -228,6 +228,7 @@ struct display_entity { struct list_head list; struct device *dev; struct module *owner;
struct device_node *of_node; struct kref ref;
char name[32];
Pixel codings describe how pixels are transmitted on a physical bus. The information can be communicated between drivers to configure devices.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- include/video/display.h | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+)
diff --git a/include/video/display.h b/include/video/display.h index 36ff637..ba319d6 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -18,6 +18,126 @@ #include <linux/module.h> #include <media/media-entity.h>
+#define DISPLAY_PIXEL_CODING(option, type, from, to, variant) \ + (((option) << 17) | ((type) << 13) | ((variant) << 10) | \ + ((to) << 5) | (from)) + +#define DISPLAY_PIXEL_CODING_FROM(coding) ((coding) & 0x1f) +#define DISPLAY_PIXEL_CODING_TO(coding) (((coding) >> 5) & 0x1f) +#define DISPLAY_PIXEL_CODING_VARIANT(coding) (((coding) >> 10) & 7) +#define DISPLAY_PIXEL_CODING_TYPE(coding) (((coding) >> 13) & 0xf) + +#define DISPLAY_PIXEL_CODING_TYPE_DBI 0 +#define DISPLAY_PIXEL_CODING_TYPE_DPI 1 + +/* DBI pixel codings. */ +#define DISPLAY_PIXEL_CODING_DBI(from, to, variant) \ + DISPLAY_PIXEL_CODING_TYPE(DISPLAY_PIXEL_CODING_TYPE_DBI, \ + from, to, variant, 0) + +/* Standard DBI codings, defined in the DBI specification. */ +/* 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 */ +/* - - - - - - - - - - R0,2 R0,1 R0,0 G0,2 G0,1 G0,0 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_8TO8 DISPLAY_PIXEL_CODING_DBI(8, 8, 0) +/* - - - - - - - - - - R0,3 R0,2 R0,1 R0,0 G0,3 G0,2 G0,1 G0,0 */ +/* - - - - - - - - - - B0,3 B0,2 B0,1 B0,0 R1,3 R1,2 R1,1 R1,0 */ +/* - - - - - - - - - - G1,3 G1,2 G1,1 G1,0 B1,3 B1,2 B1,1 b1,0 */ +#define DISPLAY_PIXEL_CODING_DBI_12TO8 DISPLAY_PIXEL_CODING_DBI(12, 8, 0) +/* - - - - - - - - - - R0,4 R0,3 R0,2 R0,1 R0,0 G0,5 G0,4 G0,3 */ +/* - - - - - - - - - - G0,2 G0,1 G0,0 B0,4 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_16TO8 DISPLAY_PIXEL_CODING_DBI(16, 8, 0) +/* - - - - - - - - - - R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 - - */ +/* - - - - - - - - - - G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 - - */ +/* - - - - - - - - - - B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 - - */ +#define DISPLAY_PIXEL_CODING_DBI_18TO8 DISPLAY_PIXEL_CODING_DBI(18, 8, 0) +/* - - - - - - - - - - R0,7 R0,6 R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 */ +/* - - - - - - - - - - G0,7 G0,6 G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 */ +/* - - - - - - - - - - B0,7 B0,6 B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_24TO8 DISPLAY_PIXEL_CODING_DBI(24, 8, 0) +/* - - - - - - - - - R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 G0,5 G0,4 G0,4 */ +/* - - - - - - - - - G0,2 G0,1 G0,0 B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_18TO9 DISPLAY_PIXEL_CODING_DBI(18, 9, 0) +/* - - R1,2 R1,1 R1,0 G1,2 G1,1 G1,0 B1,1 B1,0 R0,2 R0,1 R0,0 G0,2 G0,1 G0,0 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_8TO16 DISPLAY_PIXEL_CODING_DBI(8, 16, 0) +/* - - - - - - R0,3 R0,2 R0,1 R0,0 G0,3 G0,2 G0,1 G0,0 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_12TO16 DISPLAY_PIXEL_CODING_DBI(12, 16, 0) +/* - - R0,4 R0,3 R0,2 R0,1 R0,0 G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 B0,4 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_16TO16 DISPLAY_PIXEL_CODING_DBI(16, 16, 0) +/* - - R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 - - G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 - - */ +/* - - B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 - - R1,5 R1,4 R1,3 R1,2 R1,1 R1,0 - - */ +/* - - G1,5 G1,4 G1,3 G1,2 G1,1 G1,0 - - B1,5 B1,4 B1,3 B1,2 B1,1 B1,0 - - */ +#define DISPLAY_PIXEL_CODING_DBI_18TO16_A DISPLAY_PIXEL_CODING_DBI(18, 16, 0) +/* - - - - - - - - - - R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 - - */ +/* - - G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 - - B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 - - */ +#define DISPLAY_PIXEL_CODING_DBI_18TO16_B DISPLAY_PIXEL_CODING_DBI(18, 16, 1) +/* - - R0,7 R0,6 R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 G0,7 G0,6 G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 */ +/* - - B0,7 B0,6 B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 R1,7 R1,6 R1,5 R1,4 R1,3 R1,2 R1,1 R1,0 */ +/* - - G1,7 G1,6 G1,5 G1,4 G1,3 G1,2 G1,1 G1,0 B1,7 B1,6 B1,5 B1,4 B1,3 B1,2 B1,1 B1,0 */ +#define DISPLAY_PIXEL_CODING_DBI_24TO16_A DISPLAY_PIXEL_CODING_DBI(24, 16, 0) +/* - - - - - - - - - - R0,7 R0,6 R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 */ +/* - - G0,7 G0,6 G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 B0,7 B0,6 B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_24TO16_B DISPLAY_PIXEL_CODING_DBI(24, 16, 1) + +/* Non-standard DBI pixel codings. */ +/* 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 */ +/* - - - - - - - - - - R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 G0,5 G0,4 */ +/* - - - - - - - - - - G0,3 G0,2 G0,1 G0,0 B0,5 B0,4 B0,3 B0,2 */ +/* - - - - - - - - - - - - - - - - B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_18TO8_B DISPLAY_PIXEL_CODING_DBI(18, 8, 1) +/* - - - - - - - - - - - - - - - - R0,5 R0,4 */ +/* - - - - - - - - - - R0,3 R0,2 R0,1 R0,0 G0,5 G0,4 G0,3 G0,2 */ +/* - - - - - - - - - - G0,1 G0,0 B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_18TO8_C DISPLAY_PIXEL_CODING_DBI(18, 8, 2) +/* - - R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 B0,5 B0,4 B0,3 B0,2 */ +/* - - B0,1 B0,0 - - - - - - - - - - - - - - */ +#define DISPLAY_PIXEL_CODING_DBI_18TO16_C DISPLAY_PIXEL_CODING_DBI(18, 16, 2) +/* - - R0,5 R0,4 - - - - - - - - - - - - - - */ +/* - - R0,3 R0,2 R0,1 R0,0 G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_18TO16_D DISPLAY_PIXEL_CODING_DBI(18, 16, 3) +/* - - - - - - R0,7 R0,6 R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 G0,7 G0,6 G0,5 G0,4 */ +/* - - - - - - G0,3 G0,2 G0,1 G0,0 B0,7 B0,6 B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_24TO12 DISPLAY_PIXEL_CODING_DBI(24, 12, 0) +/* R0,5 R0,4 R0,3 R0,2 R0,1 R0,0 G0,5 G0,4 G0,3 G0,2 G0,1 G0,0 B0,5 B0,4 B0,3 B0,2 B0,1 B0,0 */ +#define DISPLAY_PIXEL_CODING_DBI_18TO18 DISPLAY_PIXEL_CODING_DBI(18, 18, 0) + +/* DPI pixel codings. */ +#define DISPLAY_PIXEL_CODING_DPI_RGB(from, to, variant) \ + DISPLAY_PIXEL_CODING_TYPE(DISPLAY_PIXEL_CODING_TYPE_DPI, \ + from, to, variant, 0) +#define DISPLAY_PIXEL_CODING_DPI_YUV(from, to, variant) \ + DISPLAY_PIXEL_CODING_TYPE(DISPLAY_PIXEL_CODING_TYPE_DPI, \ + from, to, variant, 1) + +/* Standard DPI codings, defined in the DPI specification. */ +/* 23 22 21 20 19 28 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 */ +/* - - - - - - - - R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0 */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_16TO16 DISPLAY_PIXEL_CODING_DPI_RGB(16, 16, 0) +/* - - - - - - R5 R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B5 B4 B3 B2 B1 B0 */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_18TO18 DISPLAY_PIXEL_CODING_DPI_RGB(18, 18, 0) +/* - - - R4 R3 R2 R1 R0 - - G5 G4 G3 G2 G1 G0 - - - B4 B3 B2 B1 B0 */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_16TO20 DISPLAY_PIXEL_CODING_DPI_RGB(16, 20, 0) +/* - - R4 R3 R2 R1 R0 - - - G5 G4 G3 G2 G1 G0 - - B4 B3 B2 B1 B0 - */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_16TO22 DISPLAY_PIXEL_CODING_DPI_RGB(16, 22, 0) +/* - - R5 R4 R3 R2 R1 R0 - - G5 G4 G3 G2 G1 G0 - - B5 B4 B3 B2 B1 B0 */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_18TO22 DISPLAY_PIXEL_CODING_DPI_RGB(18, 22, 0) +/* R7 R6 R5 R4 R3 R2 R1 R0 G7 G6 G5 G4 G3 G2 G1 G0 B7 B6 B5 B4 B3 B2 B1 B0 */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_24TO24 DISPLAY_PIXEL_CODING_DPI_RGB(24, 24, 0) + +/* Non-standard DPI pixel codings. */ +/* 23 22 21 20 19 28 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 */ +/* - - - - - - - - - - - - - - - - R7 R6 R5 R4 R3 R2 R1 R0 */ +/* - - - - - - - - - - - - - - - - G7 G6 G5 G4 G3 G2 G1 G0 */ +/* - - - - - - - - - - - - - - - - B7 B6 B5 B4 B3 B2 B1 B0 */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_24TO8 DISPLAY_PIXEL_CODING_DPI_RGB(24, 8, 0) +/* - - - - - - - - - - - - - - - R5 R4 R3 R2 R1 R0 G5 G4 G3 */ +/* - - - - - - - - - - - - - - - G2 G1 G0 B5 B4 B3 B2 B1 B0 */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_18TO9 DISPLAY_PIXEL_CODING_DPI_RGB(18, 9, 0) +/* - - - - - - - - - - - - R3 R2 R1 R0 G3 G2 G1 G0 B3 B2 B1 B0 */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_12TO12 DISPLAY_PIXEL_CODING_DPI_RGB(12, 12, 0) +/* - - - - - - - - - - - - R7 R6 R5 R4 R3 R2 R1 R0 G7 G6 G5 G4 */ +/* - - - - - - - - - - - - G3 G2 G1 G0 B7 B6 B5 B4 B3 B2 B1 B0 */ +#define DISPLAY_PIXEL_CODING_DPI_RGB_24TO12 DISPLAY_PIXEL_CODING_DPI_RGB(24, 12, 0) + /* ----------------------------------------------------------------------------- * Display Entity */
MIPI DBI is a configurable-width parallel display bus that transmits commands and data.
Add a new DBI Linux bus type that implements the usual bus infrastructure (including devices and drivers (un)registration and matching, and bus configuration and access functions).
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/display/Kconfig | 8 ++ drivers/video/display/Makefile | 1 + drivers/video/display/mipi-dbi-bus.c | 234 +++++++++++++++++++++++++++++++++++ include/video/display.h | 4 + include/video/mipi-dbi-bus.h | 125 +++++++++++++++++++ 5 files changed, 372 insertions(+) create mode 100644 drivers/video/display/mipi-dbi-bus.c create mode 100644 include/video/mipi-dbi-bus.h
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 1d533e7..f7532c1 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -2,3 +2,11 @@ menuconfig DISPLAY_CORE tristate "Display Core" ---help--- Support common display framework for graphics devices. + +if DISPLAY_CORE + +config DISPLAY_MIPI_DBI + tristate + default n + +endif # DISPLAY_CORE diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index b907aad..59022d2 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -1,3 +1,4 @@ display-y := display-core.o \ display-notifier.o obj-$(CONFIG_DISPLAY_CORE) += display.o +obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o diff --git a/drivers/video/display/mipi-dbi-bus.c b/drivers/video/display/mipi-dbi-bus.c new file mode 100644 index 0000000..791fb4d --- /dev/null +++ b/drivers/video/display/mipi-dbi-bus.c @@ -0,0 +1,234 @@ +/* + * MIPI DBI Bus + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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 <linux/device.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +#include <video/mipi-dbi-bus.h> + +/* ----------------------------------------------------------------------------- + * Bus operations + */ + +int mipi_dbi_set_data_width(struct mipi_dbi_device *dev, unsigned int width) +{ + if (width != 8 && width != 16) + return -EINVAL; + + dev->data_width = width; + return 0; +} +EXPORT_SYMBOL_GPL(mipi_dbi_set_data_width); + +int mipi_dbi_write_command(struct mipi_dbi_device *dev, u16 cmd) +{ + return dev->bus->ops->write_command(dev->bus, dev, cmd); +} +EXPORT_SYMBOL_GPL(mipi_dbi_write_command); + +int mipi_dbi_write_data(struct mipi_dbi_device *dev, const u8 *data, + size_t len) +{ + return dev->bus->ops->write_data(dev->bus, dev, data, len); +} +EXPORT_SYMBOL_GPL(mipi_dbi_write_data); + +int mipi_dbi_read_data(struct mipi_dbi_device *dev, u8 *data, size_t len) +{ + return dev->bus->ops->read_data(dev->bus, dev, data, len); +} +EXPORT_SYMBOL_GPL(mipi_dbi_read_data); + +/* ----------------------------------------------------------------------------- + * Bus type + */ + +static const struct mipi_dbi_device_id * +mipi_dbi_match_id(const struct mipi_dbi_device_id *id, + struct mipi_dbi_device *dev) +{ + while (id->name[0]) { + if (strcmp(dev->name, id->name) == 0) { + dev->id_entry = id; + return id; + } + id++; + } + return NULL; +} + +static int mipi_dbi_match(struct device *_dev, struct device_driver *_drv) +{ + struct mipi_dbi_device *dev = to_mipi_dbi_device(_dev); + struct mipi_dbi_driver *drv = to_mipi_dbi_driver(_drv); + + if (drv->id_table) + return mipi_dbi_match_id(drv->id_table, dev) != NULL; + + return (strcmp(dev->name, _drv->name) == 0); +} + +static ssize_t modalias_show(struct device *_dev, struct device_attribute *a, + char *buf) +{ + struct mipi_dbi_device *dev = to_mipi_dbi_device(_dev); + int len = snprintf(buf, PAGE_SIZE, MIPI_DBI_MODULE_PREFIX "%s\n", + dev->name); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} + +static struct device_attribute mipi_dbi_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static int mipi_dbi_uevent(struct device *_dev, struct kobj_uevent_env *env) +{ + struct mipi_dbi_device *dev = to_mipi_dbi_device(_dev); + + add_uevent_var(env, "MODALIAS=%s%s", MIPI_DBI_MODULE_PREFIX, + dev->name); + return 0; +} + +static const struct dev_pm_ops mipi_dbi_dev_pm_ops = { + .runtime_suspend = pm_generic_runtime_suspend, + .runtime_resume = pm_generic_runtime_resume, + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + .freeze = pm_generic_freeze, + .thaw = pm_generic_thaw, + .poweroff = pm_generic_poweroff, + .restore = pm_generic_restore, +}; + +static struct bus_type mipi_dbi_bus_type = { + .name = "mipi-dbi", + .dev_attrs = mipi_dbi_dev_attrs, + .match = mipi_dbi_match, + .uevent = mipi_dbi_uevent, + .pm = &mipi_dbi_dev_pm_ops, +}; + +/* ----------------------------------------------------------------------------- + * Device and driver (un)registration + */ + +/** + * mipi_dbi_device_register - register a DBI device + * @dev: DBI device we're registering + */ +int mipi_dbi_device_register(struct mipi_dbi_device *dev, + struct mipi_dbi_bus *bus) +{ + device_initialize(&dev->dev); + + dev->bus = bus; + dev->dev.bus = &mipi_dbi_bus_type; + dev->dev.parent = bus->dev; + + if (dev->id != -1) + dev_set_name(&dev->dev, "%s.%d", dev->name, dev->id); + else + dev_set_name(&dev->dev, "%s", dev->name); + + return device_add(&dev->dev); +} +EXPORT_SYMBOL_GPL(mipi_dbi_device_register); + +/** + * mipi_dbi_device_unregister - unregister a DBI device + * @dev: DBI device we're unregistering + */ +void mipi_dbi_device_unregister(struct mipi_dbi_device *dev) +{ + device_del(&dev->dev); + put_device(&dev->dev); +} +EXPORT_SYMBOL_GPL(mipi_dbi_device_unregister); + +static int mipi_dbi_drv_probe(struct device *_dev) +{ + struct mipi_dbi_driver *drv = to_mipi_dbi_driver(_dev->driver); + struct mipi_dbi_device *dev = to_mipi_dbi_device(_dev); + + return drv->probe(dev); +} + +static int mipi_dbi_drv_remove(struct device *_dev) +{ + struct mipi_dbi_driver *drv = to_mipi_dbi_driver(_dev->driver); + struct mipi_dbi_device *dev = to_mipi_dbi_device(_dev); + int ret; + + ret = drv->remove(dev); + if (ret < 0) + return ret; + + mipi_dbi_set_drvdata(dev, NULL); + + return 0; +} + +/** + * mipi_dbi_driver_register - register a driver for DBI devices + * @drv: DBI driver structure + */ +int mipi_dbi_driver_register(struct mipi_dbi_driver *drv) +{ + drv->driver.bus = &mipi_dbi_bus_type; + if (drv->probe) + drv->driver.probe = mipi_dbi_drv_probe; + if (drv->remove) + drv->driver.remove = mipi_dbi_drv_remove; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(mipi_dbi_driver_register); + +/** + * mipi_dbi_driver_unregister - unregister a driver for DBI devices + * @drv: DBI driver structure + */ +void mipi_dbi_driver_unregister(struct mipi_dbi_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(mipi_dbi_driver_unregister); + +/* ----------------------------------------------------------------------------- + * Init/exit + */ + +static int __init mipi_dbi_init(void) +{ + return bus_register(&mipi_dbi_bus_type); +} + +static void __exit mipi_dbi_exit(void) +{ + bus_unregister(&mipi_dbi_bus_type); +} + +module_init(mipi_dbi_init); +module_exit(mipi_dbi_exit) + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("MIPI DBI Bus"); +MODULE_LICENSE("GPL"); diff --git a/include/video/display.h b/include/video/display.h index ba319d6..3138401 100644 --- a/include/video/display.h +++ b/include/video/display.h @@ -17,6 +17,7 @@ #include <linux/list.h> #include <linux/module.h> #include <media/media-entity.h> +#include <video/mipi-dbi-bus.h>
#define DISPLAY_PIXEL_CODING(option, type, from, to, variant) \ (((option) << 17) | ((type) << 13) | ((variant) << 10) | \ @@ -189,6 +190,9 @@ enum display_entity_interface_type {
struct display_entity_interface_params { enum display_entity_interface_type type; + union { + struct mipi_dbi_interface_params dbi; + } p; };
struct display_entity_control_ops { diff --git a/include/video/mipi-dbi-bus.h b/include/video/mipi-dbi-bus.h new file mode 100644 index 0000000..876b69d --- /dev/null +++ b/include/video/mipi-dbi-bus.h @@ -0,0 +1,125 @@ +/* + * MIPI DBI Bus + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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. + */ + +#ifndef __MIPI_DBI_BUS_H__ +#define __MIPI_DBI_BUS_H__ + +#include <linux/device.h> + +struct mipi_dbi_bus; +struct mipi_dbi_device; + +struct mipi_dbi_bus_ops { + int (*write_command)(struct mipi_dbi_bus *bus, + struct mipi_dbi_device *dev, u16 cmd); + int (*write_data)(struct mipi_dbi_bus *bus, struct mipi_dbi_device *dev, + const u8 *data, size_t len); + int (*read_data)(struct mipi_dbi_bus *bus, struct mipi_dbi_device *dev, + u8 *data, size_t len); +}; + +struct mipi_dbi_bus { + struct device *dev; + const struct mipi_dbi_bus_ops *ops; +}; + +#define MIPI_DBI_MODULE_PREFIX "mipi-dbi:" +#define MIPI_DBI_NAME_SIZE 32 + +struct mipi_dbi_device_id { + char name[MIPI_DBI_NAME_SIZE]; + __kernel_ulong_t driver_data /* Data private to the driver */ + __aligned(sizeof(__kernel_ulong_t)); +}; + +enum mipi_dbi_interface_type { + MIPI_DBI_INTERFACE_TYPE_A, + MIPI_DBI_INTERFACE_TYPE_B, +}; + +#define MIPI_DBI_INTERFACE_TE (1 << 0) + +struct mipi_dbi_interface_params { + enum mipi_dbi_interface_type type; + unsigned int flags; + + unsigned int cs_setup; + unsigned int rd_setup; + unsigned int rd_latch; + unsigned int rd_cycle; + unsigned int rd_hold; + unsigned int wr_setup; + unsigned int wr_cycle; + unsigned int wr_hold; +}; + +#define MIPI_DBI_FLAG_ALIGN_LEFT (1 << 0) + +struct mipi_dbi_device { + const char *name; + int id; + struct device dev; + + const struct mipi_dbi_device_id *id_entry; + struct mipi_dbi_bus *bus; + + unsigned int flags; + unsigned int bus_width; + unsigned int data_width; +}; + +#define to_mipi_dbi_device(d) container_of(d, struct mipi_dbi_device, dev) + +int mipi_dbi_device_register(struct mipi_dbi_device *dev, + struct mipi_dbi_bus *bus); +void mipi_dbi_device_unregister(struct mipi_dbi_device *dev); + +struct mipi_dbi_driver { + int(*probe)(struct mipi_dbi_device *); + int(*remove)(struct mipi_dbi_device *); + struct device_driver driver; + const struct mipi_dbi_device_id *id_table; +}; + +#define to_mipi_dbi_driver(d) container_of(d, struct mipi_dbi_driver, driver) + +int mipi_dbi_driver_register(struct mipi_dbi_driver *drv); +void mipi_dbi_driver_unregister(struct mipi_dbi_driver *drv); + +static inline void *mipi_dbi_get_drvdata(const struct mipi_dbi_device *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void mipi_dbi_set_drvdata(struct mipi_dbi_device *dev, + void *data) +{ + dev_set_drvdata(&dev->dev, data); +} + +/* module_mipi_dbi_driver() - Helper macro for drivers that don't do + * anything special in module init/exit. This eliminates a lot of + * boilerplate. Each module may only use this macro once, and + * calling it replaces module_init() and module_exit() + */ +#define module_mipi_dbi_driver(__mipi_dbi_driver) \ + module_driver(__mipi_dbi_driver, mipi_dbi_driver_register, \ + mipi_dbi_driver_unregister) + +int mipi_dbi_set_data_width(struct mipi_dbi_device *dev, unsigned int width); + +int mipi_dbi_write_command(struct mipi_dbi_device *dev, u16 cmd); +int mipi_dbi_read_data(struct mipi_dbi_device *dev, u8 *data, size_t len); +int mipi_dbi_write_data(struct mipi_dbi_device *dev, const u8 *data, + size_t len); + +#endif /* __MIPI_DBI_BUS__ */
The Display Pixel Interface is a configurable-width video-only unidirectional parallel bus standard that defines video formats and signaling for panel devices.
This driver implements support for simple DPI panels with no runtime configuration capabilities (GPIOs- and/or regulators-based control can be implemented later when needed) and exposes it as a display entity. The panel native video mode is passed to the driver through platform data or device tree properties.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/display/Kconfig | 10 ++ drivers/video/display/Makefile | 1 + drivers/video/display/panel-dpi.c | 207 ++++++++++++++++++++++++++++++++++++++ include/video/panel-dpi.h | 24 +++++ 4 files changed, 242 insertions(+) create mode 100644 drivers/video/display/panel-dpi.c create mode 100644 include/video/panel-dpi.h
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index f7532c1..bce09d6 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -9,4 +9,14 @@ config DISPLAY_MIPI_DBI tristate default n
+config DISPLAY_PANEL_DPI + tristate "DPI (Parallel) Display Panels" + ---help--- + Support for simple digital (parallel) pixel interface panels. Those + panels receive pixel data through a parallel bus and have no control + bus. + + If you are in doubt, say N. To compile this driver as a module, choose + M here; the module will be called panel-dpi. + endif # DISPLAY_CORE diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index 59022d2..31c017b 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -2,3 +2,4 @@ display-y := display-core.o \ display-notifier.o obj-$(CONFIG_DISPLAY_CORE) += display.o obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o +obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o diff --git a/drivers/video/display/panel-dpi.c b/drivers/video/display/panel-dpi.c new file mode 100644 index 0000000..b1ecf6d --- /dev/null +++ b/drivers/video/display/panel-dpi.c @@ -0,0 +1,207 @@ +/* + * DPI Display Panel + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <video/display.h> +#include <video/of_display_timing.h> +#include <video/of_videomode.h> +#include <video/panel-dpi.h> +#include <video/videomode.h> + +struct panel_dpi { + struct display_entity entity; + + unsigned int width; + unsigned int height; + struct videomode mode; +}; + +static inline struct panel_dpi *to_panel_dpi(struct display_entity *e) +{ + return container_of(e, struct panel_dpi, entity); +} + +static const struct display_entity_interface_params panel_dpi_params = { + .type = DISPLAY_ENTITY_INTERFACE_DPI, +}; + +static int panel_dpi_set_state(struct display_entity *entity, + enum display_entity_state state) +{ + struct media_pad *source; + + source = media_entity_remote_pad(&entity->entity.pads[0]); + if (source == NULL) + return -EPIPE; + + switch (state) { + case DISPLAY_ENTITY_STATE_OFF: + case DISPLAY_ENTITY_STATE_STANDBY: + display_entity_set_stream(to_display_entity(source->entity), + source->index, + DISPLAY_ENTITY_STREAM_STOPPED); + break; + + case DISPLAY_ENTITY_STATE_ON: + display_entity_set_stream(to_display_entity(source->entity), + source->index, + DISPLAY_ENTITY_STREAM_CONTINUOUS); + break; + } + + return 0; +} + +static int panel_dpi_get_modes(struct display_entity *entity, unsigned int port, + const struct videomode **modes) +{ + struct panel_dpi *panel = to_panel_dpi(entity); + + *modes = &panel->mode; + return 1; +} + +static int panel_dpi_get_size(struct display_entity *entity, + unsigned int *width, unsigned int *height) +{ + struct panel_dpi *panel = to_panel_dpi(entity); + + *width = panel->width; + *height = panel->height; + return 0; +} + +static int panel_dpi_get_params(struct display_entity *entity, + unsigned int port, + struct display_entity_interface_params *params) +{ + *params = panel_dpi_params; + return 0; +} + +static const struct display_entity_control_ops panel_dpi_control_ops = { + .set_state = panel_dpi_set_state, + .get_modes = panel_dpi_get_modes, + .get_size = panel_dpi_get_size, + .get_params = panel_dpi_get_params, +}; + +static const struct display_entity_ops panel_dpi_ops = { + .ctrl = &panel_dpi_control_ops, +}; + +static int panel_dpi_remove(struct platform_device *pdev) +{ + struct panel_dpi *panel = platform_get_drvdata(pdev); + + display_entity_remove(&panel->entity); + display_entity_cleanup(&panel->entity); + + return 0; +} + +static int panel_dpi_parse_pdata(struct panel_dpi *panel, + struct platform_device *pdev) +{ + const struct panel_dpi_platform_data *pdata = pdev->dev.platform_data; + struct device_node *np = pdev->dev.of_node; + int ret; + + if (pdata) { + panel->width = pdata->width; + panel->height = pdata->height; + panel->mode = *pdata->mode; + } else if (IS_ENABLED(CONFIG_OF) && np) { + /* Width and height are optional. */ + of_property_read_u32(np, "width-mm", &panel->width); + of_property_read_u32(np, "height-mm", &panel->height); + + ret = of_get_videomode(np, &panel->mode, OF_USE_NATIVE_MODE); + if (ret < 0) + return ret; + } else { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + return 0; +} + +static int panel_dpi_probe(struct platform_device *pdev) +{ + struct panel_dpi *panel; + int ret; + + panel = devm_kzalloc(&pdev->dev, sizeof(*panel), GFP_KERNEL); + if (panel == NULL) + return -ENOMEM; + + ret = panel_dpi_parse_pdata(panel, pdev); + if (ret < 0) + return ret; + + panel->entity.dev = &pdev->dev; + panel->entity.ops = &panel_dpi_ops; + strlcpy(panel->entity.name, "panel-dpi", sizeof(panel->entity.name)); + + ret = display_entity_init(&panel->entity, 1, 0); + if (ret < 0) + return ret; + + ret = display_entity_add(&panel->entity); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, panel); + + return 0; +} + +static const struct dev_pm_ops panel_dpi_dev_pm_ops = { +}; + +static struct platform_device_id panel_dpi_id_table[] = { + { "panel-dpi", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, panel_dpi_id_table); + +#ifdef CONFIG_OF +static struct of_device_id panel_dpi_of_id_table[] = { + { .compatible = "panel-dpi", }, + { }, +}; +MODULE_DEVICE_TABLE(of, panel_dpi_of_id_table); +#endif + +static struct platform_driver panel_dpi_driver = { + .probe = panel_dpi_probe, + .remove = panel_dpi_remove, + .id_table = panel_dpi_id_table, + .driver = { + .name = "panel-dpi", + .owner = THIS_MODULE, + .pm = &panel_dpi_dev_pm_ops, + .of_match_table = of_match_ptr(panel_dpi_of_id_table), + }, +}; + +module_platform_driver(panel_dpi_driver); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("DPI Display Panel"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel-dpi.h b/include/video/panel-dpi.h new file mode 100644 index 0000000..af85d5b --- /dev/null +++ b/include/video/panel-dpi.h @@ -0,0 +1,24 @@ +/* + * DPI Display Panel + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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. + */ + +#ifndef __PANEL_DPI_H__ +#define __PANEL_DPI_H__ + +struct videomode; + +struct panel_dpi_platform_data { + unsigned long width; /* Panel width in mm */ + unsigned long height; /* Panel height in mm */ + const struct videomode *mode; +}; + +#endif /* __PANEL_DPI_H__ */
The R61505 is a SYS-80 bus panel controller from Renesas.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/display/Kconfig | 10 + drivers/video/display/Makefile | 1 + drivers/video/display/panel-r61505.c | 567 +++++++++++++++++++++++++++++++++++ include/video/panel-r61505.h | 27 ++ 4 files changed, 605 insertions(+) create mode 100644 drivers/video/display/panel-r61505.c create mode 100644 include/video/panel-r61505.h
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index bce09d6..76729ef 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -19,4 +19,14 @@ config DISPLAY_PANEL_DPI If you are in doubt, say N. To compile this driver as a module, choose M here; the module will be called panel-dpi.
+config DISPLAY_PANEL_R61505 + tristate "Renesas R61505-based Display Panel" + select DISPLAY_MIPI_DBI + ---help--- + Support panels based on the Renesas R61505 panel controller. + Those panels are controlled through a MIPI DBI interface. + + If you are in doubt, say N. To compile this driver as a module, choose + M here; the module will be called panel-r61505. + endif # DISPLAY_CORE diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index 31c017b..db8a4c3 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -3,3 +3,4 @@ display-y := display-core.o \ obj-$(CONFIG_DISPLAY_CORE) += display.o obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o +obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o diff --git a/drivers/video/display/panel-r61505.c b/drivers/video/display/panel-r61505.c new file mode 100644 index 0000000..c86177e --- /dev/null +++ b/drivers/video/display/panel-r61505.c @@ -0,0 +1,567 @@ +/* + * Renesas R61505-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * Based on SuperH MigoR Quarter VGA LCD Panel + * Copyright (C) 2008 Magnus Damm + * Based on lcd_powertip.c from Kenati Technologies Pvt Ltd. + * Copyright (c) 2007 Ujjwal Pande + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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 <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <video/display.h> +#include <video/mipi-dbi-bus.h> +#include <video/panel-r61505.h> +#include <video/videomode.h> + +#define R61505_DEVICE_CODE 0x0000 +#define R61505_DEVICE_CODE_VALUE 0x1505 +#define R61505_DRIVER_OUTPUT_CONTROL 0x0001 +#define R61505_DRIVER_OUTPUT_CONTROL_SM (1 << 10) +#define R61505_DRIVER_OUTPUT_CONTROL_SS (1 << 8) +#define R61505_LCD_WAVEFORM 0x0002 +#define R61505_LCD_WAVEFORM_BC0 (1 << 9) +#define R61505_LCD_WAVEFORM_EOR (1 << 8) +#define R61505_ENTRY_MODE 0x0003 +#define R61505_ENTRY_MODE_TRIREG (1 << 15) +#define R61505_ENTRY_MODE_DFM (1 << 14) +#define R61505_ENTRY_MODE_BGR (1 << 12) +#define R61505_ENTRY_MODE_HWM (1 << 9) +#define R61505_ENTRY_MODE_ORG (1 << 7) +#define R61505_ENTRY_MODE_ID1 (1 << 5) +#define R61505_ENTRY_MODE_ID0 (1 << 4) +#define R61505_ENTRY_MODE_AM (1 << 3) +#define R61505_RESIZE_CONTROL 0x0004 +#define R61505_RESIZE_CONTROL_RCV(n) (((n) & 3) << 8) +#define R61505_RESIZE_CONTROL_RCH(n) (((n) & 3) << 4) +#define R61505_RESIZE_CONTROL_RSZ_4 (3 << 0) +#define R61505_RESIZE_CONTROL_RSZ_2 (1 << 0) +#define R61505_RESIZE_CONTROL_RSZ_1 (0 << 0) +#define R61505_DISPLAY_CONTROL1 0x0007 +#define R61505_DISPLAY_CONTROL1_PTDE1 (1 << 13) +#define R61505_DISPLAY_CONTROL1_PTDE0 (1 << 12) +#define R61505_DISPLAY_CONTROL1_BASEE (1 << 8) +#define R61505_DISPLAY_CONTROL1_VON (1 << 6) +#define R61505_DISPLAY_CONTROL1_GON (1 << 5) +#define R61505_DISPLAY_CONTROL1_DTE (1 << 4) +#define R61505_DISPLAY_CONTROL1_COL (1 << 3) +#define R61505_DISPLAY_CONTROL1_D1 (1 << 1) +#define R61505_DISPLAY_CONTROL1_D0 (1 << 0) +#define R61505_DISPLAY_CONTROL2 0x0008 +#define R61505_DISPLAY_CONTROL2_FP(n) (((n) & 0xf) << 8) +#define R61505_DISPLAY_CONTROL2_BP(n) (((n) & 0xf) << 0) +#define R61505_DISPLAY_CONTROL3 0x0009 +#define R61505_DISPLAY_CONTROL3_PTS(n) (((n) & 7) << 8) +#define R61505_DISPLAY_CONTROL3_PTG(n) (((n) & 3) << 3) +#define R61505_DISPLAY_CONTROL3_ICS(n) (((n) & 0xf) << 0) +#define R61505_DISPLAY_CONTROL4 0x000a +#define R61505_DISPLAY_CONTROL4_FMARKOE (1 << 3) +#define R61505_DISPLAY_CONTROL4_FMI_6 (5 << 0) +#define R61505_DISPLAY_CONTROL4_FMI_4 (3 << 0) +#define R61505_DISPLAY_CONTROL4_FMI_2 (1 << 0) +#define R61505_DISPLAY_CONTROL4_FMI_1 (0 << 0) +#define R61505_EXT_DISPLAY_IF_CONTROL1 0x000c +#define R61505_EXT_DISPLAY_IF_CONTROL1_ENC(n) (((n) & 7) << 12) +#define R61505_EXT_DISPLAY_IF_CONTROL1_RM (1 << 8) +#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_VSYNC (2 << 4) +#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_RGB (1 << 4) +#define R61505_EXT_DISPLAY_IF_CONTROL1_DM_ICLK (0 << 4) +#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_6 (2 << 0) +#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_16 (1 << 0) +#define R61505_EXT_DISPLAY_IF_CONTROL1_RIM_18 (0 << 0) +#define R61505_FRAME_MARKER_CONTROL 0x000d +#define R61505_FRAME_MARKER_CONTROL_FMP(n) (((n) & 0x1ff) << 0) +#define R61505_EXT_DISPLAY_IF_CONTROL2 0x000f +#define R61505_POWER_CONTROL1 0x0010 +#define R61505_POWER_CONTROL1_SAP (1 << 12) +#define R61505_POWER_CONTROL1_BT(n) (((n) & 0xf) << 8) +#define R61505_POWER_CONTROL1_APE (1 << 7) +#define R61505_POWER_CONTROL1_AP_100 (3 << 4) +#define R61505_POWER_CONTROL1_AP_075 (2 << 4) +#define R61505_POWER_CONTROL1_AP_050 (1 << 4) +#define R61505_POWER_CONTROL1_AP_HALT (0 << 4) +#define R61505_POWER_CONTROL1_DSTB (1 << 2) +#define R61505_POWER_CONTROL1_SLP (1 << 1) +#define R61505_POWER_CONTROL2 0x0011 +#define R61505_POWER_CONTROL2_DC1_HALT (6 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_256 (4 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_128 (3 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_64 (2 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_32 (1 << 8) +#define R61505_POWER_CONTROL2_DC1_FOSC_16 (0 << 8) +#define R61505_POWER_CONTROL2_DC0_HALT (6 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC_16 (4 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC_8 (3 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC_4 (2 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC_2 (1 << 4) +#define R61505_POWER_CONTROL2_DC0_FOSC (0 << 4) +#define R61505_POWER_CONTROL2_VC_100 (7 << 0) +#define R61505_POWER_CONTROL2_VC_076 (4 << 0) +#define R61505_POWER_CONTROL2_VC_089 (1 << 0) +#define R61505_POWER_CONTROL2_VC_094 (0 << 0) +#define R61505_POWER_CONTROL3 0x0012 +#define R61505_POWER_CONTROL3_VCMR (1 << 8) +#define R61505_POWER_CONTROL3_PSON (1 << 5) +#define R61505_POWER_CONTROL3_PON (1 << 4) +#define R61505_POWER_CONTROL3_VRH(n) (((n) & 0xf) << 0) +#define R61505_POWER_CONTROL4 0x0013 +#define R61505_POWER_CONTROL4_VDV(n) (((n) & 0xf) << 8) +#define R61505_POWER_CONTROL5 0x0015 +#define R61505_POWER_CONTROL5_BLDM (1 << 12) +#define R61505_POWER_CONTROL6 0x0017 +#define R61505_POWER_CONTROL6_PSE (1 << 0) +#define R61505_RAM_ADDR_HORZ 0x0020 +#define R61505_RAM_ADDR_VERT 0x0021 +#define R61505_RAM_DATA 0x0022 +#define R61505_POWER_CONTROL7 0x0029 +#define R61505_POWER_CONTROL7_VCM1(n) (((n) & 0x1f) << 0) +#define R61505_GAMMA_CONTROL1 0x0030 +#define R61505_GAMMA_CONTROL2 0x0031 +#define R61505_GAMMA_CONTROL3 0x0032 +#define R61505_GAMMA_CONTROL4 0x0033 +#define R61505_GAMMA_CONTROL5 0x0034 +#define R61505_GAMMA_CONTROL6 0x0035 +#define R61505_GAMMA_CONTROL7 0x0036 +#define R61505_GAMMA_CONTROL8 0x0037 +#define R61505_GAMMA_CONTROL9 0x0038 +#define R61505_GAMMA_CONTROL10 0x0039 +#define R61505_GAMMA_CONTROL11 0x003a +#define R61505_GAMMA_CONTROL12 0x003b +#define R61505_GAMMA_CONTROL13 0x003c +#define R61505_GAMMA_CONTROL14 0x003d +#define R61505_WINDOW_HORZ_START 0x0050 +#define R61505_WINDOW_HORZ_END 0x0051 +#define R61505_WINDOW_VERT_START 0x0052 +#define R61505_WINDOW_VERT_END 0x0053 +#define R61505_DRIVER_OUTPUT_CONTROL2 0x0060 +#define R61505_DRIVER_OUTPUT_CONTROL2_GS (1 << 15) +#define R61505_DRIVER_OUTPUT_CONTROL2_NL(n) (((n) & 0x3f) << 8) +#define R61505_DRIVER_OUTPUT_CONTROL2_SCN(n) (((n) & 0x3f) << 0) +#define R61505_BASE_IMG_DISPLAY_CONTROL 0x0061 +#define R61505_BASE_IMG_DISPLAY_CONTROL_NDL (1 << 2) +#define R61505_BASE_IMG_DISPLAY_CONTROL_VLE (1 << 1) +#define R61505_BASE_IMG_DISPLAY_CONTROL_REV (1 << 0) +#define R61505_VERTICAL_SCROLL_CONTROL 0x006a +#define R61505_PANEL_IF_CONTROL1 0x0090 +#define R61505_PANEL_IF_CONTROL1_DIVI(n) (((n) & 3) << 8) +#define R61505_PANEL_IF_CONTROL1_RTNI(n) (((n) & 0x1f) << 0) +#define R61505_PANEL_IF_CONTROL2 0x0092 +#define R61505_PANEL_IF_CONTROL2_NOWI(n) (((n) & 7) << 8) +#define R61505_PANEL_IF_CONTROL3 0x0093 +#define R61505_PANEL_IF_CONTROL3_MCP(n) (((n) & 7) << 8) +#define R61505_PANEL_IF_CONTROL4 0x0095 +#define R61505_PANEL_IF_CONTROL5 0x0097 +#define R61505_PANEL_IF_CONTROL6 0x0098 +#define R61505_OSCILLATION_CONTROL 0x00a4 +#define R61505_OSCILLATION_CONTROL_CALB (1 << 0) + +struct r61505 { + struct display_entity entity; + struct mipi_dbi_device *dbi; + const struct panel_r61505_platform_data *pdata; +}; + +static inline struct r61505 *to_panel(struct display_entity *e) +{ + return container_of(e, struct r61505, entity); +} + +/* ----------------------------------------------------------------------------- + * Read, write and reset + */ + +static void r61505_write(struct r61505 *panel, u16 reg, u16 data) +{ + u8 buffer[2] = { data >> 8, data & 0xff }; + + mipi_dbi_write_command(panel->dbi, reg); + mipi_dbi_write_data(panel->dbi, buffer, 2); +} + +static u16 r61505_read(struct r61505 *panel, u16 reg) +{ + u8 buffer[2]; + int ret; + + mipi_dbi_write_command(panel->dbi, reg); + ret = mipi_dbi_read_data(panel->dbi, buffer, 2); + if (ret < 0) + return ret; + + return (buffer[0] << 8) | buffer[1]; +} + +static void r61505_write_array(struct r61505 *panel, + const u16 *data, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i += 2) + r61505_write(panel, data[i], data[i + 1]); +} + +static void r61505_reset(struct r61505 *panel) +{ + if (panel->pdata->reset < 0) + return; + + gpio_set_value(panel->pdata->reset, 0); + usleep_range(2000, 2500); + gpio_set_value(panel->pdata->reset, 1); + usleep_range(1000, 1500); +} + +/* ----------------------------------------------------------------------------- + * Configuration + */ + +static const unsigned short sync_data[] = { + 0x0000, 0x0000, + 0x0000, 0x0000, + 0x0000, 0x0000, + 0x0000, 0x0000, +}; + +static const unsigned short magic0_data[] = { + R61505_DISPLAY_CONTROL2, R61505_DISPLAY_CONTROL2_FP(8) | + R61505_DISPLAY_CONTROL2_BP(8), + R61505_PANEL_IF_CONTROL1, R61505_PANEL_IF_CONTROL1_RTNI(26), + R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_D0, + R61505_POWER_CONTROL6, R61505_POWER_CONTROL6_PSE, + 0x0019, 0x0000, + R61505_POWER_CONTROL1, R61505_POWER_CONTROL1_SAP | + R61505_POWER_CONTROL1_BT(7) | + R61505_POWER_CONTROL1_APE | + R61505_POWER_CONTROL1_AP_100, + R61505_POWER_CONTROL2, R61505_POWER_CONTROL2_DC1_FOSC_32 | + R61505_POWER_CONTROL2_DC0_FOSC_2 | 6, + R61505_POWER_CONTROL3, R61505_POWER_CONTROL3_VCMR | 0x80 | + R61505_POWER_CONTROL3_PON | + R61505_POWER_CONTROL3_VRH(8), + R61505_POWER_CONTROL4, 0x1000 | R61505_POWER_CONTROL4_VDV(4), + R61505_POWER_CONTROL7, R61505_POWER_CONTROL7_VCM1(12), + R61505_POWER_CONTROL3, R61505_POWER_CONTROL3_VCMR | 0x80 | + R61505_POWER_CONTROL3_PSON | + R61505_POWER_CONTROL3_PON | + R61505_POWER_CONTROL3_VRH(8), +}; + +static const unsigned short magic1_data[] = { + R61505_GAMMA_CONTROL1, 0x0307, + R61505_GAMMA_CONTROL2, 0x0303, + R61505_GAMMA_CONTROL3, 0x0603, + R61505_GAMMA_CONTROL4, 0x0202, + R61505_GAMMA_CONTROL5, 0x0202, + R61505_GAMMA_CONTROL6, 0x0202, + R61505_GAMMA_CONTROL7, 0x1f1f, + R61505_GAMMA_CONTROL8, 0x0303, + R61505_GAMMA_CONTROL9, 0x0303, + R61505_GAMMA_CONTROL10, 0x0603, + R61505_GAMMA_CONTROL11, 0x0202, + R61505_GAMMA_CONTROL12, 0x0102, + R61505_GAMMA_CONTROL13, 0x0204, + R61505_GAMMA_CONTROL14, 0x0000, + R61505_DRIVER_OUTPUT_CONTROL, R61505_DRIVER_OUTPUT_CONTROL_SS, + R61505_LCD_WAVEFORM, R61505_LCD_WAVEFORM_BC0 | + R61505_LCD_WAVEFORM_EOR, + R61505_ENTRY_MODE, R61505_ENTRY_MODE_DFM | + R61505_ENTRY_MODE_BGR | + R61505_ENTRY_MODE_ID1 | + R61505_ENTRY_MODE_AM, + R61505_RAM_ADDR_HORZ, 239, + R61505_RAM_ADDR_VERT, 0, + R61505_RESIZE_CONTROL, R61505_RESIZE_CONTROL_RCV(0) | + R61505_RESIZE_CONTROL_RCH(0) | + R61505_RESIZE_CONTROL_RSZ_1, + R61505_DISPLAY_CONTROL3, R61505_DISPLAY_CONTROL3_PTS(0) | + R61505_DISPLAY_CONTROL3_PTG(0) | + R61505_DISPLAY_CONTROL3_ICS(0), + R61505_DISPLAY_CONTROL4, R61505_DISPLAY_CONTROL4_FMARKOE | + R61505_DISPLAY_CONTROL4_FMI_1, + R61505_EXT_DISPLAY_IF_CONTROL1, R61505_EXT_DISPLAY_IF_CONTROL1_ENC(0) | + R61505_EXT_DISPLAY_IF_CONTROL1_DM_ICLK | + R61505_EXT_DISPLAY_IF_CONTROL1_RIM_18, + R61505_FRAME_MARKER_CONTROL, R61505_FRAME_MARKER_CONTROL_FMP(0), + R61505_POWER_CONTROL5, 0x8000, +}; + +static const unsigned short magic2_data[] = { + R61505_BASE_IMG_DISPLAY_CONTROL, R61505_BASE_IMG_DISPLAY_CONTROL_REV, + R61505_PANEL_IF_CONTROL2, R61505_PANEL_IF_CONTROL2_NOWI(1), + R61505_PANEL_IF_CONTROL3, R61505_PANEL_IF_CONTROL3_MCP(1), + R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_GON | + R61505_DISPLAY_CONTROL1_D0, +}; + +static const unsigned short magic3_data[] = { + R61505_POWER_CONTROL1, R61505_POWER_CONTROL1_SAP | + R61505_POWER_CONTROL1_BT(6) | + R61505_POWER_CONTROL1_APE | + R61505_POWER_CONTROL1_AP_100, + R61505_POWER_CONTROL2, R61505_POWER_CONTROL2_DC1_FOSC_32 | + R61505_POWER_CONTROL2_DC0_FOSC_2 | + R61505_POWER_CONTROL2_VC_089, + R61505_DISPLAY_CONTROL1, R61505_DISPLAY_CONTROL1_VON | + R61505_DISPLAY_CONTROL1_GON | + R61505_DISPLAY_CONTROL1_D0, +}; + +static void r61505_enable_panel(struct r61505 *panel) +{ + unsigned long hactive = panel->pdata->mode->hactive; + unsigned long vactive = panel->pdata->mode->vactive; + unsigned int i; + + r61505_write_array(panel, sync_data, ARRAY_SIZE(sync_data)); + + r61505_write(panel, R61505_OSCILLATION_CONTROL, + R61505_OSCILLATION_CONTROL_CALB); + usleep_range(10000, 11000); + + r61505_write(panel, R61505_DRIVER_OUTPUT_CONTROL2, + R61505_DRIVER_OUTPUT_CONTROL2_NL((hactive / 8) - 1)); + r61505_write_array(panel, magic0_data, ARRAY_SIZE(magic0_data)); + usleep_range(100000, 101000); + + r61505_write_array(panel, magic1_data, ARRAY_SIZE(magic1_data)); + + r61505_write(panel, R61505_WINDOW_HORZ_START, 239 - (vactive - 1)); + r61505_write(panel, R61505_WINDOW_HORZ_END, 239); + r61505_write(panel, R61505_WINDOW_VERT_START, 0); + r61505_write(panel, R61505_WINDOW_VERT_END, hactive - 1); + + r61505_write_array(panel, magic2_data, ARRAY_SIZE(magic2_data)); + usleep_range(10000, 11000); + + r61505_write_array(panel, magic3_data, ARRAY_SIZE(magic3_data)); + usleep_range(40000, 41000); + + /* Clear GRAM to avoid displaying garbage. */ + r61505_write(panel, R61505_RAM_ADDR_HORZ, 0); + r61505_write(panel, R61505_RAM_ADDR_VERT, 0); + + for (i = 0; i < (hactive * 256); i++) /* yes, 256 words per line */ + r61505_write(panel, R61505_RAM_DATA, 0); + + r61505_write(panel, R61505_RAM_ADDR_HORZ, 0); + r61505_write(panel, R61505_RAM_ADDR_VERT, 0); +} + +static void r61505_disable_panel(struct r61505 *panel) +{ + r61505_reset(panel); +} + +static void r61505_display_on(struct r61505 *panel) +{ + r61505_write(panel, R61505_DISPLAY_CONTROL1, + R61505_DISPLAY_CONTROL1_BASEE | + R61505_DISPLAY_CONTROL1_VON | + R61505_DISPLAY_CONTROL1_GON | + R61505_DISPLAY_CONTROL1_DTE | + R61505_DISPLAY_CONTROL1_D1 | + R61505_DISPLAY_CONTROL1_D0); + usleep_range(40000, 41000); +} + +static void r61505_display_off(struct r61505 *panel) +{ + r61505_write(panel, R61505_DISPLAY_CONTROL1, + R61505_DISPLAY_CONTROL1_VON | + R61505_DISPLAY_CONTROL1_GON | + R61505_DISPLAY_CONTROL1_D0); +} + +/* ----------------------------------------------------------------------------- + * Panel operations + */ + +static const struct display_entity_interface_params r61505_dbi_params = { + .type = DISPLAY_ENTITY_INTERFACE_DBI, + .p.dbi = { + .type = MIPI_DBI_INTERFACE_TYPE_B, + .cs_setup = 1, + .wr_setup = 0, + .wr_cycle = 10, + .wr_hold = 9, + .rd_setup = 14, + .rd_latch = 24, + .rd_cycle = 52, + .rd_hold = 24, + }, +}; + +static int r61505_set_state(struct display_entity *entity, + enum display_entity_state state) +{ + struct r61505 *panel = to_panel(entity); + + switch (state) { + case DISPLAY_ENTITY_STATE_OFF: + r61505_disable_panel(panel); + break; + + case DISPLAY_ENTITY_STATE_STANDBY: + if (entity->state == DISPLAY_ENTITY_STATE_OFF) + r61505_enable_panel(panel); + else + r61505_display_off(panel); + break; + + case DISPLAY_ENTITY_STATE_ON: + if (entity->state == DISPLAY_ENTITY_STATE_OFF) + r61505_enable_panel(panel); + + r61505_display_on(panel); + break; + } + + return 0; +} + +static int r61505_update(struct display_entity *entity) +{ + struct r61505 *panel = to_panel(entity); + struct media_pad *source; + + mipi_dbi_write_command(panel->dbi, R61505_RAM_DATA); + usleep_range(100000, 101000); + + source = media_entity_remote_pad(&entity->entity.pads[0]); + if (source == NULL) + return -EPIPE; + + display_entity_set_stream(to_display_entity(source->entity), + source->index, + DISPLAY_ENTITY_STREAM_SINGLE_SHOT); + return 0; +} + +static int r61505_get_modes(struct display_entity *entity, unsigned int port, + const struct videomode **modes) +{ + struct r61505 *panel = to_panel(entity); + + *modes = panel->pdata->mode; + return 1; +} + +static int r61505_get_size(struct display_entity *entity, + unsigned int *width, unsigned int *height) +{ + struct r61505 *panel = to_panel(entity); + + *width = panel->pdata->width; + *height = panel->pdata->height; + return 0; +} + +static int r61505_get_params(struct display_entity *entity, unsigned int port, + struct display_entity_interface_params *params) +{ + *params = r61505_dbi_params; + return 0; +} + +static const struct display_entity_control_ops r61505_control_ops = { + .set_state = r61505_set_state, + .update = r61505_update, + .get_modes = r61505_get_modes, + .get_size = r61505_get_size, + .get_params = r61505_get_params, +}; + +static const struct display_entity_ops r61505_ops = { + .ctrl = &r61505_control_ops, +}; + +static int r61505_remove(struct mipi_dbi_device *dev) +{ + struct r61505 *panel = mipi_dbi_get_drvdata(dev); + + display_entity_remove(&panel->entity); + display_entity_cleanup(&panel->entity); + + return 0; +} + +static int r61505_probe(struct mipi_dbi_device *dev) +{ + const struct panel_r61505_platform_data *pdata = dev->dev.platform_data; + struct r61505 *panel; + int ret; + + if (pdata == NULL) + return -ENODEV; + + panel = devm_kzalloc(&dev->dev, sizeof(*panel), GFP_KERNEL); + if (panel == NULL) + return -ENOMEM; + + panel->pdata = pdata; + panel->dbi = dev; + + dev->flags = MIPI_DBI_FLAG_ALIGN_LEFT; + dev->bus_width = pdata->bus_width; + mipi_dbi_set_data_width(dev, 16); + + r61505_reset(panel); + r61505_write_array(panel, sync_data, ARRAY_SIZE(sync_data)); + + if (r61505_read(panel, 0) != R61505_DEVICE_CODE_VALUE) + return -ENODEV; + + panel->entity.dev = &dev->dev; + panel->entity.ops = &r61505_ops; + + ret = display_entity_init(&panel->entity, 1, 0); + if (ret < 0) + return ret; + + ret = display_entity_add(&panel->entity); + if (ret < 0) + return ret; + + mipi_dbi_set_drvdata(dev, panel); + + return 0; +} + +static const struct dev_pm_ops r61505_dev_pm_ops = { +}; + +static struct mipi_dbi_device_id r61505_id_table[] = { + { "panel-r61505", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(mipi_dbi, r61505_id_table); + +static struct mipi_dbi_driver r61505_driver = { + .probe = r61505_probe, + .remove = r61505_remove, + .id_table = r61505_id_table, + .driver = { + .name = "panel-r61505", + .owner = THIS_MODULE, + .pm = &r61505_dev_pm_ops, + }, +}; + +module_mipi_dbi_driver(r61505_driver); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("Renesas R61505-based Display Panel"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel-r61505.h b/include/video/panel-r61505.h new file mode 100644 index 0000000..ee71f29 --- /dev/null +++ b/include/video/panel-r61505.h @@ -0,0 +1,27 @@ +/* + * Renesas R61505-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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. + */ + +#ifndef __PANEL_R61505_H__ +#define __PANEL_R61505_H__ + +struct videomode; + +struct panel_r61505_platform_data { + unsigned long width; /* Panel width in mm */ + unsigned long height; /* Panel height in mm */ + const struct videomode *mode; + + unsigned int bus_width; + int reset; /* Reset GPIO */ +}; + +#endif /* __PANEL_R61505_H__ */
The R61517 is a MIPI DBI panel controller from Renesas.
Signed-off-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- drivers/video/display/Kconfig | 10 + drivers/video/display/Makefile | 1 + drivers/video/display/panel-r61517.c | 460 +++++++++++++++++++++++++++++++++++ include/video/panel-r61517.h | 28 +++ 4 files changed, 499 insertions(+) create mode 100644 drivers/video/display/panel-r61517.c create mode 100644 include/video/panel-r61517.h
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 76729ef..9b44b5f 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -29,4 +29,14 @@ config DISPLAY_PANEL_R61505 If you are in doubt, say N. To compile this driver as a module, choose M here; the module will be called panel-r61505.
+config DISPLAY_PANEL_R61517 + tristate "Renesas R61517-based Display Panel" + select DISPLAY_MIPI_DBI + ---help--- + Support panels based on the Renesas R61517 panel controller. + Those panels are controlled through a MIPI DBI interface. + + If you are in doubt, say N. To compile this driver as a module, choose + M here; the module will be called panel-r61517. + endif # DISPLAY_CORE diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index db8a4c3..1cdc8d4 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_DISPLAY_CORE) += display.o obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o +obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o diff --git a/drivers/video/display/panel-r61517.c b/drivers/video/display/panel-r61517.c new file mode 100644 index 0000000..ccfec33 --- /dev/null +++ b/drivers/video/display/panel-r61517.c @@ -0,0 +1,460 @@ +/* + * Renesas R61517-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * Based on KFR2R09 LCD panel support + * Copyright (C) 2009 Magnus Damm + * Register settings based on the out-of-tree t33fb.c driver + * Copyright (C) 2008 Lineo Solutions, Inc. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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 <linux/delay.h> +#include <linux/err.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/gpio.h> + +#include <video/display.h> +#include <video/mipi-dbi-bus.h> +#include <video/mipi_display.h> +#include <video/panel-r61517.h> +#include <video/videomode.h> + +struct r61517 { + struct display_entity entity; + struct mipi_dbi_device *dbi; + const struct panel_r61517_platform_data *pdata; +}; + +static inline struct r61517 *to_panel(struct display_entity *e) +{ + return container_of(e, struct r61517, entity); +} + +/* ----------------------------------------------------------------------------- + * Read, write and reset + */ + +static void r61517_write_command(struct r61517 *panel, u16 cmd) +{ + mipi_dbi_write_command(panel->dbi, cmd); +} + +static void r61517_write_data(struct r61517 *panel, u8 data) +{ + mipi_dbi_write_data(panel->dbi, &data, 1); +} + +static int r61517_read_data(struct r61517 *panel) +{ + u8 data; + int ret; + + ret = mipi_dbi_read_data(panel->dbi, &data, 1); + if (ret < 0) + return ret; + + return data; +} + +static void r61517_write(struct r61517 *panel, u8 reg, const u8 *data, + size_t len) +{ + mipi_dbi_write_command(panel->dbi, reg); + mipi_dbi_write_data(panel->dbi, data, len); +} + +static void r61517_write8(struct r61517 *panel, u8 reg, u8 data) +{ + r61517_write(panel, reg, &data, 1); +} + +static void r61517_write16(struct r61517 *panel, u8 reg, u16 data) +{ + u8 buffer[2] = { (data >> 8) & 0xff, (data >> 0) & 0xff }; + + r61517_write(panel, reg, buffer, 2); +} + +static void r61517_write32(struct r61517 *panel, u8 reg, u32 data) +{ + u8 buffer[4] = { (data >> 24) & 0xff, (data >> 16) & 0xff, + (data >> 8) & 0xff, (data >> 0) & 0xff }; + + r61517_write(panel, reg, buffer, 4); +} + +#define r61517_write_array(p, c, a) \ + r61517_write((p), (c), (a), ARRAY_SIZE(a)) + +static void r61517_reset(struct r61517 *panel) +{ + gpio_set_value(panel->pdata->protect, 0); /* PROTECT/ -> L */ + gpio_set_value(panel->pdata->reset, 0); /* LCD_RST/ -> L */ + gpio_set_value(panel->pdata->protect, 1); /* PROTECT/ -> H */ + usleep_range(1100, 1200); + gpio_set_value(panel->pdata->reset, 1); /* LCD_RST/ -> H */ + usleep_range(10, 100); + gpio_set_value(panel->pdata->protect, 0); /* PROTECT/ -> L */ + msleep(20); +} + +/* ----------------------------------------------------------------------------- + * Configuration + */ + +static const u8 data_frame_if[] = { + 0x02, /* WEMODE: 1=cont, 0=one-shot */ + 0x00, 0x00, + 0x00, /* EPF, DFM */ + 0x02, /* RIM[1] : 1 (18bpp) */ +}; + +static const u8 data_panel[] = { + 0x0b, + 0x63, /* 400 lines */ + 0x04, 0x00, 0x00, 0x04, 0x11, 0x00, 0x00, +}; + +static const u8 data_timing[] = { + 0x00, 0x00, 0x13, 0x08, 0x08, +}; + +static const u8 data_timing_src[] = { + 0x11, 0x01, 0x00, 0x01, +}; + +static const u8 data_gamma[] = { + 0x01, 0x02, 0x08, 0x23, 0x03, 0x0c, 0x00, 0x06, 0x00, 0x00, + 0x01, 0x00, 0x0c, 0x23, 0x03, 0x08, 0x02, 0x06, 0x00, 0x00, +}; + +static const u8 data_power[] = { + 0x07, 0xc5, 0xdc, 0x02, 0x33, 0x0a, +}; + +static const u8 data_vcom[] = { + 0x00, 0x0f, 0x02, +}; + +static unsigned long r61517_read_device_code(struct r61517 *panel) +{ + /* access protect OFF */ + r61517_write8(panel, 0xb0, 0); + + /* deep standby OFF */ + r61517_write8(panel, 0xb1, 0); + + /* device code command */ + r61517_write_command(panel, 0xbf); + mdelay(50); + + /* dummy read */ + r61517_read_data(panel); + + /* read device code */ + return (r61517_read_data(panel) << 24) | + (r61517_read_data(panel) << 16) | + (r61517_read_data(panel) << 8) | + (r61517_read_data(panel) << 0); +} + +static void r61517_write_memory_start(struct r61517 *panel) +{ + r61517_write_command(panel, MIPI_DCS_WRITE_MEMORY_START); +} + +static void r61517_clear_memory(struct r61517 *panel) +{ + unsigned int size = panel->pdata->mode->hactive + * panel->pdata->mode->vactive; + unsigned int i; + + r61517_write_memory_start(panel); + + for (i = 0; i < size; i++) + r61517_write_data(panel, 0); +} + +static void r61517_enable_panel(struct r61517 *panel) +{ + /* access protect off */ + r61517_write8(panel, 0xb0, 0); + + /* exit deep standby mode */ + r61517_write8(panel, 0xb1, 0); + + /* frame memory I/F */ + r61517_write_array(panel, 0xb3, data_frame_if); + + /* display mode and frame memory write mode */ + r61517_write8(panel, 0xb4, 0); /* DBI, internal clock */ + + /* panel */ + r61517_write_array(panel, 0xc0, data_panel); + + /* timing (normal) */ + r61517_write_array(panel, 0xc1, data_timing); + + /* timing (partial) */ + r61517_write_array(panel, 0xc2, data_timing); + + /* timing (idle) */ + r61517_write_array(panel, 0xc3, data_timing); + + /* timing (source/VCOM/gate driving) */ + r61517_write_array(panel, 0xc4, data_timing_src); + + /* gamma (red) */ + r61517_write_array(panel, 0xc8, data_gamma); + + /* gamma (green) */ + r61517_write_array(panel, 0xc9, data_gamma); + + /* gamma (blue) */ + r61517_write_array(panel, 0xca, data_gamma); + + /* power (common) */ + r61517_write_array(panel, 0xd0, data_power); + + /* VCOM */ + r61517_write_array(panel, 0xd1, data_vcom); + + /* power (normal) */ + r61517_write16(panel, 0xd2, 0x6324); + + /* power (partial) */ + r61517_write16(panel, 0xd3, 0x6324); + + /* power (idle) */ + r61517_write16(panel, 0xd4, 0x6324); + + r61517_write16(panel, 0xd8, 0x7777); + + /* TE signal */ + r61517_write8(panel, MIPI_DCS_SET_TEAR_ON, 0); + + /* TE signal line */ + r61517_write16(panel, MIPI_DCS_SET_TEAR_SCANLINE, 0); + + /* column address */ + r61517_write32(panel, MIPI_DCS_SET_COLUMN_ADDRESS, + panel->pdata->mode->hactive - 1); + + /* page address */ + r61517_write32(panel, MIPI_DCS_SET_PAGE_ADDRESS, + panel->pdata->mode->vactive - 1); + + /* exit sleep mode */ + r61517_write_command(panel, MIPI_DCS_EXIT_SLEEP_MODE); + + mdelay(120); + + /* clear vram */ + r61517_clear_memory(panel); +} + +static void r61517_disable_panel(struct r61517 *panel) +{ + r61517_reset(panel); +} + +static void r61517_display_on(struct r61517 *panel) +{ + r61517_write_command(panel, MIPI_DCS_SET_DISPLAY_ON); + mdelay(1); +} + +static void r61517_display_off(struct r61517 *panel) +{ + r61517_write_command(panel, MIPI_DCS_SET_DISPLAY_OFF); +} + +/* ----------------------------------------------------------------------------- + * Panel operations + */ + +static const struct display_entity_interface_params r61517_dbi_params = { + .type = DISPLAY_ENTITY_INTERFACE_DBI, + .p.dbi = { + .type = MIPI_DBI_INTERFACE_TYPE_B, + .flags = MIPI_DBI_INTERFACE_TE, + .cs_setup = 1, + .wr_setup = 1, + .wr_cycle = 9, + .wr_hold = 4, + .rd_setup = 1, + .rd_latch = 20, + .rd_cycle = 41, + .rd_hold = 20, + }, +}; + +static int r61517_set_state(struct display_entity *entity, + enum display_entity_state state) +{ + struct r61517 *panel = to_panel(entity); + + switch (state) { + case DISPLAY_ENTITY_STATE_OFF: + r61517_disable_panel(panel); + break; + + case DISPLAY_ENTITY_STATE_STANDBY: + if (entity->state == DISPLAY_ENTITY_STATE_OFF) + r61517_enable_panel(panel); + else + r61517_display_off(panel); + break; + + case DISPLAY_ENTITY_STATE_ON: + if (entity->state == DISPLAY_ENTITY_STATE_OFF) + r61517_enable_panel(panel); + + r61517_display_on(panel); + break; + } + + return 0; +} + +static int r61517_update(struct display_entity *entity) +{ + struct r61517 *panel = to_panel(entity); + struct media_pad *source; + + r61517_write_memory_start(panel); + + source = media_entity_remote_pad(&entity->entity.pads[0]); + if (source == NULL) + return -EPIPE; + + display_entity_set_stream(to_display_entity(source->entity), + source->index, + DISPLAY_ENTITY_STREAM_SINGLE_SHOT); + return 0; +} + +static int r61517_get_modes(struct display_entity *entity, unsigned int port, + const struct videomode **modes) +{ + struct r61517 *panel = to_panel(entity); + + *modes = panel->pdata->mode; + return 1; +} + +static int r61517_get_size(struct display_entity *entity, + unsigned int *width, unsigned int *height) +{ + struct r61517 *panel = to_panel(entity); + + *width = panel->pdata->width; + *height = panel->pdata->height; + return 0; +} + +static int r61517_get_params(struct display_entity *entity, unsigned int port, + struct display_entity_interface_params *params) +{ + *params = r61517_dbi_params; + return 0; +} + +static const struct display_entity_control_ops r61517_control_ops = { + .set_state = r61517_set_state, + .update = r61517_update, + .get_modes = r61517_get_modes, + .get_size = r61517_get_size, + .get_params = r61517_get_params, +}; + +static const struct display_entity_ops r61517_ops = { + .ctrl = &r61517_control_ops, +}; + +static int r61517_remove(struct mipi_dbi_device *dev) +{ + struct r61517 *panel = mipi_dbi_get_drvdata(dev); + + display_entity_remove(&panel->entity); + display_entity_cleanup(&panel->entity); + + return 0; +} + +static int r61517_probe(struct mipi_dbi_device *dev) +{ + const struct panel_r61517_platform_data *pdata = dev->dev.platform_data; + struct r61517 *panel; + int ret; + + if (pdata == NULL) + return -ENODEV; + + panel = devm_kzalloc(&dev->dev, sizeof(*panel), GFP_KERNEL); + if (panel == NULL) + return -ENOMEM; + + panel->pdata = pdata; + panel->dbi = dev; + + dev->bus_width = pdata->bus_width; + mipi_dbi_set_data_width(dev, 8); + + r61517_reset(panel); + + if (r61517_read_device_code(panel) != 0x01221517) + return -ENODEV; + + pr_info("R61517 panel controller detected.\n"); + + panel->entity.dev = &dev->dev; + panel->entity.ops = &r61517_ops; + + ret = display_entity_init(&panel->entity, 1, 0); + if (ret < 0) + return ret; + + ret = display_entity_add(&panel->entity); + if (ret < 0) + return ret; + + mipi_dbi_set_drvdata(dev, panel); + + return 0; +} + +static const struct dev_pm_ops r61517_dev_pm_ops = { +}; + +static struct mipi_dbi_device_id r61517_id_table[] = { + { "panel-r61517", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(mipi_dbi, r61517_id_table); + +static struct mipi_dbi_driver r61517_driver = { + .probe = r61517_probe, + .remove = r61517_remove, + .id_table = r61517_id_table, + .driver = { + .name = "panel-r61517", + .owner = THIS_MODULE, + .pm = &r61517_dev_pm_ops, + }, +}; + +module_mipi_dbi_driver(r61517_driver); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("Renesas R61517-based Display Panel"); +MODULE_LICENSE("GPL"); diff --git a/include/video/panel-r61517.h b/include/video/panel-r61517.h new file mode 100644 index 0000000..33f16af --- /dev/null +++ b/include/video/panel-r61517.h @@ -0,0 +1,28 @@ +/* + * Renesas R61517-based Display Panels + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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. + */ + +#ifndef __PANEL_R61517_H__ +#define __PANEL_R61517_H__ + +struct videomode; + +struct panel_r61517_platform_data { + unsigned long width; /* Panel width in mm */ + unsigned long height; /* Panel height in mm */ + const struct videomode *mode; + + unsigned int bus_width; + int protect; /* Protect GPIO */ + int reset; /* Reset GPIO */ +}; + +#endif /* __PANEL_R61517_H__ */
This driver implements support for VGA Digital to Analog Converters (DACs) that receive pixel data through a DPI interface and have no control interface (GPIOs- and/or regulators-based control can be implemented later when needed). It exposes the devices a display entities.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/video/display/Kconfig | 9 +++ drivers/video/display/Makefile | 1 + drivers/video/display/vga-dac.c | 152 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 drivers/video/display/vga-dac.c
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 9b44b5f..32ce08d 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -39,4 +39,13 @@ config DISPLAY_PANEL_R61517 If you are in doubt, say N. To compile this driver as a module, choose M here; the module will be called panel-r61517.
+config DISPLAY_VGA_DAC + tristate "VGA Digital to Analog Converters" + ---help--- + Support for simple VGA digital to analog converters. Those converters + receive pixel data through a parallel bus and have no control bus. + + If you are in doubt, say N. To compile this driver as a module, choose + M here: the module will be called vga-dac. + endif # DISPLAY_CORE diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index 1cdc8d4..43cd78d 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o obj-$(CONFIG_DISPLAY_PANEL_R61517) += panel-r61517.o +obj-$(CONFIG_DISPLAY_VGA_DAC) += vga-dac.o diff --git a/drivers/video/display/vga-dac.c b/drivers/video/display/vga-dac.c new file mode 100644 index 0000000..d0256e6 --- /dev/null +++ b/drivers/video/display/vga-dac.c @@ -0,0 +1,152 @@ +/* + * VGA Digital to Analog Converter + * + * Copyright (C) 2013 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <video/display.h> + +#define VGA_DAC_PORT_SINK 0 +#define VGA_DAC_PORT_SOURCE 1 + +struct vga_dac { + struct display_entity entity; +}; + +static inline struct vga_dac *to_vga_dac(struct display_entity *e) +{ + return container_of(e, struct vga_dac, entity); +} + +static int vga_dac_set_state(struct display_entity *entity, + enum display_entity_state state) +{ + struct media_pad *source; + + source = media_entity_remote_pad(&entity->entity.pads[0]); + if (source == NULL) + return -EPIPE; + + switch (state) { + case DISPLAY_ENTITY_STATE_OFF: + case DISPLAY_ENTITY_STATE_STANDBY: + display_entity_set_stream(to_display_entity(source->entity), + source->index, + DISPLAY_ENTITY_STREAM_STOPPED); + break; + + case DISPLAY_ENTITY_STATE_ON: + display_entity_set_stream(to_display_entity(source->entity), + source->index, + DISPLAY_ENTITY_STREAM_CONTINUOUS); + break; + } + + return 0; +} + +static int vga_dac_get_params(struct display_entity *entity, unsigned int port, + struct display_entity_interface_params *params) +{ + memset(params, 0, sizeof(*params)); + + if (port == VGA_DAC_PORT_SINK) + params->type = DISPLAY_ENTITY_INTERFACE_DPI; + else + params->type = DISPLAY_ENTITY_INTERFACE_VGA; + + return 0; +} + +static const struct display_entity_control_ops vga_dac_control_ops = { + .set_state = vga_dac_set_state, + .get_params = vga_dac_get_params, +}; + +static const struct display_entity_ops vga_dac_ops = { + .ctrl = &vga_dac_control_ops, +}; + +static int vga_dac_remove(struct platform_device *pdev) +{ + struct vga_dac *dac = platform_get_drvdata(pdev); + + display_entity_remove(&dac->entity); + display_entity_cleanup(&dac->entity); + + return 0; +} + +static int vga_dac_probe(struct platform_device *pdev) +{ + struct vga_dac *dac; + int ret; + + dac = devm_kzalloc(&pdev->dev, sizeof(*dac), GFP_KERNEL); + if (dac == NULL) + return -ENOMEM; + + dac->entity.dev = &pdev->dev; + dac->entity.ops = &vga_dac_ops; + strlcpy(dac->entity.name, dev_name(&pdev->dev), + sizeof(dac->entity.name)); + + ret = display_entity_init(&dac->entity, 1, 1); + if (ret < 0) + return ret; + + ret = display_entity_add(&dac->entity); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, dac); + + return 0; +} + +static const struct dev_pm_ops vga_dac_dev_pm_ops = { +}; + +static struct platform_device_id vga_dac_id_table[] = { + { "adv7123", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, vga_dac_id_table); + +#ifdef CONFIG_OF +static struct of_device_id vga_dac_of_id_table[] = { + { .compatible = "adi,adv7123", }, + { }, +}; +MODULE_DEVICE_TABLE(of, vga_dac_of_id_table); +#endif + +static struct platform_driver vga_dac_driver = { + .probe = vga_dac_probe, + .remove = vga_dac_remove, + .id_table = vga_dac_id_table, + .driver = { + .name = "vga-dac", + .owner = THIS_MODULE, + .pm = &vga_dac_dev_pm_ops, + .of_match_table = of_match_ptr(vga_dac_of_id_table), + }, +}; + +module_platform_driver(vga_dac_driver); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("VGA Digital-to-Analog Converter"); +MODULE_LICENSE("GPL");
This driver exposes VGA connectors as display entity devices. The connectors are passive devices that pass analog VGA signals though. They optionally cary DDC signals for bidirectional control communications with the devices connected to the connectors.
EDID retrieval isn't supported yet.
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/video/display/Kconfig | 11 +++ drivers/video/display/Makefile | 1 + drivers/video/display/con-vga.c | 148 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 drivers/video/display/con-vga.c
diff --git a/drivers/video/display/Kconfig b/drivers/video/display/Kconfig index 32ce08d..9b482a8 100644 --- a/drivers/video/display/Kconfig +++ b/drivers/video/display/Kconfig @@ -5,6 +5,17 @@ menuconfig DISPLAY_CORE
if DISPLAY_CORE
+ +config DISPLAY_CONNECTOR_VGA + tristate "VGA Connector" + ---help--- + Support for simple digital (parallel) pixel interface panels. Those + panels receive pixel data through a parallel bus and have no control + bus. + + If you are in doubt, say N. To compile this driver as a module, choose + M here; the module will be called con-vga. + config DISPLAY_MIPI_DBI tristate default n diff --git a/drivers/video/display/Makefile b/drivers/video/display/Makefile index 43cd78d..d03c64a 100644 --- a/drivers/video/display/Makefile +++ b/drivers/video/display/Makefile @@ -1,6 +1,7 @@ display-y := display-core.o \ display-notifier.o obj-$(CONFIG_DISPLAY_CORE) += display.o +obj-$(CONFIG_DISPLAY_CONNECTOR_VGA) += con-vga.o obj-$(CONFIG_DISPLAY_MIPI_DBI) += mipi-dbi-bus.o obj-$(CONFIG_DISPLAY_PANEL_DPI) += panel-dpi.o obj-$(CONFIG_DISPLAY_PANEL_R61505) += panel-r61505.o diff --git a/drivers/video/display/con-vga.c b/drivers/video/display/con-vga.c new file mode 100644 index 0000000..798ac9e --- /dev/null +++ b/drivers/video/display/con-vga.c @@ -0,0 +1,148 @@ +/* + * VGA Connector + * + * Copyright (C) 2013 Renesas Solutions Corp. + * + * Contacts: Laurent Pinchart laurent.pinchart@ideasonboard.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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <video/display.h> + +struct con_vga { + struct display_entity entity; +}; + +static int con_vga_set_state(struct display_entity *entity, + enum display_entity_state state) +{ + struct media_pad *source; + + source = media_entity_remote_pad(&entity->entity.pads[0]); + if (source == NULL) + return -EPIPE; + + switch (state) { + case DISPLAY_ENTITY_STATE_OFF: + case DISPLAY_ENTITY_STATE_STANDBY: + display_entity_set_stream(to_display_entity(source->entity), + source->index, + DISPLAY_ENTITY_STREAM_STOPPED); + break; + + case DISPLAY_ENTITY_STATE_ON: + display_entity_set_stream(to_display_entity(source->entity), + source->index, + DISPLAY_ENTITY_STREAM_CONTINUOUS); + break; + } + + return 0; +} + +static int con_vga_get_modes(struct display_entity *entity, unsigned int port, + const struct videomode **modes) +{ + /* TODO: Add EDID retrieval support. */ + return 0; +} + +static int con_vga_get_params(struct display_entity *entity, unsigned int port, + struct display_entity_interface_params *params) +{ + memset(params, 0, sizeof(*params)); + + params->type = DISPLAY_ENTITY_INTERFACE_VGA; + + return 0; +} + +static const struct display_entity_control_ops con_vga_control_ops = { + .set_state = con_vga_set_state, + .get_modes = con_vga_get_modes, + .get_params = con_vga_get_params, +}; + +static const struct display_entity_ops con_vga_ops = { + .ctrl = &con_vga_control_ops, +}; + +static int con_vga_remove(struct platform_device *pdev) +{ + struct con_vga *con = platform_get_drvdata(pdev); + + display_entity_remove(&con->entity); + display_entity_cleanup(&con->entity); + + return 0; +} + +static int con_vga_probe(struct platform_device *pdev) +{ + struct con_vga *con; + int ret; + + con = devm_kzalloc(&pdev->dev, sizeof(*con), GFP_KERNEL); + if (con == NULL) + return -ENOMEM; + + con->entity.dev = &pdev->dev; + con->entity.ops = &con_vga_ops; + strlcpy(con->entity.name, "vga-con", sizeof(con->entity.name)); + + ret = display_entity_init(&con->entity, 1, 0); + if (ret < 0) + return ret; + + ret = display_entity_add(&con->entity); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, con); + + return 0; +} + +static const struct dev_pm_ops con_vga_dev_pm_ops = { +}; + +static struct platform_device_id con_vga_id_table[] = { + { "con-vga", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, con_vga_id_table); + +#ifdef CONFIG_OF +static struct of_device_id con_vga_of_id_table[] = { + { .compatible = "con-vga", }, + { }, +}; +MODULE_DEVICE_TABLE(of, con_vga_of_id_table); +#endif + +static struct platform_driver con_vga_driver = { + .probe = con_vga_probe, + .remove = con_vga_remove, + .id_table = con_vga_id_table, + .driver = { + .name = "con-vga", + .owner = THIS_MODULE, + .pm = &con_vga_dev_pm_ops, + .of_match_table = of_match_ptr(con_vga_of_id_table), + }, +}; + +module_platform_driver(con_vga_driver); + +MODULE_AUTHOR("Laurent Pinchart laurent.pinchart@ideasonboard.com"); +MODULE_DESCRIPTION("VGA Connector"); +MODULE_LICENSE("GPL");
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- arch/arm/mach-shmobile/clock-r8a7790.c | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/arch/arm/mach-shmobile/clock-r8a7790.c b/arch/arm/mach-shmobile/clock-r8a7790.c index d99b87b..7229f96 100644 --- a/arch/arm/mach-shmobile/clock-r8a7790.c +++ b/arch/arm/mach-shmobile/clock-r8a7790.c @@ -257,10 +257,15 @@ static struct clk_lookup lookups[] = {
/* MSTP */ CLKDEV_ICK_ID("lvds.0", "rcar-du-r8a7790", &mstp_clks[MSTP726]), + CLKDEV_ICK_ID("lvds.0", "feb00000.display", &mstp_clks[MSTP726]), CLKDEV_ICK_ID("lvds.1", "rcar-du-r8a7790", &mstp_clks[MSTP725]), + CLKDEV_ICK_ID("lvds.1", "feb00000.display", &mstp_clks[MSTP725]), CLKDEV_ICK_ID("du.0", "rcar-du-r8a7790", &mstp_clks[MSTP724]), + CLKDEV_ICK_ID("du.0", "feb00000.display", &mstp_clks[MSTP724]), CLKDEV_ICK_ID("du.1", "rcar-du-r8a7790", &mstp_clks[MSTP723]), + CLKDEV_ICK_ID("du.1", "feb00000.display", &mstp_clks[MSTP723]), CLKDEV_ICK_ID("du.2", "rcar-du-r8a7790", &mstp_clks[MSTP722]), + CLKDEV_ICK_ID("du.2", "feb00000.display", &mstp_clks[MSTP722]), CLKDEV_DEV_ID("sh-sci.0", &mstp_clks[MSTP204]), CLKDEV_DEV_ID("sh-sci.1", &mstp_clks[MSTP203]), CLKDEV_DEV_ID("sh-sci.2", &mstp_clks[MSTP206]),
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- arch/arm/boot/dts/r8a7790.dtsi | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+)
diff --git a/arch/arm/boot/dts/r8a7790.dtsi b/arch/arm/boot/dts/r8a7790.dtsi index ab0582c..b63f1a6 100644 --- a/arch/arm/boot/dts/r8a7790.dtsi +++ b/arch/arm/boot/dts/r8a7790.dtsi @@ -186,4 +186,37 @@ reg = <0 0xe6060000 0 0x250>; #gpio-range-cells = <3>; }; + + du: display@feb00000 { + compatible = "renesas,du-r8a7790"; + reg = <0 0xfeb00000 0 0x70000>, + <0 0xfeb90000 0 0x1c>, + <0 0xfeb94000 0 0x1c>; + reg-names = "core", "lvds.0", "lvds.1"; + interrupt-parent = <&gic>; + interrupts = <0 256 4 + 0 268 4 + 0 269 4>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + du_out_rgb: endpoint { + }; + }; + port@1 { + reg = <1>; + du_out_lvds0: endpoint { + }; + }; + port@2 { + reg = <2>; + du_out_lvds1: endpoint { + }; + }; + }; + }; };
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- arch/arm/mach-shmobile/board-marzen.c | 77 ++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 24 deletions(-)
diff --git a/arch/arm/mach-shmobile/board-marzen.c b/arch/arm/mach-shmobile/board-marzen.c index 6499f1a..be1b7cb 100644 --- a/arch/arm/mach-shmobile/board-marzen.c +++ b/arch/arm/mach-shmobile/board-marzen.c @@ -39,6 +39,8 @@ #include <linux/mmc/host.h> #include <linux/mmc/sh_mobile_sdhi.h> #include <linux/mfd/tmio.h> +#include <video/panel-dpi.h> +#include <video/videomode.h> #include <mach/r8a7779.h> #include <mach/common.h> #include <mach/irqs.h> @@ -174,35 +176,56 @@ static struct platform_device hspi_device = { * The panel only specifies the [hv]display and [hv]total values. The position * and width of the sync pulses don't matter, they're copied from VESA timings. */ -static struct rcar_du_encoder_data du_encoders[] = { +static const struct videomode marzen_panel_mode = { + .pixelclock = 65000000, + .hactive = 1024, + .hfront_porch = 24, + .hback_porch = 160, + .hsync_len = 136, + .vactive = 768, + .vfront_porch = 3, + .vback_porch = 29, + .vsync_len = 6, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_dpi_platform_data marzen_panel_data = { + .width = 210, + .height = 158, + .mode = &marzen_panel_mode, +}; + +static const struct display_entity_graph_data marzen_du_entities[] = { { - .type = RCAR_DU_ENCODER_VGA, - .output = RCAR_DU_OUTPUT_DPAD0, + .name = "adv7123", + .sources = (const struct display_entity_source_data[]) { + { + .name = "rcar-du", + .port = 0, + }, + }, }, { - .type = RCAR_DU_ENCODER_LVDS, - .output = RCAR_DU_OUTPUT_DPAD1, - .connector.lvds.panel = { - .width_mm = 210, - .height_mm = 158, - .mode = { - .clock = 65000, - .hdisplay = 1024, - .hsync_start = 1048, - .hsync_end = 1184, - .htotal = 1344, - .vdisplay = 768, - .vsync_start = 771, - .vsync_end = 777, - .vtotal = 806, - .flags = 0, + .name = "con-vga", + .sources = (const struct display_entity_source_data[]) { + { + .name = "adv7123", + .port = 1, }, }, + }, { + .name = "panel-dpi", + .sources = (const struct display_entity_source_data[]) { + { + .name = "rcar-du", + .port = 1, + }, + }, + }, { }, };
-static const struct rcar_du_platform_data du_pdata __initconst = { - .encoders = du_encoders, - .num_encoders = ARRAY_SIZE(du_encoders), +static const struct rcar_du_platform_data marzen_du_pdata __initconst = { + .graph = marzen_du_entities, };
static const struct resource du_resources[] __initconst = { @@ -217,8 +240,8 @@ static void __init marzen_add_du_device(void) .id = -1, .res = du_resources, .num_res = ARRAY_SIZE(du_resources), - .data = &du_pdata, - .size_data = sizeof(du_pdata), + .data = &marzen_du_pdata, + .size_data = sizeof(marzen_du_pdata), .dma_mask = DMA_BIT_MASK(32), };
@@ -327,6 +350,12 @@ static void __init marzen_init(void) r8a7779_add_standard_devices(); platform_add_devices(marzen_devices, ARRAY_SIZE(marzen_devices)); marzen_add_du_device(); + + platform_device_register_simple("adv7123", -1, NULL, 0); + platform_device_register_simple("con-vga", -1, NULL, 0); + platform_device_register_data(&platform_bus, "panel-dpi", -1, + &marzen_panel_data, + sizeof(marzen_panel_data)); }
static const char *marzen_boards_compat_dt[] __initdata = {
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- arch/arm/mach-shmobile/board-lager.c | 76 +++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 23 deletions(-)
diff --git a/arch/arm/mach-shmobile/board-lager.c b/arch/arm/mach-shmobile/board-lager.c index 75a01bc..d61b892 100644 --- a/arch/arm/mach-shmobile/board-lager.c +++ b/arch/arm/mach-shmobile/board-lager.c @@ -33,6 +33,8 @@ #include <linux/regulator/fixed.h> #include <linux/regulator/machine.h> #include <linux/sh_eth.h> +#include <video/panel-dpi.h> +#include <video/videomode.h> #include <mach/common.h> #include <mach/irqs.h> #include <mach/r8a7790.h> @@ -40,35 +42,56 @@ #include <asm/mach/arch.h>
/* DU */ -static struct rcar_du_encoder_data lager_du_encoders[] = { +static const struct videomode lager_panel_mode = { + .pixelclock = 65000000, + .hactive = 1024, + .hfront_porch = 24, + .hback_porch = 160, + .hsync_len = 136, + .vactive = 768, + .vfront_porch = 3, + .vback_porch = 29, + .vsync_len = 6, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_dpi_platform_data lager_panel_data = { + .width = 210, + .height = 158, + .mode = &lager_panel_mode, +}; + +static const struct display_entity_graph_data lager_du_entities[] = { { - .type = RCAR_DU_ENCODER_VGA, - .output = RCAR_DU_OUTPUT_DPAD0, + .name = "adv7123", + .sources = (const struct display_entity_source_data[]) { + { + .name = "rcar-du", + .port = 0, + }, + }, }, { - .type = RCAR_DU_ENCODER_NONE, - .output = RCAR_DU_OUTPUT_LVDS1, - .connector.lvds.panel = { - .width_mm = 210, - .height_mm = 158, - .mode = { - .clock = 65000, - .hdisplay = 1024, - .hsync_start = 1048, - .hsync_end = 1184, - .htotal = 1344, - .vdisplay = 768, - .vsync_start = 771, - .vsync_end = 777, - .vtotal = 806, - .flags = 0, + .name = "con-vga", + .sources = (const struct display_entity_source_data[]) { + { + .name = "adv7123", + .port = 1, }, }, + }, { + .name = "panel-dpi", + .sources = (const struct display_entity_source_data[]) { + { + .name = "rcar-du", + .port = 2, + }, + }, + }, { }, };
static const struct rcar_du_platform_data lager_du_pdata __initconst = { - .encoders = lager_du_encoders, - .num_encoders = ARRAY_SIZE(lager_du_encoders), + .graph = lager_du_entities, };
static const struct resource du_resources[] __initconst = { @@ -87,8 +110,8 @@ static void __init lager_add_du_device(void) .id = -1, .res = du_resources, .num_res = ARRAY_SIZE(du_resources), - .data = &du_resources, - .size_data = sizeof(du_resources), + .data = &lager_du_pdata, + .size_data = sizeof(lager_du_pdata), .dma_mask = DMA_BIT_MASK(32), };
@@ -202,6 +225,7 @@ static void __init lager_add_standard_devices(void) r8a7790_pinmux_init();
r8a7790_add_standard_devices(); + platform_device_register_data(&platform_bus, "leds-gpio", -1, &lager_leds_pdata, sizeof(lager_leds_pdata)); @@ -220,6 +244,12 @@ static void __init lager_add_standard_devices(void) ðer_pdata, sizeof(ether_pdata));
lager_add_du_device(); + + platform_device_register_simple("adv7123", -1, NULL, 0); + platform_device_register_simple("con-vga", -1, NULL, 0); + platform_device_register_data(&platform_bus, "panel-dpi", -1, + &lager_panel_data, + sizeof(lager_panel_data)); }
static const char * const lager_boards_compat_dt[] __initconst = {
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- arch/arm/boot/dts/r8a7790-lager-reference.dts | 92 +++++++++++++++++++++++++++ 1 file changed, 92 insertions(+)
diff --git a/arch/arm/boot/dts/r8a7790-lager-reference.dts b/arch/arm/boot/dts/r8a7790-lager-reference.dts index d9a25d5..ba2469b 100644 --- a/arch/arm/boot/dts/r8a7790-lager-reference.dts +++ b/arch/arm/boot/dts/r8a7790-lager-reference.dts @@ -42,6 +42,98 @@ gpios = <&gpio5 17 GPIO_ACTIVE_HIGH>; }; }; + + adv7123 { + compatible = "adi,adv7123"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + adv7123_in: endpoint { + remote-endpoint = <&du_out_rgb>; + }; + }; + port@1 { + reg = <1>; + adv7123_out: endpoint { + remote-endpoint = <&con_vga_in>; + }; + }; + }; + }; + + con-vga { + compatible = "con-vga"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + con_vga_in: endpoint { + remote-endpoint = <&adv7123_out>; + }; + }; + }; + }; + + panel-dpi { + compatible = "panel-dpi"; + + width-mm = <210>; + height-mm = <158>; + + display-timings { + timing { + /* 1024x768 @65Hz */ + clock-frequency = <65000000>; + hactive = <1024>; + vactive = <768>; + hsync-len = <136>; + hfront-porch = <20>; + hback-porch = <160>; + vfront-porch = <3>; + vback-porch = <29>; + vsync-len = <6>; + }; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + panel_in: endpoint { + remote-endpoint = <&du_out_lvds1>; + }; + }; + }; + }; +}; + +&du { + pinctrl-0 = <&du_pins>; + pinctrl-names = "default"; +}; + +&du_out_rgb { + remote-endpoint = <&adv7123_in>; +}; + +&du_out_lvds1 { + remote-endpoint = <&panel_in>; +}; + +&pfc { + du_pins: du { + renesas,groups = "du_rgb666", "du_sync_1", "du_clk_out_0"; + renesas,function = "du"; + }; };
&pfc {
Signed-off-by: Laurent Pinchart laurent.pinchart+renesas@ideasonboard.com --- drivers/gpu/drm/rcar-du/Kconfig | 3 +- drivers/gpu/drm/rcar-du/Makefile | 7 +- drivers/gpu/drm/rcar-du/rcar_du_connector.c | 164 ++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_connector.h | 36 ++++ drivers/gpu/drm/rcar-du/rcar_du_crtc.h | 2 +- drivers/gpu/drm/rcar-du/rcar_du_drv.c | 279 ++++++++++++++++++++++++---- drivers/gpu/drm/rcar-du/rcar_du_drv.h | 28 ++- drivers/gpu/drm/rcar-du/rcar_du_encoder.c | 87 ++++----- drivers/gpu/drm/rcar-du/rcar_du_encoder.h | 22 +-- drivers/gpu/drm/rcar-du/rcar_du_kms.c | 116 ++++++++++-- drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c | 131 ------------- drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h | 25 --- drivers/gpu/drm/rcar-du/rcar_du_vgacon.c | 96 ---------- drivers/gpu/drm/rcar-du/rcar_du_vgacon.h | 23 --- include/linux/platform_data/rcar-du.h | 55 +----- 15 files changed, 611 insertions(+), 463 deletions(-) create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_connector.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_connector.h delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vgacon.c delete mode 100644 drivers/gpu/drm/rcar-du/rcar_du_vgacon.h
diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig index c590cd9..a54eeba 100644 --- a/drivers/gpu/drm/rcar-du/Kconfig +++ b/drivers/gpu/drm/rcar-du/Kconfig @@ -1,9 +1,10 @@ config DRM_RCAR_DU tristate "DRM Support for R-Car Display Unit" - depends on DRM && ARM + depends on DRM && ARM && OF select DRM_KMS_HELPER select DRM_KMS_CMA_HELPER select DRM_GEM_CMA_HELPER + select VIDEOMODE_HELPERS help Choose this option if you have an R-Car chipset. If M is selected the module will be called rcar-du-drm. diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile index 12b8d44..3ac8566 100644 --- a/drivers/gpu/drm/rcar-du/Makefile +++ b/drivers/gpu/drm/rcar-du/Makefile @@ -1,11 +1,10 @@ -rcar-du-drm-y := rcar_du_crtc.o \ +rcar-du-drm-y := rcar_du_connector.o \ + rcar_du_crtc.o \ rcar_du_drv.o \ rcar_du_encoder.o \ rcar_du_group.o \ rcar_du_kms.o \ - rcar_du_lvdscon.o \ - rcar_du_plane.o \ - rcar_du_vgacon.o + rcar_du_plane.o
rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_connector.c b/drivers/gpu/drm/rcar-du/rcar_du_connector.c new file mode 100644 index 0000000..a09aada --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_connector.c @@ -0,0 +1,164 @@ +/* + * rcar_du_connector.c -- R-Car Display Unit Connector + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 <linux/export.h> +#include <video/videomode.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> + +#include "rcar_du_drv.h" +#include "rcar_du_connector.h" +#include "rcar_du_encoder.h" +#include "rcar_du_kms.h" + +static int rcar_du_connector_get_modes(struct drm_connector *connector) +{ + struct rcar_du_connector *rcon = to_rcar_connector(connector); + struct display_entity *entity = to_display_entity(rcon->pad->entity); + const struct videomode *modes; + int ret; + int i; + + ret = display_entity_get_modes(entity, rcon->pad->index, &modes); + if (ret <= 0) + return drm_add_modes_noedid(connector, 1280, 768); + + for (i = 0; i < ret; ++i) { + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + if (mode == NULL) + break; + + mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; + drm_display_mode_from_videomode(&modes[i], mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int rcar_du_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static struct drm_encoder * +rcar_du_connector_best_encoder(struct drm_connector *connector) +{ + struct rcar_du_connector *rcon = to_rcar_connector(connector); + + return &rcon->encoder->encoder; +} + +static const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = rcar_du_connector_get_modes, + .mode_valid = rcar_du_connector_mode_valid, + .best_encoder = rcar_du_connector_best_encoder, +}; + +static void rcar_du_connector_destroy(struct drm_connector *connector) +{ + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); +} + +static enum drm_connector_status +rcar_du_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = rcar_du_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = rcar_du_connector_destroy, +}; + +struct rcar_du_connector * +rcar_du_connector_create(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc, struct media_pad *pad) +{ + struct display_entity *entity = to_display_entity(pad->entity); + struct display_entity_interface_params params; + struct rcar_du_connector *rcon; + struct drm_connector *connector; + int connector_type; + unsigned int width; + unsigned int height; + int ret; + + rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL); + if (rcon == NULL) + return ERR_PTR(-ENOMEM); + + rcon->encoder = renc; + rcon->pad = pad; + + connector = &rcon->connector; + + ret = display_entity_get_size(entity, &width, &height); + if (ret < 0) { + connector->display_info.width_mm = 0; + connector->display_info.height_mm = 0; + } else { + connector->display_info.width_mm = width; + connector->display_info.height_mm = height; + } + + ret = display_entity_get_params(entity, pad->index, ¶ms); + if (ret < 0) { + dev_dbg(rcdu->dev, + "failed to retrieve connector %s parameters\n", + entity->name); + return ERR_PTR(ret); + } + + switch (params.type) { + case DISPLAY_ENTITY_INTERFACE_VGA: + connector_type = DRM_MODE_CONNECTOR_VGA; + break; + case DISPLAY_ENTITY_INTERFACE_LVDS: + connector_type = DRM_MODE_CONNECTOR_LVDS; + break; + default: + connector_type = DRM_MODE_CONNECTOR_Unknown; + break; + } + + ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, + connector_type); + if (ret < 0) + return ERR_PTR(ret); + + drm_connector_helper_add(connector, &connector_helper_funcs); + ret = drm_sysfs_connector_add(connector); + if (ret < 0) + return ERR_PTR(ret); + + drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); + drm_object_property_set_value(&connector->base, + rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); + + ret = drm_mode_connector_attach_encoder(connector, &renc->encoder); + if (ret < 0) + return ERR_PTR(ret); + + connector->encoder = &renc->encoder; + + return rcon; +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_connector.h b/drivers/gpu/drm/rcar-du/rcar_du_connector.h new file mode 100644 index 0000000..b9a0833 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_connector.h @@ -0,0 +1,36 @@ +/* + * rcar_du_connector.h -- R-Car Display Unit Connector + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __RCAR_DU_CONNECTOR_H__ +#define __RCAR_DU_CONNECTOR_H__ + +#include <drm/drm_crtc.h> + +struct media_pad; +struct rcar_du_device; +struct rcar_du_encoder; + +struct rcar_du_connector { + struct drm_connector connector; + struct rcar_du_encoder *encoder; + struct media_pad *pad; +}; + +#define to_rcar_connector(c) \ + container_of(c, struct rcar_du_connector, connector) + +struct rcar_du_connector * +rcar_du_connector_create(struct rcar_du_device *rcdu, + struct rcar_du_encoder *renc, struct media_pad *pad); + +#endif /* __RCAR_DU_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h index 43e7575..4e8bfff 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h @@ -15,11 +15,11 @@ #define __RCAR_DU_CRTC_H__
#include <linux/mutex.h> -#include <linux/platform_data/rcar-du.h>
#include <drm/drmP.h> #include <drm/drm_crtc.h>
+enum rcar_du_output; struct rcar_du_group; struct rcar_du_plane;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c index 0a9f1bb..eda9ca9 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c @@ -15,6 +15,8 @@ #include <linux/io.h> #include <linux/mm.h> #include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_data/rcar-du.h> #include <linux/platform_device.h> #include <linux/pm.h> #include <linux/slab.h> @@ -30,6 +32,153 @@ #include "rcar_du_regs.h"
/* ----------------------------------------------------------------------------- + * Display Entities + */ + +static int rcdu_entity_set_stream(struct display_entity *ent, unsigned int port, + enum display_entity_stream_state state) +{ + return 0; +} + +static const struct display_entity_video_ops rcdu_entity_video_ops = { + .set_stream = rcdu_entity_set_stream, +}; + +static const struct display_entity_ops rcdu_entity_ops = { + .video = &rcdu_entity_video_ops, +}; + +static struct media_pad *rcar_du_find_remote_pad(struct rcar_du_device *rcdu, + unsigned int local_pad) +{ + struct display_entity *entity; + unsigned int i; + + list_for_each_entry(entity, &rcdu->notifier.done, list) { + const struct display_entity_graph_data *graph = + entity->match->data; + const struct display_entity_source_data *source = + graph->sources; + + for (i = 0; i < entity->entity.num_pads; ++i) { + struct media_pad *pad = &entity->entity.pads[i]; + + if (pad->flags & MEDIA_PAD_FL_SOURCE) + continue; + + if (!strcmp(source->name, "rcar-du") && + source->port == local_pad) + return pad; + } + } + + return NULL; +} + +static int rcar_du_create_local_links(struct rcar_du_device *rcdu) +{ + u32 link_flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED; + unsigned int i; + + for (i = 0; i < rcdu->entity.entity.num_pads; ++i) { + struct media_pad *pad; + int ret; + + pad = rcar_du_find_remote_pad(rcdu, i); + if (pad == NULL) + continue; + + ret = media_entity_create_link(&rcdu->entity.entity, i, + pad->entity, pad->index, + link_flags); + if (ret < 0) { + dev_err(rcdu->dev, + "failed to create %s:%u -> %s:%u link\n", + rcdu->entity.entity.name, i, + pad->entity->name, pad->index); + return ret; + } + } + + return 0; +}; + +static int rcar_du_graph_init(struct rcar_du_device *rcdu) +{ + struct display_entity *entity; + unsigned int num_pads; + unsigned int i; + int ret; + + /* Count the number of output pads and initialize the DU entity. */ + for (i = 0, num_pads = 0; rcdu->info->routes[i].output; ++i) + num_pads++; + + rcdu->entity.dev = rcdu->dev; + rcdu->entity.ops = &rcdu_entity_ops; + strcpy(rcdu->entity.name, "R-Car DU"); + + ret = display_entity_init(&rcdu->entity, 0, num_pads); + if (ret < 0) + return ret; + + /* Create links between all entities. In the non-DT case, this is a two + * steps process, we first create links between all external entities, + * and then between the DU entity and the external entities. + */ + if (rcdu->dev->of_node) { + ret = display_of_entity_link_graph(rcdu->dev, + &rcdu->notifier.done, + &rcdu->entity); + } else { + ret = display_entity_link_graph(rcdu->dev, + &rcdu->notifier.done); + if (!ret) + ret = rcar_du_create_local_links(rcdu); + } + + if (ret < 0) { + dev_err(rcdu->dev, "unable to link graph\n"); + return ret; + } + + /* Register the media device and all entities. */ + rcdu->mdev.dev = rcdu->dev; + strlcpy(rcdu->mdev.model, dev_name(rcdu->dev), + sizeof(rcdu->mdev.model)); + + ret = media_device_register(&rcdu->mdev); + if (ret < 0) + return ret; + + list_for_each_entry(entity, &rcdu->notifier.done, list) { + ret = display_entity_register(&rcdu->mdev, entity); + if (ret < 0) + return ret; + } + + ret = display_entity_register(&rcdu->mdev, &rcdu->entity); + if (ret < 0) + return ret; + + return 0; +} + +static void rcar_du_graph_cleanup(struct rcar_du_device *rcdu) +{ + struct display_entity *entity; + + list_for_each_entry(entity, &rcdu->notifier.done, list) + display_entity_unregister(entity); + + display_entity_unregister(&rcdu->entity); + display_entity_cleanup(&rcdu->entity); + + media_device_unregister(&rcdu->mdev); +} + +/* ----------------------------------------------------------------------------- * DRM operations */
@@ -44,6 +193,8 @@ static int rcar_du_unload(struct drm_device *dev) drm_mode_config_cleanup(dev); drm_vblank_cleanup(dev);
+ rcar_du_graph_cleanup(rcdu); + dev->irq_enabled = 0; dev->dev_private = NULL;
@@ -53,25 +204,10 @@ static int rcar_du_unload(struct drm_device *dev) static int rcar_du_load(struct drm_device *dev, unsigned long flags) { struct platform_device *pdev = dev->platformdev; - struct rcar_du_platform_data *pdata = pdev->dev.platform_data; - struct rcar_du_device *rcdu; + struct rcar_du_device *rcdu = platform_get_drvdata(pdev); struct resource *mem; int ret;
- if (pdata == NULL) { - dev_err(dev->dev, "no platform data\n"); - return -ENODEV; - } - - rcdu = devm_kzalloc(&pdev->dev, sizeof(*rcdu), GFP_KERNEL); - if (rcdu == NULL) { - dev_err(dev->dev, "failed to allocate private data\n"); - return -ENOMEM; - } - - rcdu->dev = &pdev->dev; - rcdu->pdata = pdata; - rcdu->info = (struct rcar_du_device_info *)pdev->id_entry->driver_data; rcdu->ddev = dev; dev->dev_private = rcdu;
@@ -81,6 +217,13 @@ static int rcar_du_load(struct drm_device *dev, unsigned long flags) if (IS_ERR(rcdu->mmio)) return PTR_ERR(rcdu->mmio);
+ /* Display Entities */ + ret = rcar_du_graph_init(rcdu); + if (ret < 0) { + dev_err(&pdev->dev, "failed to initialize display entities\n"); + goto done; + } + /* DRM/KMS objects */ ret = rcar_du_modeset_init(rcdu); if (ret < 0) { @@ -97,8 +240,6 @@ static int rcar_du_load(struct drm_device *dev, unsigned long flags)
dev->irq_enabled = 1;
- platform_set_drvdata(pdev, rcdu); - done: if (ret) rcar_du_unload(dev); @@ -218,33 +359,23 @@ static const struct dev_pm_ops rcar_du_pm_ops = { * Platform driver */
-static int rcar_du_probe(struct platform_device *pdev) -{ - return drm_platform_init(&rcar_du_driver, pdev); -} - -static int rcar_du_remove(struct platform_device *pdev) -{ - drm_platform_exit(&rcar_du_driver, pdev); - - return 0; -} - static const struct rcar_du_device_info rcar_du_r8a7779_info = { .features = 0, .num_crtcs = 2, - .routes = { + .routes = (const struct rcar_du_output_routing[]) { /* R8A7779 has two RGB outputs and one (currently unsupported) * TCON output. */ - [RCAR_DU_OUTPUT_DPAD0] = { + { + .output = RCAR_DU_OUTPUT_DPAD0, .possible_crtcs = BIT(0), .encoder_type = DRM_MODE_ENCODER_NONE, - }, - [RCAR_DU_OUTPUT_DPAD1] = { + }, { + .output = RCAR_DU_OUTPUT_DPAD1, .possible_crtcs = BIT(1) | BIT(0), .encoder_type = DRM_MODE_ENCODER_NONE, }, + { }, }, .num_lvds = 0, }; @@ -253,22 +384,24 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = { .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK | RCAR_DU_FEATURE_ALIGN_128B | RCAR_DU_FEATURE_DEFR8, .num_crtcs = 3, - .routes = { + .routes = (const struct rcar_du_output_routing[]) { /* R8A7790 has one RGB output, two LVDS outputs and one * (currently unsupported) TCON output. */ - [RCAR_DU_OUTPUT_DPAD0] = { + { + .output = RCAR_DU_OUTPUT_DPAD0, .possible_crtcs = BIT(2) | BIT(1) | BIT(0), .encoder_type = DRM_MODE_ENCODER_NONE, - }, - [RCAR_DU_OUTPUT_LVDS0] = { + }, { + .output = RCAR_DU_OUTPUT_LVDS0, .possible_crtcs = BIT(0), .encoder_type = DRM_MODE_ENCODER_LVDS, - }, - [RCAR_DU_OUTPUT_LVDS1] = { + }, { + .output = RCAR_DU_OUTPUT_LVDS1, .possible_crtcs = BIT(2) | BIT(1), .encoder_type = DRM_MODE_ENCODER_LVDS, }, + { }, }, .num_lvds = 2, }; @@ -278,9 +411,72 @@ static const struct platform_device_id rcar_du_id_table[] = { { "rcar-du-r8a7790", (kernel_ulong_t)&rcar_du_r8a7790_info }, { } }; - MODULE_DEVICE_TABLE(platform, rcar_du_id_table);
+static const struct of_device_id rcar_du_of_table[] = { + { .compatible = "renesas,du-r8a7779", .data = &rcar_du_r8a7779_info }, + { .compatible = "renesas,du-r8a7790", .data = &rcar_du_r8a7790_info }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_du_of_table); + +static int rcar_du_notifier_complete(struct display_entity_notifier *notifier) +{ + struct platform_device *pdev = to_platform_device(notifier->dev); + + return drm_platform_init(&rcar_du_driver, pdev); +} + +static int rcar_du_probe(struct platform_device *pdev) +{ + struct rcar_du_platform_data *pdata = pdev->dev.platform_data; + struct device_node *np = pdev->dev.of_node; + struct rcar_du_device *rcdu; + int ret; + + rcdu = devm_kzalloc(&pdev->dev, sizeof(*rcdu), GFP_KERNEL); + if (rcdu == NULL) + return -ENOMEM; + + rcdu->dev = &pdev->dev; + + if (np) + rcdu->info = of_match_device(rcar_du_of_table, rcdu->dev)->data; + else + rcdu->info = (void *)platform_get_device_id(pdev)->driver_data; + + platform_set_drvdata(pdev, rcdu); + + rcdu->notifier.dev = rcdu->dev; + rcdu->notifier.complete = rcar_du_notifier_complete; + + if (np) { + ret = display_of_entity_build_notifier(&rcdu->notifier, np); + } else if (pdata) { + ret = display_entity_build_notifier(&rcdu->notifier, + pdata->graph); + } else { + dev_err(&pdev->dev, "no platform data"); + ret = -ENXIO; + } + + if (ret < 0) + return ret; + + return display_entity_register_notifier(&rcdu->notifier); +} + +static int rcar_du_remove(struct platform_device *pdev) +{ + struct rcar_du_device *rcdu = platform_get_drvdata(pdev); + + display_entity_unregister_notifier(&rcdu->notifier); + + drm_platform_exit(&rcar_du_driver, pdev); + + return 0; +} + static struct platform_driver rcar_du_platform_driver = { .probe = rcar_du_probe, .remove = rcar_du_remove, @@ -288,6 +484,7 @@ static struct platform_driver rcar_du_platform_driver = { .owner = THIS_MODULE, .name = "rcar-du", .pm = &rcar_du_pm_ops, + .of_match_table = of_match_ptr(rcar_du_of_table), }, .id_table = rcar_du_id_table, }; diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h index 65d2d63..49338e1 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h @@ -15,7 +15,8 @@ #define __RCAR_DU_DRV_H__
#include <linux/kernel.h> -#include <linux/platform_data/rcar-du.h> +#include <media/media-device.h> +#include <video/display.h>
#include "rcar_du_crtc.h" #include "rcar_du_group.h" @@ -31,16 +32,28 @@ struct rcar_du_lvdsenc; #define RCAR_DU_FEATURE_ALIGN_128B (1 << 1) /* Align pitches to 128 bytes */ #define RCAR_DU_FEATURE_DEFR8 (1 << 2) /* Has DEFR8 register */
+enum rcar_du_output { + RCAR_DU_OUTPUT_NONE, + RCAR_DU_OUTPUT_DPAD0, + RCAR_DU_OUTPUT_DPAD1, + RCAR_DU_OUTPUT_LVDS0, + RCAR_DU_OUTPUT_LVDS1, + RCAR_DU_OUTPUT_TCON, + RCAR_DU_OUTPUT_MAX, +}; + /* * struct rcar_du_output_routing - Output routing specification + * @output: DU output * @possible_crtcs: bitmask of possible CRTCs for the output * @encoder_type: DRM type of the internal encoder associated with the output * * The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). Output routing data - * specify the valid SoC outputs, which CRTCs can drive the output, and the type - * of in-SoC encoder for the output. + * specify an SoC output, which CRTCs can drive it, and the type of in-SoC + * encoder for the output. */ struct rcar_du_output_routing { + enum rcar_du_output output; unsigned int possible_crtcs; unsigned int encoder_type; }; @@ -49,23 +62,26 @@ struct rcar_du_output_routing { * struct rcar_du_device_info - DU model-specific information * @features: device features (RCAR_DU_FEATURE_*) * @num_crtcs: total number of CRTCs - * @routes: array of CRTC to output routes, indexed by output (RCAR_DU_OUTPUT_*) + * @routes: array of CRTC to output routes, indexed by port number * @num_lvds: number of internal LVDS encoders */ struct rcar_du_device_info { unsigned int features; unsigned int num_crtcs; - struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX]; + const struct rcar_du_output_routing *routes; unsigned int num_lvds; };
struct rcar_du_device { struct device *dev; - const struct rcar_du_platform_data *pdata; const struct rcar_du_device_info *info;
void __iomem *mmio;
+ struct media_device mdev; + struct display_entity entity; + struct display_entity_notifier notifier; + struct drm_device *ddev; struct drm_fbdev_cma *fbdev;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c index 3daa7a1..9991fb0 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c @@ -20,25 +20,7 @@ #include "rcar_du_drv.h" #include "rcar_du_encoder.h" #include "rcar_du_kms.h" -#include "rcar_du_lvdscon.h" #include "rcar_du_lvdsenc.h" -#include "rcar_du_vgacon.h" - -/* ----------------------------------------------------------------------------- - * Common connector functions - */ - -struct drm_encoder * -rcar_du_connector_best_encoder(struct drm_connector *connector) -{ - struct rcar_du_connector *rcon = to_rcar_connector(connector); - - return &rcon->encoder->encoder; -} - -/* ----------------------------------------------------------------------------- - * Encoder - */
static void rcar_du_encoder_dpms(struct drm_encoder *encoder, int mode) { @@ -139,10 +121,9 @@ static const struct drm_encoder_funcs encoder_funcs = { .destroy = drm_encoder_cleanup, };
-int rcar_du_encoder_init(struct rcar_du_device *rcdu, - enum rcar_du_encoder_type type, - enum rcar_du_output output, - const struct rcar_du_encoder_data *data) +struct rcar_du_encoder * +rcar_du_encoder_create(struct rcar_du_device *rcdu, unsigned int port, + struct media_pad *pad) { struct rcar_du_encoder *renc; unsigned int encoder_type; @@ -150,11 +131,13 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL); if (renc == NULL) - return -ENOMEM; + return ERR_PTR(-ENOMEM);
- renc->output = output; + renc->port = port; + renc->output = rcdu->info->routes[port].output;
- switch (output) { + /* Do we have an internal LVDS encoder? */ + switch (renc->output) { case RCAR_DU_OUTPUT_LVDS0: renc->lvds = rcdu->lvds[0]; break; @@ -167,36 +150,44 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, break; }
- switch (type) { - case RCAR_DU_ENCODER_VGA: - encoder_type = DRM_MODE_ENCODER_DAC; - break; - case RCAR_DU_ENCODER_LVDS: - encoder_type = DRM_MODE_ENCODER_LVDS; - break; - case RCAR_DU_ENCODER_NONE: - default: + /* Find the encoder type. */ + if (pad) { + struct display_entity *entity = to_display_entity(pad->entity); + struct display_entity_interface_params params; + + ret = display_entity_get_params(entity, pad->index, ¶ms); + if (ret < 0) { + dev_dbg(rcdu->dev, + "failed to retrieve encoder %s parameters\n", + entity->name); + return ERR_PTR(ret); + } + + switch (params.type) { + case DISPLAY_ENTITY_INTERFACE_DPI: + case DISPLAY_ENTITY_INTERFACE_DBI: + default: + encoder_type = DRM_MODE_ENCODER_NONE; + break; + case DISPLAY_ENTITY_INTERFACE_LVDS: + encoder_type = DRM_MODE_ENCODER_DAC; + break; + case DISPLAY_ENTITY_INTERFACE_VGA: + encoder_type = DRM_MODE_ENCODER_DAC; + break; + } + } else { /* No external encoder, use the internal encoder type. */ - encoder_type = rcdu->info->routes[output].encoder_type; - break; + encoder_type = rcdu->info->routes[port].encoder_type; }
+ /* Initialize the encoder. */ ret = drm_encoder_init(rcdu->ddev, &renc->encoder, &encoder_funcs, encoder_type); if (ret < 0) - return ret; + return ERR_PTR(ret);
drm_encoder_helper_add(&renc->encoder, &encoder_helper_funcs);
- switch (encoder_type) { - case DRM_MODE_ENCODER_LVDS: - return rcar_du_lvds_connector_init(rcdu, renc, - &data->connector.lvds.panel); - - case DRM_MODE_ENCODER_DAC: - return rcar_du_vga_connector_init(rcdu, renc); - - default: - return -EINVAL; - } + return renc; } diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h index 0e5a65e..7a19d5b 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h @@ -14,15 +14,15 @@ #ifndef __RCAR_DU_ENCODER_H__ #define __RCAR_DU_ENCODER_H__
-#include <linux/platform_data/rcar-du.h> - #include <drm/drm_crtc.h>
+struct media_pad; struct rcar_du_device; struct rcar_du_lvdsenc;
struct rcar_du_encoder { struct drm_encoder encoder; + unsigned int port; enum rcar_du_output output; struct rcar_du_lvdsenc *lvds; }; @@ -30,20 +30,8 @@ struct rcar_du_encoder { #define to_rcar_encoder(e) \ container_of(e, struct rcar_du_encoder, encoder)
-struct rcar_du_connector { - struct drm_connector connector; - struct rcar_du_encoder *encoder; -}; - -#define to_rcar_connector(c) \ - container_of(c, struct rcar_du_connector, connector) - -struct drm_encoder * -rcar_du_connector_best_encoder(struct drm_connector *connector); - -int rcar_du_encoder_init(struct rcar_du_device *rcdu, - enum rcar_du_encoder_type type, - enum rcar_du_output output, - const struct rcar_du_encoder_data *data); +struct rcar_du_encoder * +rcar_du_encoder_create(struct rcar_du_device *rcdu, unsigned int port, + struct media_pad *pad);
#endif /* __RCAR_DU_ENCODER_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c index b31ac08..2dc9e80 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c @@ -17,6 +17,7 @@ #include <drm/drm_fb_cma_helper.h> #include <drm/drm_gem_cma_helper.h>
+#include "rcar_du_connector.h" #include "rcar_du_crtc.h" #include "rcar_du_drv.h" #include "rcar_du_encoder.h" @@ -179,6 +180,89 @@ static const struct drm_mode_config_funcs rcar_du_mode_config_funcs = { .output_poll_changed = rcar_du_output_poll_changed, };
+/* ----------------------------------------------------------------------------- + * Pipeline and Mode Setting Initialization + */ + +static int rcar_du_pipe_init(struct rcar_du_device *rcdu, unsigned int port, + struct media_pad *remote) +{ + struct media_pad *pad_encoder = NULL; + struct media_pad *pad_connector = remote; + struct rcar_du_encoder *renc; + struct rcar_du_connector *rcon; + struct media_entity *entity; + + /* We need to expose one KMS encoder and one KMS connector for an + * arbitrarily long chain of external entities. Walk the chain and map + * the last entity to the connector, and the next-to-last entity to the + * encoder. + */ + + /* Start at the entity connected to the DU output. */ + entity = remote->entity; + + dev_dbg(rcdu->dev, "%s: starting at entity %s pad %u\n", __func__, + entity->name, remote->index); + + while (1) { + struct media_link *next = NULL; + unsigned int i; + + /* Search the entity for an output link. As we only support + * linear pipelines, return an error if multiple output links + * are found. + */ + dev_dbg(rcdu->dev, + "%s: searching for an output link on entity %s\n", + __func__, entity->name); + + for (i = 0; i < entity->num_links; ++i) { + struct media_link *link = &entity->links[i]; + + if (link->source->entity != entity) + continue; + + if (next) + return -EPIPE; + + next = link; + } + + if (next == NULL) { + dev_dbg(rcdu->dev, "%s: not output link found\n", + __func__); + break; + } + + dev_dbg(rcdu->dev, + "%s: output link %s:%u -> %s:%u found\n", __func__, + next->source->entity->name, next->source->index, + next->sink->entity->name, next->sink->index); + + pad_encoder = next->source; + pad_connector = next->sink; + entity = pad_connector->entity; + } + + dev_dbg(rcdu->dev, + "%s: encoder pad %s/%u connector pad %s/%u\n", __func__, + pad_encoder ? pad_encoder->entity->name : NULL, + pad_encoder ? pad_encoder->index : 0, + pad_connector->entity->name, pad_connector->index); + + /* Create the encoder and connector. */ + renc = rcar_du_encoder_create(rcdu, port, pad_encoder); + if (IS_ERR(renc)) + return PTR_ERR(renc); + + rcon = rcar_du_connector_create(rcdu, renc, pad_connector); + if (IS_ERR(rcon)) + return PTR_ERR(rcon); + + return 0; +} + int rcar_du_modeset_init(struct rcar_du_device *rcdu) { static const unsigned int mmio_offsets[] = { @@ -188,6 +272,7 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) struct drm_device *dev = rcdu->ddev; struct drm_encoder *encoder; struct drm_fbdev_cma *fbdev; + unsigned int num_encoders = 0; unsigned int num_groups; unsigned int i; int ret; @@ -226,29 +311,26 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) return ret; }
- /* Initialize the encoders. */ + /* Initialize the internal encoders. */ ret = rcar_du_lvdsenc_init(rcdu); if (ret < 0) return ret;
- for (i = 0; i < rcdu->pdata->num_encoders; ++i) { - const struct rcar_du_encoder_data *pdata = - &rcdu->pdata->encoders[i]; - const struct rcar_du_output_routing *route = - &rcdu->info->routes[pdata->output]; + /* Create an encoder and a connector for each output connected to + * external entities. + */ + for (i = 0; i < rcdu->entity.entity.num_pads; ++i) { + struct media_pad *pad;
- if (pdata->type == RCAR_DU_ENCODER_UNUSED) + pad = media_entity_remote_pad(&rcdu->entity.entity.pads[i]); + if (pad == NULL) continue;
- if (pdata->output >= RCAR_DU_OUTPUT_MAX || - route->possible_crtcs == 0) { - dev_warn(rcdu->dev, - "encoder %u references unexisting output %u, skipping\n", - i, pdata->output); - continue; - } + ret = rcar_du_pipe_init(rcdu, i, pad); + if (ret < 0) + return ret;
- rcar_du_encoder_init(rcdu, pdata->type, pdata->output, pdata); + num_encoders++; }
/* Set the possible CRTCs and possible clones. There's always at least @@ -258,10 +340,10 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { struct rcar_du_encoder *renc = to_rcar_encoder(encoder); const struct rcar_du_output_routing *route = - &rcdu->info->routes[renc->output]; + &rcdu->info->routes[renc->port];
encoder->possible_crtcs = route->possible_crtcs; - encoder->possible_clones = (1 << rcdu->pdata->num_encoders) - 1; + encoder->possible_clones = (1 << num_encoders) - 1; }
/* Now that the CRTCs have been initialized register the planes. */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c deleted file mode 100644 index 4f3ba93..0000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * rcar_du_lvdscon.c -- R-Car Display Unit LVDS Connector - * - * Copyright (C) 2013 Renesas Corporation - * - * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 <drm/drm_crtc.h> -#include <drm/drm_crtc_helper.h> - -#include "rcar_du_drv.h" -#include "rcar_du_encoder.h" -#include "rcar_du_kms.h" -#include "rcar_du_lvdscon.h" - -struct rcar_du_lvds_connector { - struct rcar_du_connector connector; - - const struct rcar_du_panel_data *panel; -}; - -#define to_rcar_lvds_connector(c) \ - container_of(c, struct rcar_du_lvds_connector, connector.connector) - -static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector) -{ - struct rcar_du_lvds_connector *lvdscon = - to_rcar_lvds_connector(connector); - struct drm_display_mode *mode; - - mode = drm_mode_create(connector->dev); - if (mode == NULL) - return 0; - - mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; - mode->clock = lvdscon->panel->mode.clock; - mode->hdisplay = lvdscon->panel->mode.hdisplay; - mode->hsync_start = lvdscon->panel->mode.hsync_start; - mode->hsync_end = lvdscon->panel->mode.hsync_end; - mode->htotal = lvdscon->panel->mode.htotal; - mode->vdisplay = lvdscon->panel->mode.vdisplay; - mode->vsync_start = lvdscon->panel->mode.vsync_start; - mode->vsync_end = lvdscon->panel->mode.vsync_end; - mode->vtotal = lvdscon->panel->mode.vtotal; - mode->flags = lvdscon->panel->mode.flags; - - drm_mode_set_name(mode); - drm_mode_probed_add(connector, mode); - - return 1; -} - -static int rcar_du_lvds_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - return MODE_OK; -} - -static const struct drm_connector_helper_funcs connector_helper_funcs = { - .get_modes = rcar_du_lvds_connector_get_modes, - .mode_valid = rcar_du_lvds_connector_mode_valid, - .best_encoder = rcar_du_connector_best_encoder, -}; - -static void rcar_du_lvds_connector_destroy(struct drm_connector *connector) -{ - drm_sysfs_connector_remove(connector); - drm_connector_cleanup(connector); -} - -static enum drm_connector_status -rcar_du_lvds_connector_detect(struct drm_connector *connector, bool force) -{ - return connector_status_connected; -} - -static const struct drm_connector_funcs connector_funcs = { - .dpms = drm_helper_connector_dpms, - .detect = rcar_du_lvds_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = rcar_du_lvds_connector_destroy, -}; - -int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, - struct rcar_du_encoder *renc, - const struct rcar_du_panel_data *panel) -{ - struct rcar_du_lvds_connector *lvdscon; - struct drm_connector *connector; - int ret; - - lvdscon = devm_kzalloc(rcdu->dev, sizeof(*lvdscon), GFP_KERNEL); - if (lvdscon == NULL) - return -ENOMEM; - - lvdscon->panel = panel; - - connector = &lvdscon->connector.connector; - connector->display_info.width_mm = panel->width_mm; - connector->display_info.height_mm = panel->height_mm; - - ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, - DRM_MODE_CONNECTOR_LVDS); - if (ret < 0) - return ret; - - drm_connector_helper_add(connector, &connector_helper_funcs); - ret = drm_sysfs_connector_add(connector); - if (ret < 0) - return ret; - - drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); - drm_object_property_set_value(&connector->base, - rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); - - ret = drm_mode_connector_attach_encoder(connector, &renc->encoder); - if (ret < 0) - return ret; - - connector->encoder = &renc->encoder; - lvdscon->connector.encoder = renc; - - return 0; -} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h deleted file mode 100644 index bff8683..0000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * rcar_du_lvdscon.h -- R-Car Display Unit LVDS Connector - * - * Copyright (C) 2013 Renesas Corporation - * - * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __RCAR_DU_LVDSCON_H__ -#define __RCAR_DU_LVDSCON_H__ - -struct rcar_du_device; -struct rcar_du_encoder; -struct rcar_du_panel_data; - -int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, - struct rcar_du_encoder *renc, - const struct rcar_du_panel_data *panel); - -#endif /* __RCAR_DU_LVDSCON_H__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c b/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c deleted file mode 100644 index 72312f7..0000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * rcar_du_vgacon.c -- R-Car Display Unit VGA Connector - * - * Copyright (C) 2013 Renesas Corporation - * - * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 <drm/drm_crtc.h> -#include <drm/drm_crtc_helper.h> - -#include "rcar_du_drv.h" -#include "rcar_du_encoder.h" -#include "rcar_du_kms.h" -#include "rcar_du_vgacon.h" - -static int rcar_du_vga_connector_get_modes(struct drm_connector *connector) -{ - return drm_add_modes_noedid(connector, 1280, 768); -} - -static int rcar_du_vga_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - return MODE_OK; -} - -static const struct drm_connector_helper_funcs connector_helper_funcs = { - .get_modes = rcar_du_vga_connector_get_modes, - .mode_valid = rcar_du_vga_connector_mode_valid, - .best_encoder = rcar_du_connector_best_encoder, -}; - -static void rcar_du_vga_connector_destroy(struct drm_connector *connector) -{ - drm_sysfs_connector_remove(connector); - drm_connector_cleanup(connector); -} - -static enum drm_connector_status -rcar_du_vga_connector_detect(struct drm_connector *connector, bool force) -{ - return connector_status_connected; -} - -static const struct drm_connector_funcs connector_funcs = { - .dpms = drm_helper_connector_dpms, - .detect = rcar_du_vga_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = rcar_du_vga_connector_destroy, -}; - -int rcar_du_vga_connector_init(struct rcar_du_device *rcdu, - struct rcar_du_encoder *renc) -{ - struct rcar_du_connector *rcon; - struct drm_connector *connector; - int ret; - - rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL); - if (rcon == NULL) - return -ENOMEM; - - connector = &rcon->connector; - connector->display_info.width_mm = 0; - connector->display_info.height_mm = 0; - - ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, - DRM_MODE_CONNECTOR_VGA); - if (ret < 0) - return ret; - - drm_connector_helper_add(connector, &connector_helper_funcs); - ret = drm_sysfs_connector_add(connector); - if (ret < 0) - return ret; - - drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF); - drm_object_property_set_value(&connector->base, - rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF); - - ret = drm_mode_connector_attach_encoder(connector, &renc->encoder); - if (ret < 0) - return ret; - - connector->encoder = &renc->encoder; - rcon->encoder = renc; - - return 0; -} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h b/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h deleted file mode 100644 index b12b0cf..0000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * rcar_du_vgacon.h -- R-Car Display Unit VGA Connector - * - * Copyright (C) 2013 Renesas Corporation - * - * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __RCAR_DU_VGACON_H__ -#define __RCAR_DU_VGACON_H__ - -struct rcar_du_device; -struct rcar_du_encoder; - -int rcar_du_vga_connector_init(struct rcar_du_device *rcdu, - struct rcar_du_encoder *renc); - -#endif /* __RCAR_DU_VGACON_H__ */ diff --git a/include/linux/platform_data/rcar-du.h b/include/linux/platform_data/rcar-du.h index 1a2e990..b424b75 100644 --- a/include/linux/platform_data/rcar-du.h +++ b/include/linux/platform_data/rcar-du.h @@ -14,61 +14,10 @@ #ifndef __RCAR_DU_H__ #define __RCAR_DU_H__
-#include <drm/drm_mode.h> - -enum rcar_du_output { - RCAR_DU_OUTPUT_DPAD0, - RCAR_DU_OUTPUT_DPAD1, - RCAR_DU_OUTPUT_LVDS0, - RCAR_DU_OUTPUT_LVDS1, - RCAR_DU_OUTPUT_TCON, - RCAR_DU_OUTPUT_MAX, -}; - -enum rcar_du_encoder_type { - RCAR_DU_ENCODER_UNUSED = 0, - RCAR_DU_ENCODER_NONE, - RCAR_DU_ENCODER_VGA, - RCAR_DU_ENCODER_LVDS, -}; - -struct rcar_du_panel_data { - unsigned int width_mm; /* Panel width in mm */ - unsigned int height_mm; /* Panel height in mm */ - struct drm_mode_modeinfo mode; -}; - -struct rcar_du_connector_lvds_data { - struct rcar_du_panel_data panel; -}; - -struct rcar_du_connector_vga_data { - /* TODO: Add DDC information for EDID retrieval */ -}; - -/* - * struct rcar_du_encoder_data - Encoder platform data - * @type: the encoder type (RCAR_DU_ENCODER_*) - * @output: the DU output the connector is connected to (RCAR_DU_OUTPUT_*) - * @connector.lvds: platform data for LVDS connectors - * @connector.vga: platform data for VGA connectors - * - * Encoder platform data describes an on-board encoder, its associated DU SoC - * output, and the connector. - */ -struct rcar_du_encoder_data { - enum rcar_du_encoder_type type; - enum rcar_du_output output; - - union { - struct rcar_du_connector_lvds_data lvds; - struct rcar_du_connector_vga_data vga; - } connector; -}; +#include <video/display.h>
struct rcar_du_platform_data { - struct rcar_du_encoder_data *encoders; - unsigned int num_encoders; + const struct display_entity_graph_data *graph; };
#endif /* __RCAR_DU_H__ */
dri-devel@lists.freedesktop.org