drm/imx: imx-ldb: add drm_panel support
This patch allows to optionally attach the lvds-channel to a panel supported by a drm_panel driver using of-graph bindings, instead of supplying the modes via display-timings in the device tree. This depends on of_graph_get_port_by_id and uses the OF graph to link the optional DRM panel to the LDB lvds-channel. The output port number is 1 on devices without the 4-port input multiplexer (i.MX5) and 4 on devices with the mux (i.MX6). Before: ldb { ... lvds-channel@0 { ... display-timings { native-timing = <&timing1>; timing1: etm0700g0dh6 { hactive = <800>; vactive = <480>; clock-frequency = <33260000>; hsync-len = <128>; hback-porch = <88>; hfront-porch = <40>; vsync-len = <2>; vback-porch = <33>; vfront-porch = <10>; hsync-active = <0>; vsync-active = <0>; ... }; }; ... }; }; After: ldb { ... lvds-channel@0 { ... port@4 { reg = <4>; lvds_out: endpoint { remote_endpoint = <&panel_in>; }; }; }; }; panel { compatible = "edt,etm0700g0dh6", "simple-panel"; ... port { panel_in: endpoint { remote-endpoint = <&lvds_out>; }; }; }; [Fixed build error due to missing select on DRM_PANEL --rmk] Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
This commit is contained in:
parent
2872c8072a
commit
751e2676ee
@ -44,23 +44,30 @@ Optional properties:
|
|||||||
LVDS Channel
|
LVDS Channel
|
||||||
============
|
============
|
||||||
|
|
||||||
Each LVDS Channel has to contain a display-timings node that describes the
|
Each LVDS Channel has to contain either an of graph link to a panel device node
|
||||||
video timings for the connected LVDS display. For detailed information, also
|
or a display-timings node that describes the video timings for the connected
|
||||||
have a look at Documentation/devicetree/bindings/video/display-timing.txt.
|
LVDS display as well as the fsl,data-mapping and fsl,data-width properties.
|
||||||
|
|
||||||
Required properties:
|
Required properties:
|
||||||
- reg : should be <0> or <1>
|
- reg : should be <0> or <1>
|
||||||
|
- port: Input and output port nodes with endpoint definitions as defined in
|
||||||
|
Documentation/devicetree/bindings/graph.txt.
|
||||||
|
On i.MX5, the internal two-input-multiplexer is used. Due to hardware
|
||||||
|
limitations, only one input port (port@[0,1]) can be used for each channel
|
||||||
|
(lvds-channel@[0,1], respectively).
|
||||||
|
On i.MX6, there should be four input ports (port@[0-3]) that correspond
|
||||||
|
to the four LVDS multiplexer inputs.
|
||||||
|
A single output port (port@2 on i.MX5, port@4 on i.MX6) must be connected
|
||||||
|
to a panel input port. Optionally, the output port can be left out if
|
||||||
|
display-timings are used instead.
|
||||||
|
|
||||||
|
Optional properties (required if display-timings are used):
|
||||||
|
- display-timings : A node that describes the display timings as defined in
|
||||||
|
Documentation/devicetree/bindings/video/display-timing.txt.
|
||||||
- fsl,data-mapping : should be "spwg" or "jeida"
|
- fsl,data-mapping : should be "spwg" or "jeida"
|
||||||
This describes how the color bits are laid out in the
|
This describes how the color bits are laid out in the
|
||||||
serialized LVDS signal.
|
serialized LVDS signal.
|
||||||
- fsl,data-width : should be <18> or <24>
|
- fsl,data-width : should be <18> or <24>
|
||||||
- port: A port node with endpoint definitions as defined in
|
|
||||||
Documentation/devicetree/bindings/media/video-interfaces.txt.
|
|
||||||
On i.MX5, the internal two-input-multiplexer is used.
|
|
||||||
Due to hardware limitations, only one port (port@[0,1])
|
|
||||||
can be used for each channel (lvds-channel@[0,1], respectively)
|
|
||||||
On i.MX6, there should be four ports (port@[0-3]) that correspond
|
|
||||||
to the four LVDS multiplexer inputs.
|
|
||||||
|
|
||||||
example:
|
example:
|
||||||
|
|
||||||
@ -73,23 +80,21 @@ ldb: ldb@53fa8008 {
|
|||||||
#size-cells = <0>;
|
#size-cells = <0>;
|
||||||
compatible = "fsl,imx53-ldb";
|
compatible = "fsl,imx53-ldb";
|
||||||
gpr = <&gpr>;
|
gpr = <&gpr>;
|
||||||
clocks = <&clks 122>, <&clks 120>,
|
clocks = <&clks IMX5_CLK_LDB_DI0_SEL>,
|
||||||
<&clks 115>, <&clks 116>,
|
<&clks IMX5_CLK_LDB_DI1_SEL>,
|
||||||
<&clks 123>, <&clks 85>;
|
<&clks IMX5_CLK_IPU_DI0_SEL>,
|
||||||
|
<&clks IMX5_CLK_IPU_DI1_SEL>,
|
||||||
|
<&clks IMX5_CLK_LDB_DI0_GATE>,
|
||||||
|
<&clks IMX5_CLK_LDB_DI1_GATE>;
|
||||||
clock-names = "di0_pll", "di1_pll",
|
clock-names = "di0_pll", "di1_pll",
|
||||||
"di0_sel", "di1_sel",
|
"di0_sel", "di1_sel",
|
||||||
"di0", "di1";
|
"di0", "di1";
|
||||||
|
|
||||||
|
/* Using an of-graph endpoint link to connect the panel */
|
||||||
lvds-channel@0 {
|
lvds-channel@0 {
|
||||||
#address-cells = <1>;
|
#address-cells = <1>;
|
||||||
#size-cells = <0>;
|
#size-cells = <0>;
|
||||||
reg = <0>;
|
reg = <0>;
|
||||||
fsl,data-mapping = "spwg";
|
|
||||||
fsl,data-width = <24>;
|
|
||||||
|
|
||||||
display-timings {
|
|
||||||
/* ... */
|
|
||||||
};
|
|
||||||
|
|
||||||
port@0 {
|
port@0 {
|
||||||
reg = <0>;
|
reg = <0>;
|
||||||
@ -98,8 +103,17 @@ ldb: ldb@53fa8008 {
|
|||||||
remote-endpoint = <&ipu_di0_lvds0>;
|
remote-endpoint = <&ipu_di0_lvds0>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
port@2 {
|
||||||
|
reg = <2>;
|
||||||
|
|
||||||
|
lvds0_out: endpoint {
|
||||||
|
remote-endpoint = <&panel_in>;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Using display-timings and fsl,data-mapping/width instead */
|
||||||
lvds-channel@1 {
|
lvds-channel@1 {
|
||||||
#address-cells = <1>;
|
#address-cells = <1>;
|
||||||
#size-cells = <0>;
|
#size-cells = <0>;
|
||||||
@ -120,3 +134,13 @@ ldb: ldb@53fa8008 {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
panel: lvds-panel {
|
||||||
|
/* ... */
|
||||||
|
|
||||||
|
port {
|
||||||
|
panel_in: endpoint {
|
||||||
|
remote-endpoint = <&lvds0_out>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -36,6 +36,7 @@ config DRM_IMX_TVE
|
|||||||
config DRM_IMX_LDB
|
config DRM_IMX_LDB
|
||||||
tristate "Support for LVDS displays"
|
tristate "Support for LVDS displays"
|
||||||
depends on DRM_IMX && MFD_SYSCON
|
depends on DRM_IMX && MFD_SYSCON
|
||||||
|
select DRM_PANEL
|
||||||
help
|
help
|
||||||
Choose this to enable the internal LVDS Display Bridge (LDB)
|
Choose this to enable the internal LVDS Display Bridge (LDB)
|
||||||
found on i.MX53 and i.MX6 processors.
|
found on i.MX53 and i.MX6 processors.
|
||||||
|
@ -19,10 +19,11 @@
|
|||||||
#include <drm/drmP.h>
|
#include <drm/drmP.h>
|
||||||
#include <drm/drm_fb_helper.h>
|
#include <drm/drm_fb_helper.h>
|
||||||
#include <drm/drm_crtc_helper.h>
|
#include <drm/drm_crtc_helper.h>
|
||||||
|
#include <drm/drm_panel.h>
|
||||||
#include <linux/mfd/syscon.h>
|
#include <linux/mfd/syscon.h>
|
||||||
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
|
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
|
||||||
#include <linux/of_address.h>
|
|
||||||
#include <linux/of_device.h>
|
#include <linux/of_device.h>
|
||||||
|
#include <linux/of_graph.h>
|
||||||
#include <video/of_videomode.h>
|
#include <video/of_videomode.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
@ -55,6 +56,7 @@ struct imx_ldb_channel {
|
|||||||
struct imx_ldb *ldb;
|
struct imx_ldb *ldb;
|
||||||
struct drm_connector connector;
|
struct drm_connector connector;
|
||||||
struct drm_encoder encoder;
|
struct drm_encoder encoder;
|
||||||
|
struct drm_panel *panel;
|
||||||
struct device_node *child;
|
struct device_node *child;
|
||||||
int chno;
|
int chno;
|
||||||
void *edid;
|
void *edid;
|
||||||
@ -91,6 +93,13 @@ static int imx_ldb_connector_get_modes(struct drm_connector *connector)
|
|||||||
struct imx_ldb_channel *imx_ldb_ch = con_to_imx_ldb_ch(connector);
|
struct imx_ldb_channel *imx_ldb_ch = con_to_imx_ldb_ch(connector);
|
||||||
int num_modes = 0;
|
int num_modes = 0;
|
||||||
|
|
||||||
|
if (imx_ldb_ch->panel && imx_ldb_ch->panel->funcs &&
|
||||||
|
imx_ldb_ch->panel->funcs->get_modes) {
|
||||||
|
num_modes = imx_ldb_ch->panel->funcs->get_modes(imx_ldb_ch->panel);
|
||||||
|
if (num_modes > 0)
|
||||||
|
return num_modes;
|
||||||
|
}
|
||||||
|
|
||||||
if (imx_ldb_ch->edid) {
|
if (imx_ldb_ch->edid) {
|
||||||
drm_mode_connector_update_edid_property(connector,
|
drm_mode_connector_update_edid_property(connector,
|
||||||
imx_ldb_ch->edid);
|
imx_ldb_ch->edid);
|
||||||
@ -190,6 +199,8 @@ static void imx_ldb_encoder_commit(struct drm_encoder *encoder)
|
|||||||
int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN;
|
int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN;
|
||||||
int mux = imx_drm_encoder_get_mux_id(imx_ldb_ch->child, encoder);
|
int mux = imx_drm_encoder_get_mux_id(imx_ldb_ch->child, encoder);
|
||||||
|
|
||||||
|
drm_panel_prepare(imx_ldb_ch->panel);
|
||||||
|
|
||||||
if (dual) {
|
if (dual) {
|
||||||
clk_prepare_enable(ldb->clk[0]);
|
clk_prepare_enable(ldb->clk[0]);
|
||||||
clk_prepare_enable(ldb->clk[1]);
|
clk_prepare_enable(ldb->clk[1]);
|
||||||
@ -223,6 +234,8 @@ static void imx_ldb_encoder_commit(struct drm_encoder *encoder)
|
|||||||
}
|
}
|
||||||
|
|
||||||
regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl);
|
regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl);
|
||||||
|
|
||||||
|
drm_panel_enable(imx_ldb_ch->panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder,
|
static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder,
|
||||||
@ -287,6 +300,8 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder)
|
|||||||
(ldb->ldb_ctrl & LDB_CH1_MODE_EN_MASK) == 0)
|
(ldb->ldb_ctrl & LDB_CH1_MODE_EN_MASK) == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
drm_panel_disable(imx_ldb_ch->panel);
|
||||||
|
|
||||||
if (imx_ldb_ch == &ldb->channel[0])
|
if (imx_ldb_ch == &ldb->channel[0])
|
||||||
ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
|
ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
|
||||||
else if (imx_ldb_ch == &ldb->channel[1])
|
else if (imx_ldb_ch == &ldb->channel[1])
|
||||||
@ -298,6 +313,8 @@ static void imx_ldb_encoder_disable(struct drm_encoder *encoder)
|
|||||||
clk_disable_unprepare(ldb->clk[0]);
|
clk_disable_unprepare(ldb->clk[0]);
|
||||||
clk_disable_unprepare(ldb->clk[1]);
|
clk_disable_unprepare(ldb->clk[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drm_panel_unprepare(imx_ldb_ch->panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct drm_connector_funcs imx_ldb_connector_funcs = {
|
static struct drm_connector_funcs imx_ldb_connector_funcs = {
|
||||||
@ -371,6 +388,9 @@ static int imx_ldb_register(struct drm_device *drm,
|
|||||||
drm_connector_init(drm, &imx_ldb_ch->connector,
|
drm_connector_init(drm, &imx_ldb_ch->connector,
|
||||||
&imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
|
&imx_ldb_connector_funcs, DRM_MODE_CONNECTOR_LVDS);
|
||||||
|
|
||||||
|
if (imx_ldb_ch->panel)
|
||||||
|
drm_panel_attach(imx_ldb_ch->panel, &imx_ldb_ch->connector);
|
||||||
|
|
||||||
drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
|
drm_mode_connector_attach_encoder(&imx_ldb_ch->connector,
|
||||||
&imx_ldb_ch->encoder);
|
&imx_ldb_ch->encoder);
|
||||||
|
|
||||||
@ -485,6 +505,7 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
|
|||||||
|
|
||||||
for_each_child_of_node(np, child) {
|
for_each_child_of_node(np, child) {
|
||||||
struct imx_ldb_channel *channel;
|
struct imx_ldb_channel *channel;
|
||||||
|
struct device_node *port;
|
||||||
|
|
||||||
ret = of_property_read_u32(child, "reg", &i);
|
ret = of_property_read_u32(child, "reg", &i);
|
||||||
if (ret || i < 0 || i > 1)
|
if (ret || i < 0 || i > 1)
|
||||||
@ -503,11 +524,34 @@ static int imx_ldb_bind(struct device *dev, struct device *master, void *data)
|
|||||||
channel->chno = i;
|
channel->chno = i;
|
||||||
channel->child = child;
|
channel->child = child;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The output port is port@4 with an external 4-port mux or
|
||||||
|
* port@2 with the internal 2-port mux.
|
||||||
|
*/
|
||||||
|
port = of_graph_get_port_by_id(child, imx_ldb->lvds_mux ? 4 : 2);
|
||||||
|
if (port) {
|
||||||
|
struct device_node *endpoint, *remote;
|
||||||
|
|
||||||
|
endpoint = of_get_child_by_name(port, "endpoint");
|
||||||
|
if (endpoint) {
|
||||||
|
remote = of_graph_get_remote_port_parent(endpoint);
|
||||||
|
if (remote)
|
||||||
|
channel->panel = of_drm_find_panel(remote);
|
||||||
|
else
|
||||||
|
return -EPROBE_DEFER;
|
||||||
|
if (!channel->panel) {
|
||||||
|
dev_err(dev, "panel not found: %s\n",
|
||||||
|
remote->full_name);
|
||||||
|
return -EPROBE_DEFER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
edidp = of_get_property(child, "edid", &channel->edid_len);
|
edidp = of_get_property(child, "edid", &channel->edid_len);
|
||||||
if (edidp) {
|
if (edidp) {
|
||||||
channel->edid = kmemdup(edidp, channel->edid_len,
|
channel->edid = kmemdup(edidp, channel->edid_len,
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
} else {
|
} else if (!channel->panel) {
|
||||||
ret = of_get_drm_display_mode(child, &channel->mode, 0);
|
ret = of_get_drm_display_mode(child, &channel->mode, 0);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
channel->mode_valid = 1;
|
channel->mode_valid = 1;
|
||||||
|
Loading…
Reference in New Issue
Block a user