mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-08 03:40:35 +09:00
drm/rockchip: rk618: refactor display pipeline
It's hard to do platform specific initialization in the current pipeline. This patch convert to generic bridge interface and let it attach to a platform specific encoder. CRTC --> Encoder --> Bridge --> Connector --> Panel (platform) This patch changed the pipeline to: CRTC --> Encoder --> Bridge --> Connector --> Panel (platform) (platform) Change-Id: I43e0dab05e41965767f55cfe15b3674a71911312 Signed-off-by: Wyon Bi <bivvy.bi@rock-chips.com>
This commit is contained in:
@@ -21,7 +21,9 @@ bindings specified in Documentation/devicetree/bindings/graph.txt.
|
||||
Example:
|
||||
|
||||
&rk618 {
|
||||
VIF: vif {
|
||||
status = "okay";
|
||||
|
||||
vif {
|
||||
compatible = "rockchip,rk618-vif";
|
||||
clocks = <&CRU VIF0_CLK>, <&CRU VIF0_PRE_CLK>;
|
||||
clock-names = "vif", "vif_pre";
|
||||
@@ -34,23 +36,74 @@ Example:
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
|
||||
vif_input_hdmi: endpoint {
|
||||
remote-endpoint = <&hdmi_output_vif>;
|
||||
bridge_input_rgb: endpoint {
|
||||
remote-endpoint = <&rgb_out_bridge>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
|
||||
vif_output_lvds: endpoint {
|
||||
remote-endpoint = <&lvds_input_vif>;
|
||||
vif_output_hdmi: endpoint {
|
||||
remote-endpoint = <&hdmi_input_vif>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
LVDS Encoder
|
||||
SCALER Module
|
||||
----------
|
||||
|
||||
Required properties:
|
||||
- compatible: value should be one of the following:
|
||||
"rockchip,rk618-scaler"
|
||||
- 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 "scaler", "vif", "dither".
|
||||
|
||||
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";
|
||||
|
||||
scaler {
|
||||
compatible = "rockchip,rk618-scaler";
|
||||
clocks = <&CRU SCALER_CLK>, <&CRU DITHER_CLK>, <&CRU VIF0_CLK>;
|
||||
clock-names = "scaler", "dither", "vif";
|
||||
status = "okay";
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
|
||||
scaler_input_hdmi: endpoint {
|
||||
remote-endpoint = <&hdmi_output_scaler>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
|
||||
scaler_output_lvds: endpoint {
|
||||
remote-endpoint = <&lvds_input_scaler>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
LVDS Connector
|
||||
------------
|
||||
|
||||
Required properties:
|
||||
@@ -60,7 +113,10 @@ Required properties:
|
||||
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 "lvds", "dither", "vif", "scaler".
|
||||
property. Must contain "lvds".
|
||||
|
||||
Optional properties:
|
||||
- dual-channel: boolean. if it exists, enable dual channel mode
|
||||
|
||||
Required nodes:
|
||||
|
||||
@@ -74,9 +130,8 @@ Example:
|
||||
|
||||
lvds {
|
||||
compatible = "rockchip,rk618-lvds";
|
||||
clocks = <&CRU LVDS_CLK>, <&CRU DITHER_CLK>,
|
||||
<&CRU VIF0_CLK>, <&CRU SCALER_CLK>;
|
||||
clock-names = "lvds", "dither", "vif", "scaler";
|
||||
clocks = <&CRU LVDS_CLK>;
|
||||
clock-names = "lvds";
|
||||
status = "okay";
|
||||
|
||||
ports {
|
||||
@@ -85,22 +140,9 @@ Example:
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
lvds_input_vop: endpoint@0 {
|
||||
reg = <0>;
|
||||
remote-endpoint = <&vop_output_lvds>;
|
||||
};
|
||||
|
||||
lvds_input_vif: endpoint@1 {
|
||||
reg = <1>;
|
||||
remote-endpoint = <&vif_output_lvds>;
|
||||
};
|
||||
|
||||
lvds_input_hdmi: endpoint@2 {
|
||||
reg = <2>;
|
||||
remote-endpoint = <&hdmi_output_lvds>;
|
||||
bridge_input_rgb: endpoint {
|
||||
remote-endpoint = <&rgb_out_bridge>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -115,7 +157,7 @@ Example:
|
||||
};
|
||||
};
|
||||
|
||||
RGB Encoder
|
||||
RGB Connector
|
||||
------------
|
||||
|
||||
Required properties:
|
||||
@@ -125,7 +167,7 @@ Required properties:
|
||||
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".
|
||||
property. Must contain "rgb".
|
||||
|
||||
Required nodes:
|
||||
|
||||
@@ -139,9 +181,8 @@ Example:
|
||||
|
||||
rgb {
|
||||
compatible = "rockchip,rk618-rgb";
|
||||
clocks = <&CRU RGB_CLK>, <&CRU DITHER_CLK>,
|
||||
<&CRU VIF0_CLK>, <&CRU SCALER_CLK>;
|
||||
clock-names = "rgb", "dither", "vif", "scaler";
|
||||
clocks = <&CRU RGB_CLK>;
|
||||
clock-names = "rgb";
|
||||
status = "okay";
|
||||
|
||||
ports {
|
||||
@@ -150,22 +191,9 @@ Example:
|
||||
|
||||
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>;
|
||||
bridge_input_rgb: endpoint {
|
||||
remote-endpoint = <&rgb_out_bridge>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -173,16 +201,12 @@ Example:
|
||||
reg = <1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
rgb_lcdc1_out_tve: endpoint@0 {
|
||||
|
||||
rgb_output_panel: endpoint@0 {
|
||||
reg = <0>;
|
||||
remote-endpoint = <&tve_in_rgb_lcdc1>;
|
||||
};
|
||||
rgb_out_tve: endpoint@1 {
|
||||
status = "disabled";
|
||||
reg = <1>;
|
||||
remote-endpoint = <&tve_in_rgb>;
|
||||
remote-endpoint = <&panel_input_rgb>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
# Makefile for the Rockchip RK618 display bridge driver.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_DRM_ROCKCHIP_RK618) += rk618_output.o \
|
||||
rk618_scaler.o \
|
||||
obj-$(CONFIG_DRM_ROCKCHIP_RK618) += rk618_scaler.o \
|
||||
rk618_vif.o \
|
||||
rk618_dither.o \
|
||||
rk618_lvds.o \
|
||||
rk618_rgb.o \
|
||||
rk618_rgb.o
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* Author: Wyon Bi <bivvy.bi@rock-chips.com>
|
||||
*/
|
||||
|
||||
#include "rk618_output.h"
|
||||
#include "rk618_dither.h"
|
||||
|
||||
#define RK618_FRC_REG 0x0054
|
||||
#define FRC_DEN_INV HIWORD_UPDATE(1, 6, 6)
|
||||
@@ -26,20 +19,20 @@
|
||||
#define FRC_DITHER_ENABLE HIWORD_UPDATE(1, 0, 0)
|
||||
#define FRC_DITHER_DISABLE HIWORD_UPDATE(0, 0, 0)
|
||||
|
||||
void rk618_dither_disable(struct rk618 *rk618)
|
||||
void rk618_frc_dither_disable(struct rk618 *rk618)
|
||||
{
|
||||
regmap_write(rk618->regmap, RK618_FRC_REG, FRC_DITHER_DISABLE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rk618_dither_disable);
|
||||
EXPORT_SYMBOL_GPL(rk618_frc_dither_disable);
|
||||
|
||||
void rk618_dither_enable(struct rk618 *rk618)
|
||||
void rk618_frc_dither_enable(struct rk618 *rk618)
|
||||
{
|
||||
regmap_write(rk618->regmap, RK618_FRC_REG, FRC_DITHER_ENABLE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rk618_dither_enable);
|
||||
EXPORT_SYMBOL_GPL(rk618_frc_dither_enable);
|
||||
|
||||
void rk618_dither_frc_dclk_invert(struct rk618 *rk618)
|
||||
void rk618_frc_dclk_invert(struct rk618 *rk618)
|
||||
{
|
||||
regmap_write(rk618->regmap, RK618_FRC_REG, FRC_DCLK_INV);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rk618_dither_frc_dclk_invert);
|
||||
EXPORT_SYMBOL_GPL(rk618_frc_dclk_invert);
|
||||
|
||||
17
drivers/gpu/drm/rockchip/rk618/rk618_dither.h
Normal file
17
drivers/gpu/drm/rockchip/rk618/rk618_dither.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) 2018 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* Author: Wyon Bi <bivvy.bi@rock-chips.com>
|
||||
*/
|
||||
|
||||
#ifndef _RK618_DITHER_H_
|
||||
#define _RK618_DITHER_H_
|
||||
|
||||
#include <linux/mfd/rk618.h>
|
||||
|
||||
void rk618_frc_dither_disable(struct rk618 *rk618);
|
||||
void rk618_frc_dither_enable(struct rk618 *rk618);
|
||||
void rk618_frc_dclk_invert(struct rk618 *rk618);
|
||||
|
||||
#endif
|
||||
@@ -1,20 +1,28 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* Author: Wyon Bi <bivvy.bi@rock-chips.com>
|
||||
*/
|
||||
|
||||
#include "rk618_output.h"
|
||||
#include <linux/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/mfd/rk618.h>
|
||||
|
||||
#define IS_DOUBLE_CHANNEL(lvds) ((lvds)->channels == 2)
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include <video/of_display_timing.h>
|
||||
#include <video/videomode.h>
|
||||
|
||||
#include "rk618_dither.h"
|
||||
|
||||
enum {
|
||||
LVDS_8BIT_MODE_FORMAT_1,
|
||||
@@ -24,31 +32,86 @@ enum {
|
||||
};
|
||||
|
||||
struct rk618_lvds {
|
||||
struct rk618_output base;
|
||||
struct drm_bridge base;
|
||||
struct drm_connector connector;
|
||||
struct drm_panel *panel;
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct clk *clock;
|
||||
u32 channels;
|
||||
struct rk618 *parent;
|
||||
bool dual_channel;
|
||||
u32 format;
|
||||
};
|
||||
|
||||
static inline struct rk618_lvds *to_lvds(struct rk618_output *output)
|
||||
static inline struct rk618_lvds *bridge_to_lvds(struct drm_bridge *b)
|
||||
{
|
||||
return container_of(output, struct rk618_lvds, base);
|
||||
return container_of(b, struct rk618_lvds, base);
|
||||
}
|
||||
|
||||
static void rk618_lvds_enable(struct rk618_output *output)
|
||||
static inline struct rk618_lvds *connector_to_lvds(struct drm_connector *c)
|
||||
{
|
||||
struct rk618_lvds *lvds = to_lvds(output);
|
||||
return container_of(c, struct rk618_lvds, connector);
|
||||
}
|
||||
|
||||
static struct drm_encoder *
|
||||
rk618_lvds_connector_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct rk618_lvds *lvds = connector_to_lvds(connector);
|
||||
|
||||
return lvds->base.encoder;
|
||||
}
|
||||
|
||||
static int rk618_lvds_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct rk618_lvds *lvds = connector_to_lvds(connector);
|
||||
|
||||
return drm_panel_get_modes(lvds->panel);
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs
|
||||
rk618_lvds_connector_helper_funcs = {
|
||||
.get_modes = rk618_lvds_connector_get_modes,
|
||||
.best_encoder = rk618_lvds_connector_best_encoder,
|
||||
};
|
||||
|
||||
static enum drm_connector_status
|
||||
rk618_lvds_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
static void rk618_lvds_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct rk618_lvds *lvds = connector_to_lvds(connector);
|
||||
|
||||
drm_panel_detach(lvds->panel);
|
||||
drm_connector_cleanup(connector);
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs rk618_lvds_connector_funcs = {
|
||||
.dpms = drm_atomic_helper_connector_dpms,
|
||||
.detect = rk618_lvds_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = rk618_lvds_connector_destroy,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static void rk618_lvds_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_lvds *lvds = bridge_to_lvds(bridge);
|
||||
u32 value;
|
||||
|
||||
clk_prepare_enable(lvds->clock);
|
||||
|
||||
rk618_frc_dclk_invert(lvds->parent);
|
||||
|
||||
value = LVDS_CON_CHA0TTL_DISABLE | LVDS_CON_CHA1TTL_DISABLE |
|
||||
LVDS_CON_CHA0_POWER_UP | LVDS_CON_CBG_POWER_UP |
|
||||
LVDS_CON_PLL_POWER_UP | LVDS_CON_SELECT(lvds->format);
|
||||
|
||||
if (IS_DOUBLE_CHANNEL(lvds))
|
||||
if (lvds->dual_channel)
|
||||
value |= LVDS_CON_CHA1_POWER_UP | LVDS_DCLK_INV |
|
||||
LVDS_CON_CHASEL_DOUBLE_CHANNEL;
|
||||
else
|
||||
@@ -56,11 +119,17 @@ static void rk618_lvds_enable(struct rk618_output *output)
|
||||
LVDS_CON_CHASEL_SINGLE_CHANNEL;
|
||||
|
||||
regmap_write(lvds->regmap, RK618_LVDS_CON, value);
|
||||
|
||||
drm_panel_prepare(lvds->panel);
|
||||
drm_panel_enable(lvds->panel);
|
||||
}
|
||||
|
||||
static void rk618_lvds_disable(struct rk618_output *output)
|
||||
static void rk618_lvds_bridge_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_lvds *lvds = to_lvds(output);
|
||||
struct rk618_lvds *lvds = bridge_to_lvds(bridge);
|
||||
|
||||
drm_panel_disable(lvds->panel);
|
||||
drm_panel_unprepare(lvds->panel);
|
||||
|
||||
regmap_write(lvds->regmap, RK618_LVDS_CON,
|
||||
LVDS_CON_CHA0_POWER_DOWN | LVDS_CON_CHA1_POWER_DOWN |
|
||||
@@ -69,17 +138,21 @@ static void rk618_lvds_disable(struct rk618_output *output)
|
||||
clk_disable_unprepare(lvds->clock);
|
||||
}
|
||||
|
||||
static void rk618_lvds_mode_set(struct rk618_output *output,
|
||||
const struct drm_display_mode *mode)
|
||||
static void rk618_lvds_bridge_mode_set(struct drm_bridge *bridge,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adj)
|
||||
{
|
||||
struct rk618_lvds *lvds = to_lvds(output);
|
||||
struct rk618_lvds *lvds = bridge_to_lvds(bridge);
|
||||
struct drm_connector *connector = &lvds->connector;
|
||||
struct drm_display_info *info = &connector->display_info;
|
||||
u32 bus_format;
|
||||
|
||||
if (mode->hdisplay > 1366 || mode->vdisplay > 1366)
|
||||
lvds->channels = 2;
|
||||
if (info->num_bus_formats)
|
||||
bus_format = info->bus_formats[0];
|
||||
else
|
||||
lvds->channels = 1;
|
||||
bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
|
||||
|
||||
switch (output->bus_format) {
|
||||
switch (bus_format) {
|
||||
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: /* jeida-18 */
|
||||
lvds->format = LVDS_6BIT_MODE;
|
||||
break;
|
||||
@@ -95,39 +168,57 @@ static void rk618_lvds_mode_set(struct rk618_output *output,
|
||||
}
|
||||
}
|
||||
|
||||
static const struct rk618_output_funcs rk618_lvds_funcs = {
|
||||
.enable = rk618_lvds_enable,
|
||||
.disable = rk618_lvds_disable,
|
||||
.mode_set = rk618_lvds_mode_set,
|
||||
};
|
||||
|
||||
static int rk618_lvds_bind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
static int rk618_lvds_bridge_attach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct drm_device *drm = data;
|
||||
struct rk618_lvds *lvds = dev_get_drvdata(dev);
|
||||
struct rk618_lvds *lvds = bridge_to_lvds(bridge);
|
||||
struct device *dev = lvds->dev;
|
||||
struct drm_connector *connector = &lvds->connector;
|
||||
struct drm_device *drm = bridge->dev;
|
||||
int ret;
|
||||
|
||||
return rk618_output_bind(&lvds->base, drm, DRM_MODE_ENCODER_LVDS,
|
||||
connector->port = dev->of_node;
|
||||
|
||||
ret = drm_connector_init(drm, connector, &rk618_lvds_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
if (ret) {
|
||||
dev_err(lvds->dev, "Failed to initialize connector with drm\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(connector, &rk618_lvds_connector_helper_funcs);
|
||||
drm_mode_connector_attach_encoder(connector, bridge->encoder);
|
||||
|
||||
ret = drm_panel_attach(lvds->panel, connector);
|
||||
if (ret) {
|
||||
dev_err(lvds->dev, "Failed to attach panel\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rk618_lvds_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
struct rk618_lvds *lvds = dev_get_drvdata(dev);
|
||||
|
||||
rk618_output_unbind(&lvds->base);
|
||||
}
|
||||
|
||||
static const struct component_ops rk618_lvds_component_ops = {
|
||||
.bind = rk618_lvds_bind,
|
||||
.unbind = rk618_lvds_unbind,
|
||||
static const struct drm_bridge_funcs rk618_lvds_bridge_funcs = {
|
||||
.attach = rk618_lvds_bridge_attach,
|
||||
.mode_set = rk618_lvds_bridge_mode_set,
|
||||
.enable = rk618_lvds_bridge_enable,
|
||||
.disable = rk618_lvds_bridge_disable,
|
||||
};
|
||||
|
||||
static int rk618_lvds_parse_dt(struct rk618_lvds *lvds)
|
||||
{
|
||||
struct device *dev = lvds->dev;
|
||||
|
||||
lvds->dual_channel = of_property_read_bool(dev->of_node,
|
||||
"dual-channel");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk618_lvds_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rk618 *rk618 = dev_get_drvdata(pdev->dev.parent);
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *endpoint;
|
||||
struct rk618_lvds *lvds;
|
||||
int ret;
|
||||
|
||||
@@ -139,9 +230,38 @@ static int rk618_lvds_probe(struct platform_device *pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
lvds->dev = dev;
|
||||
lvds->regmap = rk618->regmap;
|
||||
lvds->parent = rk618;
|
||||
platform_set_drvdata(pdev, lvds);
|
||||
|
||||
ret = rk618_lvds_parse_dt(lvds);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to parse DT\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
lvds->regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!lvds->regmap)
|
||||
return -ENODEV;
|
||||
|
||||
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
|
||||
if (endpoint) {
|
||||
struct device_node *remote;
|
||||
|
||||
remote = of_graph_get_remote_port_parent(endpoint);
|
||||
of_node_put(endpoint);
|
||||
if (!remote) {
|
||||
dev_err(dev, "no panel connected\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
lvds->panel = of_drm_find_panel(remote);
|
||||
of_node_put(remote);
|
||||
if (!lvds->panel) {
|
||||
dev_err(dev, "Waiting for panel driver\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
}
|
||||
|
||||
lvds->clock = devm_clk_get(dev, "lvds");
|
||||
if (IS_ERR(lvds->clock)) {
|
||||
ret = PTR_ERR(lvds->clock);
|
||||
@@ -149,21 +269,22 @@ static int rk618_lvds_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
lvds->base.parent = rk618;
|
||||
lvds->base.dev = dev;
|
||||
lvds->base.funcs = &rk618_lvds_funcs;
|
||||
ret = rk618_output_register(&lvds->base);
|
||||
if (ret)
|
||||
lvds->base.funcs = &rk618_lvds_bridge_funcs;
|
||||
lvds->base.of_node = dev->of_node;
|
||||
ret = drm_bridge_add(&lvds->base);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to add drm_bridge: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return component_add(dev, &rk618_lvds_component_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk618_lvds_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rk618_lvds *lvds = platform_get_drvdata(pdev);
|
||||
|
||||
component_del(lvds->dev, &rk618_lvds_component_ops);
|
||||
drm_bridge_remove(&lvds->base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,36 +1,115 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2018 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* Author: Chen Shunqing <csq@rock-chips.com>
|
||||
*/
|
||||
|
||||
#include "rk618_output.h"
|
||||
#include <linux/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <video/of_display_timing.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/mfd/rk618.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include <video/videomode.h>
|
||||
|
||||
#include "rk618_dither.h"
|
||||
|
||||
struct rk618_rgb {
|
||||
struct rk618_output base;
|
||||
struct drm_bridge base;
|
||||
struct drm_connector connector;
|
||||
struct drm_panel *panel;
|
||||
struct drm_bridge *bridge;
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct clk *clock;
|
||||
struct rk618 *parent;
|
||||
u32 id;
|
||||
};
|
||||
|
||||
static inline struct rk618_rgb *to_rgb(struct rk618_output *output)
|
||||
static inline struct rk618_rgb *bridge_to_rgb(struct drm_bridge *b)
|
||||
{
|
||||
return container_of(output, struct rk618_rgb, base);
|
||||
return container_of(b, struct rk618_rgb, base);
|
||||
}
|
||||
|
||||
static void rk618_rgb_enable(struct rk618_output *output)
|
||||
static inline struct rk618_rgb *connector_to_rgb(struct drm_connector *c)
|
||||
{
|
||||
struct rk618_rgb *rgb = to_rgb(output);
|
||||
u32 value;
|
||||
struct device_node *endpoint;
|
||||
int lcdc1_output_rgb = 0;
|
||||
return container_of(c, struct rk618_rgb, connector);
|
||||
}
|
||||
|
||||
endpoint = of_graph_get_endpoint_by_regs(output->dev->of_node, 1, 0);
|
||||
if (endpoint && of_device_is_available(endpoint))
|
||||
lcdc1_output_rgb = 1;
|
||||
static struct drm_encoder *
|
||||
rk618_rgb_connector_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct rk618_rgb *rgb = connector_to_rgb(connector);
|
||||
|
||||
return rgb->base.encoder;
|
||||
}
|
||||
|
||||
static int rk618_rgb_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct rk618_rgb *rgb = connector_to_rgb(connector);
|
||||
|
||||
return drm_panel_get_modes(rgb->panel);
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs
|
||||
rk618_rgb_connector_helper_funcs = {
|
||||
.get_modes = rk618_rgb_connector_get_modes,
|
||||
.best_encoder = rk618_rgb_connector_best_encoder,
|
||||
};
|
||||
|
||||
static enum drm_connector_status
|
||||
rk618_rgb_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
static void rk618_rgb_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct rk618_rgb *rgb = connector_to_rgb(connector);
|
||||
|
||||
drm_panel_detach(rgb->panel);
|
||||
drm_connector_cleanup(connector);
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs rk618_rgb_connector_funcs = {
|
||||
.dpms = drm_atomic_helper_connector_dpms,
|
||||
.detect = rk618_rgb_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = rk618_rgb_connector_destroy,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static void rk618_rgb_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_rgb *rgb = bridge_to_rgb(bridge);
|
||||
u32 value;
|
||||
|
||||
clk_prepare_enable(rgb->clock);
|
||||
|
||||
if (lcdc1_output_rgb) {
|
||||
rk618_frc_dclk_invert(rgb->parent);
|
||||
|
||||
dev_dbg(rgb->dev, "id=%d\n", rgb->id);
|
||||
|
||||
if (rgb->id) {
|
||||
value = LVDS_CON_CBG_POWER_DOWN | LVDS_CON_CHA1_POWER_DOWN |
|
||||
LVDS_CON_CHA0_POWER_DOWN | 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);
|
||||
} else {
|
||||
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;
|
||||
@@ -38,68 +117,86 @@ static void rk618_rgb_enable(struct rk618_output *output)
|
||||
|
||||
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);
|
||||
if (rgb->panel) {
|
||||
drm_panel_prepare(rgb->panel);
|
||||
drm_panel_enable(rgb->panel);
|
||||
}
|
||||
}
|
||||
|
||||
static void rk618_rgb_disable(struct rk618_output *output)
|
||||
static void rk618_rgb_bridge_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_rgb *rgb = to_rgb(output);
|
||||
struct rk618_rgb *rgb = bridge_to_rgb(bridge);
|
||||
|
||||
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);
|
||||
if (rgb->panel) {
|
||||
drm_panel_disable(rgb->panel);
|
||||
drm_panel_unprepare(rgb->panel);
|
||||
}
|
||||
|
||||
if (rgb->id)
|
||||
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);
|
||||
else
|
||||
regmap_write(rgb->regmap, RK618_IO_CON0,
|
||||
PORT1_OUTPUT_TTL_DISABLE);
|
||||
|
||||
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)
|
||||
static int rk618_rgb_bridge_attach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct drm_device *drm = data;
|
||||
struct rk618_rgb *rgb = dev_get_drvdata(dev);
|
||||
struct rk618_rgb *rgb = bridge_to_rgb(bridge);
|
||||
struct device *dev = rgb->dev;
|
||||
struct drm_connector *connector = &rgb->connector;
|
||||
struct drm_device *drm = bridge->dev;
|
||||
int ret;
|
||||
|
||||
return rk618_output_bind(&rgb->base, drm, DRM_MODE_ENCODER_LVDS,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
if (rgb->panel) {
|
||||
connector->port = dev->of_node;
|
||||
|
||||
ret = drm_connector_init(drm, connector,
|
||||
&rk618_rgb_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_DPI);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to initialize connector\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(connector,
|
||||
&rk618_rgb_connector_helper_funcs);
|
||||
drm_mode_connector_attach_encoder(connector, bridge->encoder);
|
||||
drm_panel_attach(rgb->panel, connector);
|
||||
} else {
|
||||
rgb->bridge->encoder = bridge->encoder;
|
||||
|
||||
ret = drm_bridge_attach(bridge->dev, rgb->bridge);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to attach bridge\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
bridge->next = rgb->bridge;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 drm_bridge_funcs rk618_rgb_bridge_funcs = {
|
||||
.attach = rk618_rgb_bridge_attach,
|
||||
.enable = rk618_rgb_bridge_enable,
|
||||
.disable = rk618_rgb_bridge_disable,
|
||||
};
|
||||
|
||||
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;
|
||||
int id, ret;
|
||||
|
||||
if (!of_device_is_available(dev->of_node))
|
||||
return -ENODEV;
|
||||
@@ -109,9 +206,13 @@ static int rk618_rgb_probe(struct platform_device *pdev)
|
||||
return -ENOMEM;
|
||||
|
||||
rgb->dev = dev;
|
||||
rgb->regmap = rk618->regmap;
|
||||
rgb->parent = rk618;
|
||||
platform_set_drvdata(pdev, rgb);
|
||||
|
||||
rgb->regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!rgb->regmap)
|
||||
return -ENODEV;
|
||||
|
||||
rgb->clock = devm_clk_get(dev, "rgb");
|
||||
if (IS_ERR(rgb->clock)) {
|
||||
ret = PTR_ERR(rgb->clock);
|
||||
@@ -119,25 +220,58 @@ static int rk618_rgb_probe(struct platform_device *pdev)
|
||||
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;
|
||||
for (id = 0; id < 2; id++) {
|
||||
struct device_node *remote, *endpoint;
|
||||
|
||||
return component_add(dev, &rk618_rgb_component_ops);
|
||||
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, id);
|
||||
if (!endpoint)
|
||||
continue;
|
||||
|
||||
remote = of_graph_get_remote_port_parent(endpoint);
|
||||
of_node_put(endpoint);
|
||||
if (!remote) {
|
||||
dev_err(dev, "no panel/bridge connected\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rgb->panel = of_drm_find_panel(remote);
|
||||
if (!rgb->panel)
|
||||
rgb->bridge = of_drm_find_bridge(remote);
|
||||
of_node_put(remote);
|
||||
if (!rgb->panel && !rgb->bridge) {
|
||||
dev_err(dev, "Waiting for panel/bridge driver\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
rgb->id = id;
|
||||
}
|
||||
|
||||
rgb->base.funcs = &rk618_rgb_bridge_funcs;
|
||||
rgb->base.of_node = dev->of_node;
|
||||
ret = drm_bridge_add(&rgb->base);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to add drm_bridge: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
drm_bridge_remove(&rgb->base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id rk618_rgb_of_match[] = {
|
||||
{ .compatible = "rockchip,rk618-rgb", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rk618_rgb_of_match);
|
||||
|
||||
static struct platform_driver rk618_rgb_driver = {
|
||||
.driver = {
|
||||
.name = "rk618-rgb",
|
||||
@@ -148,6 +282,7 @@ static struct platform_driver rk618_rgb_driver = {
|
||||
};
|
||||
module_platform_driver(rk618_rgb_driver);
|
||||
|
||||
MODULE_AUTHOR("Wyon Bi <bivvy.bi@rock-chips.com>");
|
||||
MODULE_AUTHOR("Chen Shunqing <csq@rock-chips.com>");
|
||||
MODULE_DESCRIPTION("Rockchip RK618 RGB driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* Author: Wyon Bi <bivvy.bi@rock-chips.com>
|
||||
*/
|
||||
|
||||
#include "rk618_output.h"
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mfd/rk618.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drmP.h>
|
||||
#include <video/videomode.h>
|
||||
|
||||
#define RK618_SCALER_REG0 0x0030
|
||||
#define SCL_VER_DOWN_MODE(x) HIWORD_UPDATE(x, 8, 8)
|
||||
@@ -47,17 +51,31 @@
|
||||
#define DSP_VBOR_END(x) UPDATE(x, 27, 16)
|
||||
#define DSP_VBOR_ST(x) UPDATE(x, 11, 0)
|
||||
|
||||
void rk618_scaler_enable(struct rk618 *rk618)
|
||||
{
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG0, SCL_ENABLE);
|
||||
}
|
||||
EXPORT_SYMBOL(rk618_scaler_enable);
|
||||
struct rk618_scaler {
|
||||
struct drm_bridge base;
|
||||
struct drm_bridge *bridge;
|
||||
struct drm_display_mode mode;
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct clk *vif_clk;
|
||||
struct clk *dither_clk;
|
||||
struct clk *scaler_clk;
|
||||
};
|
||||
|
||||
void rk618_scaler_disable(struct rk618 *rk618)
|
||||
static inline struct rk618_scaler *bridge_to_scaler(struct drm_bridge *bridge)
|
||||
{
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG0, SCL_DISABLE);
|
||||
return container_of(bridge, struct rk618_scaler, base);
|
||||
}
|
||||
|
||||
static void rk618_scaler_enable(struct rk618_scaler *scl)
|
||||
{
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG0, SCL_ENABLE);
|
||||
}
|
||||
|
||||
static void rk618_scaler_disable(struct rk618_scaler *scl)
|
||||
{
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG0, SCL_DISABLE);
|
||||
}
|
||||
EXPORT_SYMBOL(rk618_scaler_disable);
|
||||
|
||||
static void calc_dsp_frm_hst_vst(const struct videomode *src,
|
||||
const struct videomode *dst,
|
||||
@@ -68,7 +86,7 @@ static void calc_dsp_frm_hst_vst(const struct videomode *src,
|
||||
long long t_frm_st;
|
||||
u64 t_bp_in, t_bp_out, t_delta, tin;
|
||||
u32 src_pixclock, dst_pixclock;
|
||||
u32 dsp_htotal, dsp_vtotal, src_htotal, src_vtotal;
|
||||
u32 dsp_htotal, src_htotal, src_vtotal;
|
||||
|
||||
src_pixclock = div_u64(1000000000000llu, src->pixelclock);
|
||||
dst_pixclock = div_u64(1000000000000llu, dst->pixelclock);
|
||||
@@ -79,8 +97,6 @@ static void calc_dsp_frm_hst_vst(const struct videomode *src,
|
||||
src->vfront_porch;
|
||||
dsp_htotal = dst->hsync_len + dst->hback_porch + dst->hactive +
|
||||
dst->hfront_porch;
|
||||
dsp_vtotal = dst->vsync_len + dst->vback_porch + dst->vactive +
|
||||
dst->vfront_porch;
|
||||
|
||||
bp_in = (src->vback_porch + src->vsync_len) * src_htotal +
|
||||
src->hsync_len + src->hback_porch;
|
||||
@@ -107,29 +123,23 @@ static void calc_dsp_frm_hst_vst(const struct videomode *src,
|
||||
*dsp_frame_vst = t_frm_st;
|
||||
}
|
||||
|
||||
void rk618_scaler_configure(struct rk618 *rk618,
|
||||
const struct drm_display_mode *scale_mode,
|
||||
const struct drm_display_mode *panel_mode)
|
||||
static void rk618_scaler_init(struct rk618_scaler *scl,
|
||||
const struct drm_display_mode *s,
|
||||
const struct drm_display_mode *d)
|
||||
{
|
||||
struct device *dev = rk618->dev;
|
||||
struct videomode src, dst;
|
||||
u32 dsp_frame_hst, dsp_frame_vst;
|
||||
u32 scl_hor_mode, scl_ver_mode;
|
||||
u32 scl_v_factor, scl_h_factor;
|
||||
u32 src_htotal, src_vtotal;
|
||||
u32 dsp_htotal, dsp_hs_end, dsp_hact_st, dsp_hact_end;
|
||||
u32 dsp_vtotal, dsp_vs_end, dsp_vact_st, dsp_vact_end;
|
||||
u32 dsp_hbor_end, dsp_hbor_st, dsp_vbor_end, dsp_vbor_st;
|
||||
u16 bor_right = 0, bor_left = 0, bor_up = 0, bor_down = 0;
|
||||
u8 hor_down_mode = 0, ver_down_mode = 0;
|
||||
|
||||
drm_display_mode_to_videomode(scale_mode, &src);
|
||||
drm_display_mode_to_videomode(panel_mode, &dst);
|
||||
drm_display_mode_to_videomode(s, &src);
|
||||
drm_display_mode_to_videomode(d, &dst);
|
||||
|
||||
src_htotal = src.hsync_len + src.hback_porch + src.hactive +
|
||||
src.hfront_porch;
|
||||
src_vtotal = src.vsync_len + src.vback_porch + src.vactive +
|
||||
src.vfront_porch;
|
||||
dsp_htotal = dst.hsync_len + dst.hback_porch + dst.hactive +
|
||||
dst.hfront_porch;
|
||||
dsp_vtotal = dst.vsync_len + dst.vback_porch + dst.vactive +
|
||||
@@ -146,7 +156,7 @@ void rk618_scaler_configure(struct rk618 *rk618,
|
||||
dsp_vact_end = dsp_vbor_end - bor_down;
|
||||
|
||||
calc_dsp_frm_hst_vst(&src, &dst, &dsp_frame_hst, &dsp_frame_vst);
|
||||
dev_dbg(dev, "dsp_frame_vst=%d, dsp_frame_hst=%d\n",
|
||||
dev_dbg(scl->dev, "dsp_frame_vst=%d, dsp_frame_hst=%d\n",
|
||||
dsp_frame_vst, dsp_frame_hst);
|
||||
|
||||
if (src.hactive > dst.hactive) {
|
||||
@@ -164,17 +174,17 @@ void rk618_scaler_configure(struct rk618 *rk618,
|
||||
(src.hactive - 1);
|
||||
}
|
||||
|
||||
dev_dbg(rk618->dev, "horizontal scale down\n");
|
||||
dev_dbg(scl->dev, "horizontal scale down\n");
|
||||
} else if (src.hactive == dst.hactive) {
|
||||
scl_hor_mode = 0;
|
||||
scl_h_factor = 0;
|
||||
|
||||
dev_dbg(rk618->dev, "horizontal no scale\n");
|
||||
dev_dbg(scl->dev, "horizontal no scale\n");
|
||||
} else {
|
||||
scl_hor_mode = 1;
|
||||
scl_h_factor = ((src.hactive - 1) << 16) / (dst.hactive - 1);
|
||||
|
||||
dev_dbg(rk618->dev, "horizontal scale up\n");
|
||||
dev_dbg(scl->dev, "horizontal scale up\n");
|
||||
}
|
||||
|
||||
if (src.vactive > dst.vactive) {
|
||||
@@ -192,37 +202,223 @@ void rk618_scaler_configure(struct rk618 *rk618,
|
||||
(src.vactive - 1);
|
||||
}
|
||||
|
||||
dev_dbg(rk618->dev, "vertical scale down\n");
|
||||
dev_dbg(scl->dev, "vertical scale down\n");
|
||||
} else if (src.vactive == dst.vactive) {
|
||||
scl_ver_mode = 0;
|
||||
scl_v_factor = 0;
|
||||
|
||||
dev_dbg(rk618->dev, "vertical no scale\n");
|
||||
dev_dbg(scl->dev, "vertical no scale\n");
|
||||
} else {
|
||||
scl_ver_mode = 1;
|
||||
scl_v_factor = ((src.vactive - 1) << 16) / (dst.vactive - 1);
|
||||
|
||||
dev_dbg(rk618->dev, "vertical scale up\n");
|
||||
dev_dbg(scl->dev, "vertical scale up\n");
|
||||
}
|
||||
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG0,
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG0,
|
||||
SCL_VER_MODE(scl_ver_mode) | SCL_HOR_MODE(scl_hor_mode));
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG1,
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG1,
|
||||
SCL_V_FACTOR(scl_v_factor) | SCL_H_FACTOR(scl_h_factor));
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG2,
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG2,
|
||||
DSP_FRAME_VST(dsp_frame_vst) |
|
||||
DSP_FRAME_HST(dsp_frame_hst));
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG3,
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG3,
|
||||
DSP_HS_END(dsp_hs_end) | DSP_HTOTAL(dsp_htotal));
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG4,
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG4,
|
||||
DSP_HACT_END(dsp_hact_end) | DSP_HACT_ST(dsp_hact_st));
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG5,
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG5,
|
||||
DSP_VS_END(dsp_vs_end) | DSP_VTOTAL(dsp_vtotal));
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG6,
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG6,
|
||||
DSP_VACT_END(dsp_vact_end) | DSP_VACT_ST(dsp_vact_st));
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG7,
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG7,
|
||||
DSP_HBOR_END(dsp_hbor_end) | DSP_HBOR_ST(dsp_hbor_st));
|
||||
regmap_write(rk618->regmap, RK618_SCALER_REG8,
|
||||
regmap_write(scl->regmap, RK618_SCALER_REG8,
|
||||
DSP_VBOR_END(dsp_vbor_end) | DSP_VBOR_ST(dsp_vbor_st));
|
||||
}
|
||||
EXPORT_SYMBOL(rk618_scaler_configure);
|
||||
|
||||
static void rk618_scaler_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_scaler *scl = bridge_to_scaler(bridge);
|
||||
struct drm_connector *connector;
|
||||
const struct drm_display_mode *src = &scl->mode;
|
||||
const struct drm_display_mode *mode;
|
||||
struct drm_display_mode dst;
|
||||
unsigned long dclk_rate = src->clock * 1000;
|
||||
u64 sclk_rate;
|
||||
long rate;
|
||||
|
||||
memset(&dst, 0, sizeof(dst));
|
||||
|
||||
drm_for_each_connector(connector, bridge->dev) {
|
||||
if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA)
|
||||
continue;
|
||||
|
||||
if (connector->encoder_ids[0] != bridge->encoder->base.id)
|
||||
continue;
|
||||
|
||||
list_for_each_entry(mode, &connector->modes, head) {
|
||||
if (mode->type & DRM_MODE_TYPE_PREFERRED) {
|
||||
drm_mode_copy(&dst, mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sclk_rate = (u64)dclk_rate * dst.vdisplay * dst.htotal;
|
||||
do_div(sclk_rate, src->vdisplay * src->htotal);
|
||||
|
||||
dev_info(scl->dev, "src=%s, dst=%s\n", src->name, dst.name);
|
||||
dev_info(scl->dev, "dclk rate: %ld, sclk rate: %lld\n",
|
||||
dclk_rate, sclk_rate);
|
||||
|
||||
clk_set_parent(scl->dither_clk, scl->scaler_clk);
|
||||
|
||||
rate = clk_round_rate(scl->scaler_clk, sclk_rate);
|
||||
clk_set_rate(scl->scaler_clk, rate);
|
||||
dst.clock = rate / 1000;
|
||||
clk_prepare_enable(scl->scaler_clk);
|
||||
|
||||
rk618_scaler_init(scl, src, &dst);
|
||||
rk618_scaler_enable(scl);
|
||||
}
|
||||
|
||||
static void rk618_scaler_bridge_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_scaler *scl = bridge_to_scaler(bridge);
|
||||
|
||||
rk618_scaler_disable(scl);
|
||||
clk_disable_unprepare(scl->scaler_clk);
|
||||
clk_set_parent(scl->dither_clk, scl->vif_clk);
|
||||
}
|
||||
|
||||
static void rk618_scaler_bridge_mode_set(struct drm_bridge *bridge,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
struct rk618_scaler *scl = bridge_to_scaler(bridge);
|
||||
|
||||
drm_mode_copy(&scl->mode, adjusted);
|
||||
}
|
||||
|
||||
static int rk618_scaler_bridge_attach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_scaler *scl = bridge_to_scaler(bridge);
|
||||
struct device *dev = scl->dev;
|
||||
struct device_node *endpoint;
|
||||
int ret;
|
||||
|
||||
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
|
||||
if (endpoint && of_device_is_available(endpoint)) {
|
||||
struct device_node *remote;
|
||||
|
||||
remote = of_graph_get_remote_port_parent(endpoint);
|
||||
of_node_put(endpoint);
|
||||
if (!remote || !of_device_is_available(remote))
|
||||
return -ENODEV;
|
||||
|
||||
scl->bridge = of_drm_find_bridge(remote);
|
||||
of_node_put(remote);
|
||||
if (!scl->bridge)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
scl->bridge->encoder = bridge->encoder;
|
||||
|
||||
ret = drm_bridge_attach(bridge->dev, scl->bridge);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to attach bridge\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
bridge->next = scl->bridge;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs rk618_scaler_bridge_funcs = {
|
||||
.enable = rk618_scaler_bridge_enable,
|
||||
.disable = rk618_scaler_bridge_disable,
|
||||
.mode_set = rk618_scaler_bridge_mode_set,
|
||||
.attach = rk618_scaler_bridge_attach,
|
||||
};
|
||||
|
||||
static int rk618_scaler_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rk618_scaler *scl;
|
||||
int ret;
|
||||
|
||||
if (!of_device_is_available(dev->of_node))
|
||||
return -ENODEV;
|
||||
|
||||
scl = devm_kzalloc(dev, sizeof(*scl), GFP_KERNEL);
|
||||
if (!scl)
|
||||
return -ENOMEM;
|
||||
|
||||
scl->dev = dev;
|
||||
platform_set_drvdata(pdev, scl);
|
||||
|
||||
scl->regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!scl->regmap)
|
||||
return -ENODEV;
|
||||
|
||||
scl->vif_clk = devm_clk_get(dev, "vif");
|
||||
if (IS_ERR(scl->vif_clk)) {
|
||||
ret = PTR_ERR(scl->vif_clk);
|
||||
dev_err(dev, "failed to get vif clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
scl->dither_clk = devm_clk_get(dev, "dither");
|
||||
if (IS_ERR(scl->dither_clk)) {
|
||||
ret = PTR_ERR(scl->dither_clk);
|
||||
dev_err(dev, "failed to get dither clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
scl->scaler_clk = devm_clk_get(dev, "scaler");
|
||||
if (IS_ERR(scl->scaler_clk)) {
|
||||
ret = PTR_ERR(scl->scaler_clk);
|
||||
dev_err(dev, "failed to get scaler clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
scl->base.funcs = &rk618_scaler_bridge_funcs;
|
||||
scl->base.of_node = dev->of_node;
|
||||
ret = drm_bridge_add(&scl->base);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to add bridge\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk618_scaler_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rk618_scaler *scl = platform_get_drvdata(pdev);
|
||||
|
||||
drm_bridge_remove(&scl->base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id rk618_scaler_of_match[] = {
|
||||
{ .compatible = "rockchip,rk618-scaler", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rk618_scaler_of_match);
|
||||
|
||||
static struct platform_driver rk618_scaler_driver = {
|
||||
.driver = {
|
||||
.name = "rk618-scaler",
|
||||
.of_match_table = of_match_ptr(rk618_scaler_of_match),
|
||||
},
|
||||
.probe = rk618_scaler_probe,
|
||||
.remove = rk618_scaler_remove,
|
||||
};
|
||||
module_platform_driver(rk618_scaler_driver);
|
||||
|
||||
MODULE_AUTHOR("Wyon Bi <bivvy.bi@rock-chips.com>");
|
||||
MODULE_DESCRIPTION("Rockchip RK618 SCALER driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2017 Rockchip Electronics Co. Ltd.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* Author: Wyon Bi <bivvy.bi@rock-chips.com>
|
||||
*/
|
||||
|
||||
#include "rk618_output.h"
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mfd/rk618.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drmP.h>
|
||||
#include <video/videomode.h>
|
||||
|
||||
#define RK618_VIF0_REG0 0x0000
|
||||
#define VIF_ENABLE HIWORD_UPDATE(1, 0, 0)
|
||||
@@ -64,8 +68,8 @@ static void rk618_vif_disable(struct rk618_vif *vif)
|
||||
regmap_write(vif->regmap, RK618_VIF0_REG0, VIF_DISABLE);
|
||||
}
|
||||
|
||||
static void rk618_vif_configure(struct rk618_vif *vif,
|
||||
const struct drm_display_mode *mode)
|
||||
static void rk618_vif_init(struct rk618_vif *vif,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
struct videomode vm;
|
||||
u32 vif_frame_vst, vif_frame_hst;
|
||||
@@ -74,9 +78,22 @@ static void rk618_vif_configure(struct rk618_vif *vif,
|
||||
|
||||
drm_display_mode_to_videomode(mode, &vm);
|
||||
|
||||
/* XXX */
|
||||
vif_frame_vst = 1;
|
||||
vif_frame_hst = 207;
|
||||
if (!strcmp(mode->name, "1920x1080")) {
|
||||
vif_frame_vst = 0x001;
|
||||
vif_frame_hst = 0x0cb;
|
||||
} else if (!strcmp(mode->name, "1600x900")) {
|
||||
vif_frame_vst = 0x001;
|
||||
vif_frame_hst = 0x327;
|
||||
} else if (!strcmp(mode->name, "1280x720")) {
|
||||
vif_frame_vst = 0x001;
|
||||
vif_frame_hst = 0x0cf;
|
||||
} else {
|
||||
vif_frame_vst = 0x001;
|
||||
vif_frame_hst = 0x001;
|
||||
}
|
||||
|
||||
dev_dbg(vif->dev, "vif_frame_vst=%d, vif_frame_hst=%d\n",
|
||||
vif_frame_vst, vif_frame_hst);
|
||||
|
||||
vif_hs_end = vm.hsync_len;
|
||||
vif_htotal = vm.hsync_len + vm.hback_porch + vm.hfront_porch +
|
||||
@@ -104,7 +121,7 @@ static void rk618_vif_configure(struct rk618_vif *vif,
|
||||
VIF0_SYNC_MODE_ENABLE);
|
||||
}
|
||||
|
||||
static void rk618_vif_bridge_pre_enable(struct drm_bridge *bridge)
|
||||
static void rk618_vif_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_vif *vif = bridge_to_vif(bridge);
|
||||
const struct drm_display_mode *mode = &vif->mode;
|
||||
@@ -114,16 +131,10 @@ static void rk618_vif_bridge_pre_enable(struct drm_bridge *bridge)
|
||||
|
||||
rate = clk_round_rate(vif->vif_clk, mode->clock * 1000);
|
||||
clk_set_rate(vif->vif_clk, rate);
|
||||
|
||||
rk618_vif_configure(vif, mode);
|
||||
}
|
||||
|
||||
static void rk618_vif_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_vif *vif = bridge_to_vif(bridge);
|
||||
|
||||
rk618_vif_enable(vif);
|
||||
clk_prepare_enable(vif->vif_clk);
|
||||
|
||||
rk618_vif_init(vif, mode);
|
||||
rk618_vif_enable(vif);
|
||||
}
|
||||
|
||||
static void rk618_vif_bridge_disable(struct drm_bridge *bridge)
|
||||
@@ -131,12 +142,6 @@ static void rk618_vif_bridge_disable(struct drm_bridge *bridge)
|
||||
struct rk618_vif *vif = bridge_to_vif(bridge);
|
||||
|
||||
rk618_vif_disable(vif);
|
||||
}
|
||||
|
||||
static void rk618_vif_bridge_post_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rk618_vif *vif = bridge_to_vif(bridge);
|
||||
|
||||
clk_disable_unprepare(vif->vif_clk);
|
||||
}
|
||||
|
||||
@@ -185,10 +190,8 @@ static int rk618_vif_bridge_attach(struct drm_bridge *bridge)
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs rk618_vif_bridge_funcs = {
|
||||
.pre_enable = rk618_vif_bridge_pre_enable,
|
||||
.enable = rk618_vif_bridge_enable,
|
||||
.disable = rk618_vif_bridge_disable,
|
||||
.post_disable = rk618_vif_bridge_post_disable,
|
||||
.mode_set = rk618_vif_bridge_mode_set,
|
||||
.attach = rk618_vif_bridge_attach,
|
||||
};
|
||||
@@ -209,7 +212,7 @@ static int rk618_vif_probe(struct platform_device *pdev)
|
||||
vif->dev = dev;
|
||||
platform_set_drvdata(pdev, vif);
|
||||
|
||||
vif->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
vif->regmap = dev_get_regmap(dev->parent, NULL);
|
||||
if (!vif->regmap)
|
||||
return -ENODEV;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user