480 lines
13 KiB
C
480 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2022 Rockchip Electronics Co., Ltd
|
|
*/
|
|
|
|
#include <clk.h>
|
|
#include "rk628.h"
|
|
#include "rk628_cru.h"
|
|
#include "rk628_dsi.h"
|
|
#include "rk628_gvi.h"
|
|
#include "rk628_hdmirx.h"
|
|
#include "rk628_hdmitx.h"
|
|
#include "rk628_lvds.h"
|
|
#include "rk628_post_process.h"
|
|
#include "rk628_rgb.h"
|
|
|
|
static int rk628_power_on(struct rk628 *rk628)
|
|
{
|
|
if (rk628->power_supply)
|
|
regulator_set_enable(rk628->power_supply, 1);
|
|
|
|
if (dm_gpio_is_valid(&rk628->enable_gpio)) {
|
|
dm_gpio_set_value(&rk628->enable_gpio, 1);
|
|
mdelay(10);
|
|
}
|
|
|
|
if (dm_gpio_is_valid(&rk628->reset_gpio)) {
|
|
dm_gpio_set_value(&rk628->reset_gpio, 0);
|
|
mdelay(10);
|
|
dm_gpio_set_value(&rk628->reset_gpio, 1);
|
|
mdelay(10);
|
|
dm_gpio_set_value(&rk628->reset_gpio, 0);
|
|
mdelay(10);
|
|
}
|
|
|
|
/* selete int io function */
|
|
rk628_i2c_write(rk628, GRF_GPIO3AB_SEL_CON, 0x30002000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_version_info(struct rk628 *rk628)
|
|
{
|
|
int ret;
|
|
char *version;
|
|
|
|
ret = rk628_i2c_read(rk628, GRF_SOC_VERSION, &rk628->version);
|
|
if (ret < 0) {
|
|
printf("failed to access rk628 register: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
switch (rk628->version) {
|
|
case RK628D_VERSION:
|
|
version = "D/E";
|
|
break;
|
|
case RK628F_VERSION:
|
|
version = "F/H";
|
|
break;
|
|
default:
|
|
version = "unknown";
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
printf("the IC version is: RK628-%s\n", version);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rk628_display_route_info_parse(struct rk628 *rk628)
|
|
{
|
|
int ret = 0;
|
|
ofnode np;
|
|
u32 val;
|
|
|
|
if (dev_read_bool(rk628->dev, "rk628-hdmi-in") ||
|
|
dev_read_bool(rk628->dev, "rk628,hdmi-in")) {
|
|
rk628->input_mode = BIT(INPUT_MODE_HDMI);
|
|
} else if (dev_read_bool(rk628->dev, "rk628-rgb-in") ||
|
|
dev_read_bool(rk628->dev, "rk628,rgb-in")) {
|
|
rk628->input_mode = BIT(INPUT_MODE_RGB);
|
|
ret = rk628_rgbrx_parse(rk628);
|
|
} else if (dev_read_bool(rk628->dev, "rk628-bt1120-in") ||
|
|
dev_read_bool(rk628->dev, "rk628,bt1120-in")) {
|
|
rk628->input_mode = BIT(INPUT_MODE_BT1120);
|
|
ret = rk628_rgbrx_parse(rk628);
|
|
} else {
|
|
rk628->input_mode = BIT(INPUT_MODE_RGB);
|
|
}
|
|
|
|
if (ofnode_valid(dev_read_subnode(rk628->dev, "rk628-gvi-out")) ||
|
|
ofnode_valid(dev_read_subnode(rk628->dev, "rk628-gvi"))) {
|
|
np = dev_read_subnode(rk628->dev, "rk628-gvi-out");
|
|
if (!ofnode_valid(np))
|
|
np = dev_read_subnode(rk628->dev, "rk628-gvi");
|
|
|
|
rk628->output_mode |= BIT(OUTPUT_MODE_GVI);
|
|
ret = rk628_gvi_parse(rk628, np);
|
|
} else if (ofnode_valid(dev_read_subnode(rk628->dev, "rk628-lvds-out")) ||
|
|
ofnode_valid(dev_read_subnode(rk628->dev, "rk628-lvds"))) {
|
|
np = dev_read_subnode(rk628->dev, "rk628-lvds-out");
|
|
if (!ofnode_valid(np))
|
|
np = dev_read_subnode(rk628->dev, "rk628-lvds");
|
|
|
|
rk628->output_mode |= BIT(OUTPUT_MODE_LVDS);
|
|
ret = rk628_lvds_parse(rk628, np);
|
|
} else if (ofnode_valid(dev_read_subnode(rk628->dev, "rk628-dsi-out")) ||
|
|
ofnode_valid(dev_read_subnode(rk628->dev, "rk628-dsi"))) {
|
|
np = dev_read_subnode(rk628->dev, "rk628-dsi-out");
|
|
if (!ofnode_valid(np))
|
|
np = dev_read_subnode(rk628->dev, "rk628-dsi");
|
|
|
|
rk628->output_mode |= BIT(OUTPUT_MODE_DSI);
|
|
ret = rk628_dsi_parse(rk628, np);
|
|
} else if (dev_read_bool(rk628->dev, "rk628-csi-out") ||
|
|
dev_read_bool(rk628->dev, "rk628,csi-out")) {
|
|
rk628->output_mode |= BIT(OUTPUT_MODE_CSI);
|
|
}
|
|
|
|
if (dev_read_bool(rk628->dev, "rk628-hdmi-out") ||
|
|
dev_read_bool(rk628->dev, "rk628,hdmi-out"))
|
|
rk628->output_mode |= BIT(OUTPUT_MODE_HDMI);
|
|
|
|
if (dev_read_bool(rk628->dev, "rk628-rgb-out") ||
|
|
dev_read_bool(rk628->dev, "rk628-rgb")) {
|
|
np = dev_read_subnode(rk628->dev, "rk628-rgb-out");
|
|
if (!ofnode_valid(np))
|
|
np = dev_read_subnode(rk628->dev, "rk628-rgb");
|
|
|
|
rk628->output_mode |= BIT(OUTPUT_MODE_RGB);
|
|
ret = rk628_rgbtx_parse(rk628, np);
|
|
} else if (dev_read_bool(rk628->dev, "rk628-bt1120-out") ||
|
|
dev_read_bool(rk628->dev, "rk628-bt1120")) {
|
|
np = dev_read_subnode(rk628->dev, "rk628-bt1120-out");
|
|
if (!ofnode_valid(np))
|
|
np = dev_read_subnode(rk628->dev, "rk628-bt1120");
|
|
|
|
rk628->output_mode |= BIT(OUTPUT_MODE_BT1120);
|
|
ret = rk628_rgbtx_parse(rk628, np);
|
|
}
|
|
|
|
val = dev_read_u32_default(rk628->dev, "mode-sync-pol", 1);
|
|
rk628->sync_pol = (!val ? MODE_FLAG_NSYNC : MODE_FLAG_PSYNC);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool rk628_display_route_check(struct rk628 *rk628)
|
|
{
|
|
if (!hweight32(rk628->input_mode) || !hweight32(rk628->output_mode))
|
|
return false;
|
|
|
|
/*
|
|
* the RGB/BT1120 RX and RGB/BT1120 TX are the same shared IP
|
|
* and cannot be used as both input and output simultaneously.
|
|
*/
|
|
if ((rk628_input_is_rgb(rk628) || rk628_input_is_bt1120(rk628)) &&
|
|
(rk628_output_is_rgb(rk628) || rk628_output_is_bt1120(rk628)))
|
|
return false;
|
|
|
|
if (rk628->version == RK628F_VERSION)
|
|
return true;
|
|
|
|
/* rk628d only support rgb and hdmi output simultaneously */
|
|
if (hweight32(rk628->output_mode) > 2)
|
|
return false;
|
|
|
|
if (hweight32(rk628->output_mode) == 2 &&
|
|
!(rk628_output_is_rgb(rk628) && rk628_output_is_hdmi(rk628)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline size_t strlcat(char *dest, const char *src, size_t n)
|
|
{
|
|
strcat(dest, src);
|
|
return strlen(dest) + strlen(src);
|
|
}
|
|
|
|
static void rk628_current_display_route(struct rk628 *rk628, char *input_s,
|
|
int input_s_len, char *output_s,
|
|
int output_s_len)
|
|
{
|
|
*input_s = '\0';
|
|
*output_s = '\0';
|
|
|
|
if (rk628_input_is_rgb(rk628))
|
|
strlcat(input_s, "RGB", input_s_len);
|
|
else if (rk628_input_is_bt1120(rk628))
|
|
strlcat(input_s, "BT1120", input_s_len);
|
|
else if (rk628_input_is_hdmi(rk628))
|
|
strlcat(input_s, "HDMI", input_s_len);
|
|
else
|
|
strlcat(input_s, "unknown", input_s_len);
|
|
|
|
if (rk628_output_is_rgb(rk628))
|
|
strlcat(output_s, "RGB ", output_s_len);
|
|
|
|
if (rk628_output_is_bt1120(rk628))
|
|
strlcat(output_s, "BT1120 ", output_s_len);
|
|
|
|
if (rk628_output_is_gvi(rk628))
|
|
strlcat(output_s, "GVI ", output_s_len);
|
|
|
|
if (rk628_output_is_lvds(rk628))
|
|
strncat(output_s, "LVDS ", output_s_len);
|
|
|
|
if (rk628_output_is_dsi(rk628))
|
|
strlcat(output_s, "DSI ", output_s_len);
|
|
|
|
if (rk628_output_is_csi(rk628))
|
|
strlcat(output_s, "CSI ", output_s_len);
|
|
|
|
if (rk628_output_is_hdmi(rk628))
|
|
strlcat(output_s, "HDMI ", output_s_len);
|
|
|
|
if (!strlen(output_s))
|
|
strlcat(output_s, "unknown", output_s_len);
|
|
}
|
|
|
|
static void rk628_show_current_display_route(struct rk628 *rk628)
|
|
{
|
|
char input_s[10], output_s[30];
|
|
|
|
rk628_current_display_route(rk628, input_s, sizeof(input_s),
|
|
output_s, sizeof(output_s));
|
|
|
|
printf("rk628 input_mode: %s, output_mode: %s\n", input_s, output_s);
|
|
}
|
|
|
|
static void rk628_pwr_consumption_init(struct rk628 *rk628)
|
|
{
|
|
/* set pin as int function to allow output interrupt */
|
|
rk628_i2c_write(rk628, GRF_GPIO3AB_SEL_CON, 0x30002000);
|
|
|
|
/*
|
|
* set unuse pin as GPIO input and
|
|
* pull down to reduce power consumption
|
|
*/
|
|
rk628_i2c_write(rk628, GRF_GPIO2AB_SEL_CON, 0xffff0000);
|
|
rk628_i2c_write(rk628, GRF_GPIO2C_SEL_CON, 0xffff0000);
|
|
rk628_i2c_write(rk628, GRF_GPIO3AB_SEL_CON, 0x10b0000);
|
|
rk628_i2c_write(rk628, GRF_GPIO2C_P_CON, 0x3f0015);
|
|
rk628_i2c_write(rk628, GRF_GPIO3A_P_CON, 0xcc0044);
|
|
|
|
if (!rk628_output_is_hdmi(rk628)) {
|
|
u32 mask = SW_OUTPUT_MODE_MASK;
|
|
u32 val = SW_OUTPUT_MODE(OUTPUT_MODE_HDMI);
|
|
|
|
if (rk628->version == RK628F_VERSION) {
|
|
mask = SW_HDMITX_EN_MASK;
|
|
val = SW_HDMITX_EN(1);
|
|
}
|
|
|
|
/* disable clock/data channel and band gap when hdmitx not work */
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0, mask, val);
|
|
rk628_i2c_write(rk628, HDMI_PHY_SYS_CTL, 0x17);
|
|
rk628_i2c_write(rk628, HDMI_PHY_CHG_PWR, 0x0);
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0, mask, 0);
|
|
}
|
|
}
|
|
|
|
static void rk628_display_enable(struct rk628 *rk628)
|
|
{
|
|
if (rk628_input_is_rgb(rk628))
|
|
rk628_rgb_rx_enable(rk628);
|
|
|
|
/*
|
|
* bt1120 needs to configure the timing register, but hdmitx will modify
|
|
* the timing as needed, so the bt1120 enable process is moved to the
|
|
* configuration of post_process (function rk628_post_process_enable in
|
|
* rk628_post_process.c)
|
|
*/
|
|
|
|
if (rk628_output_is_rgb(rk628))
|
|
rk628_rgb_tx_enable(rk628);
|
|
|
|
if (rk628_input_is_hdmi(rk628))
|
|
rk628_hdmirx_enable(rk628);
|
|
|
|
if (rk628_output_is_dsi(rk628)) {
|
|
rk628_mipi_dsi_pre_enable(rk628);
|
|
rk628_mipi_dsi_enable(rk628);
|
|
}
|
|
|
|
|
|
if (rk628_output_is_bt1120(rk628))
|
|
rk628_bt1120_tx_enable(rk628);
|
|
|
|
if (!rk628_output_is_hdmi(rk628)) {
|
|
rk628_post_process_init(rk628);
|
|
rk628_post_process_enable(rk628);
|
|
}
|
|
|
|
if (rk628_output_is_lvds(rk628))
|
|
rk628_lvds_enable(rk628);
|
|
|
|
if (rk628_output_is_gvi(rk628))
|
|
rk628_gvi_enable(rk628);
|
|
}
|
|
|
|
static void
|
|
of_parse_rk628_display_timing( ofnode np, struct rk628_videomode *vm)
|
|
{
|
|
u32 val = 0;
|
|
|
|
ofnode_read_u32(np, "clock-frequency", &vm->pixelclock);
|
|
ofnode_read_u32(np, "hactive", &vm->hactive);
|
|
ofnode_read_u32(np, "hfront-porch", &vm->hfront_porch);
|
|
ofnode_read_u32(np, "hback-porch", &vm->hback_porch);
|
|
ofnode_read_u32(np, "hsync-len", &vm->hsync_len);
|
|
|
|
ofnode_read_u32(np, "vactive", &vm->vactive);
|
|
ofnode_read_u32(np, "vfront-porch", &vm->vfront_porch);
|
|
ofnode_read_u32(np, "vback-porch", &vm->vback_porch);
|
|
ofnode_read_u32(np, "vsync-len", &vm->vsync_len);
|
|
|
|
vm->flags = 0;
|
|
ofnode_read_u32(np, "hsync-active", &val);
|
|
vm->flags |= val ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC;
|
|
|
|
ofnode_read_u32(np, "vsync-active", &val);
|
|
vm->flags |= val ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC;
|
|
}
|
|
|
|
static void
|
|
rk628_display_mode_from_videomode(const struct rk628_videomode *vm,
|
|
struct drm_display_mode *dmode)
|
|
{
|
|
dmode->hdisplay = vm->hactive;
|
|
dmode->hsync_start = dmode->hdisplay + vm->hfront_porch;
|
|
dmode->hsync_end = dmode->hsync_start + vm->hsync_len;
|
|
dmode->htotal = dmode->hsync_end + vm->hback_porch;
|
|
|
|
dmode->vdisplay = vm->vactive;
|
|
dmode->vsync_start = dmode->vdisplay + vm->vfront_porch;
|
|
dmode->vsync_end = dmode->vsync_start + vm->vsync_len;
|
|
dmode->vtotal = dmode->vsync_end + vm->vback_porch;
|
|
|
|
dmode->clock = vm->pixelclock / 1000;
|
|
dmode->flags = vm->flags;
|
|
}
|
|
|
|
static int rk628_get_video_mode(struct rk628 *rk628)
|
|
{
|
|
ofnode timings_np, src_np, dst_np;
|
|
struct rk628_videomode vm;
|
|
|
|
timings_np = ofnode_find_subnode(dev_ofnode(rk628->dev), "display-timings");
|
|
if (!ofnode_valid(timings_np))
|
|
return -EINVAL;
|
|
|
|
src_np = ofnode_find_subnode(timings_np, "src-timing");
|
|
if (!ofnode_valid(src_np)) {
|
|
printf("rk628 failed to found src timing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
of_parse_rk628_display_timing(src_np, &vm);
|
|
rk628_display_mode_from_videomode(&vm, &rk628->src_mode);
|
|
printf("rk628 src mode: %d %d %d %d %d %d %d %d %d 0x%x\n",
|
|
rk628->src_mode.clock, rk628->src_mode.hdisplay, rk628->src_mode.hsync_start,
|
|
rk628->src_mode.hsync_end, rk628->src_mode.htotal, rk628->src_mode.vdisplay,
|
|
rk628->src_mode.vsync_start, rk628->src_mode.vsync_end, rk628->src_mode.vtotal,
|
|
rk628->src_mode.flags);
|
|
|
|
dst_np = ofnode_find_subnode(timings_np, "dst-timing");
|
|
if (!ofnode_valid(dst_np)) {
|
|
printf("rk628 failed to found dst timing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
of_parse_rk628_display_timing(dst_np, &vm);
|
|
rk628_display_mode_from_videomode(&vm, &rk628->dst_mode);
|
|
printf("rk628 dst mode: %d %d %d %d %d %d %d %d %d 0x%x\n",
|
|
rk628->dst_mode.clock, rk628->dst_mode.hdisplay, rk628->dst_mode.hsync_start,
|
|
rk628->dst_mode.hsync_end, rk628->dst_mode.htotal, rk628->dst_mode.vdisplay,
|
|
rk628->dst_mode.vsync_start, rk628->dst_mode.vsync_end, rk628->dst_mode.vtotal,
|
|
rk628->dst_mode.flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_display_timings_get(struct rk628 *rk628)
|
|
{
|
|
int ret;
|
|
|
|
ret = rk628_get_video_mode(rk628);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int rk628_probe(struct udevice *dev)
|
|
{
|
|
struct rk628 *rk628 = dev_get_priv(dev);
|
|
int ret;
|
|
|
|
ret = i2c_set_chip_offset_len(dev, 4);
|
|
if (ret)
|
|
return ret;
|
|
|
|
rk628->dev = dev;
|
|
|
|
ret = rk628_display_route_info_parse(rk628);
|
|
if (ret) {
|
|
printf("display route parse err\n");
|
|
return ret;
|
|
}
|
|
|
|
if (!rk628_output_is_csi(rk628) && !rk628_output_is_hdmi(rk628)) {
|
|
ret = rk628_display_timings_get(rk628);
|
|
if (ret) {
|
|
printf("rk628 display timings err\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = uclass_get_device_by_phandle(UCLASS_REGULATOR, dev,
|
|
"power-supply",
|
|
&rk628->power_supply);
|
|
if (ret && ret != -ENOENT) {
|
|
dev_err(dev, "Cannot get power supply: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = gpio_request_by_name(dev, "enable-gpios", 0,
|
|
&rk628->enable_gpio, GPIOD_IS_OUT);
|
|
if (ret && ret != -ENOENT) {
|
|
dev_err(dev, "%s: failed to get enable GPIO: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = gpio_request_by_name(dev, "reset-gpios", 0,
|
|
&rk628->reset_gpio, GPIOD_IS_OUT);
|
|
if (ret && ret != -ENOENT) {
|
|
dev_err(dev, "%s: failed to get reset GPIO: %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Process 'assigned-{clocks/clock-parents/clock-rates}'
|
|
* properties for ref clock from soc
|
|
*/
|
|
ret = clk_set_defaults(dev);
|
|
if (ret)
|
|
dev_err(dev, "%s clk_set_defaults failed %d\n", __func__, ret);
|
|
|
|
rk628_power_on(rk628);
|
|
rk628_version_info(rk628);
|
|
|
|
rk628_show_current_display_route(rk628);
|
|
|
|
if (!rk628_display_route_check(rk628)) {
|
|
printf("rk628 display route check err\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rk628_pwr_consumption_init(rk628);
|
|
rk628_cru_init(rk628);
|
|
rk628_display_enable(rk628);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct udevice_id rk628_of_match[] = {
|
|
{ .compatible = "rockchip,rk628" },
|
|
{}
|
|
};
|
|
|
|
U_BOOT_DRIVER(rk628) = {
|
|
.name = "rk628",
|
|
.id = UCLASS_I2C_GENERIC,
|
|
.of_match = rk628_of_match,
|
|
.bind = dm_scan_fdt_dev,
|
|
.probe = rk628_probe,
|
|
.priv_auto_alloc_size = sizeof(struct rk628),
|
|
};
|