/* * fake_panel.c: fake panel driver. * * Copyright (c) 2014-2020, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "fake_panel.h" #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)) #if defined(CONFIG_ARCH_TEGRA_210_SOC) #define INT_GIC_BASE 0 #define INT_PRI_BASE (INT_GIC_BASE + 32) #define INT_SEC_BASE (INT_PRI_BASE + 32) #define INT_TRI_BASE (INT_SEC_BASE + 32) #define INT_QUAD_BASE (INT_TRI_BASE + 32) #define INT_QUINT_BASE (INT_QUAD_BASE + 32) #define INT_DISPLAY_GENERAL (INT_TRI_BASE + 9) #define INT_DPAUX (INT_QUINT_BASE + 31) #endif #else #include /*for INT_DISPLAY_GENERAL, INT_DPAUX*/ #endif #define DSI_PANEL_RESET 1 #define DC_CTRL_MODE TEGRA_DC_OUT_CONTINUOUS_MODE static struct tegra_dsi_out dsi_fake_panel_pdata = { .controller_vs = DSI_VS_1, .n_data_lanes = 4, .video_data_type = TEGRA_DSI_VIDEO_TYPE_VIDEO_MODE, .video_burst_mode = TEGRA_DSI_VIDEO_NONE_BURST_MODE, .refresh_rate = 60, .pixel_format = TEGRA_DSI_PIXEL_FORMAT_24BIT_P, .virtual_channel = TEGRA_DSI_VIRTUAL_CHANNEL_0, .panel_reset = DSI_PANEL_RESET, .power_saving_suspend = true, .video_clock_mode = TEGRA_DSI_VIDEO_CLOCK_TX_ONLY, .ulpm_not_supported = true, }; static struct tegra_dc_mode dsi_fake_panel_modes[] = { { /* This mode defined here is for Nvdisplay. * tegra_dc_populate_fake_panel_modes overrides the mode * based on the chip. */ .pclk = 193224000, /* @60Hz*/ .h_ref_to_sync = 1, .v_ref_to_sync = 11, .h_sync_width = 1, .v_sync_width = 1, .h_back_porch = 20, .v_back_porch = 7, .h_active = 1200, .v_active = 1920, .h_front_porch = 107, .v_front_porch = 497, }, }; static void tegra_dc_populate_fake_panel_modes(void) { struct tegra_dc_mode fake_panel_mode = {0}; if (tegra_dc_is_nvdisplay()) { fake_panel_mode.pclk = 193224000; /* @60Hz*/ fake_panel_mode.h_ref_to_sync = 1; fake_panel_mode.v_ref_to_sync = 11; fake_panel_mode.h_sync_width = 1; fake_panel_mode.v_sync_width = 1; fake_panel_mode.h_back_porch = 20; fake_panel_mode.v_back_porch = 7; fake_panel_mode.h_active = 1200; fake_panel_mode.v_active = 1920; fake_panel_mode.h_front_porch = 107; fake_panel_mode.v_front_porch = 497; } else { fake_panel_mode.pclk = 155774400; /* @60Hz*/ fake_panel_mode.h_ref_to_sync = 1; fake_panel_mode.v_ref_to_sync = 2; fake_panel_mode.h_sync_width = 10; fake_panel_mode.v_sync_width = 2; fake_panel_mode.h_back_porch = 54; fake_panel_mode.v_back_porch = 30; fake_panel_mode.h_active = 1200; fake_panel_mode.v_active = 1920; fake_panel_mode.h_front_porch = 64; fake_panel_mode.v_front_porch = 3; } dsi_fake_panel_modes[0] = fake_panel_mode; } static int tegra_dc_reset_fakedsi_panel(struct tegra_dc *dc, long dc_outtype) { struct tegra_dc_out *dc_out = dc->out; if (dc_outtype == TEGRA_DC_OUT_FAKE_DSI_GANGED) { dc_out->dsi->ganged_type = TEGRA_DSI_GANGED_SYMMETRIC_EVEN_ODD; dc_out->dsi->even_odd_split_width = 1; dc_out->dsi->dsi_instance = tegra_dc_get_dsi_instance_0(); dc_out->dsi->n_data_lanes = 8; } else if (dc_outtype == TEGRA_DC_OUT_FAKE_DSIB) { dc_out->dsi->ganged_type = 0; dc_out->dsi->dsi_instance = tegra_dc_get_dsi_instance_1(); dc_out->dsi->n_data_lanes = 4; } else if (dc_outtype == TEGRA_DC_OUT_FAKE_DSIA) { dc_out->dsi->ganged_type = 0; dc_out->dsi->dsi_instance = tegra_dc_get_dsi_instance_0(); dc_out->dsi->n_data_lanes = 4; } return 0; } int tegra_dc_init_fakedsi_panel(struct tegra_dc *dc, long dc_outtype) { struct tegra_dc_out *dc_out = dc->out; struct tegra_dc_dsi_data *dsi; /* Set the needed resources */ dc_out->dsi = &dsi_fake_panel_pdata; if (tegra_dc_is_nvdisplay()) dc_out->parent_clk = "pll_d_out1"; else dc_out->parent_clk = "pll_d_out0"; tegra_dc_populate_fake_panel_modes(); dc_out->modes = dsi_fake_panel_modes; dc_out->n_modes = ARRAY_SIZE(dsi_fake_panel_modes); dc_out->enable = NULL; dc_out->postpoweron = NULL; dc_out->disable = NULL; dc_out->postsuspend = NULL; dc_out->width = 217; dc_out->height = 135; dc_out->flags = DC_CTRL_MODE; tegra_dc_reset_fakedsi_panel(dc, dc_outtype); /* DrivePX2: DSI->sn65dsi85(LVDS)->ds90ub947(FPDLink) */ dsi = tegra_dc_get_outdata(dc); if (dsi->info.dsi2lvds_bridge_enable) dc->connected = true; return 0; } int tegra_dc_destroy_dsi_resources(struct tegra_dc *dc, long dc_outtype) { int i = 0; struct tegra_dc_dsi_data *dsi = tegra_dc_get_outdata(dc); if (!dsi) { dev_err(&dc->ndev->dev, " dsi out_data not found\n"); return -EINVAL; } dsi->max_instances = dc->out->dsi->ganged_type ? tegra_dc_get_max_dsi_instance() : 1; mutex_lock(&dsi->lock); tegra_dc_io_start(dc); for (i = 0; i < dsi->max_instances; i++) { if (dsi->base[i]) { iounmap(dsi->base[i]); dsi->base[i] = NULL; } } if (dsi->avdd_dsi_csi) { dsi->avdd_dsi_csi = NULL; } if (tegra_dc_is_nvdisplay() && dsi->pad_ctrl) tegra_dsi_padctrl_shutdown(dc); tegra_dc_io_end(dc); mutex_unlock(&dsi->lock); return 0; } int tegra_dc_reinit_dsi_resources(struct tegra_dc *dc, long dc_outtype) { int err = 0, i; int dsi_instance; void __iomem *base; struct device_node *np_dsi = tegra_dc_get_conn_np(dc); struct tegra_dc_dsi_data *dsi = tegra_dc_get_outdata(dc); if (!dsi) { dev_err(&dc->ndev->dev, " dsi: allocation deleted\n"); return -ENOMEM; } /* Since all fake DSI share the same DSI pointer, need to reset here */ /* to avoid misconfigurations when switching between fake DSI types */ tegra_dc_reset_fakedsi_panel(dc, dc_outtype); dsi->max_instances = tegra_dsi_get_max_active_instances_num(dc->out->dsi); dsi_instance = (int)dc->out->dsi->dsi_instance; for (i = 0; i < dsi->max_instances; i++) { base = of_iomap(np_dsi, i + dsi_instance); if (!base) { dev_err(&dc->ndev->dev, "dsi: ioremap failed\n"); err = -ENOENT; goto err_iounmap; } dsi->base[i] = base; } dsi->avdd_dsi_csi = devm_regulator_get(&dc->ndev->dev, "avdd_dsi_csi"); if (IS_ERR(dsi->avdd_dsi_csi)) { dev_err(&dc->ndev->dev, "dsi: avdd_dsi_csi reg get failed\n"); err = -ENODEV; goto err_release_regs; } if (tegra_dc_is_nvdisplay()) { dsi->pad_ctrl = tegra_dsi_padctrl_init(dc); if (IS_ERR(dsi->pad_ctrl)) { dev_err(&dc->ndev->dev, "dsi: Padctrl sw init failed\n"); err = PTR_ERR(dsi->pad_ctrl); goto err_release_regs; } dsi->info.enable_hs_clock_on_lp_cmd_mode = true; } /* Need to always reinitialize clocks to ensure proper functionality */ tegra_dsi_init_clock_param(dc); #ifdef CONFIG_DEBUG_FS tegra_dsi_csi_test_init(dsi); #endif return 0; err_release_regs: if (dsi->avdd_dsi_csi) dsi->avdd_dsi_csi = NULL; err_iounmap: for (; i >= 0; i--) { if (dsi->base[i]) { iounmap(dsi->base[i]); dsi->base[i] = NULL; } } return err; }