Qubot 3a1667dda6 Revert "drm: sunxi: log only HDMI state changes"
This reverts commit 7fcd7b732ec3e9aa7935eaf69a0ee8e43048c8e6.
2026-06-06 11:14:48 +08:00

1147 lines
29 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
/* sunxi_tcon_v35x.c
*
* Copyright (C) 2023 Allwinnertech 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.
*/
#include <linux/dma-mapping.h>
#include <linux/clk.h>
#include <drm/drm_print.h>
#include <linux/of_graph.h>
#include <linux/interrupt.h>
#include <linux/component.h>
#include <linux/phy/phy.h>
#include <linux/of_platform.h>
#include <linux/reset.h>
#include <linux/delay.h>
#include "sunxi_tcon.h"
#include "sunxi_tcon_top.h"
static int sunxi_tcon_request_irq(struct sunxi_tcon *hwtcon);
static int sunxi_tcon_free_irq(struct sunxi_tcon *hwtcon);
static const struct of_device_id sunxi_tcon_match[] = {
{ .compatible = "allwinner,tcon-lcd", },
{ .compatible = "allwinner,tcon-tv", },
{},
};
static enum tcon_type get_dev_tcon_type(struct device_node *node)
{
if (of_device_is_compatible(node, "allwinner,tcon-lcd"))
return TCON_LCD;
if (of_device_is_compatible(node, "allwinner,tcon-tv"))
return TCON_TV;
DRM_ERROR("invalid tcon match compatible\n");
return TCON_LCD;
}
//remember top put tcon_top
static int sunxi_tcon_get_tcon_top(struct sunxi_tcon *tcon)
{
struct device_node *top_node =
of_parse_phandle(tcon->dev->of_node, "top", 0);
struct platform_device *pdev = of_find_device_by_node(top_node);
if (!pdev) {
DRM_INFO("tcon %d use no tcon top\n", tcon->id);
return 0;
}
tcon->tcon_top = &pdev->dev;
return 0;
}
static int sunxi_tcon_output_create(struct device *dev, struct drm_device *drm)
{
return 0;
}
static int sunxi_tcon_output_destroy(struct device *dev, struct drm_device *drm)
{
return 0;
}
static int sunxi_tcon_lcd_set_clk(struct sunxi_tcon *hwtcon, unsigned long pixel_clk)
{
int ret = 0;
unsigned long tcon_rate, tcon_rate_set;
if (!hwtcon->tcon_ctrl.cfg.sw_enable && pixel_clk) {
tcon_rate = pixel_clk;
clk_set_rate(hwtcon->mclk, tcon_rate);
tcon_rate_set = clk_get_rate(hwtcon->mclk);
if (tcon_rate_set != tcon_rate)
DRM_INFO("tcon rate to be set:%luHz, real clk rate:%luHz\n", tcon_rate,
tcon_rate_set);
}
if (hwtcon->rst_bus_tcon) {
ret = reset_control_deassert(hwtcon->rst_bus_tcon);
if (ret) {
DRM_ERROR("reset_control_deassert for rst_bus_tcon failed!\n");
return -1;
}
}
if (hwtcon->ahb_vid_out) {
ret = clk_prepare_enable(hwtcon->ahb_vid_out);
if (ret) {
DRM_ERROR("clk_prepare_enable for ahb_vid_out failed!\n");
return -1;
}
}
if (hwtcon->mbus_vo_sys) {
ret = clk_prepare_enable(hwtcon->mbus_vo_sys);
if (ret) {
DRM_ERROR("clk_prepare_enable for mbus_vo_sys failed!\n");
return -1;
}
}
ret = clk_prepare_enable(hwtcon->mclk);
if (ret != 0) {
DRM_ERROR("fail enable TCON%d's clock!\n", hwtcon->id);
return -1;
}
ret = clk_prepare_enable(hwtcon->mclk_bus);
if (ret != 0) {
DRM_ERROR("fail enable TCON%d's bus clock!\n", hwtcon->id);
return -1;
}
return 0;
}
static void sunxi_tcon_calc_judge_line(struct sunxi_tcon *hwtcon,
const struct disp_video_timings *p_timgs)
{
u64 usec_per_line, start_delay = 0;
unsigned int usec_start_delay, usec_judge_point;
unsigned int tcon_type = hwtcon->type;
usec_per_line = p_timgs->hor_total_time * 1000000ull;
if (p_timgs->pixel_clk)
do_div(usec_per_line, p_timgs->pixel_clk);
if (tcon_type == TCON_LCD) {
start_delay = tcon_lcd_get_start_delay(&hwtcon->tcon_lcd);
} else {
start_delay = tcon_tv_get_start_delay(&hwtcon->tcon_tv);
}
usec_start_delay = start_delay * usec_per_line;
if (usec_start_delay <= 200)
usec_judge_point = usec_start_delay * 3 / 7;
else if (usec_start_delay <= 400)
usec_judge_point = usec_start_delay / 2;
else
usec_judge_point = 200;
if (usec_per_line)
hwtcon->judge_line = usec_judge_point / usec_per_line;
if (hwtcon->judge_line == 0)
hwtcon->judge_line = 1;
}
static int sunxi_tcon_lcd_prepare(struct sunxi_tcon *hwtcon, unsigned long pixel_clk)
{
int ret = 0;
if (hwtcon->tcon_top)
sunxi_tcon_top_clk_enable(hwtcon->tcon_top);
ret = sunxi_tcon_lcd_set_clk(hwtcon, pixel_clk);
if (ret < 0) {
DRM_ERROR("sunxi_tcon_lcd_set_clk failed\n");
return -1;
}
return 0;
}
static void sunxi_tcon_lcd_unprepare(struct sunxi_tcon *hwtcon)
{
clk_disable_unprepare(hwtcon->mclk_bus);
clk_disable_unprepare(hwtcon->mclk);
if (hwtcon->ahb_vid_out)
clk_disable_unprepare(hwtcon->ahb_vid_out);
if (hwtcon->mbus_vo_sys)
clk_disable_unprepare(hwtcon->mbus_vo_sys);
reset_control_assert(hwtcon->rst_bus_tcon);
if (hwtcon->tcon_top)
sunxi_tcon_top_clk_disable(hwtcon->tcon_top);
}
int sunxi_tcon_dsi_enable_output(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
struct disp_dsi_para *dsi_para = &hwtcon->tcon_ctrl.cfg.dsi_para;
tcon_dsi_open(&hwtcon->tcon_lcd, dsi_para);
return 0;
}
int sunxi_tcon_dsi_disable_output(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
tcon_dsi_close(&hwtcon->tcon_lcd);
return 0;
}
static int sunxi_tcon_dsi_mode_init(struct device *tcon_dev)
{
int ret = 0;
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
struct disp_dsi_para *dsi_para = &hwtcon->tcon_ctrl.cfg.dsi_para;
bool sw_enable = hwtcon->tcon_ctrl.cfg.sw_enable;
bool displl_clk = hwtcon->tcon_ctrl.cfg.displl_clk;
unsigned int tcon_div = hwtcon->tcon_ctrl.cfg.tcon_lcd_div;
DRM_INFO("[DSI] %s start\n", __FUNCTION__);
hwtcon->is_enabled = true;
if (displl_clk)
ret = sunxi_tcon_lcd_prepare(hwtcon, 0);
else
ret = sunxi_tcon_lcd_prepare(hwtcon, dsi_para->timings.pixel_clk * tcon_div);
if (ret < 0) {
DRM_ERROR("sunxi_tcon_lcd_prepare failed\n");
return ret;
}
if (!sw_enable) {
tcon_lcd_init(&hwtcon->tcon_lcd);
tcon_lcd_set_dclk_div(&hwtcon->tcon_lcd, tcon_div);
if (displl_clk)
tcon_lcd_dsi_clk_source(hwtcon->id, dsi_para->dual_dsi);
if (tcon_dsi_cfg(&hwtcon->tcon_lcd, dsi_para) != 0) { /* the final param is rcq related */
DRM_ERROR("lcd cfg fail!\n");
return -1;
}
tcon_lcd_src_select(&hwtcon->tcon_lcd, CONFIG_AW_DRM_BOOT_COLORBAR);
// tcon_dsi_open(&hwtcon->tcon_lcd, dsi_para);
}
sunxi_tcon_calc_judge_line(hwtcon, &dsi_para->timings);
if (hwtcon->pending_enable_vblank) {
sunxi_tcon_enable_vblank(tcon_dev, 1);
hwtcon->pending_enable_vblank = false;
}
if (hwtcon->tcon_ctrl.cfg.slave_dsi)
sunxi_tcon_request_irq(hwtcon);
return 0;
}
static int sunxi_tcon_dsi_mode_exit(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
if (hwtcon->tcon_ctrl.cfg.slave_dsi)
sunxi_tcon_free_irq(hwtcon);
hwtcon->is_enabled = false;
hwtcon->judge_line = 0;
// tcon_dsi_close(&hwtcon->tcon_lcd);
tcon_lcd_exit(&hwtcon->tcon_lcd);
sunxi_tcon_lcd_unprepare(hwtcon);
return 0;
}
int sunxi_lvds_enable_output(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
struct disp_lvds_para *lvds_para = &hwtcon->tcon_ctrl.cfg.lvds_para;
tcon_lvds_open(&hwtcon->tcon_lcd);
lvds_open(&hwtcon->tcon_lcd, lvds_para);
return 0;
}
int sunxi_lvds_disable_output(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
tcon_lvds_close(&hwtcon->tcon_lcd);
lvds_close(&hwtcon->tcon_lcd);
return 0;
}
static int sunxi_tcon_lvds_mode_init(struct device *tcon_dev)
{
int ret = 0;
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
struct disp_lvds_para *lvds_para = &hwtcon->tcon_ctrl.cfg.lvds_para;
bool sw_enable = hwtcon->tcon_ctrl.cfg.sw_enable;
bool displl_clk = hwtcon->tcon_ctrl.cfg.displl_clk;
unsigned int tcon_div = hwtcon->tcon_ctrl.cfg.tcon_lcd_div;
DRM_INFO("[LVDS] %s start\n", __FUNCTION__);
hwtcon->is_enabled = true;
if (displl_clk)
ret = sunxi_tcon_lcd_prepare(hwtcon, 0);
else
ret = sunxi_tcon_lcd_prepare(hwtcon, lvds_para->timings.pixel_clk * tcon_div);
if (ret < 0) {
DRM_ERROR("sunxi_tcon_lcd_prepare failed\n");
return ret;
}
if (!sw_enable) {
tcon_lcd_init(&hwtcon->tcon_lcd);
tcon_lcd_set_dclk_div(&hwtcon->tcon_lcd, tcon_div);
if (displl_clk)
tcon_lcd_dsi_clk_source(hwtcon->id, 0);
if (tcon_lvds_cfg(&hwtcon->tcon_lcd, lvds_para) != 0) { /* the final param is rcq related */
DRM_ERROR("lcd cfg fail!\n");
return -1;
}
tcon_lcd_src_select(&hwtcon->tcon_lcd, CONFIG_AW_DRM_BOOT_COLORBAR);
}
sunxi_tcon_calc_judge_line(hwtcon, &lvds_para->timings);
if (hwtcon->pending_enable_vblank) {
sunxi_tcon_enable_vblank(tcon_dev, 1);
hwtcon->pending_enable_vblank = false;
}
sunxi_tcon_request_irq(hwtcon);
return 0;
}
static int sunxi_tcon_lvds_mode_exit(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
hwtcon->is_enabled = false;
hwtcon->judge_line = 0;
sunxi_tcon_free_irq(hwtcon);
tcon_lcd_exit(&hwtcon->tcon_lcd);
sunxi_tcon_lcd_unprepare(hwtcon);
return 0;
}
int sunxi_rgb_enable_output(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
tcon_rgb_open(&hwtcon->tcon_lcd);
return 0;
}
int sunxi_rgb_disable_output(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
tcon_rgb_close(&hwtcon->tcon_lcd);
return 0;
}
static int sunxi_tcon_rgb_mode_init(struct device *tcon_dev)
{
int ret = 0;
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
struct disp_rgb_para *rgb_para = &hwtcon->tcon_ctrl.cfg.rgb_para;
bool sw_enable = hwtcon->tcon_ctrl.cfg.sw_enable;
bool displl_clk = hwtcon->tcon_ctrl.cfg.displl_clk;
unsigned int tcon_div = hwtcon->tcon_ctrl.cfg.tcon_lcd_div;
DRM_INFO("[RGB] %s start\n", __FUNCTION__);
hwtcon->is_enabled = true;
ret = sunxi_tcon_lcd_prepare(hwtcon, rgb_para->timings.pixel_clk * tcon_div);
if (ret < 0) {
DRM_ERROR("sunxi_tcon_lcd_prepare failed\n");
return ret;
}
if (!sw_enable) {
tcon_lcd_init(&hwtcon->tcon_lcd);
tcon_lcd_set_dclk_div(&hwtcon->tcon_lcd, tcon_div);
if (displl_clk)
tcon_lcd_dsi_clk_source(hwtcon->id, 0);
if (tcon_rgb_cfg(&hwtcon->tcon_lcd, rgb_para) != 0) {
DRM_ERROR("lcd-rgb cfg fail!\n");
return -1;
}
tcon_lcd_src_select(&hwtcon->tcon_lcd, CONFIG_AW_DRM_BOOT_COLORBAR);
}
sunxi_tcon_calc_judge_line(hwtcon, &rgb_para->timings);
if (hwtcon->pending_enable_vblank) {
sunxi_tcon_enable_vblank(tcon_dev, 1);
hwtcon->pending_enable_vblank = false;
}
sunxi_tcon_request_irq(hwtcon);
return 0;
}
static int sunxi_tcon_rgb_mode_exit(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
hwtcon->is_enabled = false;
hwtcon->judge_line = 0;
sunxi_tcon_free_irq(hwtcon);
tcon_lcd_exit(&hwtcon->tcon_lcd);
sunxi_tcon_lcd_unprepare(hwtcon);
return 0;
}
static int sunxi_tcon_device_query_irq(struct sunxi_tcon *hwtcon)
{
int ret = 0;
if (hwtcon->type == TCON_LCD)
ret = tcon_lcd_irq_query(&hwtcon->tcon_lcd, LCD_IRQ_TCON0_VBLK);
else
ret = tcon_tv_irq_query(&hwtcon->tcon_tv, LCD_IRQ_TCON1_VBLK);
return ret;
}
u64 sunxi_tcon_vblank_cnt_and_time(struct counter *cnt, ktime_t *vblanktime)
{
*vblanktime = cnt->vblank_timestamp;
return atomic64_read(&cnt->vblank_cnt);
}
u64 sunxi_tcon_get_refreshraw_and_vblankcnt(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
static ktime_t time_us[2];
u64 vblank_cnt[2];
u64 fps_raw, vblank_delta, time_us_delta;
vblank_cnt[0] = sunxi_tcon_vblank_cnt_and_time(&hwtcon->cnt, &time_us[0]);
msleep(100);
vblank_cnt[1] = sunxi_tcon_vblank_cnt_and_time(&hwtcon->cnt, &time_us[1]);
vblank_delta = vblank_cnt[1] - vblank_cnt[0];
time_us_delta = ktime_us_delta(time_us[1], time_us[0]);
fps_raw = div64_u64(vblank_delta * 1000000 * 1000, time_us_delta); /* For Float */
return fps_raw;
}
static irqreturn_t sunxi_tcon_irq_event_proc(int irq, void *parg)
{
struct sunxi_tcon *hwtcon = parg;
struct disp_output_config *disp_cfg = &hwtcon->tcon_ctrl.cfg;
struct disp_video_timings timing_t;
u32 dsi_line, tcon_line;
/* Increment TCON interrupt counter */
atomic64_inc(&hwtcon->cnt.irqcnt);
if ((hwtcon->type == TCON_LCD) &&
tcon_lcd_irq_query(&hwtcon->tcon_lcd, LCD_IRQ_TCON0_LINE)) {
tcon_lcd_get_timing(&hwtcon->tcon_lcd, &timing_t);
dsi_line = disp_cfg->get_dsi_line(disp_cfg->dev);
tcon_line = tcon_lcd_get_cur_line(&hwtcon->tcon_lcd);
if (dsi_line > 4 && dsi_line < (timing_t.ver_sync_time - 10)) {
sunxi_tcon_updata_vt(&hwtcon->tcon_lcd, &disp_cfg->dsi_para.timings,
disp_cfg->dsi_para.vrr_setp);
if (disp_cfg->set_dsi_vbp)
disp_cfg->set_dsi_vbp(disp_cfg->dev);
} else
DRM_WARN("[TCON-VRR] dsi_line:%d, tcon_line:%d\n", dsi_line, tcon_line);
return IRQ_HANDLED;
}
/* Check if this is a vblank interrupt and increment counter */
if (sunxi_tcon_device_query_irq(hwtcon)) {
atomic64_inc(&hwtcon->cnt.vblank_cnt);
hwtcon->cnt.vblank_timestamp = ktime_get();
}
return hwtcon->irq_handler(irq, hwtcon->irq_data);
}
static int sunxi_tcon_request_irq(struct sunxi_tcon *hwtcon)
{
int ret;
hwtcon->irq_handler = hwtcon->tcon_ctrl.cfg.irq_handler;
hwtcon->irq_data = hwtcon->tcon_ctrl.cfg.irq_data;
ret = devm_request_irq(hwtcon->dev, hwtcon->irq_no,
sunxi_tcon_irq_event_proc, 0, dev_name(hwtcon->dev),
hwtcon);
if (ret) {
DRM_ERROR("Couldn't request the IRQ for tcon\n");
}
return ret;
}
static int sunxi_tcon_free_irq(struct sunxi_tcon *hwtcon)
{
if (hwtcon->irq_data != hwtcon->tcon_ctrl.cfg.irq_data) {
DRM_ERROR("Couldn't free the IRQ for tcon\n");
return -EINVAL;
}
devm_free_irq(hwtcon->dev, hwtcon->irq_no, hwtcon);
return 0;
}
static int
_sunxi_tcon_hdmi_set_rate(struct sunxi_tcon *hwtcon, unsigned long pclk)
{
long rate_diff = 0;
if (IS_ERR_OR_NULL(hwtcon)) {
DRM_ERROR("check point hwtcon is null\n");
return -1;
}
/* disable tcon tv clock */
if (!IS_ERR_OR_NULL(hwtcon->mclk))
clk_disable_unprepare(hwtcon->mclk);
if (!IS_ERR_OR_NULL(hwtcon->mclk_bus))
clk_disable_unprepare(hwtcon->mclk_bus);
if (!IS_ERR_OR_NULL(hwtcon->rst_bus_tcon))
reset_control_assert(hwtcon->rst_bus_tcon);
/* enable tcon tv clock and set rate */
if (!IS_ERR_OR_NULL(hwtcon->rst_bus_tcon))
reset_control_deassert(hwtcon->rst_bus_tcon);
clk_set_rate(hwtcon->mclk, pclk);
rate_diff = (pclk - clk_round_rate(hwtcon->mclk, pclk));
if (rate_diff != 0)
DRM_WARN("suxni tcon hdmi set rate: %ldHz and get diff: %ldHz\n",
pclk, rate_diff);
if (!IS_ERR_OR_NULL(hwtcon->mclk_bus))
clk_prepare_enable(hwtcon->mclk_bus);
if (!IS_ERR_OR_NULL(hwtcon->mclk))
clk_prepare_enable(hwtcon->mclk);
return 0;
}
static int _sunxi_tcon_hdmi_config(struct sunxi_tcon *hwtcon, unsigned int de_id,
struct disp_video_timings *p_timing, enum disp_csc_type format)
{
struct disp_video_timings *timings = NULL;
if (!p_timing) {
DRM_ERROR("point p_timing is null\n");
return -1;
}
timings = kmalloc(sizeof(struct disp_video_timings), GFP_KERNEL | __GFP_ZERO);
if (timings) {
memcpy(timings, p_timing, sizeof(struct disp_video_timings));
if (format == DISP_CSC_TYPE_YUV420) {
timings->x_res /= 2;
timings->hor_total_time /= 2;
timings->hor_back_porch /= 2;
timings->hor_front_porch /= 2;
timings->hor_sync_time /= 2;
}
}
tcon_tv_init(&hwtcon->tcon_tv);
tcon_tv_set_timming(&hwtcon->tcon_tv, timings ? timings : p_timing);
tcon_tv_src_select(&hwtcon->tcon_tv, LCD_SRC_DE, de_id);
tcon_tv_black_src(&hwtcon->tcon_tv, 0x0, format);
#if IS_ENABLED(CONFIG_ARCH_SUN55IW3)
tcon_tv_volume_force(&hwtcon->tcon_tv, 0x00040014);
#endif /* CONFIG_ARCH_SUN55IW3 */
tcon_tv_open(&hwtcon->tcon_tv);
kfree(timings);
return 0;
}
/**
* @desc: for hdmi module init or resume flow
* open base resource
*/
int sunxi_tcon_hdmi_open(struct device *dev, u8 src)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(dev);
if (IS_ERR_OR_NULL(hwtcon)) {
DRM_ERROR("check point hwtcon is null\n");
return -1;
}
if (!IS_ERR_OR_NULL(hwtcon->tcon_top))
sunxi_tcon_top_clk_enable(hwtcon->tcon_top);
tcon_top_hdmi_set_gate(hwtcon->id, 1);
tcon_top_hdmi_set_clk_src(hwtcon->id, src);
if (!IS_ERR_OR_NULL(hwtcon->rst_bus_tcon))
reset_control_deassert(hwtcon->rst_bus_tcon);
if (!IS_ERR_OR_NULL(hwtcon->mclk_bus))
clk_prepare_enable(hwtcon->mclk_bus);
if (!IS_ERR_OR_NULL(hwtcon->mclk))
clk_prepare_enable(hwtcon->mclk);
return 0;
}
/**
* @desc: for hdmi module exit or suspend flow
*/
int sunxi_tcon_hdmi_close(struct device *dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(dev);
if (IS_ERR_OR_NULL(hwtcon)) {
DRM_ERROR("check point hwtcon is null\n");
return -1;
}
if (!IS_ERR_OR_NULL(hwtcon->mclk))
clk_disable_unprepare(hwtcon->mclk);
if (!IS_ERR_OR_NULL(hwtcon->mclk_bus))
clk_disable_unprepare(hwtcon->mclk_bus);
if (!IS_ERR_OR_NULL(hwtcon->rst_bus_tcon))
reset_control_assert(hwtcon->rst_bus_tcon);
tcon_top_hdmi_set_gate(hwtcon->id, 0);
if (IS_ERR_OR_NULL(hwtcon->tcon_top)) {
DRM_ERROR("check point hwtcon->tcon_top is null\n");
return -1;
}
sunxi_tcon_top_clk_disable(hwtcon->tcon_top);
return 0;
}
/**
* @desc: for tcon-hdmi output.
* only handle clock when need change rate
*/
int sunxi_tcon_hdmi_mode_init(struct device *dev)
{
int ret = 0;
unsigned long pclk;
struct sunxi_tcon *hwtcon = dev_get_drvdata(dev);
unsigned int de_id = hwtcon->tcon_ctrl.cfg.de_id;
struct disp_video_timings *p_timing = &hwtcon->tcon_ctrl.cfg.timing;
enum disp_csc_type format = hwtcon->tcon_ctrl.cfg.format;
bool sw_enable = hwtcon->tcon_ctrl.cfg.sw_enable;
if (hwtcon->is_enabled) {
DRM_WARN("tcon hdmi has been enable");
return 0;
}
/* if sw enable. will not handle tcon clock and timing config */
if (!sw_enable) {
/* calculate actual pixel clock */
pclk = p_timing->pixel_clk * (p_timing->pixel_repeat + 1);
if (format == DISP_CSC_TYPE_YUV420)
pclk /= 2;
_sunxi_tcon_hdmi_set_rate(hwtcon, pclk);
ret = _sunxi_tcon_hdmi_config(hwtcon, de_id, p_timing, format);
if (ret != 0) {
DRM_ERROR("tcon hdmi config failed\n");
return ret;
}
}
sunxi_tcon_calc_judge_line(hwtcon, p_timing);
if (hwtcon->pending_enable_vblank) {
sunxi_tcon_enable_vblank(dev, 1);
hwtcon->pending_enable_vblank = false;
}
sunxi_tcon_request_irq(hwtcon);
hwtcon->is_enabled = true;
return 0;
}
/**
* @desc: for tcon-hdmi disoutput
*/
int sunxi_tcon_hdmi_mode_exit(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
if (!hwtcon->is_enabled) {
DRM_WARN("tcon hdmi has been disable");
return 0;
}
sunxi_tcon_free_irq(hwtcon);
tcon_tv_close(&hwtcon->tcon_tv);
tcon_tv_exit(&hwtcon->tcon_tv);
hwtcon->judge_line = 0;
hwtcon->is_enabled = false;
return 0;
}
static int edp_tcon_clk_enable(struct sunxi_tcon *hwtcon)
{
int ret = 0;
if (hwtcon->tcon_top)
sunxi_tcon_top_clk_enable(hwtcon->tcon_top);
if (hwtcon->rst_bus_tcon) {
ret = reset_control_deassert(hwtcon->rst_bus_tcon);
if (ret) {
DRM_ERROR("[%s] deassert reset tcon edp failed!\n", __func__);
return -1;
}
}
if (hwtcon->mclk_bus) {
ret = clk_prepare_enable(hwtcon->mclk_bus);
if (ret != 0) {
DRM_WARN("fail enable edp's bus clock!\n");
return -1;
}
}
if (hwtcon->mclk) {
if (clk_prepare_enable(hwtcon->mclk)) {
DRM_WARN("fail to enable edp clk\n");
return -1;
}
}
return 0;
}
static int edp_tcon_clk_disable(struct sunxi_tcon *hwtcon)
{
int ret = 0;
if (hwtcon->mclk)
clk_disable_unprepare(hwtcon->mclk);
if (hwtcon->mclk_bus)
clk_disable_unprepare(hwtcon->mclk_bus);
if (hwtcon->rst_bus_tcon) {
ret = reset_control_assert(hwtcon->rst_bus_tcon);
if (ret) {
DRM_ERROR("[%s] assert reset tcon edp failed!\n", __func__);
return -1;
}
}
if (hwtcon->tcon_top)
sunxi_tcon_top_clk_disable(hwtcon->tcon_top);
return 0;
}
int sunxi_tcon_show_pattern(struct device *tcon_dev, int pattern)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
if (hwtcon->type == TCON_LCD)
tcon_lcd_show_builtin_patten(&hwtcon->tcon_lcd, pattern);
else
tcon_tv_show_builtin_patten(&hwtcon->tcon_tv, pattern);
return 0;
}
int sunxi_tcon_pattern_get(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
if (hwtcon->type == TCON_LCD)
return tcon_lcd_src_get(&hwtcon->tcon_lcd);
else
return tcon_tv_src_get(&hwtcon->tcon_tv);
}
static int sunxi_tcon_edp_mode_init(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
struct disp_video_timings *timings = &hwtcon->tcon_ctrl.cfg.timing;
unsigned int de_id = hwtcon->tcon_ctrl.cfg.de_id;
bool sw_enable = hwtcon->tcon_ctrl.cfg.sw_enable;
unsigned int pixel_mode = hwtcon->tcon_ctrl.cfg.pixel_mode;
if (hwtcon->is_enabled) {
DRM_WARN("tcon edp has been enable!\n");
return 0;
}
hwtcon->is_enabled = true;
edp_tcon_clk_enable(hwtcon);
if (hwtcon->mclk)
clk_set_rate(hwtcon->mclk, timings->pixel_clk / pixel_mode);
if (!sw_enable) {
tcon_tv_init(&hwtcon->tcon_tv);
tcon_tv_set_timming(&hwtcon->tcon_tv, timings);
tcon_tv_set_pixel_mode(&hwtcon->tcon_tv, pixel_mode);
tcon_tv_src_select(&hwtcon->tcon_tv, LCD_SRC_DE, de_id);
tcon1_edp_clk_enable(hwtcon->id, 1);
tcon_tv_open(&hwtcon->tcon_tv);
}
sunxi_tcon_calc_judge_line(hwtcon, timings);
if (hwtcon->pending_enable_vblank) {
sunxi_tcon_enable_vblank(tcon_dev, 1);
hwtcon->pending_enable_vblank = false;
}
sunxi_tcon_request_irq(hwtcon);
return 0;
}
static int sunxi_tcon_edp_mode_exit(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
if (!hwtcon->is_enabled) {
DRM_WARN("tcon edp has been disable!\n");
return 0;
}
sunxi_tcon_free_irq(hwtcon);
tcon_tv_close(&hwtcon->tcon_tv);
tcon_tv_exit(&hwtcon->tcon_tv);
tcon1_edp_clk_enable(hwtcon->id, 0);
edp_tcon_clk_disable(hwtcon);
hwtcon->is_enabled = false;
hwtcon->judge_line = 0;
return 0;
}
int sunxi_tcon_get_current_line(struct device *tcon_dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
unsigned int tcon_type;
tcon_type = hwtcon->type;
if (tcon_type == TCON_LCD) {
return tcon_lcd_get_cur_line(&hwtcon->tcon_lcd);
} else {
return tcon_tv_get_cur_line(&hwtcon->tcon_tv);
}
}
bool sunxi_tcon_is_sync_time_enough(struct device *tcon_dev)
{
int cur_line = 0, judge_line = 0, start_delay = 0;
unsigned int tcon_type;
struct sunxi_tcon *hwtcon = dev_get_drvdata(tcon_dev);
tcon_type = hwtcon->type;
judge_line = hwtcon->judge_line;
if (tcon_type == TCON_LCD) {
cur_line = tcon_lcd_get_cur_line(&hwtcon->tcon_lcd);
start_delay = tcon_lcd_get_start_delay(&hwtcon->tcon_lcd);
} else {
cur_line = tcon_tv_get_cur_line(&hwtcon->tcon_tv);
start_delay = tcon_tv_get_start_delay(&hwtcon->tcon_tv);
}
WARN_ON(!judge_line || !start_delay);
return cur_line <= (start_delay - judge_line);
}
static int sunxi_tcon_parse_dts(struct device *dev)
{
struct sunxi_tcon *hwtcon = dev_get_drvdata(dev);
struct platform_device *pdev = to_platform_device(dev);
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hwtcon->res = res;
hwtcon->reg_base = (uintptr_t)devm_ioremap_resource(dev, res);
if (!hwtcon->reg_base) {
DRM_ERROR("unable to map io for tcon\n");
return -EINVAL;
}
hwtcon->irq_no = platform_get_irq(pdev, 0);
if (!hwtcon->irq_no) {
DRM_ERROR("get irq no of tcon failed\n");
return -EINVAL;
}
hwtcon->mclk = devm_clk_get_optional(dev, "clk_tcon");
if (IS_ERR(hwtcon->mclk)) {
DRM_ERROR("fail to get clk for tcon \n");
return -EINVAL;
}
hwtcon->ahb_clk = devm_clk_get_optional(dev, "clk_ahb_tcon");
if (IS_ERR(hwtcon->ahb_clk)) {
DRM_ERROR("fail to get ahb clk for tcon \n");
return -EINVAL;
}
hwtcon->mclk_bus = devm_clk_get_optional(dev, "clk_bus_tcon");
if (IS_ERR(hwtcon->mclk_bus)) {
DRM_ERROR("fail to get clk bus for tcon\n");
return -EINVAL;
}
hwtcon->rst_bus_tcon =
devm_reset_control_get_shared(dev, "rst_bus_tcon");
if (IS_ERR(hwtcon->rst_bus_tcon)) {
DRM_ERROR("fail to get reset clk for tcon\n");
return -EINVAL;
}
hwtcon->ahb_vid_out = devm_clk_get_optional(dev, "ahb_vid_out");
if (IS_ERR(hwtcon->ahb_vid_out)) {
DRM_ERROR("fail to get ahb_vid_out clk for tcon \n");
return -EINVAL;
}
hwtcon->mbus_vo_sys = devm_clk_get_optional(dev, "mbus_vo_sys");
if (IS_ERR(hwtcon->mbus_vo_sys)) {
DRM_ERROR("fail to get mbus_vo_sys clk for tcon \n");
return -EINVAL;
}
return 0;
}
bool sunxi_tcon_check_fifo_status(struct device *tcon_dev)
{
int status = 0;
struct sunxi_tcon *tcon = dev_get_drvdata(tcon_dev);
if (tcon->type == TCON_TV) {
status = tcon_tv_get_status(&tcon->tcon_tv);
}
if (tcon->type == TCON_LCD) {
status = tcon_lcd_get_status(&tcon->tcon_lcd);
}
return status ? true : false;
}
void sunxi_tcon_enable_vblank(struct device *tcon_dev, bool enable)
{
struct sunxi_tcon *tcon = dev_get_drvdata(tcon_dev);
if (!tcon->is_enabled) {
tcon->pending_enable_vblank = enable;
return;
}
if (tcon->type == TCON_TV)
tcon_tv_enable_vblank(&tcon->tcon_tv, enable);
if (tcon->type == TCON_LCD)
tcon_lcd_enable_vblank(&tcon->tcon_lcd, enable);
}
int sunxi_tcon_of_get_id(struct device *tcon_dev)
{
struct device_node *node = tcon_dev->of_node;
struct device_node *disp0_output_ep;
struct device_node *tcon_in_disp0_ep;
struct of_endpoint endpoint;
int ret;
tcon_in_disp0_ep = of_graph_get_endpoint_by_regs(node, 0, 0);
if (!tcon_in_disp0_ep) {
DRM_ERROR("endpoint tcon_in_disp0_ep not fount\n");
return -EINVAL;
}
disp0_output_ep = of_graph_get_remote_endpoint(tcon_in_disp0_ep);
if (!disp0_output_ep) {
DRM_ERROR("endpoint disp0_output_ep not fount\n");
return -EINVAL;
}
ret = of_graph_parse_endpoint(disp0_output_ep, &endpoint);
if (ret) {
DRM_ERROR("endpoint parse fail\n");
return -EINVAL;
}
DRM_INFO("[SUNXI-TCON] %s %d\n", __FUNCTION__, endpoint.id);
of_node_put(tcon_in_disp0_ep);
of_node_put(disp0_output_ep);
return endpoint.id;
}
void sunxi_tcon_vfp_vrr_set(struct device *tcon_dev, struct disp_video_timings *timings)
{
struct sunxi_tcon *tcon = dev_get_drvdata(tcon_dev);
sunxi_tcon_updata_vt_2(&tcon->tcon_lcd, timings);
}
void sunxi_tcon_vrr_set(struct device *tcon_dev, struct disp_output_config *disp_cfg)
{
struct sunxi_tcon *tcon = dev_get_drvdata(tcon_dev);
struct tcon_device *ctrl = &tcon->tcon_ctrl;
memcpy(&ctrl->cfg, disp_cfg, sizeof(struct disp_output_config));
tcon_lcd_vrr_irq(&tcon->tcon_lcd, true);
}
struct resource *sunxi_tcon_get_res(struct device *tcon_dev)
{
struct sunxi_tcon *tcon = dev_get_drvdata(tcon_dev);
return tcon->res;
}
int sunxi_tcon_mode_init(struct device *tcon_dev, struct disp_output_config *disp_cfg)
{
struct sunxi_tcon *tcon = dev_get_drvdata(tcon_dev);
struct tcon_device *ctrl = &tcon->tcon_ctrl;
memcpy(&ctrl->cfg, disp_cfg, sizeof(struct disp_output_config));
switch (ctrl->cfg.type) {
case INTERFACE_EDP:
return sunxi_tcon_edp_mode_init(tcon_dev);
case INTERFACE_HDMI:
return sunxi_tcon_hdmi_mode_init(tcon_dev);
case INTERFACE_DSI:
return sunxi_tcon_dsi_mode_init(tcon_dev);
case INTERFACE_LVDS:
return sunxi_tcon_lvds_mode_init(tcon_dev);
case INTERFACE_RGB:
return sunxi_tcon_rgb_mode_init(tcon_dev);
default:
break;
}
return 0;
}
int sunxi_tcon_mode_exit(struct device *tcon_dev)
{
struct sunxi_tcon *tcon = dev_get_drvdata(tcon_dev);
struct tcon_device *ctrl = &tcon->tcon_ctrl;
switch (ctrl->cfg.type) {
case INTERFACE_EDP:
return sunxi_tcon_edp_mode_exit(tcon_dev);
case INTERFACE_HDMI:
return sunxi_tcon_hdmi_mode_exit(tcon_dev);
case INTERFACE_DSI:
return sunxi_tcon_dsi_mode_exit(tcon_dev);
case INTERFACE_LVDS:
return sunxi_tcon_lvds_mode_exit(tcon_dev);
case INTERFACE_RGB:
return sunxi_tcon_rgb_mode_exit(tcon_dev);
default:
break;
}
return 0;
}
static int sunxi_tcon_bind(struct device *dev, struct device *master,
void *data)
{
return sunxi_tcon_output_create(dev, (struct drm_device *)data);
}
static void sunxi_tcon_unbind(struct device *dev, struct device *master,
void *data)
{
sunxi_tcon_output_destroy(dev, (struct drm_device *)data);
}
static const struct component_ops sunxi_tcon_component_ops = {
.bind = sunxi_tcon_bind,
.unbind = sunxi_tcon_unbind,
};
static int sunxi_tcon_probe(struct platform_device *pdev)
{
int ret;
struct sunxi_tcon *tcon;
struct device *dev = &pdev->dev;
DRM_INFO("[TCON] sunxi_tcon_probe start\n");
tcon = devm_kzalloc(&pdev->dev, sizeof(*tcon), GFP_KERNEL);
if (!tcon) {
DRM_ERROR("can NOT allocate memory for tcon_drv\n");
ret = -ENOMEM;
goto out;
}
tcon->dev = dev;
dev_set_drvdata(dev, tcon);
tcon->id = sunxi_tcon_of_get_id(dev);
tcon->type = get_dev_tcon_type(dev->of_node);
ret = sunxi_tcon_parse_dts(dev);
if (ret) {
DRM_ERROR("sunxi_tcon_parse_dts failed\n");
goto out;
}
if (tcon->type == TCON_TV) {
tcon->tcon_tv.tcon_index = tcon->id;
tcon_tv_set_reg_base(&tcon->tcon_tv, tcon->reg_base);
}
if (tcon->type == TCON_LCD) {
tcon->tcon_lcd.tcon_index = tcon->id;
tcon_lcd_set_reg_base(&tcon->tcon_lcd, tcon->reg_base);
}
ret = sunxi_tcon_get_tcon_top(tcon);
if (ret)
goto out;
ret = component_add(&pdev->dev, &sunxi_tcon_component_ops);
if (ret < 0) {
DRM_ERROR("failed to add component tcon\n");
}
out:
DRM_INFO("[TCON] sunxi_tcon_probe ret = %d\n", ret);
return ret;
}
//TODO
static int sunxi_tcon_remove(struct platform_device *pdev)
{
return 0;
}
struct platform_driver sunxi_tcon_platform_driver = {
.probe = sunxi_tcon_probe,
.remove = sunxi_tcon_remove,
.driver = {
.name = "tcon",
.owner = THIS_MODULE,
.of_match_table = sunxi_tcon_match,
},
};