drm/rockchip: add RGB support for rk618

Change-Id: Ieddeeb842ee9db11b8c56cb4171bd630b6b63acb
Signed-off-by: Shunqing Chen <csq@rock-chips.com>
This commit is contained in:
Shunqing Chen
2018-09-03 17:48:34 +08:00
committed by Tao Huang
parent 923b9f5752
commit 4752f415e0
6 changed files with 325 additions and 53 deletions

View File

@@ -114,3 +114,75 @@ Example:
};
};
};
RGB Encoder
------------
Required properties:
- compatible: value should be one of the following:
"rockchip,rk618-rgb"
- clocks: must include clock specifiers corresponding to entries in the
clock-names property.
See ../clocks/clock-bindings.txt for details.
- clock-names: list of clock names sorted in the same order as the clocks
property. Must contain "rgb", "dither", "vif", "scaler".
Required nodes:
The connections to the video ports are modeled using the OF graph
bindings specified in Documentation/devicetree/bindings/graph.txt.
Example:
&rk618 {
status = "okay";
rgb {
compatible = "rockchip,rk618-rgb";
clocks = <&CRU RGB_CLK>, <&CRU DITHER_CLK>,
<&CRU VIF0_CLK>, <&CRU SCALER_CLK>;
clock-names = "rgb", "dither", "vif", "scaler";
status = "okay";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
rgb_input_vop: endpoint@0 {
reg = <0>;
remote-endpoint = <&vop_output_rgb>;
};
rgb_input_vif: endpoint@1 {
reg = <1>;
remote-endpoint = <&vif_output_rgb>;
};
rgb_input_hdmi: endpoint@2 {
reg = <2>;
remote-endpoint = <&hdmi_output_rgb>;
};
};
port@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
rgb_lcdc1_out_tve: endpoint@0 {
reg = <0>;
remote-endpoint = <&tve_in_rgb_lcdc1>;
};
rgb_out_tve: endpoint@1 {
status = "disabled";
reg = <1>;
remote-endpoint = <&tve_in_rgb>;
};
};
};
};
};

View File

@@ -8,3 +8,4 @@ obj-$(CONFIG_DRM_ROCKCHIP_RK618) += rk618_output.o \
rk618_vif.o \
rk618_dither.o \
rk618_lvds.o \
rk618_rgb.o \

View File

@@ -14,37 +14,6 @@
#include "rk618_output.h"
#define RK618_LVDS_CON 0x0084
#define LVDS_CON_START_PHASE(x) HIWORD_UPDATE(x, 14, 14)
#define LVDS_DCLK_INV HIWORD_UPDATE(1, 13, 13)
#define LVDS_CON_CHADS_10PF HIWORD_UPDATE(3, 12, 11)
#define LVDS_CON_CHADS_5PF HIWORD_UPDATE(2, 12, 11)
#define LVDS_CON_CHADS_7PF HIWORD_UPDATE(1, 12, 11)
#define LVDS_CON_CHADS_3PF HIWORD_UPDATE(0, 12, 11)
#define LVDS_CON_CHA1TTL_ENABLE HIWORD_UPDATE(1, 10, 10)
#define LVDS_CON_CHA1TTL_DISABLE HIWORD_UPDATE(0, 10, 10)
#define LVDS_CON_CHA0TTL_ENABLE HIWORD_UPDATE(1, 9, 9)
#define LVDS_CON_CHA0TTL_DISABLE HIWORD_UPDATE(0, 9, 9)
#define LVDS_CON_CHA1_POWER_UP HIWORD_UPDATE(1, 8, 8)
#define LVDS_CON_CHA1_POWER_DOWN HIWORD_UPDATE(0, 8, 8)
#define LVDS_CON_CHA0_POWER_UP HIWORD_UPDATE(1, 7, 7)
#define LVDS_CON_CHA0_POWER_DOWN HIWORD_UPDATE(0, 7, 7)
#define LVDS_CON_CBG_POWER_UP HIWORD_UPDATE(1, 6, 6)
#define LVDS_CON_CBG_POWER_DOWN HIWORD_UPDATE(0, 6, 6)
#define LVDS_CON_PLL_POWER_DOWN HIWORD_UPDATE(1, 5, 5)
#define LVDS_CON_PLL_POWER_UP HIWORD_UPDATE(0, 5, 5)
#define LVDS_CON_START_SEL_EVEN_PIXEL HIWORD_UPDATE(1, 4, 4)
#define LVDS_CON_START_SEL_ODD_PIXEL HIWORD_UPDATE(0, 4, 4)
#define LVDS_CON_CHASEL_DOUBLE_CHANNEL HIWORD_UPDATE(1, 3, 3)
#define LVDS_CON_CHASEL_SINGLE_CHANNEL HIWORD_UPDATE(0, 3, 3)
#define LVDS_CON_MSBSEL_D7 HIWORD_UPDATE(1, 2, 2)
#define LVDS_CON_MSBSEL_D0 HIWORD_UPDATE(0, 2, 2)
#define LVDS_CON_SELECT(x) HIWORD_UPDATE(x, 1, 0)
#define LVDS_CON_SELECT_6BIT_MODE HIWORD_UPDATE(3, 1, 0)
#define LVDS_CON_SELECT_8BIT_MODE_3 HIWORD_UPDATE(2, 1, 0)
#define LVDS_CON_SELECT_8BIT_MODE_2 HIWORD_UPDATE(1, 1, 0)
#define LVDS_CON_SELECT_8BIT_MODE_1 HIWORD_UPDATE(0, 1, 0)
#define IS_DOUBLE_CHANNEL(lvds) ((lvds)->channels == 2)
enum {

View File

@@ -99,24 +99,28 @@ static void rk618_output_encoder_enable(struct drm_encoder *encoder)
if (output->funcs->pre_enable)
output->funcs->pre_enable(output);
drm_panel_prepare(output->panel);
if (output->panel)
drm_panel_prepare(output->panel);
if (output->funcs->enable)
output->funcs->enable(output);
drm_panel_enable(output->panel);
if (output->panel)
drm_panel_enable(output->panel);
}
static void rk618_output_encoder_disable(struct drm_encoder *encoder)
{
struct rk618_output *output = encoder_to_output(encoder);
drm_panel_disable(output->panel);
if (output->panel)
drm_panel_disable(output->panel);
if (output->funcs->disable)
output->funcs->disable(output);
drm_panel_unprepare(output->panel);
if (output->panel)
drm_panel_unprepare(output->panel);
if (output->funcs->post_disable)
output->funcs->post_disable(output);
@@ -187,7 +191,8 @@ static void rk618_output_bridge_pre_enable(struct drm_bridge *bridge)
if (output->funcs->pre_enable)
output->funcs->pre_enable(output);
drm_panel_prepare(output->panel);
if (output->panel)
drm_panel_prepare(output->panel);
}
static void rk618_output_bridge_enable(struct drm_bridge *bridge)
@@ -197,14 +202,16 @@ static void rk618_output_bridge_enable(struct drm_bridge *bridge)
if (output->funcs->enable)
output->funcs->enable(output);
drm_panel_enable(output->panel);
if (output->panel)
drm_panel_enable(output->panel);
}
static void rk618_output_bridge_disable(struct drm_bridge *bridge)
{
struct rk618_output *output = bridge_to_output(bridge);
drm_panel_disable(output->panel);
if (output->panel)
drm_panel_disable(output->panel);
if (output->funcs->disable)
output->funcs->disable(output);
@@ -214,7 +221,8 @@ static void rk618_output_bridge_post_disable(struct drm_bridge *bridge)
{
struct rk618_output *output = bridge_to_output(bridge);
drm_panel_unprepare(output->panel);
if (output->panel)
drm_panel_unprepare(output->panel);
if (output->funcs->post_disable)
output->funcs->post_disable(output);
@@ -246,7 +254,7 @@ static const struct drm_bridge_funcs rk618_output_bridge_funcs = {
int rk618_output_register(struct rk618_output *output)
{
struct device *dev = output->dev;
struct device_node *endpoint, *remote;
struct device_node *endpoint, *remote = NULL, *port;
int ret;
output->dither_clk = devm_clk_get(dev, "dither");
@@ -270,14 +278,26 @@ int rk618_output_register(struct rk618_output *output)
return ret;
}
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
if (!endpoint) {
dev_err(dev, "no valid endpoint\n");
return -ENODEV;
port = of_graph_get_port_by_id(dev->of_node, 1);
if (!port) {
dev_err(dev, "can't found port point.\n");
return -EINVAL;
}
for_each_child_of_node(port, endpoint) {
remote = of_graph_get_remote_port_parent(endpoint);
if (!remote) {
dev_err(dev, "can't found panel node, please init!\n");
return -EINVAL;
}
if (!of_device_is_available(remote)) {
of_node_put(remote);
remote = NULL;
continue;
}
break;
}
remote = of_graph_get_remote_port_parent(endpoint);
of_node_put(endpoint);
if (!remote) {
dev_err(dev, "no valid remote node\n");
return -ENODEV;
@@ -300,7 +320,12 @@ int rk618_output_bind(struct rk618_output *output, struct drm_device *drm,
output->panel = of_drm_find_panel(output->panel_node);
if (!output->panel)
output->ext_bridge = of_drm_find_bridge(output->panel_node);
if (!output->panel && !output->ext_bridge) {
dev_info(dev, "Waiting for panel or bridge driver\n");
return -EPROBE_DEFER;
}
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm,
dev->of_node);
@@ -308,15 +333,36 @@ int rk618_output_bind(struct rk618_output *output, struct drm_device *drm,
encoder_type, NULL);
drm_encoder_helper_add(encoder, &rk618_output_encoder_helper_funcs);
connector->port = dev->of_node;
drm_connector_init(drm, connector, &rk618_output_connector_funcs,
connector_type);
drm_connector_helper_add(connector,
&rk618_output_connector_helper_funcs);
if (output->ext_bridge) {
output->ext_bridge->encoder = encoder;
ret = drm_bridge_attach(drm, output->ext_bridge);
if (ret) {
dev_err(dev, "failed to attach bridge\n");
return ret;
}
encoder->bridge = output->ext_bridge;
} else {
connector->port = dev->of_node;
ret = drm_connector_init(drm, connector,
&rk618_output_connector_funcs,
connector_type);
if (ret) {
dev_err(dev, "Failed to init connector with drm\n");
return ret;
}
drm_mode_connector_attach_encoder(connector, encoder);
drm_connector_helper_add(connector,
&rk618_output_connector_helper_funcs);
drm_panel_attach(output->panel, connector);
drm_mode_connector_attach_encoder(connector,
output->bridge.encoder);
ret = drm_panel_attach(output->panel, connector);
if (ret) {
dev_err(dev, "Failed to attach panel\n");
return ret;
}
}
bridge->funcs = &rk618_output_bridge_funcs;
bridge->of_node = dev->of_node;

View File

@@ -40,6 +40,36 @@
#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l)))
#define HIWORD_UPDATE(v, h, l) (((v) << (l)) | (GENMASK((h), (l)) << 16))
#define RK618_LVDS_CON 0x0084
#define LVDS_CON_START_PHASE(x) HIWORD_UPDATE(x, 14, 14)
#define LVDS_DCLK_INV HIWORD_UPDATE(1, 13, 13)
#define LVDS_CON_CHADS_10PF HIWORD_UPDATE(3, 12, 11)
#define LVDS_CON_CHADS_5PF HIWORD_UPDATE(2, 12, 11)
#define LVDS_CON_CHADS_7PF HIWORD_UPDATE(1, 12, 11)
#define LVDS_CON_CHADS_3PF HIWORD_UPDATE(0, 12, 11)
#define LVDS_CON_CHA1TTL_ENABLE HIWORD_UPDATE(1, 10, 10)
#define LVDS_CON_CHA1TTL_DISABLE HIWORD_UPDATE(0, 10, 10)
#define LVDS_CON_CHA0TTL_ENABLE HIWORD_UPDATE(1, 9, 9)
#define LVDS_CON_CHA0TTL_DISABLE HIWORD_UPDATE(0, 9, 9)
#define LVDS_CON_CHA1_POWER_UP HIWORD_UPDATE(1, 8, 8)
#define LVDS_CON_CHA1_POWER_DOWN HIWORD_UPDATE(0, 8, 8)
#define LVDS_CON_CHA0_POWER_UP HIWORD_UPDATE(1, 7, 7)
#define LVDS_CON_CHA0_POWER_DOWN HIWORD_UPDATE(0, 7, 7)
#define LVDS_CON_CBG_POWER_UP HIWORD_UPDATE(1, 6, 6)
#define LVDS_CON_CBG_POWER_DOWN HIWORD_UPDATE(0, 6, 6)
#define LVDS_CON_PLL_POWER_DOWN HIWORD_UPDATE(1, 5, 5)
#define LVDS_CON_PLL_POWER_UP HIWORD_UPDATE(0, 5, 5)
#define LVDS_CON_START_SEL_EVEN_PIXEL HIWORD_UPDATE(1, 4, 4)
#define LVDS_CON_START_SEL_ODD_PIXEL HIWORD_UPDATE(0, 4, 4)
#define LVDS_CON_CHASEL_DOUBLE_CHANNEL HIWORD_UPDATE(1, 3, 3)
#define LVDS_CON_CHASEL_SINGLE_CHANNEL HIWORD_UPDATE(0, 3, 3)
#define LVDS_CON_MSBSEL_D7 HIWORD_UPDATE(1, 2, 2)
#define LVDS_CON_MSBSEL_D0 HIWORD_UPDATE(0, 2, 2)
#define LVDS_CON_SELECT(x) HIWORD_UPDATE(x, 1, 0)
#define LVDS_CON_SELECT_6BIT_MODE HIWORD_UPDATE(3, 1, 0)
#define LVDS_CON_SELECT_8BIT_MODE_3 HIWORD_UPDATE(2, 1, 0)
#define LVDS_CON_SELECT_8BIT_MODE_2 HIWORD_UPDATE(1, 1, 0)
#define LVDS_CON_SELECT_8BIT_MODE_1 HIWORD_UPDATE(0, 1, 0)
#define RK618_IO_CON0 0x0088
#define VIF1_SYNC_MODE_ENABLE HIWORD_UPDATE(1, 15, 15)
#define VIF1_SYNC_MODE_DISABLE HIWORD_UPDATE(0, 15, 15)
@@ -118,6 +148,7 @@ struct rk618_output {
struct drm_display_mode panel_mode;
struct drm_display_mode scale_mode;
u32 bus_format;
struct drm_bridge *ext_bridge;
struct device *dev;
struct device_node *panel_node;

View File

@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018 Rockchip Electronics Co. Ltd.
*/
#include "rk618_output.h"
struct rk618_rgb {
struct rk618_output base;
struct device *dev;
struct regmap *regmap;
struct clk *clock;
};
static inline struct rk618_rgb *to_rgb(struct rk618_output *output)
{
return container_of(output, struct rk618_rgb, base);
}
static void rk618_rgb_enable(struct rk618_output *output)
{
struct rk618_rgb *rgb = to_rgb(output);
u32 value;
struct device_node *endpoint;
int lcdc1_output_rgb = 0;
endpoint = of_graph_get_endpoint_by_regs(output->dev->of_node, 1, 0);
if (endpoint && of_device_is_available(endpoint))
lcdc1_output_rgb = 1;
clk_prepare_enable(rgb->clock);
if (lcdc1_output_rgb) {
value = LVDS_CON_CHA1TTL_DISABLE | LVDS_CON_CHA0TTL_DISABLE |
LVDS_CON_CHA1_POWER_DOWN | LVDS_CON_CHA0_POWER_DOWN |
LVDS_CON_CBG_POWER_DOWN | LVDS_CON_PLL_POWER_DOWN;
regmap_write(rgb->regmap, RK618_LVDS_CON, value);
regmap_write(rgb->regmap, RK618_IO_CON0,
PORT1_OUTPUT_TTL_ENABLE);
} else {
value = LVDS_CON_CBG_POWER_DOWN | LVDS_CON_CHA1_POWER_DOWN |
LVDS_CON_CHA0_POWER_DOWN;
value |= LVDS_CON_CHA0TTL_ENABLE | LVDS_CON_CHA1TTL_ENABLE |
LVDS_CON_PLL_POWER_DOWN;
regmap_write(rgb->regmap, RK618_LVDS_CON, value);
regmap_write(rgb->regmap, RK618_IO_CON0, PORT2_OUTPUT_TTL);
}
}
static void rk618_rgb_disable(struct rk618_output *output)
{
struct rk618_rgb *rgb = to_rgb(output);
regmap_write(rgb->regmap, RK618_LVDS_CON,
LVDS_CON_CHA0_POWER_DOWN | LVDS_CON_CHA1_POWER_DOWN |
LVDS_CON_CBG_POWER_DOWN | LVDS_CON_PLL_POWER_DOWN);
clk_disable_unprepare(rgb->clock);
}
static const struct rk618_output_funcs rk618_rgb_funcs = {
.enable = rk618_rgb_enable,
.disable = rk618_rgb_disable,
};
static int rk618_rgb_bind(struct device *dev, struct device *master,
void *data)
{
struct drm_device *drm = data;
struct rk618_rgb *rgb = dev_get_drvdata(dev);
return rk618_output_bind(&rgb->base, drm, DRM_MODE_ENCODER_LVDS,
DRM_MODE_CONNECTOR_LVDS);
}
static void rk618_rgb_unbind(struct device *dev, struct device *master,
void *data)
{
struct rk618_rgb *rgb = dev_get_drvdata(dev);
rk618_output_unbind(&rgb->base);
}
static const struct component_ops rk618_rgb_component_ops = {
.bind = rk618_rgb_bind,
.unbind = rk618_rgb_unbind,
};
static const struct of_device_id rk618_rgb_of_match[] = {
{ .compatible = "rockchip,rk618-rgb", },
{},
};
MODULE_DEVICE_TABLE(of, rk618_rgb_of_match);
static int rk618_rgb_probe(struct platform_device *pdev)
{
struct rk618 *rk618 = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct rk618_rgb *rgb;
int ret;
if (!of_device_is_available(dev->of_node))
return -ENODEV;
rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL);
if (!rgb)
return -ENOMEM;
rgb->dev = dev;
rgb->regmap = rk618->regmap;
platform_set_drvdata(pdev, rgb);
rgb->clock = devm_clk_get(dev, "rgb");
if (IS_ERR(rgb->clock)) {
ret = PTR_ERR(rgb->clock);
dev_err(dev, "failed to get rgb clock: %d\n", ret);
return ret;
}
rgb->base.parent = rk618;
rgb->base.dev = dev;
rgb->base.funcs = &rk618_rgb_funcs;
ret = rk618_output_register(&rgb->base);
if (ret)
return ret;
return component_add(dev, &rk618_rgb_component_ops);
}
static int rk618_rgb_remove(struct platform_device *pdev)
{
struct rk618_rgb *rgb = platform_get_drvdata(pdev);
component_del(rgb->dev, &rk618_rgb_component_ops);
return 0;
}
static struct platform_driver rk618_rgb_driver = {
.driver = {
.name = "rk618-rgb",
.of_match_table = of_match_ptr(rk618_rgb_of_match),
},
.probe = rk618_rgb_probe,
.remove = rk618_rgb_remove,
};
module_platform_driver(rk618_rgb_driver);
MODULE_AUTHOR("Chen Shunqing <csq@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip RK618 RGB driver");
MODULE_LICENSE("GPL v2");