diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,rk618.txt b/Documentation/devicetree/bindings/display/rockchip/rockchip,rk618.txt index 112d5f57aa30..e94ca4b90ff2 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,rk618.txt +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,rk618.txt @@ -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>; + }; + }; + }; + }; +}; \ No newline at end of file diff --git a/drivers/gpu/drm/rockchip/rk618/Makefile b/drivers/gpu/drm/rockchip/rk618/Makefile index 417549328823..ad508944f29a 100644 --- a/drivers/gpu/drm/rockchip/rk618/Makefile +++ b/drivers/gpu/drm/rockchip/rk618/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_DRM_ROCKCHIP_RK618) += rk618_output.o \ rk618_vif.o \ rk618_dither.o \ rk618_lvds.o \ + rk618_rgb.o \ diff --git a/drivers/gpu/drm/rockchip/rk618/rk618_lvds.c b/drivers/gpu/drm/rockchip/rk618/rk618_lvds.c index 08e12c784cc3..df45e7ec50ec 100644 --- a/drivers/gpu/drm/rockchip/rk618/rk618_lvds.c +++ b/drivers/gpu/drm/rockchip/rk618/rk618_lvds.c @@ -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 { diff --git a/drivers/gpu/drm/rockchip/rk618/rk618_output.c b/drivers/gpu/drm/rockchip/rk618/rk618_output.c index 503425b894f3..37abf55aa5bc 100644 --- a/drivers/gpu/drm/rockchip/rk618/rk618_output.c +++ b/drivers/gpu/drm/rockchip/rk618/rk618_output.c @@ -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; diff --git a/drivers/gpu/drm/rockchip/rk618/rk618_output.h b/drivers/gpu/drm/rockchip/rk618/rk618_output.h index 0e2b0430ba90..e70b6d080bd6 100644 --- a/drivers/gpu/drm/rockchip/rk618/rk618_output.h +++ b/drivers/gpu/drm/rockchip/rk618/rk618_output.h @@ -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; diff --git a/drivers/gpu/drm/rockchip/rk618/rk618_rgb.c b/drivers/gpu/drm/rockchip/rk618/rk618_rgb.c new file mode 100644 index 000000000000..7bbe2481255f --- /dev/null +++ b/drivers/gpu/drm/rockchip/rk618/rk618_rgb.c @@ -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 "); +MODULE_DESCRIPTION("Rockchip RK618 RGB driver"); +MODULE_LICENSE("GPL v2");