/* * dc.c: dc driver. * * Copyright (C) 2010 Google, Inc. * Author: Erik Gilling * * Copyright (c) 2010-2020, NVIDIA CORPORATION, All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_SWITCH #include #endif #include #include #include #include #if defined(CONFIG_TRUSTED_LITTLE_KERNEL) || defined(CONFIG_TRUSTY) #include #endif #include #include #define CREATE_TRACE_POINTS #include EXPORT_TRACEPOINT_SYMBOL(display_writel); EXPORT_TRACEPOINT_SYMBOL(display_readl); #include #include #include #include #include #include #include "dc.h" #include "dc_reg.h" #include "dc_config.h" #include "dc_priv.h" #include "dc_shared_isr.h" #include "nvhost_sync.h" #include "nvhost_syncpt.h" /* Preset and flush vblank_syncpt*/ #include "dpaux.h" #include "lvds.h" #include "dc_common.h" #include "edid.h" #ifdef CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT #include "fake_panel.h" #endif /*CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT*/ /* HACK! This needs to come from DT */ #include "../../../../arch/arm/mach-tegra/iomap.h" #define TEGRA_CRC_LATCHED_DELAY 34 #define DC_COM_PIN_OUTPUT_POLARITY1_INIT_VAL 0x01000000 #define DC_COM_PIN_OUTPUT_POLARITY3_INIT_VAL 0x0 #define MAX_VRR_V_FRONT_PORCH 0x1000 static struct tegra_dc_hw_data *hw_data; static struct tegra_dc_hw_data t21x_hw_data; static struct tegra_dc_hw_data t18x_hw_data; static struct tegra_dc_hw_data t19x_hw_data; static const struct of_device_id tegra_display_of_match[] = { {.compatible = "nvidia,tegra210-dc", .data = &t21x_hw_data }, {.compatible = "nvidia,tegra186-dc", .data = &t18x_hw_data }, {.compatible = "nvidia,tegra194-dc", .data = &t19x_hw_data }, { }, }; MODULE_DEVICE_TABLE(of, tegra_display_of_match); /* Used only on T21x*/ static struct of_device_id tegra_disa_pd[] = { { .compatible = "nvidia,tegra210-disa-pd", }, {}, }; /* Used only on T21x */ static struct of_device_id tegra_disb_pd[] = { { .compatible = "nvidia,tegra210-disb-pd", }, {}, }; static unsigned int display_la_emc_client_id[] = { TEGRA_BWMGR_CLIENT_DISP1_LA_EMC, TEGRA_BWMGR_CLIENT_DISP2_LA_EMC }; struct fb_videomode tegra_dc_vga_mode = { .refresh = 60, .xres = 640, .yres = 480, .pixclock = KHZ2PICOS(25200), .hsync_len = 96, /* h_sync_width */ .vsync_len = 2, /* v_sync_width */ .left_margin = 48, /* h_back_porch */ .upper_margin = 33, /* v_back_porch */ .right_margin = 16, /* h_front_porch */ .lower_margin = 10, /* v_front_porch */ .vmode = 0, .sync = 0, }; /* needs to be big enough to be index by largest supported out->type */ static struct tegra_dc_mode override_disp_mode[TEGRA_DC_OUT_NULL + 1]; static void _tegra_dc_controller_disable(struct tegra_dc *dc); static void tegra_dc_disable_irq_ops(struct tegra_dc *dc, bool from_irq); static int _tegra_dc_config_frame_end_intr(struct tegra_dc *dc, bool enable); static int tegra_dc_set_out(struct tegra_dc *dc, struct tegra_dc_out *out, bool initialized); #ifdef PM static int tegra_dc_suspend(struct platform_device *ndev, pm_message_t state); static int tegra_dc_resume(struct platform_device *ndev); #endif static struct tegra_dc **tegra_dcs; /* Used only on Nvdisplay */ static struct tegra_dc_win tegra_dc_windows[DC_N_WINDOWS]; static u64 tegra_dc_get_scanline_timestamp(struct tegra_dc *dc, const u32 scanline); static void tegra_dc_collect_latency_data(struct tegra_dc *dc); static DEFINE_MUTEX(tegra_dc_lock); /* Lock to serialize extcon switch reporting across heads*/ static DEFINE_MUTEX(tegra_dc_extcon_lock); /* Lock to serialize dc registration during probe */ static DEFINE_MUTEX(tegra_dc_registration_lock); static struct device_dma_parameters tegra_dc_dma_parameters = { .max_segment_size = UINT_MAX, }; static const struct { bool h; bool v; } can_filter[] = { /* Window A has no filtering */ { false, false }, /* Window B has both H and V filtering */ { true, true }, /* Window C has only H filtering */ { false, true }, }; static struct tegra_dc_cmu default_cmu = { /* lut1 maps sRGB to linear space. */ { 0, 1, 2, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15, 16, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, 40, 42, 45, 48, 50, 53, 56, 59, 62, 66, 69, 72, 76, 79, 83, 87, 91, 95, 99, 103, 107, 112, 116, 121, 126, 131, 136, 141, 146, 151, 156, 162, 168, 173, 179, 185, 191, 197, 204, 210, 216, 223, 230, 237, 244, 251, 258, 265, 273, 280, 288, 296, 304, 312, 320, 329, 337, 346, 354, 363, 372, 381, 390, 400, 409, 419, 428, 438, 448, 458, 469, 479, 490, 500, 511, 522, 533, 544, 555, 567, 578, 590, 602, 614, 626, 639, 651, 664, 676, 689, 702, 715, 728, 742, 755, 769, 783, 797, 811, 825, 840, 854, 869, 884, 899, 914, 929, 945, 960, 976, 992, 1008, 1024, 1041, 1057, 1074, 1091, 1108, 1125, 1142, 1159, 1177, 1195, 1213, 1231, 1249, 1267, 1286, 1304, 1323, 1342, 1361, 1381, 1400, 1420, 1440, 1459, 1480, 1500, 1520, 1541, 1562, 1582, 1603, 1625, 1646, 1668, 1689, 1711, 1733, 1755, 1778, 1800, 1823, 1846, 1869, 1892, 1916, 1939, 1963, 1987, 2011, 2035, 2059, 2084, 2109, 2133, 2159, 2184, 2209, 2235, 2260, 2286, 2312, 2339, 2365, 2392, 2419, 2446, 2473, 2500, 2527, 2555, 2583, 2611, 2639, 2668, 2696, 2725, 2754, 2783, 2812, 2841, 2871, 2901, 2931, 2961, 2991, 3022, 3052, 3083, 3114, 3146, 3177, 3209, 3240, 3272, 3304, 3337, 3369, 3402, 3435, 3468, 3501, 3535, 3568, 3602, 3636, 3670, 3705, 3739, 3774, 3809, 3844, 3879, 3915, 3950, 3986, 4022, 4059, 4095, }, /* csc */ { 0x100, 0x0, 0x0, 0x0, 0x100, 0x0, 0x0, 0x0, 0x100, }, /* lut2 maps linear space to sRGB*/ { 0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 10, 11, 12, 13, 13, 14, 15, 15, 16, 16, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 23, 24, 24, 25, 25, 25, 26, 26, 27, 27, 27, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 67, 68, 68, 68, 68, 68, 68, 68, 69, 69, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 73, 74, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, 75, 75, 75, 76, 76, 76, 76, 76, 76, 76, 77, 77, 77, 77, 77, 77, 77, 77, 78, 78, 78, 78, 78, 78, 78, 78, 78, 79, 79, 79, 79, 79, 79, 79, 79, 80, 80, 80, 80, 80, 80, 80, 80, 81, 81, 81, 81, 81, 81, 81, 81, 81, 82, 82, 82, 82, 82, 82, 82, 82, 83, 83, 83, 83, 83, 83, 83, 83, 83, 84, 84, 84, 84, 84, 84, 84, 84, 84, 85, 85, 85, 85, 85, 85, 85, 85, 85, 86, 86, 86, 86, 86, 86, 86, 86, 86, 87, 87, 87, 87, 87, 87, 87, 87, 87, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 99, 100, 101, 101, 102, 103, 103, 104, 105, 105, 106, 107, 107, 108, 109, 109, 110, 111, 111, 112, 113, 113, 114, 115, 115, 116, 116, 117, 118, 118, 119, 119, 120, 120, 121, 122, 122, 123, 123, 124, 124, 125, 126, 126, 127, 127, 128, 128, 129, 129, 130, 130, 131, 131, 132, 132, 133, 133, 134, 134, 135, 135, 136, 136, 137, 137, 138, 138, 139, 139, 140, 140, 141, 141, 142, 142, 143, 143, 144, 144, 145, 145, 145, 146, 146, 147, 147, 148, 148, 149, 149, 150, 150, 150, 151, 151, 152, 152, 153, 153, 153, 154, 154, 155, 155, 156, 156, 156, 157, 157, 158, 158, 158, 159, 159, 160, 160, 160, 161, 161, 162, 162, 162, 163, 163, 164, 164, 164, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 200, 201, 201, 201, 202, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 205, 206, 206, 206, 207, 207, 207, 207, 208, 208, 208, 209, 209, 209, 209, 210, 210, 210, 211, 211, 211, 211, 212, 212, 212, 213, 213, 213, 213, 214, 214, 214, 214, 215, 215, 215, 216, 216, 216, 216, 217, 217, 217, 217, 218, 218, 218, 219, 219, 219, 219, 220, 220, 220, 220, 221, 221, 221, 221, 222, 222, 222, 223, 223, 223, 223, 224, 224, 224, 224, 225, 225, 225, 225, 226, 226, 226, 226, 227, 227, 227, 227, 228, 228, 228, 228, 229, 229, 229, 229, 230, 230, 230, 230, 231, 231, 231, 231, 232, 232, 232, 232, 233, 233, 233, 233, 234, 234, 234, 234, 235, 235, 235, 235, 236, 236, 236, 236, 237, 237, 237, 237, 238, 238, 238, 238, 239, 239, 239, 239, 240, 240, 240, 240, 240, 241, 241, 241, 241, 242, 242, 242, 242, 243, 243, 243, 243, 244, 244, 244, 244, 244, 245, 245, 245, 245, 246, 246, 246, 246, 247, 247, 247, 247, 247, 248, 248, 248, 248, 249, 249, 249, 249, 249, 250, 250, 250, 250, 251, 251, 251, 251, 251, 252, 252, 252, 252, 253, 253, 253, 253, 253, 254, 254, 254, 254, 255, 255, }, }; static struct tegra_dc_cmu default_limited_cmu = { /* lut1 maps sRGB to linear space. */ { 0, 1, 2, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15, 16, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, 40, 42, 45, 48, 50, 53, 56, 59, 62, 66, 69, 72, 76, 79, 83, 87, 91, 95, 99, 103, 107, 112, 116, 121, 126, 131, 136, 141, 146, 151, 156, 162, 168, 173, 179, 185, 191, 197, 204, 210, 216, 223, 230, 237, 244, 251, 258, 265, 273, 280, 288, 296, 304, 312, 320, 329, 337, 346, 354, 363, 372, 381, 390, 400, 409, 419, 428, 438, 448, 458, 469, 479, 490, 500, 511, 522, 533, 544, 555, 567, 578, 590, 602, 614, 626, 639, 651, 664, 676, 689, 702, 715, 728, 742, 755, 769, 783, 797, 811, 825, 840, 854, 869, 884, 899, 914, 929, 945, 960, 976, 992, 1008, 1024, 1041, 1057, 1074, 1091, 1108, 1125, 1142, 1159, 1177, 1195, 1213, 1231, 1249, 1267, 1286, 1304, 1323, 1342, 1361, 1381, 1400, 1420, 1440, 1459, 1480, 1500, 1520, 1541, 1562, 1582, 1603, 1625, 1646, 1668, 1689, 1711, 1733, 1755, 1778, 1800, 1823, 1846, 1869, 1892, 1916, 1939, 1963, 1987, 2011, 2035, 2059, 2084, 2109, 2133, 2159, 2184, 2209, 2235, 2260, 2286, 2312, 2339, 2365, 2392, 2419, 2446, 2473, 2500, 2527, 2555, 2583, 2611, 2639, 2668, 2696, 2725, 2754, 2783, 2812, 2841, 2871, 2901, 2931, 2961, 2991, 3022, 3052, 3083, 3114, 3146, 3177, 3209, 3240, 3272, 3304, 3337, 3369, 3402, 3435, 3468, 3501, 3535, 3568, 3602, 3636, 3670, 3705, 3739, 3774, 3809, 3844, 3879, 3915, 3950, 3986, 4022, 4059, 4095, }, /* csc */ { 0x100, 0x000, 0x000, 0x000, 0x100, 0x000, 0x000, 0x000, 0x100, }, /* * lut2 maps linear space back to sRGB, where * the output range is [16...235] (limited). */ { 16, 17, 18, 18, 19, 19, 20, 21, 21, 22, 23, 24, 25, 25, 25, 26, 27, 27, 28, 28, 29, 30, 30, 31, 31, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 45, 45, 45, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 69, 69, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 73, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, 75, 75, 76, 76, 76, 76, 76, 76, 76, 77, 77, 77, 77, 77, 77, 77, 78, 78, 78, 78, 78, 78, 78, 78, 79, 79, 79, 79, 79, 79, 79, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 81, 81, 81, 81, 81, 81, 81, 81, 82, 82, 82, 82, 82, 82, 82, 82, 83, 83, 83, 83, 83, 83, 83, 83, 84, 84, 84, 84, 84, 84, 84, 84, 84, 85, 85, 85, 85, 85, 85, 85, 85, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 87, 87, 87, 87, 87, 87, 87, 87, 87, 88, 88, 88, 88, 88, 88, 88, 88, 88, 89, 89, 89, 89, 89, 89, 89, 89, 89, 90, 90, 90, 90, 90, 90, 90, 90, 90, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 93, 93, 93, 93, 93, 93, 93, 93, 93, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 101, 101, 102, 103, 103, 104, 104, 105, 105, 106, 107, 107, 108, 109, 109, 110, 110, 110, 111, 111, 112, 113, 113, 114, 115, 115, 116, 116, 116, 117, 117, 118, 118, 119, 120, 120, 121, 121, 122, 122, 122, 122, 123, 124, 124, 125, 125, 126, 126, 127, 127, 128, 128, 129, 129, 129, 129, 130, 130, 131, 131, 132, 132, 133, 133, 134, 134, 135, 135, 135, 135, 136, 136, 137, 137, 138, 138, 139, 139, 140, 140, 141, 141, 141, 141, 141, 142, 142, 143, 143, 144, 144, 144, 145, 145, 146, 146, 147, 147, 147, 147, 147, 148, 148, 149, 149, 149, 150, 150, 151, 151, 151, 152, 152, 153, 153, 153, 153, 153, 154, 154, 154, 155, 155, 156, 156, 156, 157, 157, 158, 158, 158, 159, 159, 159, 159, 159, 160, 160, 160, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 165, 165, 165, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 181, 182, 182, 182, 183, 183, 183, 183, 183, 183, 184, 184, 184, 185, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 188, 189, 189, 189, 189, 189, 189, 190, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 193, 194, 194, 194, 195, 195, 195, 195, 195, 195, 195, 196, 196, 196, 196, 197, 197, 197, 197, 198, 198, 198, 199, 199, 199, 199, 200, 200, 200, 201, 201, 201, 201, 202, 202, 202, 202, 202, 202, 202, 203, 203, 203, 203, 204, 204, 204, 204, 205, 205, 205, 205, 206, 206, 206, 207, 207, 207, 207, 208, 208, 208, 208, 208, 208, 208, 208, 209, 209, 209, 209, 210, 210, 210, 210, 211, 211, 211, 211, 212, 212, 212, 212, 213, 213, 213, 213, 214, 214, 214, 214, 214, 214, 214, 214, 215, 215, 215, 215, 216, 216, 216, 216, 217, 217, 217, 217, 218, 218, 218, 218, 219, 219, 219, 219, 220, 220, 220, 220, 220, 220, 220, 220, 221, 221, 221, 221, 221, 222, 222, 222, 222, 223, 223, 223, 223, 224, 224, 224, 224, 225, 225, 225, 225, 225, 226, 226, 226, 226, 226, 226, 226, 226, 227, 227, 227, 227, 227, 228, 228, 228, 228, 229, 229, 229, 229, 230, 230, 230, 230, 230, 231, 231, 231, 231, 232, 232, 232, 232, 232, 232, 232, 232, 232, 233, 233, 233, 233, 233, 234, 234, 234, 234, 235 }, }; static struct tegra_dc_nvdisp_cmu default_nvdisp_cmu = { {}, }; static struct tegra_dc_nvdisp_cmu default_limited_nvdisp_cmu = { {}, }; #define DSC_MAX_RC_BUF_THRESH_REGS 4 static int dsc_rc_buf_thresh_regs[DSC_MAX_RC_BUF_THRESH_REGS] = { DC_COM_DSC_RC_BUF_THRESH_0, DC_COM_DSC_RC_BUF_THRESH_1, DC_COM_DSC_RC_BUF_THRESH_2, DC_COM_DSC_RC_BUF_THRESH_3, }; /* * Always set the first two values to 0. This is to ensure that RC threshold * values are programmed in the correct registers. */ static int dsc_rc_buf_thresh[] = { 0, 0, 14, 28, 42, 56, 70, 84, 98, 105, 112, 119, 121, 123, 125, 126, }; #define DSC_MAX_RC_RANGE_CFG_REGS 8 static int dsc_rc_range_config[DSC_MAX_RC_RANGE_CFG_REGS] = { DC_COM_DSC_RC_RANGE_CFG_0, DC_COM_DSC_RC_RANGE_CFG_1, DC_COM_DSC_RC_RANGE_CFG_2, DC_COM_DSC_RC_RANGE_CFG_3, DC_COM_DSC_RC_RANGE_CFG_4, DC_COM_DSC_RC_RANGE_CFG_5, DC_COM_DSC_RC_RANGE_CFG_6, DC_COM_DSC_RC_RANGE_CFG_7, }; static int dsc_rc_ranges_8bpp_8bpc[16][3] = { {0, 4, 2}, {0, 4, 0}, {1, 5, 0}, {1, 6, -2}, {3, 7, -4}, {3, 7, -6}, {3, 7, -8}, {3, 8, -8}, {3, 9, -8}, {3, 10, -10}, {5, 11, -10}, {5, 12, -12}, {5, 13, -12}, {7, 13, -12}, {13, 15, -12}, {0, 0, 0}, }; static void tegra_dc_t21x_activate_general_channel(struct tegra_dc *dc) { tegra_dc_writel(dc, GENERAL_UPDATE, DC_CMD_STATE_CONTROL); tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); /* flush */ tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); /* flush */ } void tegra_dc_activate_general_channel(struct tegra_dc *dc) { if (tegra_dc_is_t21x()) tegra_dc_t21x_activate_general_channel(dc); else tegra_nvdisp_activate_general_channel(dc); } unsigned long tegra_dc_readl_exported(struct tegra_dc *dc, unsigned long reg) { return tegra_dc_readl(dc, reg); } EXPORT_SYMBOL(tegra_dc_readl_exported); void tegra_dc_writel_exported(struct tegra_dc *dc, unsigned long val, unsigned long reg) { tegra_dc_writel(dc, val, reg); } EXPORT_SYMBOL(tegra_dc_writel_exported); void tegra_dc_clk_enable(struct tegra_dc *dc) { tegra_disp_clk_prepare_enable(dc->clk); #ifdef CONFIG_TEGRA_CORE_DVFS tegra_dvfs_set_rate(dc->clk, dc->mode.pclk); #endif } void tegra_dc_clk_disable(struct tegra_dc *dc) { tegra_disp_clk_disable_unprepare(dc->clk); #ifdef CONFIG_TEGRA_CORE_DVFS tegra_dvfs_set_rate(dc->clk, 0); #endif } void tegra_dc_get(struct tegra_dc *dc) { int enable_count = atomic_inc_return(&dc->enable_count); BUG_ON(enable_count < 1); if (enable_count == 1) { tegra_dc_io_start(dc); /* extra reference to dc clk */ tegra_disp_clk_prepare_enable(dc->clk); } } EXPORT_SYMBOL(tegra_dc_get); void tegra_dc_put(struct tegra_dc *dc) { if (WARN_ONCE(atomic_read(&dc->enable_count) == 0, "unbalanced clock calls")) return; if (atomic_dec_return(&dc->enable_count) == 0) { /* balance extra dc clk reference */ tegra_disp_clk_disable_unprepare(dc->clk); tegra_dc_io_end(dc); } } EXPORT_SYMBOL(tegra_dc_put); unsigned tegra_dc_out_flags_from_dev(struct device *dev) { struct platform_device *ndev = NULL; struct tegra_dc *dc = NULL; if (dev) ndev = to_platform_device(dev); if (ndev) dc = platform_get_drvdata(ndev); if (dc) return dc->out->flags; else return 0; } EXPORT_SYMBOL(tegra_dc_out_flags_from_dev); inline bool tegra_dc_in_cmode(struct tegra_dc *dc) { u32 nc_mode_flags = (TEGRA_DC_OUT_ONE_SHOT_MODE | TEGRA_DC_OUT_N_SHOT_MODE | TEGRA_DC_OUT_ONE_SHOT_LP_MODE); return !(dc->out->flags & nc_mode_flags); } EXPORT_SYMBOL(tegra_dc_in_cmode); bool tegra_dc_initialized(struct device *dev) { struct platform_device *ndev = NULL; struct tegra_dc *dc = NULL; if (dev) ndev = to_platform_device(dev); if (ndev) dc = platform_get_drvdata(ndev); if (dc) return dc->initialized; else return false; } EXPORT_SYMBOL(tegra_dc_initialized); void tegra_dc_hold_dc_out(struct tegra_dc *dc) { if (1 == atomic_inc_return(&dc->holding)) { tegra_dc_get(dc); if (dc->out_ops && dc->out_ops->hold) dc->out_ops->hold(dc); } } void tegra_dc_release_dc_out(struct tegra_dc *dc) { if (0 == atomic_dec_return(&dc->holding)) { if (dc->out_ops && dc->out_ops->release) dc->out_ops->release(dc); tegra_dc_put(dc); } } bool tegra_dc_hotplug_supported(struct tegra_dc *dc) { /* For HDMI|DP, hotplug always supported * For eDP, hotplug is never supported * For fake DP, SW hotplug is supported * Else GPIO# determines if hotplug supported */ if (dc->out->type == TEGRA_DC_OUT_HDMI) return true; else if (dc->out->type == TEGRA_DC_OUT_DP || dc->out->type == TEGRA_DC_OUT_FAKE_DP || dc->out->type == TEGRA_DC_OUT_DSI) return tegra_dc_is_ext_panel(dc); else return (dc->out->hotplug_gpio > 0 ? true : false); } #define DUMP_REG(a) do { \ snprintf(buff, sizeof(buff), "%-32s\t%03x\t%08lx\n", \ #a, a, tegra_dc_readl(dc, a)); \ print(data, buff); \ } while (0) void tegra_dc_reg_dump(struct tegra_dc *dc, void *data, void (* print)(void *data, const char *str)) { int i; char buff[256]; const char winname[] = "ABCDHT"; /* for above, see also: DC_CMD_DISPLAY_WINDOW_HEADER and DC_N_WINDOWS */ unsigned long cmd_state; /* If gated, quietly return. */ if (!tegra_dc_is_powered(dc)) return; mutex_lock(&dc->lock); tegra_dc_get(dc); cmd_state = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); tegra_dc_writel(dc, WRITE_MUX_ACTIVE | READ_MUX_ACTIVE, DC_CMD_STATE_ACCESS); DUMP_REG(DC_CMD_DISPLAY_COMMAND_OPTION0); DUMP_REG(DC_CMD_DISPLAY_COMMAND); DUMP_REG(DC_CMD_SIGNAL_RAISE); DUMP_REG(DC_CMD_INT_STATUS); DUMP_REG(DC_CMD_INT_MASK); DUMP_REG(DC_CMD_INT_ENABLE); DUMP_REG(DC_CMD_INT_TYPE); DUMP_REG(DC_CMD_INT_POLARITY); DUMP_REG(DC_CMD_SIGNAL_RAISE1); DUMP_REG(DC_CMD_SIGNAL_RAISE2); DUMP_REG(DC_CMD_SIGNAL_RAISE3); DUMP_REG(DC_CMD_STATE_ACCESS); DUMP_REG(DC_CMD_STATE_CONTROL); DUMP_REG(DC_CMD_DISPLAY_WINDOW_HEADER); DUMP_REG(DC_CMD_REG_ACT_CONTROL); DUMP_REG(DC_DISP_DISP_SIGNAL_OPTIONS0); DUMP_REG(DC_DISP_DISP_SIGNAL_OPTIONS1); DUMP_REG(DC_DISP_DISP_WIN_OPTIONS); DUMP_REG(DC_DISP_MEM_HIGH_PRIORITY); DUMP_REG(DC_DISP_MEM_HIGH_PRIORITY_TIMER); DUMP_REG(DC_DISP_DISP_TIMING_OPTIONS); DUMP_REG(DC_DISP_REF_TO_SYNC); DUMP_REG(DC_DISP_SYNC_WIDTH); DUMP_REG(DC_DISP_BACK_PORCH); DUMP_REG(DC_DISP_DISP_ACTIVE); DUMP_REG(DC_DISP_FRONT_PORCH); DUMP_REG(DC_DISP_H_PULSE0_CONTROL); DUMP_REG(DC_DISP_H_PULSE0_POSITION_A); DUMP_REG(DC_DISP_H_PULSE0_POSITION_B); DUMP_REG(DC_DISP_H_PULSE0_POSITION_C); DUMP_REG(DC_DISP_H_PULSE0_POSITION_D); DUMP_REG(DC_DISP_H_PULSE1_CONTROL); DUMP_REG(DC_DISP_H_PULSE1_POSITION_A); DUMP_REG(DC_DISP_H_PULSE1_POSITION_B); DUMP_REG(DC_DISP_H_PULSE1_POSITION_C); DUMP_REG(DC_DISP_H_PULSE1_POSITION_D); DUMP_REG(DC_DISP_H_PULSE2_CONTROL); DUMP_REG(DC_DISP_H_PULSE2_POSITION_A); DUMP_REG(DC_DISP_H_PULSE2_POSITION_B); DUMP_REG(DC_DISP_H_PULSE2_POSITION_C); DUMP_REG(DC_DISP_H_PULSE2_POSITION_D); DUMP_REG(DC_DISP_V_PULSE0_CONTROL); DUMP_REG(DC_DISP_V_PULSE0_POSITION_A); DUMP_REG(DC_DISP_V_PULSE0_POSITION_B); DUMP_REG(DC_DISP_V_PULSE0_POSITION_C); DUMP_REG(DC_DISP_V_PULSE1_CONTROL); DUMP_REG(DC_DISP_V_PULSE1_POSITION_A); DUMP_REG(DC_DISP_V_PULSE1_POSITION_B); DUMP_REG(DC_DISP_V_PULSE1_POSITION_C); DUMP_REG(DC_DISP_V_PULSE2_CONTROL); DUMP_REG(DC_DISP_V_PULSE2_POSITION_A); DUMP_REG(DC_DISP_V_PULSE3_CONTROL); DUMP_REG(DC_DISP_V_PULSE3_POSITION_A); DUMP_REG(DC_DISP_M0_CONTROL); DUMP_REG(DC_DISP_M1_CONTROL); DUMP_REG(DC_DISP_DI_CONTROL); DUMP_REG(DC_DISP_PP_CONTROL); DUMP_REG(DC_DISP_PP_SELECT_A); DUMP_REG(DC_DISP_PP_SELECT_B); DUMP_REG(DC_DISP_PP_SELECT_C); DUMP_REG(DC_DISP_PP_SELECT_D); DUMP_REG(DC_DISP_DISP_CLOCK_CONTROL); DUMP_REG(DC_DISP_DISP_INTERFACE_CONTROL); DUMP_REG(DC_DISP_DISP_COLOR_CONTROL); DUMP_REG(DC_DISP_SHIFT_CLOCK_OPTIONS); DUMP_REG(DC_DISP_DATA_ENABLE_OPTIONS); DUMP_REG(DC_DISP_SERIAL_INTERFACE_OPTIONS); DUMP_REG(DC_DISP_LCD_SPI_OPTIONS); DUMP_REG(DC_DISP_COLOR_KEY0_LOWER); DUMP_REG(DC_DISP_COLOR_KEY0_UPPER); DUMP_REG(DC_DISP_COLOR_KEY1_LOWER); DUMP_REG(DC_DISP_COLOR_KEY1_UPPER); DUMP_REG(DC_DISP_CURSOR_FOREGROUND); DUMP_REG(DC_DISP_CURSOR_BACKGROUND); DUMP_REG(DC_DISP_CURSOR_START_ADDR); DUMP_REG(DC_DISP_CURSOR_START_ADDR_NS); if (tegra_dc_is_t21x()) { DUMP_REG(DC_DISP_CURSOR_START_ADDR_HI); DUMP_REG(DC_DISP_CURSOR_START_ADDR_HI_NS); } DUMP_REG(DC_DISP_CURSOR_POSITION); DUMP_REG(DC_DISP_CURSOR_POSITION_NS); DUMP_REG(DC_DISP_INIT_SEQ_CONTROL); DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_A); DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_B); DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_C); DUMP_REG(DC_DISP_SPI_INIT_SEQ_DATA_D); DUMP_REG(DC_DISP_DC_MCCIF_FIFOCTRL); DUMP_REG(DC_DISP_MCCIF_DISPLAY0A_HYST); DUMP_REG(DC_DISP_MCCIF_DISPLAY0B_HYST); DUMP_REG(DC_DISP_MCCIF_DISPLAY0C_HYST); DUMP_REG(DC_DISP_DAC_CRT_CTRL); DUMP_REG(DC_DISP_DISP_MISC_CONTROL); DUMP_REG(DC_DISP_INTERLACE_CONTROL); DUMP_REG(DC_DISP_INTERLACE_FIELD2_REF_TO_SYNC); DUMP_REG(DC_DISP_INTERLACE_FIELD2_SYNC_WIDTH); DUMP_REG(DC_DISP_INTERLACE_FIELD2_BACK_PORCH); DUMP_REG(DC_DISP_INTERLACE_FIELD2_FRONT_PORCH); DUMP_REG(DC_DISP_INTERLACE_FIELD2_DISP_ACTIVE); DUMP_REG(DC_CMD_DISPLAY_POWER_CONTROL); DUMP_REG(DC_COM_PIN_OUTPUT_ENABLE2); DUMP_REG(DC_COM_PIN_OUTPUT_POLARITY2); DUMP_REG(DC_COM_PIN_OUTPUT_DATA2); DUMP_REG(DC_COM_PIN_INPUT_ENABLE2); DUMP_REG(DC_COM_PIN_OUTPUT_SELECT5); DUMP_REG(DC_DISP_DISP_SIGNAL_OPTIONS0); DUMP_REG(DC_DISP_M1_CONTROL); DUMP_REG(DC_COM_PM1_CONTROL); DUMP_REG(DC_COM_PM1_DUTY_CYCLE); DUMP_REG(DC_DISP_SD_CONTROL); if (tegra_dc_is_t21x()) { DUMP_REG(DC_COM_CMU_CSC_KRR); DUMP_REG(DC_COM_CMU_CSC_KGR); DUMP_REG(DC_COM_CMU_CSC_KBR); DUMP_REG(DC_COM_CMU_CSC_KRG); DUMP_REG(DC_COM_CMU_CSC_KGG); DUMP_REG(DC_COM_CMU_CSC_KBG); DUMP_REG(DC_COM_CMU_CSC_KRB); DUMP_REG(DC_COM_CMU_CSC_KGB); DUMP_REG(DC_COM_CMU_CSC_KBB); } for_each_set_bit(i, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { print(data, "\n"); snprintf(buff, sizeof(buff), "WINDOW %c:\n", winname[i]); print(data, buff); tegra_dc_writel(dc, WINDOW_A_SELECT << i, DC_CMD_DISPLAY_WINDOW_HEADER); DUMP_REG(DC_CMD_DISPLAY_WINDOW_HEADER); DUMP_REG(DC_WIN_WIN_OPTIONS); DUMP_REG(DC_WIN_BYTE_SWAP); DUMP_REG(DC_WIN_BUFFER_CONTROL); DUMP_REG(DC_WIN_COLOR_DEPTH); DUMP_REG(DC_WIN_POSITION); DUMP_REG(DC_WIN_SIZE); DUMP_REG(DC_WIN_PRESCALED_SIZE); DUMP_REG(DC_WIN_H_INITIAL_DDA); DUMP_REG(DC_WIN_V_INITIAL_DDA); DUMP_REG(DC_WIN_DDA_INCREMENT); DUMP_REG(DC_WIN_LINE_STRIDE); DUMP_REG(DC_WIN_BLEND_NOKEY); DUMP_REG(DC_WIN_BLEND_1WIN); DUMP_REG(DC_WIN_BLEND_2WIN_X); DUMP_REG(DC_WIN_BLEND_2WIN_Y); DUMP_REG(DC_WIN_BLEND_3WIN_XY); DUMP_REG(DC_WIN_GLOBAL_ALPHA); DUMP_REG(DC_WINBUF_BLEND_LAYER_CONTROL); DUMP_REG(DC_WINBUF_START_ADDR); DUMP_REG(DC_WINBUF_START_ADDR_U); DUMP_REG(DC_WINBUF_START_ADDR_V); DUMP_REG(DC_WINBUF_ADDR_H_OFFSET); DUMP_REG(DC_WINBUF_ADDR_V_OFFSET); DUMP_REG(DC_WINBUF_START_ADDR_HI); DUMP_REG(DC_WINBUF_START_ADDR_HI_U); DUMP_REG(DC_WINBUF_START_ADDR_HI_V); DUMP_REG(DC_WINBUF_START_ADDR_FIELD2); DUMP_REG(DC_WINBUF_START_ADDR_FIELD2_U); DUMP_REG(DC_WINBUF_START_ADDR_FIELD2_V); DUMP_REG(DC_WINBUF_START_ADDR_FIELD2_HI); DUMP_REG(DC_WINBUF_START_ADDR_FIELD2_HI_U); DUMP_REG(DC_WINBUF_START_ADDR_FIELD2_HI_V); DUMP_REG(DC_WINBUF_ADDR_H_OFFSET_FIELD2); DUMP_REG(DC_WINBUF_ADDR_V_OFFSET_FIELD2); DUMP_REG(DC_WINBUF_UFLOW_STATUS); DUMP_REG(DC_WIN_CSC_YOF); DUMP_REG(DC_WIN_CSC_KYRGB); DUMP_REG(DC_WIN_CSC_KUR); DUMP_REG(DC_WIN_CSC_KVR); DUMP_REG(DC_WIN_CSC_KUG); DUMP_REG(DC_WIN_CSC_KVG); DUMP_REG(DC_WIN_CSC_KUB); DUMP_REG(DC_WIN_CSC_KVB); if (tegra_dc_is_t21x()) { DUMP_REG(DC_WINBUF_CDE_CONTROL); DUMP_REG(DC_WINBUF_CDE_COMPTAG_BASE_0); DUMP_REG(DC_WINBUF_CDE_COMPTAG_BASEHI_0); DUMP_REG(DC_WINBUF_CDE_ZBC_COLOR_0); DUMP_REG(DC_WINBUF_CDE_SURFACE_OFFSET_0); DUMP_REG(DC_WINBUF_CDE_CTB_ENTRY_0); DUMP_REG(DC_WINBUF_CDE_CG_SW_OVR); DUMP_REG(DC_WINBUF_CDE_PM_CONTROL); DUMP_REG(DC_WINBUF_CDE_PM_COUNTER); } } tegra_dc_writel(dc, cmd_state, DC_CMD_STATE_ACCESS); tegra_dc_put(dc); mutex_unlock(&dc->lock); } #undef DUMP_REG #ifdef DEBUG static void dump_regs_print(void *data, const char *str) { struct tegra_dc *dc = data; dev_dbg(&dc->ndev->dev, "%s", str); } void dump_regs(struct tegra_dc *dc) { reg_dump(dc, dc, dump_regs_print); } #else /* !DEBUG */ void dump_regs(struct tegra_dc *dc) {} #endif /* DEBUG */ #ifdef CONFIG_DEBUG_FS static int dbg_timestamp_show(struct seq_file *s, void *unused) { u32 tmp; u64 timestamp; u32 frame_cnt; struct tegra_dc *dc; dc = s->private; do { tmp = (u32)(tegra_dc_readl(dc, DC_COM_RG_DPCA) >> 16); timestamp = tegra_dc_get_vsync_timestamp(dc); frame_cnt = tegra_dc_get_frame_cnt(dc); } while (tmp != frame_cnt); seq_printf(s, "vsync_timestamp : %llu\n" "frame_no : %u\n", timestamp, frame_cnt); return 0; } static int dbg_timestamp_open(struct inode *inode, struct file *file) { return single_open(file, dbg_timestamp_show, inode->i_private); } static const struct file_operations timestamp_fops = { .open = dbg_timestamp_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static void dbg_regs_print(void *data, const char *str) { struct seq_file *s = data; seq_printf(s, "%s", str); } #undef DUMP_REG static int dbg_dc_show(struct seq_file *s, void *unused) { struct tegra_dc *dc = s->private; reg_dump(dc, s, dbg_regs_print); return 0; } static int dbg_dc_open(struct inode *inode, struct file *file) { return single_open(file, dbg_dc_show, inode->i_private); } static const struct file_operations regs_fops = { .open = dbg_dc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int dbg_dc_mode_show(struct seq_file *s, void *unused) { struct tegra_dc *dc = s->private; struct tegra_dc_mode *m; mutex_lock(&dc->lock); m = &dc->mode; seq_printf(s, "pclk: %d\n" "h_ref_to_sync: %d\n" "v_ref_to_sync: %d\n" "h_sync_width: %d\n" "v_sync_width: %d\n" "h_back_porch: %d\n" "v_back_porch: %d\n" "h_active: %d\n" "v_active: %d\n" "h_front_porch: %d\n" "v_front_porch: %d\n" "flags: 0x%x\n" "stereo_mode: %d\n" "avi_m: 0x%x\n" "vmode: 0x%x\n", m->pclk, m->h_ref_to_sync, m->v_ref_to_sync, m->h_sync_width, m->v_sync_width, m->h_back_porch, m->v_back_porch, m->h_active, m->v_active, m->h_front_porch, m->v_front_porch, m->flags, m->stereo_mode, m->avi_m, m->vmode); mutex_unlock(&dc->lock); return 0; } static int dbg_dc_mode_open(struct inode *inode, struct file *file) { return single_open(file, dbg_dc_mode_show, inode->i_private); } static const struct file_operations mode_fops = { .open = dbg_dc_mode_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int dbg_dc_stats_show(struct seq_file *s, void *unused) { struct tegra_dc *dc = s->private; mutex_lock(&dc->lock); seq_printf(s, "underflows: %llu\n", dc->stats.underflows); if (tegra_dc_is_nvdisplay()) { seq_printf(s, "underflow_frames: %llu\n", dc->stats.underflow_frames); } else { seq_printf(s, "underflows_a: %llu\n" "underflows_b: %llu\n" "underflows_c: %llu\n", dc->stats.underflows_a, dc->stats.underflows_b, dc->stats.underflows_c); seq_printf(s, "underflows_d: %llu\n" "underflows_h: %llu\n" "underflows_t: %llu\n", dc->stats.underflows_d, dc->stats.underflows_h, dc->stats.underflows_t); } mutex_unlock(&dc->lock); return 0; } static int dbg_dc_stats_open(struct inode *inode, struct file *file) { return single_open(file, dbg_dc_stats_show, inode->i_private); } static int dbg_dc_event_inject_show(struct seq_file *s, void *unused) { return 0; } static ssize_t dbg_dc_event_inject_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; /* single_open() initialized */ struct tegra_dc *dc = m ? m->private : NULL; long event; int ret; if (!dc) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, &event); if (ret < 0) return ret; /* * ADF has two seperate events for hotplug connect and disconnect. * We map event 0x0, and 0x1 for them accordingly. For DC_EXT, * both events map to HOTPLUG. */ if (event == 0x0 || event == 0x1) /* TEGRA_DC_EXT_EVENT_HOTPLUG */ tegra_dc_ext_process_hotplug(dc->ndev->id); else if (event == 0x2) /* TEGRA_DC_EXT_EVENT_BANDWIDTH_DEC */ tegra_dc_ext_process_bandwidth_renegotiate( dc->ndev->id, NULL); else { dev_err(&dc->ndev->dev, "Unknown event 0x%lx\n", event); return -EINVAL; /* unknown event number */ } return len; } /* Update the strings as dc.h get updated for new output types*/ static const char * const dc_outtype_strings[] = { "TEGRA_DC_OUT_RGB", "TEGRA_DC_OUT_HDMI", "TEGRA_DC_OUT_DSI", "TEGRA_DC_OUT_DP", "TEGRA_DC_OUT_LVDS", "TEGRA_DC_OUT_NVSR_DP", "TEGRA_DC_OUT_FAKE_DP", "TEGRA_DC_OUT_FAKE_DSIA", "TEGRA_DC_OUT_FAKE_DSIB", "TEGRA_DC_OUT_FAKE_DSI_GANGED", "TEGRA_DC_OUT_NULL", "TEGRA_DC_OUT_UNKNOWN" }; static int dbg_dc_outtype_show(struct seq_file *s, void *unused) { struct tegra_dc *dc = s->private; mutex_lock(&dc->lock); seq_puts(s, "\n"); seq_printf(s, "\tDC OUTPUT: \t%s (%d)\n", dc_outtype_strings[dc->out->type], dc->out->type); seq_puts(s, "\n"); mutex_unlock(&dc->lock); return 0; } /* Add specific variable related to each output type. * Save and reuse on changing the output type */ #if defined(CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT) /* array for saving the out_type for each head */ static int *boot_out_type; static int is_invalid_dc_out(struct tegra_dc *dc, long dc_outtype) { if ((dc_outtype != boot_out_type[dc->ndev->id]) && (dc_outtype != TEGRA_DC_OUT_FAKE_DP) && (dc_outtype != TEGRA_DC_OUT_FAKE_DSIA) && (dc_outtype != TEGRA_DC_OUT_FAKE_DSIB) && (dc_outtype != TEGRA_DC_OUT_FAKE_DSI_GANGED) && (dc_outtype != TEGRA_DC_OUT_NULL)) { dev_err(&dc->ndev->dev, "Request 0x%lx is unsupported target out_type\n", dc_outtype); dev_err(&dc->ndev->dev, "boot_out_type[%d] is 0x%x\n", dc->ndev->id, boot_out_type[dc->ndev->id]); return -EINVAL; } return 0; } static int is_valid_dsi_out(struct tegra_dc *dc, long dc_outtype) { if (((dc_outtype >= TEGRA_DC_OUT_FAKE_DSIA) && (dc_outtype <= TEGRA_DC_OUT_FAKE_DSI_GANGED)) || (dc_outtype == TEGRA_DC_OUT_DSI)) return 1; return 0; } static int is_valid_fake_support(struct tegra_dc *dc, long dc_outtype) { if ((dc_outtype == TEGRA_DC_OUT_FAKE_DP) || (dc_outtype == TEGRA_DC_OUT_FAKE_DSIA) || (dc_outtype == TEGRA_DC_OUT_FAKE_DSIB) || (dc_outtype == TEGRA_DC_OUT_FAKE_DSI_GANGED) || (dc_outtype == TEGRA_DC_OUT_NULL)) return 1; return 0; } static int set_avdd(struct tegra_dc *dc, long cur_out, long new_out) { /* T210 macro_clk is failing SOR access * if avdd_lcd is not enabled */ bool is_enable = false; struct tegra_dc_out *dc_out = &dc->dbg_dc_out_info[boot_out_type[dc->ndev->id]].out; if (tegra_dc_is_nvdisplay()) return 0; /* cur is fake and new is fake - skip */ if (is_valid_fake_support(dc, cur_out) && is_valid_fake_support(dc, new_out)) return 0; /* cur is valid and new is fake - enable */ if (!is_valid_fake_support(dc, cur_out) && is_valid_fake_support(dc, new_out)) is_enable = true; if (is_enable) { if (dc_out && dc_out->enable) dc_out->enable(&dc->ndev->dev); } else { if (dc_out && dc_out->disable) dc_out->disable(&dc->ndev->dev); } return 0; } static ssize_t dbg_dc_out_type_set(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; /* single_open() initialized */ struct tegra_dc *dc = m ? m->private : NULL; long cur_dc_out; long out_type; int ret = 0; bool allocate = false; if (!dc) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, &out_type); if (ret < 0) return ret; if (!dc->pdata->default_out) return -EINVAL; /* check out type is out of range then skip */ if (out_type < TEGRA_DC_OUT_RGB || out_type >= TEGRA_DC_OUT_MAX) { dev_err(&dc->ndev->dev, "Unknown out_type 0x%lx\n", out_type); return -EINVAL; } if (boot_out_type[dc->ndev->id] == -1) boot_out_type[dc->ndev->id] = dc->pdata->default_out->type; cur_dc_out = dc->pdata->default_out->type; /* Nothing to do if new outtype is same as old * Allow to switch between booted out type and fake panel out */ if ((cur_dc_out == out_type) || is_invalid_dc_out(dc, out_type)) return -EINVAL; /* disable the dc and output controllers */ if (dc->enabled) tegra_dc_disable(dc); /* Clear EDID error flags */ if (dc->edid) dc->edid->errors = 0; /* If output is already created - save it */ if (dc->out_data) { dc->dbg_dc_out_info[cur_dc_out].out_data = dc->out_data; dc->dbg_dc_out_info[cur_dc_out].out_ops = dc->out_ops; memcpy(&dc->dbg_dc_out_info[cur_dc_out].out, dc->out, sizeof(struct tegra_dc_out)); dc->dbg_dc_out_info[cur_dc_out].mode = dc->mode; dc->dbg_dc_out_info[cur_dc_out].edid = dc->edid; if (is_valid_dsi_out(dc, cur_dc_out) && dc->dbg_dc_out_info[cur_dc_out].out_data) tegra_dc_destroy_dsi_resources(dc, cur_dc_out); if (!is_valid_fake_support(dc, cur_dc_out)) dc->dbg_dc_out_info[cur_dc_out].fblistindex = tegra_fb_update_modelist(dc, 0); set_avdd(dc, cur_dc_out, out_type); } /* If output already created - reuse it */ if (dc->dbg_dc_out_info[out_type].out_data) { mutex_lock(&dc->lp_lock); mutex_lock(&dc->lock); /* Change the out type */ dc->pdata->default_out->type = out_type; dc->out_ops = dc->dbg_dc_out_info[out_type].out_ops; dc->out_data = dc->dbg_dc_out_info[out_type].out_data; memcpy(dc->out, &dc->dbg_dc_out_info[out_type].out, sizeof(struct tegra_dc_out)); dc->mode = dc->dbg_dc_out_info[out_type].mode; dc->edid = dc->dbg_dc_out_info[out_type].edid; /* Re-init the resources that are destroyed for dsi */ if (is_valid_dsi_out(dc, out_type)) ret = tegra_dc_reinit_dsi_resources(dc, out_type); if (!is_valid_fake_support(dc, out_type)) tegra_fb_update_modelist(dc, dc->dbg_dc_out_info[out_type].fblistindex); mutex_unlock(&dc->lock); mutex_unlock(&dc->lp_lock); if (ret) { dev_err(&dc->ndev->dev, "Failed to reinit!!!\n"); return -EINVAL; } } else { /* Change the out type */ dc->pdata->default_out->type = out_type; /* create new - now restricted to fake_dp only */ if (out_type == TEGRA_DC_OUT_FAKE_DP) { /* set to default bpp */ if (!dc->pdata->default_out->depth) dc->pdata->default_out->depth = 24; /* DP and Fake_DP use same data * Reuse DP data for fake_DP */ if (cur_dc_out != TEGRA_DC_OUT_DP) { allocate = true; } } else if ((out_type >= TEGRA_DC_OUT_FAKE_DSIA) && (out_type <= TEGRA_DC_OUT_FAKE_DSI_GANGED)) { /* DSI and fake DSI use same data * create new if not created yet */ if (!dc->pdata->default_out->depth) dc->pdata->default_out->depth = 18; allocate = true; tegra_dc_init_fakedsi_panel(dc, out_type); } else if (out_type == TEGRA_DC_OUT_NULL) { if (!dc->dbg_dc_out_info[TEGRA_DC_OUT_NULL].out_data) { allocate = true; } } else { /* set back to existing one */ dc->pdata->default_out->type = cur_dc_out; dev_err(&dc->ndev->dev, "Unknown type is asked: %ld\n", out_type); goto by_pass; } if (allocate) { ret = tegra_dc_set_out(dc, dc->pdata->default_out, true); if (ret < 0) { dev_err(&dc->ndev->dev, "Failed to initialize DC out ops\n"); return -EINVAL; } } dc->dbg_dc_out_info[out_type].out_ops = dc->out_ops; dc->dbg_dc_out_info[out_type].out_data = dc->out_data; memcpy(&dc->dbg_dc_out_info[out_type].out, dc->out, sizeof(struct tegra_dc_out)); dc->dbg_dc_out_info[out_type].mode = dc->mode; dc->dbg_dc_out_info[out_type].edid = dc->edid; } if (tegra_fb_is_console_enabled(dc->pdata)) switch (out_type) { case TEGRA_DC_OUT_DSI: case TEGRA_DC_OUT_DP: if (dc->out_ops->detect) dc->out_ops->detect(dc); break; case TEGRA_DC_OUT_FAKE_DP: case TEGRA_DC_OUT_FAKE_DSIA: case TEGRA_DC_OUT_FAKE_DSIB: case TEGRA_DC_OUT_FAKE_DSI_GANGED: tegra_fb_update_monspecs(dc->fb, NULL, NULL); break; } by_pass: /*enable the dc and output controllers */ if (!dc->enabled) tegra_dc_enable(dc); return len; } #else static ssize_t dbg_dc_out_type_set(struct file *file, const char __user *addr, size_t len, loff_t *pos) { return -EINVAL; } #endif /*CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT*/ static const struct file_operations stats_fops = { .open = dbg_dc_stats_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int dbg_dc_event_inject_open(struct inode *inode, struct file *file) { return single_open(file, dbg_dc_event_inject_show, inode->i_private); } static const struct file_operations event_inject_fops = { .open = dbg_dc_event_inject_open, .read = seq_read, .write = dbg_dc_event_inject_write, .llseek = seq_lseek, .release = single_release, }; static int dbg_dc_outtype_open(struct inode *inode, struct file *file) { return single_open(file, dbg_dc_outtype_show, inode->i_private); } static const struct file_operations outtype_fops = { .open = dbg_dc_outtype_open, .read = seq_read, .write = dbg_dc_out_type_set, .llseek = seq_lseek, .release = single_release, }; static int dbg_edid_show(struct seq_file *s, void *unused) { struct tegra_dc *dc = s->private; struct tegra_edid *edid = dc->edid; struct tegra_dc_edid *data; u8 *buf; int i; if (WARN_ON(!dc || !dc->out)) return -EINVAL; if (dc->out->type == TEGRA_DC_OUT_DSI || dc->out->type == TEGRA_DC_OUT_FAKE_DSIA || dc->out->type == TEGRA_DC_OUT_FAKE_DSIB || dc->out->type == TEGRA_DC_OUT_FAKE_DSI_GANGED) { seq_puts(s, "No EDID\n"); return 0; } if (WARN_ON(!dc->edid)) return -EINVAL; data = tegra_edid_get_data(edid); if (!data) { seq_puts(s, "No EDID\n"); return 0; } buf = data->buf; for (i = 0; i < data->len; i++) { #ifdef DEBUG if (i % 16 == 0) seq_printf(s, "edid[%03x] =", i); #endif seq_printf(s, " %02x", buf[i]); if (i % 16 == 15) seq_puts(s, "\n"); } tegra_edid_put_data(data); return 0; } static int dbg_edid_open(struct inode *inode, struct file *file) { return single_open(file, dbg_edid_show, inode->i_private); } static ssize_t dbg_edid_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_dc *dc = m ? m->private : NULL; int ret; if (WARN_ON(!dc || !dc->out)) return -EINVAL; dc->vedid = false; kfree(dc->vedid_data); dc->vedid_data = NULL; if (len < 128) /* invalid edid, turn off vedid */ return 1; dc->vedid_data = kmalloc(sizeof(char) * len, GFP_KERNEL); if (!dc->vedid_data) { dev_err(&dc->ndev->dev, "no memory for edid\n"); return 0; /* dc->vedid is false */ } ret = copy_from_user(dc->vedid_data, addr, len); if (ret < 0) { dev_err(&dc->ndev->dev, "error copying edid\n"); kfree(dc->vedid_data); dc->vedid_data = NULL; return ret; /* dc->vedid is false */ } dc->vedid = true; return len; } static const struct file_operations edid_fops = { .open = dbg_edid_open, .read = seq_read, .llseek = seq_lseek, .write = dbg_edid_write, .release = single_release, }; static int dbg_hotplug_show(struct seq_file *s, void *unused) { struct tegra_dc *dc = s->private; if (WARN_ON(!dc || !dc->out)) return -EINVAL; rmb(); #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) seq_put_decimal_ll(s, '\0', dc->out->hotplug_state); #else seq_put_decimal_ll(s, "", dc->out->hotplug_state); #endif seq_putc(s, '\n'); return 0; } static int dbg_hotplug_open(struct inode *inode, struct file *file) { return single_open(file, dbg_hotplug_show, inode->i_private); } static ssize_t dbg_hotplug_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; /* single_open() initialized */ struct tegra_dc *dc = m ? m->private : NULL; int ret; long new_state; int hotplug_state; if (WARN_ON(!dc || !dc->out)) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, &new_state); if (ret < 0) return ret; mutex_lock(&dc->lock); rmb(); hotplug_state = dc->out->hotplug_state; if (tegra_platform_is_sim() || tegra_platform_is_fpga()) goto skip_gpio; if (hotplug_state == TEGRA_HPD_STATE_NORMAL && new_state != TEGRA_HPD_STATE_NORMAL && dc->hotplug_supported) { /* we are overriding the hpd GPIO, so ignore the interrupt. */ int gpio_irq = gpio_to_irq(dc->out->hotplug_gpio); disable_irq(gpio_irq); } else if (hotplug_state != TEGRA_HPD_STATE_NORMAL && new_state == TEGRA_HPD_STATE_NORMAL && dc->hotplug_supported) { /* restore the interrupt for hpd GPIO. */ int gpio_irq = gpio_to_irq(dc->out->hotplug_gpio); enable_irq(gpio_irq); } skip_gpio: dc->out->hotplug_state = new_state; wmb(); /* retrigger the hotplug * exception: dont trigger for simulator with hotplug * state TEGRA_HPD_STATE_NORMAL since hpd_state * callback for sim always returns true * */ reinit_completion(&dc->hpd_complete); if (dc->out_ops->detect) dc->connected = dc->out_ops->detect(dc); mutex_unlock(&dc->lock); wait_for_completion(&dc->hpd_complete); return len; } static const struct file_operations dbg_hotplug_fops = { .open = dbg_hotplug_open, .read = seq_read, .write = dbg_hotplug_write, .llseek = seq_lseek, .release = single_release, }; static int dbg_color_expand_enable_show(struct seq_file *m, void *unused) { struct tegra_dc_win *win = m->private; if (!win) return -EINVAL; seq_printf(m, "%d\n", win->color_expand_enable); return 0; } static int dbg_color_expand_enable_open(struct inode *inode, struct file *file) { return single_open(file, dbg_color_expand_enable_show, inode->i_private); } static ssize_t dbg_color_expand_enable_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_dc_win *win = m->private; long new_state; int ret; if (!win) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, &new_state); if (ret < 0) return ret; if (new_state == 1) win->color_expand_enable = true; else if (new_state == 0) win->color_expand_enable = false; return len; } static const struct file_operations dbg_color_expand_enable_fops = { .open = dbg_color_expand_enable_open, .read = seq_read, .write = dbg_color_expand_enable_write, .llseek = seq_lseek, .release = single_release, }; static int dbg_degamma_show(struct seq_file *m, void *unused) { struct tegra_dc_win *win = m->private; long degamma_setting = 0; if (!win) return -EINVAL; degamma_setting = tegra_nvdisp_get_degamma_user_config(win); seq_printf(m, "%ld\n", degamma_setting); return 0; } static int dbg_degamma_open(struct inode *inode, struct file *file) { return single_open(file, dbg_degamma_show, inode->i_private); } /* Note that degamma programmed here will not be persistent across * window attach/detach. */ static ssize_t dbg_degamma_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_dc_win *win = m->private; long degamma_setting; int ret; if (!win) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, °amma_setting); if (ret < 0) return ret; tegra_nvdisp_set_degamma_user_config(win, degamma_setting); return len; } static int dbg_force_user_degamma_show(struct seq_file *m, void *unused) { struct tegra_dc_win *win = m->private; if (!win) return -EINVAL; seq_printf(m, "%d\n", win->force_user_degamma); return 0; } static int dbg_force_user_degamma_open(struct inode *inode, struct file *file) { return single_open(file, dbg_force_user_degamma_show, inode->i_private); } static ssize_t dbg_force_user_degamma_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_dc_win *win = m->private; long new_state; int ret; if (!win) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, &new_state); if (ret < 0) return ret; if (new_state == 1) win->force_user_degamma = true; else if (new_state == 0) win->force_user_degamma = false; return len; } static const struct file_operations dbg_force_user_degamma_fops = { .open = dbg_force_user_degamma_open, .read = seq_read, .write = dbg_force_user_degamma_write, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations dbg_degamma_fops = { .open = dbg_degamma_open, .read = seq_read, .write = dbg_degamma_write, .llseek = seq_lseek, .release = single_release, }; static bool tegra_dc_or_is_dsi(struct tegra_dc *dc) { if (dc->out_ops && !dc->out_ops->get_connector_instance && dc->out->type == TEGRA_DC_OUT_DSI) return true; else return false; } static int dbg_nvdisp_topology_show(struct seq_file *m, void *unused) { int i; struct tegra_dc *dc; for (i = 0; i < tegra_dc_get_numof_reg_disps(); i++) { dc = tegra_dc_get_dc(i); if (!dc->current_topology.valid) { seq_printf(m, "DISPLAY ID:%d HEAD:%d DANGLING TOPOLOGY\n", i, tegra_dc_get_head(dc)); continue; } if (tegra_dc_or_is_dsi(dc)) seq_printf(m, "DISPLAY ID:%d HEAD:%d OUT_TYPE:%d DSI\n", i, tegra_dc_get_head(dc), dc->current_topology.protocol); else seq_printf(m, "DISPLAY ID:%d HEAD:%d OUT_TYPE:%d SOR%d\n", i, tegra_dc_get_head(dc), dc->current_topology.protocol, dc->current_topology.conn_inst); } return 0; } static int dbg_nvdisp_topology_open(struct inode *inode, struct file *file) { return single_open(file, dbg_nvdisp_topology_show, inode->i_private); } static bool is_topology_same(struct tegra_dc_topology topology1, struct tegra_dc_topology topology2) { if (topology1.disp_id != topology2.disp_id) return false; if (topology1.protocol != topology2.protocol) return false; if (topology1.conn_inst != topology2.conn_inst) return false; return true; } static bool is_topology_reset(struct tegra_dc_topology topology) { if ((topology.disp_id == TEGRA_DC_TOPOLOGY_RESTORE) && (topology.protocol == TEGRA_DC_TOPOLOGY_RESTORE) && (topology.conn_inst == TEGRA_DC_TOPOLOGY_RESTORE)) return true; return false; } static bool is_topology_possible(struct tegra_dc_topology topology) { struct tegra_dc *dc = NULL; /* Check to see if restore boot topology is called */ if (is_topology_reset(topology)) return true; if (topology.disp_id >= tegra_dc_get_numof_reg_disps()) return false; if (topology.conn_inst >= tegra_dc_get_numof_dispsors()) return false; /* Prevent crossbar on DSI display until support is added in sw */ dc = tegra_dc_get_dc(topology.disp_id); if (tegra_dc_or_is_dsi(dc)) return false; /* Currently supports only DP,FAKE_DP and HDMI in SW */ if ((topology.protocol == TEGRA_DC_OUT_FAKE_DP) || (topology.protocol == TEGRA_DC_OUT_DP) || (topology.protocol == TEGRA_DC_OUT_HDMI)) { return true; } return false; } static int tegra_dc_topology_parse(char *buf, int nargs, struct tegra_dc_topology *topology) { char *b = NULL, *orig_b = NULL, *c = NULL; int i = 0, args[TEGRA_DC_TOPOLOGY_NARGS] = {TEGRA_DC_TOPOLOGY_INVALID}; orig_b = kstrdup(buf, GFP_KERNEL); b = orig_b; for (i = 0; i < nargs; i++) { if (!b) break; b = strim(b); c = strsep(&b, ":"); if (!strlen(c)) break; args[i] = simple_strtol(c, NULL, 10); if (args[i] < TEGRA_DC_TOPOLOGY_ARG_MIN || args[i] > TEGRA_DC_TOPOLOGY_ARG_MAX) break; } kfree(orig_b); if (i == nargs) { topology->disp_id = args[0]; topology->protocol = args[1]; topology->conn_inst = args[2]; return 0; } else { return -EINVAL; } } static int tegra_dc_crossbar_display_reinit(struct tegra_dc *dc, struct tegra_dc_topology topology) { int ret = 0; dc->pdata->default_out->type = topology.protocol; dc->current_topology = topology; /* Parse the platform data again */ dc->pdata = of_dc_parse_platform_data(dc->ndev, dc->pdata); if (IS_ERR_OR_NULL(dc->pdata)) { pr_warn("crossbar: parsing platform data for new topology failed\n"); return -EFAULT; } if (dc->out && dc->out->hotplug_init) dc->out->hotplug_init(&dc->ndev->dev); ret = tegra_dc_set_out(dc, dc->pdata->default_out, true); if (ret < 0) { pr_warn("crossbar: initialize DC out ops for new topology failed\n"); return -EFAULT; } if (dc->out_ops->get_connector_instance) { char sor_path[CHAR_BUF_SIZE_MAX]; int ctrl_num = -1; ctrl_num = dc->out_ops->get_connector_instance(dc); if (ctrl_num < 0) { pr_err("crossbar: SOR controller instance not found\n"); return -EFAULT; } snprintf(sor_path, sizeof(sor_path), "/sys/kernel/debug/tegra_sor%d", ctrl_num); dc->sor_link = debugfs_create_symlink("sor", dc->debugdir, sor_path); if (!dc->sor_link) { pr_err("crossbar: couldn't create symbolic link to SOR%d for DC%d\n", ctrl_num, dc->ctrl_num); return -EFAULT; } } if (dc->out_ops && dc->out_ops->hotplug_init) dc->out_ops->hotplug_init(dc); if (dc->out_ops && dc->out_ops->detect) dc->connected = dc->out_ops->detect(dc); dc->current_topology.valid = true; return ret; } static ssize_t dbg_nvdisp_topology_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { int res = 0, ret = 0; int i = 0; char buf[CHAR_BUF_SIZE_MAX+1] = {'\0'}; struct tegra_dc *primary = NULL; struct tegra_dc *curr = NULL; bool dangling = false; struct tegra_dc_topology topology = {false, TEGRA_DC_TOPOLOGY_INVALID, TEGRA_DC_TOPOLOGY_INVALID, TEGRA_DC_TOPOLOGY_INVALID}; res = copy_from_user(buf, addr, CHAR_BUF_SIZE_MAX); ret = tegra_dc_topology_parse(buf, TEGRA_DC_TOPOLOGY_NARGS, &topology); if (ret < 0) { pr_warn("crossbar: invalid input format for topology\n"); return -EINVAL; } /* Check to see if all registered displays are disabled */ for (i = 0; i < tegra_dc_get_numof_reg_disps(); i++) { curr = tegra_dc_get_dc(i); if (curr->enabled) { pr_warn("crossbar: all displays are not disabled\n"); return -EINVAL; } } curr = NULL; if (!is_topology_possible(topology)) { pr_warn("crossbar: topology %d:%d:%d not possible\n", topology.disp_id, topology.protocol, topology.conn_inst); return -EINVAL; } if (is_topology_reset(topology)) { /* Reset topology */ /* Destroy out_type and sor for reconfigured displays */ for (i = 0; i < tegra_dc_get_numof_reg_disps(); i++) { curr = tegra_dc_get_dc(i); if (curr->current_topology.valid && !is_topology_same(curr->current_topology, curr->boot_topology)) { /* disable the dc and output controllers */ tegra_dc_shutdown(curr->ndev); if (curr->out_ops && curr->out_ops->destroy) curr->out_ops->destroy(curr); } } /* Reinitialize only reconfigured displays */ for (i = 0; i < tegra_dc_get_numof_reg_disps(); i++) { curr = tegra_dc_get_dc(i); if (!curr->current_topology.valid || !is_topology_same(curr->current_topology, curr->boot_topology)) { ret = tegra_dc_crossbar_display_reinit(curr, topology); curr->current_topology = curr->boot_topology; if (ret < 0) break; } } } else { /* Single display workflow */ primary = tegra_dc_get_dc(topology.disp_id); for (i = 0; i < tegra_dc_get_numof_reg_disps(); i++) { curr = tegra_dc_get_dc(i); if (tegra_dc_or_is_dsi(curr)) { continue; } else { if (curr->out_ops && curr->out_ops->get_connector_instance && (curr->out_ops->get_connector_instance(curr) == topology.conn_inst)) { dangling = true; break; } } } tegra_dc_shutdown(primary->ndev); if (primary->out_ops && primary->out_ops->destroy) primary->out_ops->destroy(primary); if (dangling) { tegra_dc_shutdown(curr->ndev); if (curr->out_ops && curr->out_ops->destroy) curr->out_ops->destroy(curr); } ret = tegra_dc_crossbar_display_reinit(primary, topology); } if (ret < 0) { pr_warn("crossbar: switching to topology %d:%d:%d failed\n", topology.disp_id, topology.protocol, topology.conn_inst); return -EINVAL; } pr_info("crossbar: switching to topology %d:%d:%d success\n", topology.disp_id, topology.protocol, topology.conn_inst); return len; } static const struct file_operations dbg_nvdisp_topology_fops = { .open = dbg_nvdisp_topology_open, .read = seq_read, .write = dbg_nvdisp_topology_write, .llseek = seq_lseek, .release = single_release, }; static int dbg_vrr_enable_show(struct seq_file *m, void *unused) { struct tegra_vrr *vrr = m->private; if (!vrr) return -EINVAL; seq_printf(m, "vrr enable state: %d\n", vrr->enable); return 0; } static int dbg_vrr_enable_open(struct inode *inode, struct file *file) { return single_open(file, dbg_vrr_enable_show, inode->i_private); } static const struct file_operations dbg_vrr_enable_ops = { .open = dbg_vrr_enable_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int dbg_vrr_dcb_show(struct seq_file *m, void *unused) { struct tegra_vrr *vrr = m->private; if (!vrr) return -EINVAL; seq_printf(m, "vrr dc balance: %d\n", vrr->dcb); return 0; } static int dbg_vrr_dcb_open(struct inode *inode, struct file *file) { return single_open(file, dbg_vrr_dcb_show, inode->i_private); } static const struct file_operations dbg_vrr_dcb_ops = { .open = dbg_vrr_dcb_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int dbg_vrr_db_tolerance_show(struct seq_file *m, void *unused) { struct tegra_vrr *vrr = m->private; if (!vrr) return -EINVAL; seq_printf(m, "vrr db tolerance: %d\n", vrr->db_tolerance); return 0; } static ssize_t dbg_vrr_db_tolerance_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_vrr *vrr = m->private; long new_value; int ret; if (!vrr) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, &new_value); if (ret < 0) return ret; vrr->db_tolerance = new_value; return len; } static int dbg_vrr_db_tolerance_open(struct inode *inode, struct file *file) { return single_open(file, dbg_vrr_db_tolerance_show, inode->i_private); } static const struct file_operations dbg_vrr_db_tolerance_ops = { .open = dbg_vrr_db_tolerance_open, .read = seq_read, .write = dbg_vrr_db_tolerance_write, .llseek = seq_lseek, .release = single_release, }; static int dbg_vrr_frame_avg_pct_show(struct seq_file *m, void *unused) { struct tegra_vrr *vrr = m->private; if (!vrr) return -EINVAL; seq_printf(m, "vrr frame average percent: %d\n", vrr->frame_avg_pct); return 0; } static ssize_t dbg_vrr_frame_avg_pct_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_vrr *vrr = m->private; long new_pct; int ret; if (!vrr) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, &new_pct); if (ret < 0) return ret; vrr->frame_avg_pct = new_pct; return len; } static int dbg_vrr_frame_avg_pct_open(struct inode *inode, struct file *file) { return single_open(file, dbg_vrr_frame_avg_pct_show, inode->i_private); } static const struct file_operations dbg_vrr_frame_avg_pct_ops = { .open = dbg_vrr_frame_avg_pct_open, .read = seq_read, .write = dbg_vrr_frame_avg_pct_write, .llseek = seq_lseek, .release = single_release, }; static int dbg_vrr_fluct_avg_pct_show(struct seq_file *m, void *unused) { struct tegra_vrr *vrr = m->private; if (!vrr) return -EINVAL; seq_printf(m, "vrr fluct average percent: %d\n", vrr->fluct_avg_pct); return 0; } static ssize_t dbg_vrr_fluct_avg_pct_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_vrr *vrr = m->private; long new_pct; int ret; if (!vrr) return -EINVAL; ret = kstrtol_from_user(addr, len, 10, &new_pct); if (ret < 0) return ret; vrr->fluct_avg_pct = new_pct; return len; } static int dbg_vrr_fluct_avg_pct_open(struct inode *inode, struct file *file) { return single_open(file, dbg_vrr_fluct_avg_pct_show, inode->i_private); } static const struct file_operations dbg_vrr_fluct_avg_pct_ops = { .open = dbg_vrr_fluct_avg_pct_open, .read = seq_read, .write = dbg_vrr_fluct_avg_pct_write, .llseek = seq_lseek, .release = single_release, }; static int dbg_tegrahw_type_show(struct seq_file *m, void *unused) { struct tegra_dc *dc = m->private; if (WARN_ON(!dc || !dc->out)) return -EINVAL; /* All platforms other than real silicon are taken as simulation */ seq_printf(m, "real_silicon: %d\n", tegra_platform_is_silicon()); return 0; } static int dbg_tegrahw_type_open(struct inode *inode, struct file *file) { return single_open(file, dbg_tegrahw_type_show, inode->i_private); } static const struct file_operations dbg_tegrahw_type_ops = { .open = dbg_tegrahw_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static ssize_t dbg_background_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_dc *dc = m->private; unsigned long background; u32 old_state; if (!dc) return -EINVAL; if (kstrtoul_from_user(addr, len, 0, &background) < 0) return -EINVAL; if (!dc->enabled) return -EBUSY; tegra_dc_get(dc); mutex_lock(&dc->lock); old_state = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); /* write active version */ tegra_dc_writel(dc, WRITE_MUX_ACTIVE | READ_MUX_ACTIVE, DC_CMD_STATE_ACCESS); tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); /* flush */ tegra_dc_writel(dc, background, DC_DISP_BLEND_BACKGROUND_COLOR); /* write assembly version */ tegra_dc_writel(dc, WRITE_MUX_ASSEMBLY | READ_MUX_ASSEMBLY, DC_CMD_STATE_ACCESS); tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); /* flush */ tegra_dc_writel(dc, background, DC_DISP_BLEND_BACKGROUND_COLOR); /* cycle the values through assemby -> arm -> active */ tegra_dc_writel(dc, GENERAL_UPDATE, DC_CMD_STATE_CONTROL); tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); /* flush */ tegra_dc_writel(dc, NC_HOST_TRIG | GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, old_state, DC_CMD_STATE_ACCESS); tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); /* flush */ mutex_unlock(&dc->lock); tegra_dc_put(dc); return len; } static int dbg_background_show(struct seq_file *m, void *unused) { struct tegra_dc *dc = m->private; u32 old_state; u32 background; if (WARN_ON(!dc || !dc->out)) return -EINVAL; if (!dc->enabled) return -EBUSY; tegra_dc_get(dc); mutex_lock(&dc->lock); old_state = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); tegra_dc_writel(dc, WRITE_MUX_ACTIVE | READ_MUX_ACTIVE, DC_CMD_STATE_ACCESS); background = tegra_dc_readl(dc, DC_DISP_BLEND_BACKGROUND_COLOR); tegra_dc_writel(dc, old_state, DC_CMD_STATE_ACCESS); mutex_unlock(&dc->lock); tegra_dc_put(dc); seq_printf(m, "%#x\n", (unsigned)background); return 0; } static int dbg_background_open(struct inode *inode, struct file *file) { return single_open(file, dbg_background_show, inode->i_private); } static const struct file_operations dbg_background_ops = { .open = dbg_background_open, .write = dbg_background_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; /* toggly the enable/disable for any windows with 1 bit set */ static ssize_t dbg_window_toggle_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_dc *dc = m->private; unsigned long windows; int i; u32 status; int retries; if (!dc) return -EINVAL; if (kstrtoul_from_user(addr, len, 0, &windows) < 0) return -EINVAL; if (!dc->enabled) return 0; mutex_lock(&dc->lock); tegra_dc_get(dc); /* limit the request only to valid windows */ windows &= dc->valid_windows; for_each_set_bit(i, &windows, tegra_dc_get_numof_dispwindows()) { u32 val; /* select the assembly registers for window i */ tegra_dc_writel(dc, WRITE_MUX_ASSEMBLY | READ_MUX_ASSEMBLY, DC_CMD_STATE_ACCESS); tegra_dc_writel(dc, WINDOW_A_SELECT << i, DC_CMD_DISPLAY_WINDOW_HEADER); /* toggle the enable bit */ val = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); val ^= WIN_ENABLE; dev_dbg(&dc->ndev->dev, "%s window #%d\n", (val & WIN_ENABLE) ? "enabling" : "disabling", i); tegra_dc_writel(dc, val, DC_WIN_WIN_OPTIONS); /* post the update */ tegra_dc_writel(dc, WIN_A_UPDATE << i, DC_CMD_STATE_CONTROL); retries = 8; do { status = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); retries--; } while (retries && (status & (WIN_A_UPDATE << i))); tegra_dc_writel(dc, WIN_A_ACT_REQ << i, DC_CMD_STATE_CONTROL); } tegra_dc_put(dc); mutex_unlock(&dc->lock); return len; } /* reading shows the enabled windows */ static int dbg_window_toggle_show(struct seq_file *m, void *unused) { struct tegra_dc *dc = m->private; int i; unsigned long windows; if (WARN_ON(!dc || !dc->out)) return -EINVAL; mutex_lock(&dc->lock); tegra_dc_get(dc); /* limit the request only to valid windows */ windows = 0; for_each_set_bit(i, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { u32 val; /* select the active registers for window i */ tegra_dc_writel(dc, WRITE_MUX_ACTIVE | READ_MUX_ACTIVE, DC_CMD_STATE_ACCESS); tegra_dc_writel(dc, WINDOW_A_SELECT << i, DC_CMD_DISPLAY_WINDOW_HEADER); /* add i to a bitmap if WIN_ENABLE is set */ val = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); if (val & WIN_ENABLE) set_bit(i, &windows); } tegra_dc_put(dc); mutex_unlock(&dc->lock); seq_printf(m, "%#lx %#lx\n", dc->valid_windows, windows); return 0; } static int dbg_window_toggle_open(struct inode *inode, struct file *file) { return single_open(file, dbg_window_toggle_show, inode->i_private); } static const struct file_operations dbg_window_toggle_ops = { .open = dbg_window_toggle_open, .write = dbg_window_toggle_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int dbg_dc_cmu_lut1_show(struct seq_file *s, void *unused) { struct tegra_dc *dc = s->private; u32 val; int i; mutex_lock(&dc->lock); tegra_dc_get(dc); /* Disable CMU while reading LUTs */ val = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL); tegra_dc_writel(dc, val & ~CMU_ENABLE, DC_DISP_DISP_COLOR_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); _tegra_dc_wait_for_frame_end(dc, div_s64(dc->frametime_ns, 1000000ll) * 2); for (i = 0; i < 256; i++) { tegra_dc_writel(dc, LUT1_READ_EN | LUT1_READ_ADDR(i), DC_COM_CMU_LUT1_READ); seq_printf(s, "%lu\n", LUT1_READ_DATA(tegra_dc_readl(dc, DC_COM_CMU_LUT1))); } tegra_dc_writel(dc, 0, DC_COM_CMU_LUT1_READ); tegra_dc_writel(dc, val, DC_DISP_DISP_COLOR_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_put(dc); mutex_unlock(&dc->lock); return 0; } static int dbg_dc_cmu_lut1_open(struct inode *inode, struct file *file) { return single_open(file, dbg_dc_cmu_lut1_show, inode->i_private); } static const struct file_operations cmu_lut1_fops = { .open = dbg_dc_cmu_lut1_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int dbg_dc_cmu_lut2_show(struct seq_file *s, void *unused) { struct tegra_dc *dc = s->private; u32 val; int i; mutex_lock(&dc->lock); tegra_dc_get(dc); /* Disable CMU while reading LUTs */ val = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL); tegra_dc_writel(dc, val & ~CMU_ENABLE, DC_DISP_DISP_COLOR_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); _tegra_dc_wait_for_frame_end(dc, div_s64(dc->frametime_ns, 1000000ll) * 2); for (i = 0; i < 960; i++) { tegra_dc_writel(dc, LUT2_READ_EN | LUT2_READ_ADDR(i), DC_COM_CMU_LUT2_READ); seq_printf(s, "%lu\n", LUT2_READ_DATA(tegra_dc_readl(dc, DC_COM_CMU_LUT2))); } tegra_dc_writel(dc, 0, DC_COM_CMU_LUT2_READ); tegra_dc_writel(dc, val, DC_DISP_DISP_COLOR_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_put(dc); mutex_unlock(&dc->lock); return 0; } static int dbg_dc_cmu_lut2_open(struct inode *inode, struct file *file) { return single_open(file, dbg_dc_cmu_lut2_show, inode->i_private); } static const struct file_operations cmu_lut2_fops = { .open = dbg_dc_cmu_lut2_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #define NVDISP_WIN_CSC_FILEOPS(name) \ static int dbg_nvdisp_win_csc_##name##_show(struct seq_file *m, void *unused)\ { \ struct tegra_dc_win *win = m->private; \ \ if (!win) \ return -EINVAL; \ seq_printf(m, "%u\n", win->nvdisp_win_csc.name); \ return 0; \ } \ \ static int dbg_nvdisp_win_csc_##name##_open(struct inode *inode, \ struct file *file) \ { \ return single_open(file, dbg_nvdisp_win_csc_##name##_show, \ inode->i_private); \ } \ \ static ssize_t dbg_nvdisp_win_csc_##name##_write(struct file *file, \ const char __user *addr, size_t len, loff_t *pos) \ { \ struct tegra_dc_win *win; \ struct seq_file *m = file->private_data; \ u32 user_csc; \ \ win = m ? m->private : NULL; \ if (kstrtou32_from_user(addr, len, 10, &user_csc) < 0) \ return -EINVAL; \ \ win->nvdisp_win_csc.name = user_csc; \ win->csc_dirty = 1; \ return len; \ } \ \ static const struct file_operations dbg_nvdisp_win_csc_##name##_fops = {\ .open = dbg_nvdisp_win_csc_##name##_open, \ .read = seq_read, \ .llseek = seq_lseek, \ .release = single_release, \ .write = dbg_nvdisp_win_csc_##name##_write, \ } NVDISP_WIN_CSC_FILEOPS(r2r); NVDISP_WIN_CSC_FILEOPS(g2r); NVDISP_WIN_CSC_FILEOPS(b2r); NVDISP_WIN_CSC_FILEOPS(const2r); NVDISP_WIN_CSC_FILEOPS(r2g); NVDISP_WIN_CSC_FILEOPS(g2g); NVDISP_WIN_CSC_FILEOPS(b2g); NVDISP_WIN_CSC_FILEOPS(const2g); NVDISP_WIN_CSC_FILEOPS(r2b); NVDISP_WIN_CSC_FILEOPS(g2b); NVDISP_WIN_CSC_FILEOPS(b2b); NVDISP_WIN_CSC_FILEOPS(const2b); #undef NVDISP_WIN_CSC_FILEOPS static int dbg_nvdisp_win_csc_force_user_csc_show(struct seq_file *m, void *unused) { struct tegra_dc_win *win = m ? m->private : NULL; if (win == NULL) return -EINVAL; #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) seq_put_decimal_ll(m, '\0', win->force_user_csc); #else seq_put_decimal_ll(m, "", win->force_user_csc); #endif seq_putc(m, '\n'); return 0; } static int dbg_nvdisp_win_csc_force_user_csc_open(struct inode *inode, struct file *file) { return single_open(file, dbg_nvdisp_win_csc_force_user_csc_show, inode->i_private); } static ssize_t dbg_nvdisp_win_csc_force_user_csc_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { int ret = 0; int force_user_csc = 0; struct tegra_dc_win *win; struct seq_file *m = file->private_data; win = m ? m->private : NULL; if (!win) return -EFAULT; ret = kstrtoint_from_user(addr, len, 10, &force_user_csc); if (ret < 0) return ret; win->force_user_csc = (force_user_csc == 0)?0:1; return len; } static const struct file_operations nvdisp_win_csc_force_user_csc_fops = { .open = dbg_nvdisp_win_csc_force_user_csc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = dbg_nvdisp_win_csc_force_user_csc_write, }; static int dbg_measure_refresh_show(struct seq_file *m, void *unused) { struct tegra_dc *dc = m->private; if (WARN_ON(!dc || !dc->out)) return -EINVAL; seq_puts(m, "Write capture time in seconds to this node.\n"); seq_puts(m, "Results will show up in dmesg.\n"); return 0; } static ssize_t dbg_measure_refresh_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { struct seq_file *m = file->private_data; struct tegra_dc *dc = m->private; s32 seconds; u32 fe_count; int ret; fixed20_12 refresh_rate; fixed20_12 seconds_fixed; if (WARN_ON(!dc || !dc->out)) return -EINVAL; ret = kstrtoint_from_user(addr, len, 10, &seconds); if (ret < 0 || seconds < 1) { dev_info(&dc->ndev->dev, "specify integer number of seconds greater than 0\n"); return -EINVAL; } dev_info(&dc->ndev->dev, "measuring for %d seconds\n", seconds); mutex_lock(&dc->lock); _tegra_dc_config_frame_end_intr(dc, true); dc->dbg_fe_count = 0; mutex_unlock(&dc->lock); msleep(1000 * seconds); mutex_lock(&dc->lock); _tegra_dc_config_frame_end_intr(dc, false); fe_count = dc->dbg_fe_count; mutex_unlock(&dc->lock); refresh_rate.full = dfixed_const(fe_count); seconds_fixed.full = dfixed_const(seconds); refresh_rate.full = dfixed_div(refresh_rate, seconds_fixed); /* Print fixed point 20.12 in decimal, truncating the 12-bit fractional part to 2 decimal points */ dev_info(&dc->ndev->dev, "refresh rate: %d.%dHz\n", dfixed_trunc(refresh_rate), dfixed_frac(refresh_rate) * 100 / 4096); return len; } static int dbg_measure_refresh_open(struct inode *inode, struct file *file) { return single_open(file, dbg_measure_refresh_show, inode->i_private); } static const struct file_operations dbg_measure_refresh_ops = { .open = dbg_measure_refresh_open, .read = seq_read, .write = dbg_measure_refresh_write, .llseek = seq_lseek, .release = single_release, }; static int dbg_hw_index_show(struct seq_file *m, void *unused) { struct tegra_dc *dc = m->private; if (WARN_ON(!dc || !dc->out)) return -EINVAL; seq_printf(m, "Hardware index: %d\n", dc->ctrl_num); return 0; } static int dbg_hw_index_open(struct inode *inode, struct file *file) { return single_open(file, dbg_hw_index_show, inode->i_private); } static const struct file_operations dbg_hw_index_ops = { .open = dbg_hw_index_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int dbg_flip_stats_show(struct seq_file *m, void *unused) { struct tegra_dc *dc = m->private; if (WARN_ON(!dc || !dc->out)) return -EINVAL; seq_printf(m, "Flips queued: %ld\n", atomic64_read(&dc->flip_stats.flips_queued)); seq_printf(m, "Flips skipped: %ld\n", atomic64_read(&dc->flip_stats.flips_skipped)); seq_printf(m, "Flips completed: %ld\n", atomic64_read(&dc->flip_stats.flips_cmpltd)); return 0; } static int dbg_flip_stats_open(struct inode *inode, struct file *file) { return single_open(file, dbg_flip_stats_show, inode->i_private); } static const struct file_operations dbg_flip_stats_ops = { .open = dbg_flip_stats_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int dbg_measure_latency_show(struct seq_file *m, void *unused) { struct tegra_dc *dc = m->private; if (WARN_ON(!dc)) return -EINVAL; mutex_lock(&dc->msrmnt_info.lock); seq_printf(m, "%d\n", dc->msrmnt_info.enabled); mutex_unlock(&dc->msrmnt_info.lock); return 0; } static ssize_t dbg_measure_latency_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { int ret; int enable_val; struct seq_file *m = file->private_data; struct tegra_dc *dc = m->private; ret = kstrtoint_from_user(addr, len, 10, &enable_val); if (ret < 0) return ret; ret = tegra_dc_en_dis_latency_msrmnt_mode(dc, enable_val); return len; } static int dbg_measure_latency_open(struct inode *inode, struct file *file) { return single_open(file, dbg_measure_latency_show, inode->i_private); } static const struct file_operations dbg_measure_latency_ops = { .open = dbg_measure_latency_open, .read = seq_read, .write = dbg_measure_latency_write, .llseek = seq_lseek, .release = single_release, }; static void tegra_dc_remove_debugfs(struct tegra_dc *dc) { if (dc->debugdir) debugfs_remove_recursive(dc->debugdir); dc->debugdir = NULL; if (tegra_dc_is_nvdisplay()) { debugfs_remove_recursive(dc->debug_common_dir); dc->debug_common_dir = NULL; } } /*Create file for all elements of nvdc_nvdisp_cmu per window*/ #define CREATE_NVDISP_WIN_CSC_SYSFS(name) \ do { \ retval = debugfs_create_file(#name, 0444, wincscdir, win, \ &dbg_nvdisp_win_csc_##name##_fops); \ if (!retval) \ goto remove_out; \ } \ while (0) static void tegra_dc_create_debugfs(struct tegra_dc *dc) { struct dentry *retval, *vrrdir; struct dentry *windir, *wincscdir, *windegammadir; char winname[50]; u32 i; char devname[50]; snprintf(devname, sizeof(devname), "tegradc.%d", dc->ndev->id); dc->debugdir = debugfs_create_dir(devname, NULL); if (!dc->debugdir) goto remove_out; retval = debugfs_create_file("timestamp", 0444, dc->debugdir, dc, ×tamp_fops); if (!retval) goto remove_out; retval = debugfs_create_file("regs", 0444, dc->debugdir, dc, ®s_fops); if (!retval) goto remove_out; retval = debugfs_create_file("mode", 0444, dc->debugdir, dc, &mode_fops); if (!retval) goto remove_out; retval = debugfs_create_file("stats", 0444, dc->debugdir, dc, &stats_fops); if (!retval) goto remove_out; retval = debugfs_create_file("event_inject", 0444, dc->debugdir, dc, &event_inject_fops); if (!retval) goto remove_out; retval = debugfs_create_file("out_type", 0444, dc->debugdir, dc, &outtype_fops); if (!retval) goto remove_out; retval = debugfs_create_file("edid", 0444, dc->debugdir, dc, &edid_fops); if (!retval) goto remove_out; if (dc->out_ops->detect) { /* only create the file if hotplug is supported */ retval = debugfs_create_file("hotplug", 0444, dc->debugdir, dc, &dbg_hotplug_fops); if (!retval) goto remove_out; } vrrdir = debugfs_create_dir("vrr", dc->debugdir); if (!vrrdir) goto remove_out; retval = debugfs_create_file("enable", 0444, vrrdir, dc->out->vrr, &dbg_vrr_enable_ops); if (!retval) goto remove_out; retval = debugfs_create_file("dcb", 0444, vrrdir, dc->out->vrr, &dbg_vrr_dcb_ops); if (!retval) goto remove_out; retval = debugfs_create_file("frame_avg_pct", 0444, vrrdir, dc->out->vrr, &dbg_vrr_frame_avg_pct_ops); if (!retval) goto remove_out; retval = debugfs_create_file("fluct_avg_pct", 0444, vrrdir, dc->out->vrr, &dbg_vrr_fluct_avg_pct_ops); if (!retval) goto remove_out; retval = debugfs_create_file("tegrahw_type", 0444, dc->debugdir, dc, &dbg_tegrahw_type_ops); if (!retval) goto remove_out; retval = debugfs_create_file("background", 0444, dc->debugdir, dc, &dbg_background_ops); if (!retval) goto remove_out; retval = debugfs_create_file("window_toggle", 0444, dc->debugdir, dc, &dbg_window_toggle_ops); if (!retval) goto remove_out; retval = debugfs_create_file("cmu_lut1", 0444, dc->debugdir, dc, &cmu_lut1_fops); if (!retval) goto remove_out; retval = debugfs_create_file("cmu_lut2", 0444, dc->debugdir, dc, &cmu_lut2_fops); if (!retval) goto remove_out; retval = debugfs_create_file("measure_refresh", 0444, dc->debugdir, dc, &dbg_measure_refresh_ops); if (!retval) goto remove_out; retval = debugfs_create_file("hw_index", 0444, dc->debugdir, dc, &dbg_hw_index_ops); if (!retval) goto remove_out; retval = debugfs_create_file("flip_stats", 0444, dc->debugdir, dc, &dbg_flip_stats_ops); if (!retval) goto remove_out; if (dc->out_ops->get_connector_instance) { char sor_path[CHAR_BUF_SIZE_MAX]; int ctrl_num = -1; ctrl_num = dc->out_ops->get_connector_instance(dc); if (ctrl_num < 0) goto remove_out; snprintf(sor_path, sizeof(sor_path), "/sys/kernel/debug/tegra_sor%d", ctrl_num); dc->sor_link = debugfs_create_symlink("sor", dc->debugdir, sor_path); if (!dc->sor_link) goto remove_out; } if (tegra_dc_is_nvdisplay()) { /*Create directory for elements common to all DC heads*/ if (!dc->ndev->id) { dc->debug_common_dir = debugfs_create_dir( "tegradc.common", NULL); if (!dc->debug_common_dir) goto remove_out; for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) { struct tegra_dc_win *win = &tegra_dc_windows[i]; snprintf(winname, sizeof(winname), "tegra_win.%d", i); windir = debugfs_create_dir(winname, dc->debug_common_dir); if (!windir) goto remove_out; retval = debugfs_create_file( "color_expand_enable", 0444, windir, win, &dbg_color_expand_enable_fops); if (!retval) goto remove_out; windegammadir = debugfs_create_dir("degamma", windir); if (!windegammadir) goto remove_out; retval = debugfs_create_file("degamma", 0444, windegammadir, win, &dbg_degamma_fops); retval = debugfs_create_file( "force_user_degamma", 0444, windegammadir, win, &dbg_force_user_degamma_fops); if (!retval) goto remove_out; wincscdir = debugfs_create_dir("csc", windir); if (!wincscdir) goto remove_out; CREATE_NVDISP_WIN_CSC_SYSFS(r2r); CREATE_NVDISP_WIN_CSC_SYSFS(g2r); CREATE_NVDISP_WIN_CSC_SYSFS(b2r); CREATE_NVDISP_WIN_CSC_SYSFS(const2r); CREATE_NVDISP_WIN_CSC_SYSFS(r2g); CREATE_NVDISP_WIN_CSC_SYSFS(g2g); CREATE_NVDISP_WIN_CSC_SYSFS(b2g); CREATE_NVDISP_WIN_CSC_SYSFS(const2g); CREATE_NVDISP_WIN_CSC_SYSFS(r2b); CREATE_NVDISP_WIN_CSC_SYSFS(g2b); CREATE_NVDISP_WIN_CSC_SYSFS(b2b); CREATE_NVDISP_WIN_CSC_SYSFS(const2b); /* Create file to use user-defined CSC values as * override over user-space CSC */ retval = debugfs_create_file("force_user_csc", 0444, wincscdir, win, &nvdisp_win_csc_force_user_csc_fops); if (!retval) goto remove_out; } retval = debugfs_create_file("nvdisp_topology", 0444, dc->debug_common_dir, NULL, &dbg_nvdisp_topology_fops); if (!retval) goto remove_out; retval = tegra_nvdisp_create_imp_lock_debugfs(dc); if (!retval) goto remove_out; } } if (tegra_dc_is_nvdisplay()) { retval = debugfs_create_file("measure_latency", 0444, dc->debugdir, dc, &dbg_measure_latency_ops); if (!retval) goto remove_out; } return; remove_out: dev_err(&dc->ndev->dev, "could not create debugfs\n"); tegra_dc_remove_debugfs(dc); } #undef CREATE_NVDISP_WIN_CSC_SYSFS #else /* !CONFIG_DEBUGFS */ static inline void tegra_dc_create_debugfs(struct tegra_dc *dc) { }; static inline void tegra_dc_remove_debugfs(struct tegra_dc *dc) { }; #endif /* CONFIG_DEBUGFS */ s32 tegra_dc_calc_v_front_porch(struct tegra_dc_mode *mode, int desired_fps) { int vfp = 0; if (desired_fps > 0) { int line = mode->h_sync_width + mode->h_back_porch + mode->h_active + mode->h_front_porch; int lines_per_frame = mode->pclk / line / desired_fps; vfp = lines_per_frame - mode->v_sync_width - mode->v_active - mode->v_back_porch; } return vfp; } static void tegra_dc_setup_vrr(struct tegra_dc *dc) { int lines_per_frame_max, lines_per_frame_min; struct tegra_dc_mode *m; struct tegra_vrr *vrr = dc->out->vrr; if (!vrr) return; m = &dc->out->modes[dc->out->n_modes-1]; vrr->v_front_porch = m->v_front_porch; vrr->v_back_porch = m->v_back_porch; vrr->pclk = m->pclk; if (vrr->vrr_min_fps > 0) vrr->v_front_porch_max = tegra_dc_calc_v_front_porch(m, vrr->vrr_min_fps); vrr->vrr_max_fps = (s32)div_s64(NSEC_PER_SEC, dc->frametime_ns); vrr->v_front_porch_min = m->v_front_porch; vrr->line_width = m->h_sync_width + m->h_back_porch + m->h_active + m->h_front_porch; vrr->lines_per_frame_common = m->v_sync_width + m->v_back_porch + m->v_active; lines_per_frame_max = vrr->lines_per_frame_common + vrr->v_front_porch_max; lines_per_frame_min = vrr->lines_per_frame_common + vrr->v_front_porch_min; if (lines_per_frame_max < 2*lines_per_frame_min) { pr_err("max fps is less than 2 times min fps.\n"); return; } vrr->frame_len_max = vrr->line_width * lines_per_frame_max / (m->pclk / 1000000); vrr->frame_len_min = vrr->line_width * lines_per_frame_min / (m->pclk / 1000000); vrr->vfp_extend = vrr->v_front_porch_max; vrr->vfp_shrink = vrr->v_front_porch_min; vrr->frame_type = 0; vrr->frame_delta_us = 0; vrr->max_adj_pct = 50; vrr->max_flip_pct = 20; vrr->max_dcb = 20000; vrr->max_inc_pct = 5; vrr->dcb = 0; vrr->frame_avg_pct = 75; vrr->fluct_avg_pct = 75; vrr->db_tolerance = 5000; } unsigned long tegra_dc_poll_register(struct tegra_dc *dc, u32 reg, u32 mask, u32 exp_val, u32 poll_interval_us, u32 timeout_ms) { unsigned long timeout_jf = jiffies + msecs_to_jiffies(timeout_ms); u32 reg_val = 0; if (tegra_platform_is_vdk()) return 0; do { usleep_range(poll_interval_us, poll_interval_us << 1); /* usleep_range(1000, 1500);*/ reg_val = tegra_dc_readl(dc, reg); } while (((reg_val & mask) != exp_val) && time_after(timeout_jf, jiffies)); if ((reg_val & mask) == exp_val) return 0; /* success */ dev_err(&dc->ndev->dev, "dc_poll_register 0x%x: timeout\n", reg); return jiffies - timeout_jf + 1; } void tegra_dc_enable_general_act(struct tegra_dc *dc) { tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); if (tegra_dc_poll_register(dc, DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ, 0, 1, TEGRA_DC_POLL_TIMEOUT_MS)) dev_err(&dc->ndev->dev, "dc timeout waiting for DC to stop\n"); } int tegra_dc_enable_update_and_act(struct tegra_dc *dc, u32 update_mask, u32 act_req_mask) { tegra_dc_writel(dc, update_mask, DC_CMD_STATE_CONTROL); tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); /* flush */ tegra_dc_writel(dc, act_req_mask, DC_CMD_STATE_CONTROL); tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); /* flush */ return tegra_dc_poll_register(dc, DC_CMD_STATE_CONTROL, act_req_mask, 0, 1, TEGRA_DC_POLL_TIMEOUT_MS); } void tegra_dc_disable_disp_ctrl_mode(struct tegra_dc *dc) { tegra_dc_get(dc); tegra_dc_writel(dc, DISP_CTRL_MODE_STOP, DC_CMD_DISPLAY_COMMAND); tegra_dc_enable_general_act(dc); tegra_dc_put(dc); } void tegra_dc_enable_disp_ctrl_mode(struct tegra_dc *dc) { tegra_dc_get(dc); /* Enable DC */ if (dc->out->flags & TEGRA_DC_OUT_NVSR_MODE) tegra_dc_writel(dc, DISP_CTRL_MODE_NC_DISPLAY, DC_CMD_DISPLAY_COMMAND); else tegra_dc_writel(dc, DISP_CTRL_MODE_C_DISPLAY, DC_CMD_DISPLAY_COMMAND); tegra_dc_enable_general_act(dc); tegra_dc_put(dc); } /* Set dc at the next available index in the tegra_dcs array. */ static int tegra_dc_set(struct tegra_dc *dc) { int ret = -EBUSY; int i; if (!dc) return -EINVAL; mutex_lock(&tegra_dc_lock); for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) { if (tegra_dcs[i] == NULL) { tegra_dcs[i] = dc; ret = i; break; } } mutex_unlock(&tegra_dc_lock); return ret; } /* Clear this dc from its current slot in the tegra_dcs array. */ static void tegra_dc_clear(struct tegra_dc *dc) { int i; if (!dc) return; mutex_lock(&tegra_dc_lock); for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) { if (tegra_dcs[i] == dc) { tegra_dcs[i] = NULL; break; } } mutex_unlock(&tegra_dc_lock); } unsigned int tegra_dc_get_numof_reg_disps(void) { unsigned int idx; unsigned int cnt = 0; struct tegra_dc *dc; mutex_lock(&tegra_dc_lock); for (idx = 0; idx < tegra_dc_get_numof_dispheads(); idx++) cnt += ((dc = tegra_dcs[idx]) != NULL) ? 1 : 0; mutex_unlock(&tegra_dc_lock); return cnt; } unsigned int tegra_dc_has_multiple_dc(void) { unsigned int idx; unsigned int cnt = 0; struct tegra_dc *dc; mutex_lock(&tegra_dc_lock); for (idx = 0; idx < tegra_dc_get_numof_dispheads(); idx++) cnt += ((dc = tegra_dcs[idx]) != NULL && dc->enabled) ? 1 : 0; mutex_unlock(&tegra_dc_lock); return (cnt > 1); } static const char * const extcon_cable_strings[] = { [TEGRA_DC_OUT_HDMI] = "HDMI", [TEGRA_DC_OUT_DSI] = "DSI", [TEGRA_DC_OUT_DP] = "DP" }; static const int hdmi_extcon_cable_id[] = { EXTCON_DISP_HDMI, EXTCON_DISP_HDMI2 }; /* the map of dc->ctrl_num to the index of tegra_hdmi_extcon_cable_id[] */ unsigned long extcon_hdmi_dc_map[ARRAY_SIZE(hdmi_extcon_cable_id)] = { [0 ... (ARRAY_SIZE(hdmi_extcon_cable_id) - 1)] = -1}; void tegra_dc_extcon_hpd_notify(struct tegra_dc *dc) { unsigned int cable = 0; int i; mutex_lock(&tegra_dc_extcon_lock); if (dc && dc->out) { switch (dc->out->type) { case TEGRA_DC_OUT_HDMI: cable = EXTCON_NONE; for (i = 0; i < ARRAY_SIZE(hdmi_extcon_cable_id); i++) { if (extcon_hdmi_dc_map[i] == dc->ctrl_num) { cable = hdmi_extcon_cable_id[i]; break; } } break; case TEGRA_DC_OUT_DP: cable = EXTCON_DISP_DP; break; case TEGRA_DC_OUT_DSI: cable = EXTCON_DISP_DSIHPD; break; default: mutex_unlock(&tegra_dc_extcon_lock); return; } if (dc->connected) { disp_state_extcon_switch_report(cable, EXTCON_DISP_HPD_STATE_ENABLED); pr_info("Extcon %s: HPD enabled\n", extcon_cable_strings[dc->out->type]); } else { disp_state_extcon_switch_report(cable, EXTCON_DISP_HPD_STATE_DISABLED); pr_info("Extcon %s: HPD disabled\n", extcon_cable_strings[dc->out->type]); } } mutex_unlock(&tegra_dc_extcon_lock); } /* get the stride size of a window. * return: stride size in bytes for window win. or 0 if unavailble. */ int tegra_dc_get_stride(struct tegra_dc *dc, unsigned win) { u32 stride; if (!dc->enabled) return 0; WARN_ON(win > tegra_dc_get_numof_dispwindows()); mutex_lock(&dc->lock); tegra_dc_get(dc); if (tegra_dc_is_nvdisplay()) { stride = tegra_nvdisp_get_linestride(dc, win); } else { tegra_dc_writel(dc, WINDOW_A_SELECT << win, DC_CMD_DISPLAY_WINDOW_HEADER); stride = tegra_dc_readl(dc, DC_WIN_LINE_STRIDE); } tegra_dc_put(dc); mutex_unlock(&dc->lock); return GET_LINE_STRIDE(stride); } EXPORT_SYMBOL(tegra_dc_get_stride); struct tegra_dc *tegra_dc_get_dc(unsigned idx) { if (idx < tegra_dc_get_numof_dispheads()) return tegra_dcs[idx]; else return NULL; } EXPORT_SYMBOL(tegra_dc_get_dc); struct tegra_dc_win *tegra_dc_get_window(struct tegra_dc *dc, unsigned win) { if (win >= tegra_dc_get_numof_dispwindows() || !test_bit(win, &dc->valid_windows)) return NULL; if (tegra_dc_is_nvdisplay()) return &tegra_dc_windows[win]; else return &dc->windows[win]; } EXPORT_SYMBOL(tegra_dc_get_window); bool tegra_dc_get_connected(struct tegra_dc *dc) { return dc->connected; } EXPORT_SYMBOL(tegra_dc_get_connected); bool tegra_dc_hpd(struct tegra_dc *dc) { int hpd = false; int hotplug_state; if (WARN_ON(!dc || !dc->out)) return false; rmb(); hotplug_state = dc->out->hotplug_state; if (hotplug_state != TEGRA_HPD_STATE_NORMAL) { if (hotplug_state == TEGRA_HPD_STATE_FORCE_ASSERT) return true; if (hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT) return false; } if (!dc->hotplug_supported) return true; if (dc->out_ops && dc->out_ops->hpd_state) hpd = dc->out_ops->hpd_state(dc); if (dc->out->hotplug_report) dc->out->hotplug_report(hpd); return hpd; } EXPORT_SYMBOL(tegra_dc_hpd); /* Used only on T21x */ static void tegra_dc_set_scaling_filter(struct tegra_dc *dc) { unsigned i; unsigned v0 = 128; unsigned v1 = 0; /* linear horizontal and vertical filters */ for (i = 0; i < 16; i++) { tegra_dc_writel(dc, (v1 << 16) | (v0 << 8), DC_WIN_H_FILTER_P(i)); tegra_dc_writel(dc, v0, DC_WIN_V_FILTER_P(i)); v0 -= 8; v1 += 8; } } static int _tegra_dc_config_frame_end_intr(struct tegra_dc *dc, bool enable) { tegra_dc_get(dc); if (enable) { atomic_inc(&dc->frame_end_ref); tegra_dc_unmask_interrupt(dc, FRAME_END_INT); } else if (!atomic_dec_return(&dc->frame_end_ref)) tegra_dc_mask_interrupt(dc, FRAME_END_INT); tegra_dc_put(dc); return 0; } static struct tegra_dc_cmu *tegra_dc_get_cmu(struct tegra_dc *dc) { if (dc->out->type == TEGRA_DC_OUT_FAKE_DP || dc->out->type == TEGRA_DC_OUT_FAKE_DSIA || dc->out->type == TEGRA_DC_OUT_FAKE_DSIB || dc->out->type == TEGRA_DC_OUT_FAKE_DSI_GANGED || dc->out->type == TEGRA_DC_OUT_NULL) { return &default_cmu; } if (dc->pdata->cmu && !dc->pdata->default_clr_space) return dc->pdata->cmu; else if (dc->pdata->cmu_adbRGB && dc->pdata->default_clr_space) return dc->pdata->cmu_adbRGB; else if (dc->out->type == TEGRA_DC_OUT_HDMI) return &default_limited_cmu; else return &default_cmu; } static struct tegra_dc_nvdisp_cmu *tegra_dc_get_nvdisp_cmu(struct tegra_dc *dc) { if (dc->out->type == TEGRA_DC_OUT_FAKE_DP || dc->out->type == TEGRA_DC_OUT_FAKE_DSIA || dc->out->type == TEGRA_DC_OUT_FAKE_DSIB || dc->out->type == TEGRA_DC_OUT_FAKE_DSI_GANGED || dc->out->type == TEGRA_DC_OUT_NULL) { tegra_nvdisp_get_default_cmu(&default_nvdisp_cmu); return &default_nvdisp_cmu; } if (dc->pdata->cmu && !dc->pdata->default_clr_space) { return dc->pdata->nvdisp_cmu; } else if (dc->out->type == TEGRA_DC_OUT_HDMI) { tegra_nvdisp_get_default_cmu(&default_limited_nvdisp_cmu); return &default_limited_nvdisp_cmu; } tegra_nvdisp_get_default_cmu(&default_nvdisp_cmu); return &default_nvdisp_cmu; } void tegra_dc_cmu_enable(struct tegra_dc *dc, bool cmu_enable) { dc->cmu_enabled = cmu_enable; dc->pdata->cmu_enable = cmu_enable; if (tegra_dc_is_nvdisplay()) { tegra_dc_cache_nvdisp_cmu(dc, tegra_dc_get_nvdisp_cmu(dc)); tegra_nvdisp_update_cmu(dc, &dc->nvdisp_postcomp_lut); } else if (tegra_dc_is_t21x()) { tegra_dc_update_cmu(dc, tegra_dc_get_cmu(dc)); } } EXPORT_SYMBOL(tegra_dc_cmu_enable); static void tegra_dc_cache_cmu(struct tegra_dc *dc, struct tegra_dc_cmu *src_cmu) { if (&dc->cmu != src_cmu) /* ignore if it would require memmove() */ memcpy(&dc->cmu, src_cmu, sizeof(*src_cmu)); dc->cmu_dirty = true; } static void tegra_dc_set_cmu(struct tegra_dc *dc, struct tegra_dc_cmu *cmu) { u32 val; u32 i; for (i = 0; i < 256; i++) { val = LUT1_ADDR(i) | LUT1_DATA(cmu->lut1[i]); tegra_dc_writel(dc, val, DC_COM_CMU_LUT1); } tegra_dc_writel(dc, cmu->csc.krr, DC_COM_CMU_CSC_KRR); tegra_dc_writel(dc, cmu->csc.kgr, DC_COM_CMU_CSC_KGR); tegra_dc_writel(dc, cmu->csc.kbr, DC_COM_CMU_CSC_KBR); tegra_dc_writel(dc, cmu->csc.krg, DC_COM_CMU_CSC_KRG); tegra_dc_writel(dc, cmu->csc.kgg, DC_COM_CMU_CSC_KGG); tegra_dc_writel(dc, cmu->csc.kbg, DC_COM_CMU_CSC_KBG); tegra_dc_writel(dc, cmu->csc.krb, DC_COM_CMU_CSC_KRB); tegra_dc_writel(dc, cmu->csc.kgb, DC_COM_CMU_CSC_KGB); tegra_dc_writel(dc, cmu->csc.kbb, DC_COM_CMU_CSC_KBB); for (i = 0; i < 960; i++) { val = LUT2_ADDR(i) | LUT1_DATA(cmu->lut2[i]); tegra_dc_writel(dc, val, DC_COM_CMU_LUT2); } dc->cmu_dirty = false; } static void _tegra_dc_update_cmu(struct tegra_dc *dc, struct tegra_dc_cmu *cmu) { u32 val; if (!dc->cmu_enabled) return; tegra_dc_cache_cmu(dc, cmu); if (dc->cmu_dirty) { /* Disable CMU to avoid programming it while it is in use */ val = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL); if (val & CMU_ENABLE) { val &= ~CMU_ENABLE; tegra_dc_writel(dc, val, DC_DISP_DISP_COLOR_CONTROL); val = GENERAL_ACT_REQ; tegra_dc_writel(dc, val, DC_CMD_STATE_CONTROL); /*TODO: Sync up with vsync */ mdelay(20); } dev_dbg(&dc->ndev->dev, "updating CMU cmu_dirty=%d\n", dc->cmu_dirty); tegra_dc_set_cmu(dc, &dc->cmu); } } void _tegra_dc_cmu_enable(struct tegra_dc *dc, bool cmu_enable) { dc->cmu_enabled = cmu_enable; _tegra_dc_update_cmu(dc, tegra_dc_get_cmu(dc)); tegra_dc_set_color_control(dc); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); } EXPORT_SYMBOL(_tegra_dc_cmu_enable); int tegra_dc_update_cmu(struct tegra_dc *dc, struct tegra_dc_cmu *cmu) { mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return 0; } tegra_dc_get(dc); _tegra_dc_update_cmu(dc, cmu); tegra_dc_set_color_control(dc); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_put(dc); mutex_unlock(&dc->lock); return 0; } EXPORT_SYMBOL(tegra_dc_update_cmu); static int _tegra_dc_update_cmu_aligned(struct tegra_dc *dc, struct tegra_dc_cmu *cmu, bool force) { memcpy(&dc->cmu_shadow, cmu, sizeof(dc->cmu)); dc->cmu_shadow_dirty = true; dc->cmu_shadow_force_update = dc->cmu_shadow_force_update || force; _tegra_dc_config_frame_end_intr(dc, true); return 0; } int tegra_dc_update_cmu_aligned(struct tegra_dc *dc, struct tegra_dc_cmu *cmu) { int ret; mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return 0; } ret = _tegra_dc_update_cmu_aligned(dc, cmu, false); mutex_unlock(&dc->lock); return ret; } EXPORT_SYMBOL(tegra_dc_update_cmu_aligned); int tegra_dc_set_hdr(struct tegra_dc *dc, struct tegra_dc_hdr *hdr, bool cache_dirty) { int ret; mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return 0; } trace_hdr_data_update(dc, hdr); if (cache_dirty) { dc->hdr.eotf = hdr->eotf; dc->hdr.static_metadata_id = hdr->static_metadata_id; memcpy(dc->hdr.static_metadata, hdr->static_metadata, sizeof(dc->hdr.static_metadata)); } else if (dc->hdr.enabled == hdr->enabled) { mutex_unlock(&dc->lock); return 0; } dc->hdr.enabled = hdr->enabled; dc->hdr_cache_dirty = true; if (!dc->hdr.enabled) memset(&dc->hdr, 0, sizeof(dc->hdr)); ret = _tegra_dc_config_frame_end_intr(dc, true); mutex_unlock(&dc->lock); return ret; } EXPORT_SYMBOL(tegra_dc_set_hdr); /* disable_irq() blocks until handler completes, calling this function while * holding dc->lock can deadlock. */ static inline void disable_dc_irq(const struct tegra_dc *dc) { disable_irq(dc->irq); } u32 tegra_dc_get_syncpt_id(struct tegra_dc *dc, int i) { struct tegra_dc_win *win = tegra_dc_get_window(dc, i); BUG_ON(!win); return win->syncpt.id; } EXPORT_SYMBOL(tegra_dc_get_syncpt_id); static u32 tegra_dc_incr_syncpt_max_locked(struct tegra_dc *dc, int i) { u32 max; struct tegra_dc_win *win = tegra_dc_get_window(dc, i); BUG_ON(!win); max = nvhost_syncpt_incr_max_ext(dc->ndev, win->syncpt.id, ((dc->enabled) ? 1 : 0)); win->syncpt.max = max; return max; } u32 tegra_dc_incr_syncpt_max(struct tegra_dc *dc, int i) { u32 max; mutex_lock(&dc->lock); tegra_dc_get(dc); max = tegra_dc_incr_syncpt_max_locked(dc, i); tegra_dc_put(dc); mutex_unlock(&dc->lock); return max; } void tegra_dc_incr_syncpt_min(struct tegra_dc *dc, int i, u32 val) { struct tegra_dc_win *win = tegra_dc_get_window(dc, i); BUG_ON(!win); mutex_lock(&dc->lock); tegra_dc_get(dc); while (win->syncpt.min < val) { win->syncpt.min++; nvhost_syncpt_cpu_incr_ext(dc->ndev, win->syncpt.id); } tegra_dc_put(dc); mutex_unlock(&dc->lock); } struct sync_fence *tegra_dc_create_fence(struct tegra_dc *dc, int i, u32 val) { struct nvhost_ctrl_sync_fence_info syncpt; u32 id = tegra_dc_get_syncpt_id(dc, i); syncpt.id = id; syncpt.thresh = val; return nvhost_sync_create_fence( to_platform_device(dc->ndev->dev.parent), &syncpt, 1, dev_name(&dc->ndev->dev)); } void tegra_dc_config_pwm(struct tegra_dc *dc, struct tegra_dc_pwm_params *cfg) { unsigned int ctrl; unsigned long out_sel; unsigned long cmd_state; mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return; } tegra_dc_get(dc); ctrl = ((cfg->period << PM_PERIOD_SHIFT) | (cfg->clk_div << PM_CLK_DIVIDER_SHIFT) | cfg->clk_select); /* The new value should be effected immediately */ cmd_state = tegra_dc_readl(dc, DC_CMD_STATE_ACCESS); tegra_dc_writel(dc, (cmd_state | (1 << 2)), DC_CMD_STATE_ACCESS); switch (cfg->which_pwm) { case TEGRA_PWM_PM0: /* Select the LM0 on PM0 */ out_sel = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT5); out_sel &= ~(7 << 0); out_sel |= (3 << 0); tegra_dc_writel(dc, out_sel, DC_COM_PIN_OUTPUT_SELECT5); tegra_dc_writel(dc, ctrl, DC_COM_PM0_CONTROL); tegra_dc_writel(dc, cfg->duty_cycle, DC_COM_PM0_DUTY_CYCLE); break; case TEGRA_PWM_PM1: /* Select the LM1 on PM1 */ out_sel = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_SELECT5); out_sel &= ~(7 << 4); out_sel |= (3 << 4); tegra_dc_writel(dc, out_sel, DC_COM_PIN_OUTPUT_SELECT5); tegra_dc_writel(dc, ctrl, DC_COM_PM1_CONTROL); tegra_dc_writel(dc, cfg->duty_cycle, DC_COM_PM1_DUTY_CYCLE); break; default: dev_err(&dc->ndev->dev, "Error: Need which_pwm\n"); break; } tegra_dc_writel(dc, cmd_state, DC_CMD_STATE_ACCESS); tegra_dc_put(dc); mutex_unlock(&dc->lock); } EXPORT_SYMBOL(tegra_dc_config_pwm); void tegra_dc_set_out_pin_polars(struct tegra_dc *dc, const struct tegra_dc_out_pin *pins, const unsigned int n_pins) { unsigned int i; int name; int pol; u32 pol1, pol3; u32 set1, unset1; u32 set3, unset3; set1 = set3 = unset1 = unset3 = 0; for (i = 0; i < n_pins; i++) { name = (pins + i)->name; pol = (pins + i)->pol; /* set polarity by name */ switch (name) { case TEGRA_DC_OUT_PIN_DATA_ENABLE: if (pol == TEGRA_DC_OUT_PIN_POL_LOW) set3 |= LSPI_OUTPUT_POLARITY_LOW; else unset3 |= LSPI_OUTPUT_POLARITY_LOW; break; case TEGRA_DC_OUT_PIN_H_SYNC: if (pol == TEGRA_DC_OUT_PIN_POL_LOW) set1 |= LHS_OUTPUT_POLARITY_LOW; else unset1 |= LHS_OUTPUT_POLARITY_LOW; break; case TEGRA_DC_OUT_PIN_V_SYNC: if (pol == TEGRA_DC_OUT_PIN_POL_LOW) set1 |= LVS_OUTPUT_POLARITY_LOW; else unset1 |= LVS_OUTPUT_POLARITY_LOW; break; case TEGRA_DC_OUT_PIN_PIXEL_CLOCK: if (pol == TEGRA_DC_OUT_PIN_POL_LOW) set1 |= LSC0_OUTPUT_POLARITY_LOW; else unset1 |= LSC0_OUTPUT_POLARITY_LOW; break; default: printk("Invalid argument in function %s\n", __FUNCTION__); break; } } pol1 = DC_COM_PIN_OUTPUT_POLARITY1_INIT_VAL; pol3 = DC_COM_PIN_OUTPUT_POLARITY3_INIT_VAL; pol1 |= set1; pol1 &= ~unset1; pol3 |= set3; pol3 &= ~unset3; tegra_dc_writel(dc, pol1, DC_COM_PIN_OUTPUT_POLARITY1); tegra_dc_writel(dc, pol3, DC_COM_PIN_OUTPUT_POLARITY3); } static struct tegra_dc_mode *tegra_dc_get_override_mode(struct tegra_dc *dc) { unsigned long refresh; if (((dc->out->type == TEGRA_DC_OUT_HDMI) || (dc->out->type == TEGRA_DC_OUT_DP)) && tegra_is_bl_display_initialized(dc->ctrl_num)) { /* For seamless HDMI, read mode parameters from bootloader * set DC configuration */ u32 val = 0; struct tegra_dc_mode *mode = &override_disp_mode[dc->out->type]; struct clk *parent_clk = NULL; if (tegra_dc_is_nvdisplay()) parent_clk = tegra_disp_clk_get(&dc->ndev->dev, dc->out->parent_clk ? : "plld2"); else parent_clk = clk_get_sys(NULL, dc->out->parent_clk ? : "pll_d2"); memset(mode, 0, sizeof(struct tegra_dc_mode)); mode->pclk = clk_get_rate(parent_clk); mode->rated_pclk = 0; tegra_dc_get(dc); /* {V,H}_REF_TO_SYNC do NOT exist on nvdisplay. */ if (!tegra_dc_is_nvdisplay()) { val = tegra_dc_readl(dc, DC_DISP_REF_TO_SYNC); mode->h_ref_to_sync = val & 0xffff; mode->v_ref_to_sync = (val >> 16) & 0xffff; } val = tegra_dc_readl(dc, DC_DISP_SYNC_WIDTH); mode->h_sync_width = val & 0xffff; mode->v_sync_width = (val >> 16) & 0xffff; val = tegra_dc_readl(dc, DC_DISP_BACK_PORCH); mode->h_back_porch = val & 0xffff; mode->v_back_porch = (val >> 16) & 0xffff; val = tegra_dc_readl(dc, DC_DISP_FRONT_PORCH); mode->h_front_porch = val & 0xffff; mode->v_front_porch = (val >> 16) & 0xffff; val = tegra_dc_readl(dc, DC_DISP_DISP_ACTIVE); mode->h_active = val & 0xffff; mode->v_active = (val >> 16) & 0xffff; /* Check the freq setup by the BL, 59.94 or 60Hz * If 59.94, vmode needs to be FB_VMODE_1000DIV1001 * for seamless */ refresh = tegra_dc_calc_refresh(mode); if (refresh % 1000) mode->vmode |= FB_VMODE_1000DIV1001; /* * Implicit contract between BL and us. If CMU is enabled, * assume limited range. This sort of works because we know * BL doesn't support YUV */ val = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL); if (val & CMU_ENABLE) mode->vmode |= FB_VMODE_LIMITED_RANGE; tegra_dc_put(dc); } if (dc->out->type == TEGRA_DC_OUT_RGB || dc->out->type == TEGRA_DC_OUT_HDMI || dc->out->type == TEGRA_DC_OUT_DP || dc->out->type == TEGRA_DC_OUT_DSI || dc->out->type == TEGRA_DC_OUT_NULL) return override_disp_mode[dc->out->type].pclk ? &override_disp_mode[dc->out->type] : NULL; else return NULL; } static int tegra_dc_set_out(struct tegra_dc *dc, struct tegra_dc_out *out, bool initialized) { struct tegra_dc_mode *mode = NULL; int err = 0; int i, free_slot = -1; dc->out = out; dc->hotplug_supported = tegra_dc_hotplug_supported(dc); if (dc->out->type == TEGRA_DC_OUT_HDMI) { for (i = 0; i < ARRAY_SIZE(hdmi_extcon_cable_id); i++) { /* bail out if the map has already been done */ if (extcon_hdmi_dc_map[i] == dc->ctrl_num) { free_slot = i; break; } else if (extcon_hdmi_dc_map[i] == -1) { free_slot = i; } } if (unlikely(free_slot == -1)) { dev_err(&dc->ndev->dev, "No free extcon HDMI slot for DC %d\n", dc->ctrl_num); } else { extcon_hdmi_dc_map[free_slot] = dc->ctrl_num; } } if (initialized) { dc->initialized = false; goto bypass_init_check; } if (((dc->out->type == TEGRA_DC_OUT_HDMI) || (dc->out->type == TEGRA_DC_OUT_DP)) && tegra_is_bl_display_initialized(dc->ctrl_num)) { /* * Bootloader enables clk and host1x in seamless * usecase. Below extra reference accounts for it */ tegra_dc_get(dc); } /* * This config enables seamless feature only for * android usecase as a WAR for improper DSI initialization * in bootloader for L4T usecase. * Bug 200122858 */ #ifdef CONFIG_ANDROID /* * Seamless supporting panels can work in seamless mode * only if BL initializes DC/DSI. If not, panel should * go with complete initialization. */ if (dc->out->type == TEGRA_DC_OUT_DSI && !tegra_is_bl_display_initialized(dc->ctrl_num)) { dc->initialized = false; } else if (dc->out->type == TEGRA_DC_OUT_DSI && tegra_is_bl_display_initialized(dc->ctrl_num)) { /* * In case of dsi->csi loopback support, force re-initialize * all DSI controllers. So, set dc->initialized to false. */ if (dc->out->dsi->dsi_csi_loopback) dc->initialized = false; else dc->initialized = true; } #endif mode = tegra_dc_get_override_mode(dc); bypass_init_check: if (mode && tegra_is_bl_display_initialized(dc->ctrl_num)) { tegra_dc_set_mode(dc, mode); /* * Bootloader should and should only pass disp_params if * it has initialized display controller. Whenever we see * override modes, we should skip things cause display resets. */ dev_info(&dc->ndev->dev, "Bootloader disp_param detected. " "Detected mode: %dx%d (on %dx%dmm) pclk=%d\n", dc->mode.h_active, dc->mode.v_active, dc->out->h_size, dc->out->v_size, dc->mode.pclk); dc->initialized = true; } else if (out->n_modes > 0) { /* For VRR panels, default mode is first in the list, * and native panel mode is the last. * Initialization must occur using the native panel mode. */ if (dc->out->vrr) { tegra_dc_set_mode(dc, &dc->out->modes[dc->out->n_modes-1]); tegra_dc_setup_vrr(dc); } else tegra_dc_set_mode(dc, &dc->out->modes[0]); } switch (out->type) { case TEGRA_DC_OUT_HDMI: #if defined(CONFIG_TEGRA_HDMI2_0) dc->out_ops = &tegra_dc_hdmi2_0_ops; #endif break; case TEGRA_DC_OUT_DSI: case TEGRA_DC_OUT_FAKE_DSIA: case TEGRA_DC_OUT_FAKE_DSIB: case TEGRA_DC_OUT_FAKE_DSI_GANGED: dc->out_ops = &tegra_dc_dsi_ops; break; #ifdef CONFIG_TEGRA_DP case TEGRA_DC_OUT_FAKE_DP: case TEGRA_DC_OUT_DP: dc->out_ops = &tegra_dc_dp_ops; break; #endif #ifdef CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT case TEGRA_DC_OUT_NULL: break; #endif /*CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT*/ default: dc->out_ops = NULL; break; } if (tegra_dc_is_t21x()) tegra_dc_cache_cmu(dc, tegra_dc_get_cmu(dc)); if (dc->out_ops && dc->out_ops->init) { err = dc->out_ops->init(dc); if (err < 0) { dc->out = NULL; dc->out_ops = NULL; dev_err(&dc->ndev->dev, "Error: out->type:%d out_ops->init() failed. err=%d\n", out->type, err); return err; } } return err; } void tegra_dc_out_destroy(struct tegra_dc *dc) { if (dc->out->hdmi_out) { if (dc->out->hdmi_out->spd_infoframe) devm_kfree(&dc->ndev->dev, dc->out->hdmi_out->spd_infoframe); dc->out->hdmi_out->spd_infoframe = NULL; devm_kfree(&dc->ndev->dev, dc->out->hdmi_out); } dc->out->hdmi_out = NULL; if (dc->out->dp_out) devm_kfree(&dc->ndev->dev, dc->out->dp_out); dc->out->dp_out = NULL; if (dc->out->modes) devm_kfree(&dc->ndev->dev, dc->out->modes); dc->out->modes = NULL; dc->out->n_modes = 0; if (dc->out->vrr) devm_kfree(&dc->ndev->dev, dc->out->vrr); dc->out->vrr = NULL; if (dc->out->out_pins) devm_kfree(&dc->ndev->dev, dc->out->out_pins); dc->out->out_pins = NULL; tegra_panel_unregister_ops(dc->out); } int tegra_dc_get_head(const struct tegra_dc *dc) { if (dc) return dc->ctrl_num; return -EINVAL; } /* returns on error: -EINVAL * on success: TEGRA_DC_OUT_RGB, TEGRA_DC_OUT_HDMI, ... */ int tegra_dc_get_out(const struct tegra_dc *dc) { if (dc && dc->out) return dc->out->type; return -EINVAL; } int tegra_dc_get_source_physical_address(u8 *phy_address) { int i; struct tegra_dc *dc; if (!phy_address) return -EFAULT; for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) { dc = tegra_dc_get_dc(i); if (dc && dc->enabled && dc->edid && dc->out && (dc->out->type == TEGRA_DC_OUT_HDMI)) return tegra_edid_get_source_physical_address(dc->edid, phy_address); } return -ENODEV; } bool tegra_dc_is_ext_panel(const struct tegra_dc *dc) { if (dc && dc->out) return dc->out->is_ext_panel; return false; } unsigned tegra_dc_get_out_height(const struct tegra_dc *dc) { unsigned height = 0; if (dc->out) { if (dc->out->height) height = dc->out->height; else if (dc->out->h_size && dc->out->v_size) height = dc->out->v_size; } return height; } EXPORT_SYMBOL(tegra_dc_get_out_height); unsigned tegra_dc_get_out_width(const struct tegra_dc *dc) { unsigned width = 0; if (dc->out) { if (dc->out->width) width = dc->out->width; else if (dc->out->h_size && dc->out->v_size) width = dc->out->h_size; } return width; } EXPORT_SYMBOL(tegra_dc_get_out_width); unsigned tegra_dc_get_out_max_pixclock(const struct tegra_dc *dc) { if (dc && dc->out) return dc->out->max_pixclock; else return 0; } EXPORT_SYMBOL(tegra_dc_get_out_max_pixclock); /* * Check if mode's pixel clock requirement can be satisfied. Note that * the pixclock value is in pico seconds. */ bool tegra_dc_valid_pixclock(const struct tegra_dc *dc, const struct fb_videomode *mode) { unsigned max_pixclock = tegra_dc_get_out_max_pixclock(dc); if (max_pixclock) return mode->pixclock >= max_pixclock; else return true; } EXPORT_SYMBOL(tegra_dc_valid_pixclock); void tegra_dc_sysfs_enable_crc(struct tegra_dc *dc) { u32 val; if (dc->crc_ref_cnt.legacy) { pr_err("CRC is already enabled.\n"); return; } dc->crc_ref_cnt.legacy = true; mutex_lock(&dc->lock); tegra_dc_get(dc); val = CRC_ALWAYS_ENABLE | CRC_INPUT_DATA_ACTIVE_DATA | CRC_ENABLE_ENABLE; tegra_dc_writel(dc, val, DC_COM_CRC_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_put(dc); mutex_unlock(&dc->lock); /* Register a client of frame_end interrupt */ tegra_dc_config_frame_end_intr(dc, true); } void tegra_dc_sysfs_disable_crc(struct tegra_dc *dc) { if (!dc->crc_ref_cnt.legacy) { pr_err("CRC is already disabled.\n"); return; } dc->crc_ref_cnt.legacy = false; /* Unregister a client of frame_end interrupt */ tegra_dc_config_frame_end_intr(dc, false); mutex_lock(&dc->lock); tegra_dc_get(dc); tegra_dc_writel(dc, 0x0, DC_COM_CRC_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_put(dc); mutex_unlock(&dc->lock); } u32 tegra_dc_sysfs_read_checksum_latched(struct tegra_dc *dc) { int crc = 0; if (!dc) { pr_err("Failed to get dc: NULL parameter.\n"); goto crc_error; } if (!dc->crc_ref_cnt.legacy) { pr_err("CRC is not enabled.\n"); goto crc_error; } /* If gated quitely return */ if (!tegra_dc_is_powered(dc)) return 0; reinit_completion(&dc->crc_complete); if (dc->crc_pending && wait_for_completion_interruptible(&dc->crc_complete)) { pr_err("CRC read interrupted.\n"); goto crc_error; } mutex_lock(&dc->lock); tegra_dc_get(dc); crc = tegra_dc_readl(dc, DC_COM_CRC_CHECKSUM_LATCHED); tegra_dc_put(dc); mutex_unlock(&dc->lock); crc_error: return crc; } EXPORT_SYMBOL(tegra_dc_sysfs_read_checksum_latched); bool tegra_dc_windows_are_dirty(struct tegra_dc *dc, u32 win_act_req_mask) { u32 val; if (tegra_platform_is_vdk()) return false; val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL); if (val & (win_act_req_mask)) return true; return false; } static inline void __maybe_unused enable_dc_irq(const struct tegra_dc *dc) { if (tegra_platform_is_fpga()) /* Always disable DC interrupts on FPGA. */ disable_irq(dc->irq); else enable_irq(dc->irq); } /* assumes dc->lock is already taken. */ static void _tegra_dc_vsync_enable(struct tegra_dc *dc) { int vsync_irq; if (test_bit(V_BLANK_USER, &dc->vblank_ref_count)) return; /* already set, nothing needs to be done */ if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) vsync_irq = MSF_INT; else vsync_irq = V_BLANK_INT; tegra_dc_hold_dc_out(dc); set_bit(V_BLANK_USER, &dc->vblank_ref_count); tegra_dc_unmask_interrupt(dc, vsync_irq); } int tegra_dc_vsync_enable(struct tegra_dc *dc) { mutex_lock(&dc->lock); if (dc->enabled) { _tegra_dc_vsync_enable(dc); mutex_unlock(&dc->lock); return 0; } mutex_unlock(&dc->lock); return 1; } /* assumes dc->lock is already taken. */ static void _tegra_dc_vsync_disable(struct tegra_dc *dc) { int vsync_irq; if (!test_bit(V_BLANK_USER, &dc->vblank_ref_count)) return; /* already clear, nothing needs to be done */ if (dc->out->type == TEGRA_DC_OUT_DSI) vsync_irq = MSF_INT; else vsync_irq = V_BLANK_INT; clear_bit(V_BLANK_USER, &dc->vblank_ref_count); if (!dc->vblank_ref_count) tegra_dc_mask_interrupt(dc, vsync_irq); tegra_dc_release_dc_out(dc); } void tegra_dc_vsync_disable(struct tegra_dc *dc) { mutex_lock(&dc->lock); _tegra_dc_vsync_disable(dc); mutex_unlock(&dc->lock); } bool tegra_dc_has_vsync(struct tegra_dc *dc) { return true; } /* assumes dc->lock is already taken. */ static void _tegra_dc_user_vsync_enable(struct tegra_dc *dc, bool enable) { if (enable) { dc->out->user_needs_vblank++; init_completion(&dc->out->user_vblank_comp); _tegra_dc_vsync_enable(dc); } else { _tegra_dc_vsync_disable(dc); if (dc->out->user_needs_vblank > 0) dc->out->user_needs_vblank--; } } int tegra_dc_wait_for_vsync(struct tegra_dc *dc) { unsigned long timeout_ms; unsigned long refresh; /* in 1000th Hz */ int ret; mutex_lock(&dc->lp_lock); mutex_lock(&dc->lock); if (!dc->enabled) { ret = -ENOTTY; goto out; } refresh = tegra_dc_calc_refresh(&dc->mode); if (refresh == 0) { dev_err(&dc->ndev->dev, "dc:refresh is %lu\n", refresh); ret = -ERANGE; goto out; } /* time out if waiting took more than 2 frames */ timeout_ms = DIV_ROUND_UP(2 * 1000000, refresh); _tegra_dc_user_vsync_enable(dc, true); mutex_unlock(&dc->lock); ret = wait_for_completion_interruptible_timeout( &dc->out->user_vblank_comp, msecs_to_jiffies(timeout_ms)); mutex_lock(&dc->lock); _tegra_dc_user_vsync_enable(dc, false); out: mutex_unlock(&dc->lock); mutex_unlock(&dc->lp_lock); return ret; } int _tegra_dc_wait_for_frame_end(struct tegra_dc *dc, u32 timeout_ms) { int ret; reinit_completion(&dc->frame_end_complete); tegra_dc_get(dc); tegra_dc_flush_interrupt(dc, FRAME_END_INT); /* unmask frame end interrupt */ _tegra_dc_config_frame_end_intr(dc, true); ret = wait_for_completion_interruptible_timeout( &dc->frame_end_complete, msecs_to_jiffies(timeout_ms)); _tegra_dc_config_frame_end_intr(dc, false); tegra_dc_put(dc); return ret; } void tegra_dc_set_act_vfp(struct tegra_dc *dc, int vfp) { WARN_ON(!mutex_is_locked(&dc->lock)); WARN_ON(!tegra_dc_is_nvdisplay() && vfp < dc->mode.v_ref_to_sync + 1); /* It's very unlikely that active vfp will need to * be changed outside of vrr context */ WARN_ON(!dc->out->vrr || !dc->out->vrr->capability); tegra_dc_writel(dc, WRITE_MUX_ACTIVE | READ_MUX_ACTIVE, DC_CMD_STATE_ACCESS); tegra_dc_writel(dc, dc->mode.h_front_porch | (vfp << 16), DC_DISP_FRONT_PORCH); tegra_dc_writel(dc, WRITE_MUX_ASSEMBLY | READ_MUX_ASSEMBLY, DC_CMD_STATE_ACCESS); } static void tegra_dc_vrr_extend_vfp(struct tegra_dc *dc) { struct tegra_vrr *vrr = dc->out->vrr; if (!vrr || !vrr->capability) return; if (!vrr->enable) return; tegra_dc_set_act_vfp(dc, MAX_VRR_V_FRONT_PORCH); } int tegra_dc_get_v_count(struct tegra_dc *dc) { u32 value; value = tegra_dc_readl(dc, DC_DISP_DISPLAY_DBG_TIMING); return (value & DBG_V_COUNT_MASK) >> DBG_V_COUNT_SHIFT; } static void tegra_dc_vrr_get_ts(struct tegra_dc *dc) { struct timespec time_now; struct tegra_vrr *vrr = dc->out->vrr; if (!vrr || !vrr->capability || (!vrr->enable && !vrr->lastenable)) return; getnstimeofday(&time_now); vrr->fe_time_us = (s64)time_now.tv_sec * 1000000 + time_now.tv_nsec / 1000; vrr->v_count = tegra_dc_get_v_count(dc); } static void tegra_dc_vrr_sec(struct tegra_dc *dc) { struct tegra_vrr *vrr = dc->out->vrr; if (!vrr || !vrr->capability) return; if (!vrr->enable && !vrr->fe_intr_req) return; if (tegra_dc_is_nvdisplay()) cancel_delayed_work_sync(&dc->vrr_work); /* Decrement frame end interrupt refcount previously requested by secure library */ if (vrr->fe_intr_req) { _tegra_dc_config_frame_end_intr(dc, false); vrr->fe_intr_req = 0; } #if defined(CONFIG_TEGRA_HDMIVRR) && (defined(CONFIG_TRUSTED_LITTLE_KERNEL) || defined(CONFIG_OTE_TRUSTY)) if (te_is_secos_dev_enabled()) tegra_hdmivrr_te_vrr_sec(vrr); #endif /* Increment frame end interrupt refcount requested by secure library */ if (vrr->fe_intr_req) _tegra_dc_config_frame_end_intr(dc, true); if (tegra_dc_is_nvdisplay()) { if (vrr->insert_frame) schedule_delayed_work(&dc->vrr_work, msecs_to_jiffies(vrr->insert_frame/1000)); } } static void tegra_dc_vblank(struct work_struct *work) { struct tegra_dc *dc = container_of(work, struct tegra_dc, vblank_work); mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return; } tegra_dc_get(dc); /* Clear the V_BLANK_FLIP bit of vblank ref-count if update is clean. */ if (!tegra_dc_windows_are_dirty(dc, WIN_ALL_ACT_REQ)) clear_bit(V_BLANK_FLIP, &dc->vblank_ref_count); tegra_dc_put(dc); mutex_unlock(&dc->lock); } #define CSC_UPDATE_IF_CHANGED(entry, ENTRY) do { \ if (cmu_active->csc.entry != cmu_shadow->csc.entry || \ dc->cmu_shadow_force_update) { \ cmu_active->csc.entry = cmu_shadow->csc.entry; \ tegra_dc_writel(dc, \ cmu_active->csc.entry, \ DC_COM_CMU_CSC_##ENTRY); \ } \ } while (0) static void _tegra_dc_handle_hdr(struct tegra_dc *dc) { mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return; } tegra_dc_get(dc); if (dc->out_ops->set_hdr) dc->out_ops->set_hdr(dc); tegra_dc_put(dc); mutex_unlock(&dc->lock); return; } static void tegra_dc_frame_end(struct work_struct *work) { struct tegra_dc *dc = container_of(work, struct tegra_dc, frame_end_work); u32 val; u32 i; if (tegra_dc_is_t21x()) { mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return; } tegra_dc_get(dc); if (dc->cmu_shadow_dirty) { struct tegra_dc_cmu *cmu_active = &dc->cmu; struct tegra_dc_cmu *cmu_shadow = &dc->cmu_shadow; for (i = 0; i < 256; i++) { if (cmu_active->lut1[i] != cmu_shadow->lut1[i] || dc->cmu_shadow_force_update) { cmu_active->lut1[i] = cmu_shadow->lut1[i]; val = LUT1_ADDR(i) | LUT1_DATA(cmu_shadow->lut1[i]); tegra_dc_writel(dc, val, DC_COM_CMU_LUT1); } } CSC_UPDATE_IF_CHANGED(krr, KRR); CSC_UPDATE_IF_CHANGED(kgr, KGR); CSC_UPDATE_IF_CHANGED(kbr, KBR); CSC_UPDATE_IF_CHANGED(krg, KRG); CSC_UPDATE_IF_CHANGED(kgg, KGG); CSC_UPDATE_IF_CHANGED(kbg, KBG); CSC_UPDATE_IF_CHANGED(krb, KRB); CSC_UPDATE_IF_CHANGED(kgb, KGB); CSC_UPDATE_IF_CHANGED(kbb, KBB); for (i = 0; i < 960; i++) if (cmu_active->lut2[i] != cmu_shadow->lut2[i] || dc->cmu_shadow_force_update) { cmu_active->lut2[i] = cmu_shadow->lut2[i]; val = LUT2_ADDR(i) | LUT2_DATA(cmu_active->lut2[i]); tegra_dc_writel(dc, val, DC_COM_CMU_LUT2); } dc->cmu_shadow_dirty = false; dc->cmu_shadow_force_update = false; _tegra_dc_config_frame_end_intr(dc, false); } tegra_dc_put(dc); mutex_unlock(&dc->lock); } if (dc->hdr_cache_dirty) { _tegra_dc_handle_hdr(dc); _tegra_dc_config_frame_end_intr(dc, false); dc->hdr_cache_dirty = false; } return; } static void tegra_dc_one_shot_worker(struct work_struct *work) { struct tegra_dc *dc = container_of( to_delayed_work(work), struct tegra_dc, one_shot_work); mutex_lock(&dc->lock); /* memory client has gone idle */ tegra_dc_clear_bandwidth(dc); if (dc->out_ops && dc->out_ops->idle) { tegra_dc_io_start(dc); dc->out_ops->idle(dc); tegra_dc_io_end(dc); } mutex_unlock(&dc->lock); } /* return an arbitrarily large number if count overflow occurs. * make it a nice base-10 number to show up in stats output */ static u64 tegra_dc_underflow_count(struct tegra_dc *dc, unsigned reg) { unsigned count = tegra_dc_readl(dc, reg); tegra_dc_writel(dc, 0, reg); return ((count & 0x80000000) == 0) ? count : 10000000000ll; } static void tegra_dc_underflow_handler(struct tegra_dc *dc) { if (tegra_dc_is_t21x()) { const u32 masks[] = { WIN_A_UF_INT, WIN_B_UF_INT, WIN_C_UF_INT, WIN_D_UF_INT, HC_UF_INT, WIN_T_UF_INT, }; int i; dc->stats.underflows++; if (dc->underflow_mask & WIN_A_UF_INT) dc->stats.underflows_a += tegra_dc_underflow_count(dc, DC_WINBUF_AD_UFLOW_STATUS); if (dc->underflow_mask & WIN_B_UF_INT) dc->stats.underflows_b += tegra_dc_underflow_count(dc, DC_WINBUF_BD_UFLOW_STATUS); if (dc->underflow_mask & WIN_C_UF_INT) dc->stats.underflows_c += tegra_dc_underflow_count(dc, DC_WINBUF_CD_UFLOW_STATUS); if (dc->underflow_mask & HC_UF_INT) dc->stats.underflows_h += tegra_dc_underflow_count(dc, DC_WINBUF_HD_UFLOW_STATUS); if (dc->underflow_mask & WIN_D_UF_INT) dc->stats.underflows_d += tegra_dc_underflow_count(dc, DC_WINBUF_DD_UFLOW_STATUS); if (dc->underflow_mask & WIN_T_UF_INT) dc->stats.underflows_t += tegra_dc_underflow_count(dc, DC_WINBUF_TD_UFLOW_STATUS); /* Check for any underflow reset conditions */ for_each_set_bit(i, &dc->valid_windows, DC_N_WINDOWS) { struct tegra_dc_win *win = tegra_dc_get_window(dc, i); if (WARN_ONCE(i >= ARRAY_SIZE(masks), "underflow stats unsupported")) /* bail if the table above is missing entries */ break; if (!masks[i]) continue; /* skip empty entries */ if (dc->underflow_mask & masks[i]) win->underflows++; else win->underflows = 0; } } else { tegra_nvdisp_underflow_handler(dc); } /* Clear the underflow mask now that we've checked it. */ tegra_dc_writel(dc, dc->underflow_mask, DC_CMD_INT_STATUS); dc->underflow_mask = 0; tegra_dc_unmask_interrupt(dc, ALL_UF_INT()); trace_underflow(dc); } static void tegra_dc_vpulse2(struct work_struct *work) { struct tegra_dc *dc = container_of(work, struct tegra_dc, vpulse2_work); mutex_lock(&dc->lock); if (!dc->enabled) { mutex_unlock(&dc->lock); return; } tegra_dc_get(dc); /* Clear the V_PULSE2_FLIP if no update */ if (!tegra_dc_windows_are_dirty(dc, WIN_ALL_ACT_REQ)) clear_bit(V_PULSE2_FLIP, &dc->vpulse2_ref_count); tegra_dc_put(dc); mutex_unlock(&dc->lock); } static void tegra_dc_process_vblank(struct tegra_dc *dc) { /* pending user vblank, so wakeup */ if (dc->out->user_needs_vblank) { dc->out->user_needs_vblank = false; complete(&dc->out->user_vblank_comp); } if (test_bit(V_BLANK_USER, &dc->vblank_ref_count)) { u64 timestamp = tegra_dc_get_scanline_timestamp(dc, dc->mode_metadata.vblank_lines); if (unlikely(!timestamp)) dev_err(&dc->ndev->dev, "Invalid Timestamp Value\n"); tegra_dc_ext_process_vblank(dc->ndev->id, timestamp); } } int tegra_dc_config_frame_end_intr(struct tegra_dc *dc, bool enable) { int ret; mutex_lock(&dc->lock); ret = _tegra_dc_config_frame_end_intr(dc, enable); mutex_unlock(&dc->lock); return ret; } static void tegra_dc_one_shot_irq(struct tegra_dc *dc, unsigned long status) { if (status & MSF_INT) tegra_dc_process_vblank(dc); if (status & V_BLANK_INT) { /* Sync up windows. */ tegra_dc_trigger_windows(dc); /* Check COMMON_ACT_REQ. */ if (tegra_dc_handle_common_channel_promotion(dc)) clear_bit(V_BLANK_IMP, &dc->vblank_ref_count); /* Schedule any additional bottom-half vblank actvities. */ queue_work(system_freezable_wq, &dc->vblank_work); } if (status & FRAME_END_INT) { if (atomic_read(&dc->crc_ref_cnt.global)) tegra_dc_crc_process(dc); /* Mark the frame_end as complete. */ dc->crc_pending = false; if (!completion_done(&dc->frame_end_complete)) complete(&dc->frame_end_complete); if (!completion_done(&dc->crc_complete)) complete(&dc->crc_complete); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) tegra_dc_put(dc); queue_work(system_freezable_wq, &dc->frame_end_work); } if (status & V_PULSE2_INT) { if (test_bit(V_PULSE2_LATENCY_MSRMNT, &dc->vpulse2_ref_count)) tegra_dc_collect_latency_data(dc); queue_work(system_freezable_wq, &dc->vpulse2_work); } } static void tegra_dc_continuous_irq(struct tegra_dc *dc, unsigned long status) { /* Schedule any additional bottom-half vblank actvities. */ if (status & V_BLANK_INT) queue_work(system_freezable_wq, &dc->vblank_work); if (status & (V_BLANK_INT | MSF_INT)) { if (dc->out->user_needs_vblank) { dc->out->user_needs_vblank = false; complete(&dc->out->user_vblank_comp); } tegra_dc_process_vblank(dc); } if (status & FRAME_END_INT) { struct timespec tm; ktime_get_ts(&tm); dc->frame_end_timestamp = timespec_to_ns(&tm); wake_up(&dc->timestamp_wq); if (!tegra_dc_windows_are_dirty(dc, WIN_ALL_ACT_REQ)) { if (dc->out->type == TEGRA_DC_OUT_DSI) { tegra_dc_vrr_get_ts(dc); tegra_dc_vrr_sec(dc); } else tegra_dc_vrr_extend_vfp(dc); } if (atomic_read(&dc->crc_ref_cnt.global)) tegra_dc_crc_process(dc); /* Mark the frame_end as complete. */ if (!completion_done(&dc->frame_end_complete)) complete(&dc->frame_end_complete); if (!completion_done(&dc->crc_complete)) complete(&dc->crc_complete); if (dc->frm_lck_info.frame_lock_enable && ((dc->out->type == TEGRA_DC_OUT_HDMI) || (dc->out->type == TEGRA_DC_OUT_DP) || (dc->out->type == TEGRA_DC_OUT_FAKE_DP))) { mutex_unlock(&dc->lock); tegra_dc_common_handle_flip_lock_error(dc); mutex_lock(&dc->lock); } else { tegra_dc_trigger_windows(dc); } /* Check COMMON_ACT_REQ. */ if (tegra_dc_handle_common_channel_promotion(dc)) _tegra_dc_config_frame_end_intr(dc, false); queue_work(system_freezable_wq, &dc->frame_end_work); } if (status & V_PULSE2_INT) { if (test_bit(V_PULSE2_LATENCY_MSRMNT, &dc->vpulse2_ref_count)) tegra_dc_collect_latency_data(dc); queue_work(system_freezable_wq, &dc->vpulse2_work); } } /* XXX: Not sure if we limit look ahead to 1 frame */ bool tegra_dc_is_within_n_vsync(struct tegra_dc *dc, s64 ts) { BUG_ON(!dc->frametime_ns); return ((ts - dc->frame_end_timestamp) < dc->frametime_ns); } bool tegra_dc_does_vsync_separate(struct tegra_dc *dc, s64 new_ts, s64 old_ts) { BUG_ON(!dc->frametime_ns); return (((new_ts - old_ts) > dc->frametime_ns) || (div_s64((new_ts - dc->frame_end_timestamp), dc->frametime_ns) != div_s64((old_ts - dc->frame_end_timestamp), dc->frametime_ns))); } #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)) inline u64 tegra_dc_get_tsc_time(void) { struct timecounter *tc; cycle_t value; u64 frac = 0; tc = arch_timer_get_timecounter(); if (tc) { const struct cyclecounter *cc = tc->cc; value = cc->read(cc); return cyclecounter_cyc2ns(cc, value, 0, &frac); } return 0; } #else inline u64 tegra_dc_get_tsc_time(void) { u64 frac = 0; const struct cyclecounter *cc; struct arch_timer_kvm_info *info; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)) u64 value; #else cycle_t value; #endif info = arch_timer_get_kvm_info(); cc = info->timecounter.cc; value = cc->read(cc); return cyclecounter_cyc2ns(cc, value, 0, &frac); } #endif static inline void tegra_dc_scanline_trace(struct tegra_dc *dc) { int current_scanline, vp3_scanline; int current_frame, diff_frames; int vblank_diff_lines, vp3_diff_lines; u64 vblank_delta_time, vp3_delta_time; u64 curr_timestamp, vblank_timestamp, vp3_timestamp; /* TBD: Currently, if this function is delayed by greater than * one frame duration, then prints for those frames would be skipped. */ /* Get current TSC time & raster position */ curr_timestamp = tegra_dc_get_tsc_time(); current_scanline = tegra_dc_get_v_count(dc); current_frame = tegra_dc_readl(dc, DC_COM_RG_DPCA) >> 16; /* TBD: Extend for more than one programmable scanline. */ /* Is there a programmed scanline ? */ vp3_scanline = tegra_dc_ext_get_scanline(dc->ext); /* Compare current scanline to actual event occurrence */ if (current_scanline >= dc->mode_metadata.vblank_lines && current_scanline <= dc->mode_metadata.vtotal_lines) { /* If control still in old frame; where vblank occurred */ vblank_diff_lines = current_scanline - dc->mode_metadata.vblank_lines; if (vp3_scanline >= 0) vp3_diff_lines = current_scanline - vp3_scanline; diff_frames = 0; } else { /* If in next frame */ vblank_diff_lines = current_scanline + dc->mode.v_front_porch; if (vp3_scanline >= 0) vp3_diff_lines = (dc->mode_metadata.vtotal_lines - vp3_scanline) + current_scanline; diff_frames = -1; } /* Calculate time delta & accurate timestamps */ vblank_delta_time = (u64)vblank_diff_lines * dc->mode_metadata.line_in_nsec; vblank_timestamp = curr_timestamp - vblank_delta_time; if (vp3_scanline >= 0) { vp3_delta_time = (u64)vp3_diff_lines * dc->mode_metadata.line_in_nsec; vp3_timestamp = curr_timestamp - vp3_delta_time; } /* Write to ftrace buffer */ if (vp3_scanline >= 0) { trace_display_scanline(dc->ctrl_num, vp3_scanline, current_frame+diff_frames, vp3_timestamp); } /* Write -1 for line number to indicate its vblank line # */ trace_display_scanline(dc->ctrl_num, -1, current_frame+diff_frames, vblank_timestamp); } static irqreturn_t tegra_dc_irq(int irq, void *ptr) { struct tegra_dc *dc = ptr; unsigned long status; unsigned long underflow_mask; u32 val; int need_disable = 0; if (tegra_dc_is_t21x() && tegra_platform_is_fpga()) return IRQ_NONE; mutex_lock(&dc->lock); if (!tegra_dc_is_powered(dc)) { mutex_unlock(&dc->lock); return IRQ_HANDLED; } tegra_dc_get(dc); if (!dc->enabled || !nvhost_module_powered_ext(dc->ndev)) { dev_dbg(&dc->ndev->dev, "IRQ when DC not powered!\n"); status = tegra_dc_readl(dc, DC_CMD_INT_STATUS); tegra_dc_writel(dc, status, DC_CMD_INT_STATUS); tegra_dc_put(dc); mutex_unlock(&dc->lock); return IRQ_HANDLED; } /* clear all status flags except underflow, save those for the worker */ status = tegra_dc_readl(dc, DC_CMD_INT_STATUS); tegra_dc_writel(dc, status & ~ALL_UF_INT(), DC_CMD_INT_STATUS); val = tegra_dc_readl(dc, DC_CMD_INT_MASK); tegra_dc_writel(dc, val & ~ALL_UF_INT(), DC_CMD_INT_MASK); /* * Overlays can get thier internal state corrupted during and underflow * condition. The only way to fix this state is to reset the DC. * if we get 4 consecutive frames with underflows, assume we're * hosed and reset. */ underflow_mask = status & ALL_UF_INT(); /* Check underflow */ if (underflow_mask) { dc->underflow_mask |= underflow_mask; schedule_delayed_work(&dc->underflow_work, msecs_to_jiffies(1)); } if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) tegra_dc_one_shot_irq(dc, status); else tegra_dc_continuous_irq(dc, status); /* Trace scanlines before mode change takes effect below */ if (status & V_BLANK_INT) { /* Do any extra work only if tracepoint enabled */ if (trace_display_scanline_enabled()) tegra_dc_scanline_trace(dc); } /* update video mode if it has changed since the last frame */ if (status & (FRAME_END_INT | V_BLANK_INT)) if (tegra_dc_update_mode(dc)) need_disable = 1; /* force display off on error */ if (status & FRAME_END_INT) { dc->dbg_fe_count++; if (dc->disp_active_dirty) { tegra_dc_writel(dc, dc->mode.h_active | (dc->mode.v_active << 16), DC_DISP_DISP_ACTIVE); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); dc->disp_active_dirty = false; } } if (status & V_BLANK_INT) trace_display_vblank(dc->ctrl_num, tegra_dc_readl(dc, DC_COM_RG_DPCA) >> 16); tegra_dc_put(dc); #ifdef TEGRA_DC_USR_SHARED_IRQ /* user shared display ISR call-back */ if (dc->isr_usr_cb) dc->isr_usr_cb(dc->ctrl_num, status, dc->isr_usr_pdt); #endif /* TEGRA_DC_USR_SHARED_IRQ */ mutex_unlock(&dc->lock); if (need_disable) tegra_dc_disable_irq_ops(dc, true); return IRQ_HANDLED; } void tegra_dc_set_color_control(struct tegra_dc *dc) { u32 color_control; switch (dc->out->depth) { case 3: color_control = BASE_COLOR_SIZE111; break; case 6: color_control = BASE_COLOR_SIZE222; break; case 8: color_control = BASE_COLOR_SIZE332; break; case 9: color_control = BASE_COLOR_SIZE333; break; case 12: color_control = BASE_COLOR_SIZE444; break; case 15: color_control = BASE_COLOR_SIZE555; break; case 16: color_control = BASE_COLOR_SIZE565; break; case 18: color_control = BASE_COLOR_SIZE666; break; default: color_control = BASE_COLOR_SIZE888; break; } switch (dc->out->dither) { case TEGRA_DC_UNDEFINED_DITHER: case TEGRA_DC_DISABLE_DITHER: color_control |= DITHER_CONTROL_DISABLE; break; case TEGRA_DC_ORDERED_DITHER: color_control |= DITHER_CONTROL_ORDERED; break; #ifdef CONFIG_TEGRA_DC_TEMPORAL_DITHER case TEGRA_DC_TEMPORAL_DITHER: color_control |= DITHER_CONTROL_TEMPORAL; break; #else case TEGRA_DC_ERRDIFF_DITHER: /* The line buffer for error-diffusion dither is limited * to 1280 pixels per line. This limits the maximum * horizontal active area size to 1280 pixels when error * diffusion is enabled. */ BUG_ON(dc->mode.h_active > 1280); color_control |= DITHER_CONTROL_ERRDIFF; break; #endif default: dev_err(&dc->ndev->dev, "Error: Unsupported dithering mode\n"); } if (tegra_dc_is_t21x()) { if (dc->cmu_enabled) color_control |= CMU_ENABLE; } tegra_dc_writel(dc, color_control, DC_DISP_DISP_COLOR_CONTROL); } /* * Due to the limitations in DSC architecture, program DSC block with predefined * values. */ void tegra_dc_dsc_init(struct tegra_dc *dc) { struct tegra_dc_mode *mode = &dc->mode; u32 val; u32 slice_width, slice_height, chunk_size, hblank; u32 min_rate_buf_size, num_xtra_mux_bits, hrdelay; u32 initial_offset, final_offset; u32 initial_xmit_delay, initial_dec_delay; u32 initial_scale_value, final_scale; u32 scale_dec_interval, scale_inc_interval; u32 groups_per_line, total_groups, first_line_bpg_offset; u32 nfl_bpg_offset, slice_bpg_offset; u32 rc_model_size = DSC_DEF_RC_MODEL_SIZE; u32 delay_in_slice, output_delay, wrap_output_delay; u8 i, j; u8 bpp; u32 check_flatness; /* Link compression is only supported for DSI panels */ if ((dc->out->type != TEGRA_DC_OUT_DSI) || !dc->out->dsc_en) { dev_dbg(&dc->ndev->dev, "Link compression not supported by the panel\n"); return; } dev_info(&dc->ndev->dev, "Configuring DSC\n"); /* * Slice height and width are in pixel. When the whole picture is one * slice, slice height and width should be equal to picture height or * width. */ bpp = dc->out->dsc_bpp; slice_height = dc->out->slice_height; slice_width = (mode->h_active / dc->out->num_of_slices); val = DSC_VALID_SLICE_HEIGHT(slice_height) | DSC_VALID_SLICE_WIDTH(slice_width); tegra_dc_writel(dc, val, DSC_COM_DSC_SLICE_INFO); if (tegra_dc_is_t21x()) { /* * Use RC overflow solution 2. * Program overflow threshold values and * enable flatness checking. */ check_flatness = ((3 - (slice_width % 3)) != 2); val = DC_DISP_SPARE0_VALID_OVERFLOW_THRES( DC_DISP_DEF_OVERFLOW_THRES) | DC_DISP_SPARE0_RC_SOLUTION_MODE( DC_SPARE0_RC_SOLUTION_2) | (check_flatness << 1) | 0x1; tegra_dc_writel(dc, val, DC_DISP_DISPLAY_SPARE0); } /* * Calculate chunk size based on slice width. Enable block prediction * and set compressed bpp rate. */ chunk_size = DIV_ROUND_UP((slice_width * bpp), 8); val = DSC_VALID_BITS_PER_PIXEL(bpp << 4) | DSC_VALID_CHUNK_SIZE(chunk_size); if (dc->out->en_block_pred) val |= DSC_BLOCK_PRED_ENABLE; tegra_dc_writel(dc, val, DSC_COM_DSC_COMMON_CTRL); /* Set output delay */ initial_xmit_delay = (4096 / bpp); if (slice_height == mode->v_active) initial_xmit_delay = 475; delay_in_slice = DIV_ROUND_UP(DSC_ENC_FIFO_SIZE * 8 * 3, bpp) + slice_width + initial_xmit_delay + DSC_START_PIXEL_POS; hblank = mode->h_sync_width + mode->h_front_porch + mode->h_back_porch; output_delay = ((delay_in_slice / slice_width) * (mode->h_active + hblank)) + (delay_in_slice % slice_width); if (dc->out->dual_dsc_en) { wrap_output_delay = output_delay * 2 + mode->h_active / 2 + 28; if (wrap_output_delay % 2) wrap_output_delay += 1; } else { wrap_output_delay = output_delay + 20; } val = DSC_VALID_OUTPUT_DELAY(output_delay); val |= DSC_VALID_WRAP_OUTPUT_DELAY(wrap_output_delay); tegra_dc_writel(dc, val, DC_COM_DSC_DELAY); /* Set RC flatness info and bpg offset for first line of slice */ first_line_bpg_offset = (bpp == 8) ? DSC_DEF_8BPP_FIRST_LINE_BPG_OFFS : DSC_DEF_12BPP_FIRST_LINE_BPG_OFFS; val = DSC_VALID_FLATNESS_MAX_QP(12) | DSC_VALID_FLATNESS_MIN_QP(3) | DSC_VALID_FIRST_LINE_BPG_OFFS(first_line_bpg_offset); tegra_dc_writel(dc, val, DC_COM_DSC_RC_FLATNESS_INFO); /* Set RC model offset values to be used at slice start and end */ initial_offset = (bpp == 8) ? DSC_DEF_8BPP_INITIAL_OFFSET : DSC_DEF_12BPP_INITIAL_OFFSET; num_xtra_mux_bits = 198 + ((chunk_size * slice_height * 8 - 246) % 48); final_offset = rc_model_size - (initial_xmit_delay * bpp) + num_xtra_mux_bits; val = DSC_VALID_INITIAL_OFFSET(initial_offset) | DSC_VALID_FINAL_OFFSET(final_offset); tegra_dc_writel(dc, val, DC_COM_DSC_RC_OFFSET_INFO); /* * DSC_SLICE_BPG_OFFSET:Bpg offset used to enforce slice bit constraint * DSC_NFL_BPG_OFFSET:Non-first line bpg offset to use */ nfl_bpg_offset = DIV_ROUND_UP((first_line_bpg_offset << 11), (slice_height - 1)); slice_bpg_offset = (rc_model_size - initial_offset + num_xtra_mux_bits) * (1 << 11); groups_per_line = slice_width / 3; total_groups = slice_height * groups_per_line; slice_bpg_offset = DIV_ROUND_UP(slice_bpg_offset, total_groups); val = DSC_VALID_SLICE_BPG_OFFSET(slice_bpg_offset) | DSC_VALID_NFL_BPG_OFFSET(nfl_bpg_offset); tegra_dc_writel(dc, val, DC_COM_DSC_RC_BPGOFF_INFO); /* * INITIAL_DEC_DELAY:Num of pixels to delay the VLD on the decoder * INITIAL_XMIT_DELAY:Num of pixels to delay the initial transmission */ min_rate_buf_size = rc_model_size - initial_offset + (initial_xmit_delay * bpp) + (groups_per_line * first_line_bpg_offset); hrdelay = DIV_ROUND_UP(min_rate_buf_size, bpp); initial_dec_delay = hrdelay - initial_xmit_delay; val = DSC_VALID_INITIAL_XMIT_DELAY(initial_xmit_delay) | DSC_VALID_INITIAL_DEC_DELAY(initial_dec_delay); tegra_dc_writel(dc, val, DSC_COM_DSC_RC_RELAY_INFO); /* * SCALE_DECR_INTERVAL:Decrement scale factor every scale_decr_interval * groups. * INITIAL_SCALE_VALUE:Initial value for scale factor * SCALE_INCR_INTERVAL:Increment scale factor every scale_incr_interval * groups. */ initial_scale_value = (8 * rc_model_size) / (rc_model_size - initial_offset); scale_dec_interval = groups_per_line / (initial_scale_value - 8); val = DSC_VALID_SCALE_DECR_INTERVAL(scale_dec_interval) | DSC_VALID_INITIAL_SCALE_VALUE(initial_scale_value); tegra_dc_writel(dc, val, DC_COM_DSC_RC_SCALE_INFO); final_scale = (8 * rc_model_size) / (rc_model_size - final_offset); scale_inc_interval = (2048 * final_offset) / ((final_scale - 9) * (slice_bpg_offset + nfl_bpg_offset)); val = DSC_VALID_SCALE_INCR_INTERVAL(scale_inc_interval); tegra_dc_writel(dc, val, DC_COM_DSC_RC_SCALE_INFO_2); /* Set the RC parameters */ val = DSC_VALID_RC_TGT_OFFSET_LO(3) | DSC_VALID_RC_TGT_OFFSET_HI(3) | DSC_VALID_RC_EDGE_FACTOR(6) | DSC_VALID_RC_QUANT_INCR_LIMIT1(11) | DSC_VALID_RC_QUANT_INCR_LIMIT0(11); tegra_dc_writel(dc, val, DC_COM_DSC_RC_PARAM_SET); for (i = 0, j = 0; j < DSC_MAX_RC_BUF_THRESH_REGS; j++) { val = DSC_VALID_RC_BUF_THRESH_0(dsc_rc_buf_thresh[i++]); val |= DSC_VALID_RC_BUF_THRESH_1(dsc_rc_buf_thresh[i++]); val |= DSC_VALID_RC_BUF_THRESH_2(dsc_rc_buf_thresh[i++]); val |= DSC_VALID_RC_BUF_THRESH_3(dsc_rc_buf_thresh[i++]); if (dsc_rc_buf_thresh_regs[j] == DC_COM_DSC_RC_BUF_THRESH_0) val |= DSC_VALID_RC_MODEL_SIZE(rc_model_size); tegra_dc_writel(dc, val, dsc_rc_buf_thresh_regs[j]); } for (i = 0, j = 0; j < DSC_MAX_RC_RANGE_CFG_REGS; j++) { val = DSC_VALID_RC_RANGE_PARAM_LO( SET_RC_RANGE_MIN_QP(dsc_rc_ranges_8bpp_8bpc[i][0]) | SET_RC_RANGE_MAX_QP(dsc_rc_ranges_8bpp_8bpc[i][1]) | SET_RC_RANGE_BPG_OFFSET(dsc_rc_ranges_8bpp_8bpc[i][2])); i++; val |= DSC_VALID_RC_RANGE_PARAM_HI( SET_RC_RANGE_MIN_QP(dsc_rc_ranges_8bpp_8bpc[i][0]) | SET_RC_RANGE_MAX_QP(dsc_rc_ranges_8bpp_8bpc[i][1]) | SET_RC_RANGE_BPG_OFFSET(dsc_rc_ranges_8bpp_8bpc[i][2])); i++; tegra_dc_writel(dc, val, dsc_rc_range_config[j]); } val = tegra_dc_readl(dc, DC_COM_DSC_UNIT_SET); val &= ~DSC_LINEBUF_DEPTH_8_BIT; /* If dual dsc is enabled then the number slices are distributed * between each link i.e 2 links with 4 lanes each. */ val |= DSC_VALID_SLICE_NUM_MINUS1_IN_LINE((dc->out->dual_dsc_en ? dc->out->num_of_slices / 2 : dc->out->num_of_slices) - 1); val |= DSC_CHECK_FLATNESS2; val |= DSC_FLATNESS_FIX_EN; tegra_dc_writel(dc, val, DC_COM_DSC_UNIT_SET); dev_info(&dc->ndev->dev, "DSC configured\n"); } void tegra_dc_en_dis_dsc(struct tegra_dc *dc, bool enable) { u32 val, set_bits = 0x0; bool is_enabled = false, set_reg = false; if ((dc->out->type != TEGRA_DC_OUT_DSI) || !dc->out->dsc_en) return; val = tegra_dc_readl(dc, DC_COM_DSC_TOP_CTL); if (dc->out->dual_dsc_en) { set_bits = DSC_ENABLE | DSC_DUAL_ENABLE; if ((val & DSC_ENABLE) && (val & DSC_DUAL_ENABLE)) is_enabled = true; } else { set_bits = DSC_ENABLE; if (val & DSC_ENABLE) is_enabled = true; } if (enable && !is_enabled) { val |= set_bits; set_reg = true; } else if (!enable && is_enabled) { val &= ~set_bits; set_reg = true; } if (set_reg) { dev_info(&dc->ndev->dev, "Link compression %s\n", enable ? "enabled" : "disabled"); val &= ~DSC_AUTO_RESET; tegra_dc_writel(dc, val, DC_COM_DSC_TOP_CTL); } } /* Used only on T21x */ static void tegra_dc_init_vpulse2_int(struct tegra_dc *dc) { u32 start, end; unsigned long val; val = V_PULSE2_H_POSITION(0) | V_PULSE2_LAST(0x1); tegra_dc_writel(dc, val, DC_DISP_V_PULSE2_CONTROL); start = dc->mode.v_ref_to_sync + dc->mode.v_sync_width + dc->mode.v_back_porch + dc->mode.v_active; end = start + 1; val = V_PULSE2_START_A(start) + V_PULSE2_END_A(end); tegra_dc_writel(dc, val, DC_DISP_V_PULSE2_POSITION_A); val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE); val |= V_PULSE2_INT; tegra_dc_writel(dc, val , DC_CMD_INT_ENABLE); tegra_dc_mask_interrupt(dc, V_PULSE2_INT); val = tegra_dc_readl(dc, DC_DISP_DISP_SIGNAL_OPTIONS0); val |= V_PULSE_2_ENABLE; tegra_dc_writel(dc, val, DC_DISP_DISP_SIGNAL_OPTIONS0); } /* Used only on T21x */ static int tegra_dc_init(struct tegra_dc *dc) { int i; int int_enable; u32 val; tegra_dc_io_start(dc); tegra_dc_writel(dc, 0x00000100, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL); tegra_dc_writel(dc, 0x00000100 | dc->vblank_syncpt, DC_CMD_CONT_SYNCPT_VSYNC); tegra_dc_writel(dc, 0x00004700, DC_CMD_INT_TYPE); tegra_dc_writel(dc, WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT | WIN_T_UF_INT | WIN_D_UF_INT | HC_UF_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT, DC_CMD_INT_POLARITY); tegra_dc_writel(dc, 0x00202020, DC_DISP_MEM_HIGH_PRIORITY); tegra_dc_writel(dc, 0x00010101, DC_DISP_MEM_HIGH_PRIORITY_TIMER); /* enable interrupts for vblank, frame_end and underflows */ int_enable = (FRAME_END_INT | V_BLANK_INT | ALL_UF_INT()); /* for panels with one-shot mode enable tearing effect interrupt */ if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) int_enable |= MSF_INT; tegra_dc_writel(dc, int_enable, DC_CMD_INT_ENABLE); tegra_dc_writel(dc, ALL_UF_INT(), DC_CMD_INT_MASK); tegra_dc_init_vpulse2_int(dc); tegra_dc_writel(dc, WRITE_MUX_ASSEMBLY | READ_MUX_ASSEMBLY, DC_CMD_STATE_ACCESS); tegra_dc_writel(dc, 0x00000000, DC_DISP_BLEND_BACKGROUND_COLOR); if (tegra_dc_is_t21x()) { if (dc->is_cmu_set_bl) _tegra_dc_update_cmu_aligned(dc, &dc->cmu, true); else _tegra_dc_update_cmu(dc, &dc->cmu); dc->is_cmu_set_bl = false; } tegra_dc_set_color_control(dc); for_each_set_bit(i, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { struct tegra_dc_win *win = tegra_dc_get_window(dc, i); tegra_dc_writel(dc, WINDOW_A_SELECT << i, DC_CMD_DISPLAY_WINDOW_HEADER); if (tegra_dc_is_t21x()) { tegra_dc_set_win_csc(dc, &win->win_csc); tegra_dc_set_lut(dc, win); } if (tegra_dc_is_nvdisplay()) { tegra_dc_set_nvdisp_win_csc(dc, &win->nvdisp_win_csc); tegra_dc_set_nvdisp_lut(dc, win); } tegra_dc_set_scaling_filter(dc); } for_each_set_bit(i, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { struct tegra_dc_win *win = tegra_dc_get_window(dc, i); BUG_ON(!win); /* refuse to operate on invalid syncpts */ if (WARN_ON(win->syncpt.id == NVSYNCPT_INVALID)) continue; if (!nvhost_syncpt_read_ext_check(dc->ndev, win->syncpt.id, &val)) win->syncpt.min = win->syncpt.max = val; } dc->crc_pending = false; trace_display_mode(dc, &dc->mode); if (dc->mode.pclk) { if (!dc->initialized) { if (tegra_dc_program_mode(dc, &dc->mode)) { tegra_dc_io_end(dc); dev_warn(&dc->ndev->dev, "%s: tegra_dc_program_mode failed\n", __func__); return -EINVAL; } } else { dev_info(&dc->ndev->dev, "DC initialized, " "skipping tegra_dc_program_mode.\n"); } } tegra_dc_io_end(dc); return 0; } /* Used only on T21x */ static bool _tegra_dc_controller_enable(struct tegra_dc *dc) { int failed_init = 0; int i; if (WARN_ON(!dc || !dc->out || !dc->out_ops)) return false; tegra_dc_unpowergate_locked(dc); if (dc->out->enable) dc->out->enable(&dc->ndev->dev); tegra_dc_setup_clk(dc, dc->clk); /* dc clk always on for continuous mode */ if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)) tegra_dc_clk_enable(dc); else #ifdef CONFIG_TEGRA_CORE_DVFS tegra_dvfs_set_rate(dc->clk, dc->mode.pclk); #else ; #endif tegra_dc_get(dc); tegra_dc_power_on(dc); /* do not accept interrupts during initialization */ tegra_dc_writel(dc, 0, DC_CMD_INT_MASK); enable_dc_irq(dc); failed_init = tegra_dc_init(dc); if (failed_init) { tegra_dc_writel(dc, 0, DC_CMD_INT_MASK); disable_irq_nosync(dc->irq); tegra_dc_clear_bandwidth(dc); if (dc->out && dc->out->disable) dc->out->disable(&dc->ndev->dev); tegra_dc_put(dc); if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)) tegra_dc_clk_disable(dc); else #ifdef CONFIG_TEGRA_CORE_DVFS tegra_dvfs_set_rate(dc->clk, 0); #else ; #endif dev_warn(&dc->ndev->dev, "%s: tegra_dc_init failed\n", __func__); return false; } if (dc->out_ops && dc->out_ops->enable) dc->out_ops->enable(dc); /* force a full blending update */ for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) dc->blend.z[i] = -1; tegra_dc_ext_enable(dc->ext); /* initialize cursor to defaults, as driver depends on HW state */ tegra_dc_writel(dc, 0, DC_DISP_CURSOR_START_ADDR); tegra_dc_writel(dc, 0, DC_DISP_CURSOR_START_ADDR_NS); tegra_dc_writel(dc, 0, DC_DISP_CURSOR_START_ADDR_HI); tegra_dc_writel(dc, 0, DC_DISP_CURSOR_START_ADDR_HI_NS); tegra_dc_writel(dc, 0, DC_DISP_CURSOR_POSITION); tegra_dc_writel(dc, 0, DC_DISP_CURSOR_POSITION_NS); tegra_dc_writel(dc, 0xffffff, DC_DISP_CURSOR_FOREGROUND); /* white */ tegra_dc_writel(dc, 0x000000, DC_DISP_CURSOR_BACKGROUND); /* black */ tegra_dc_writel(dc, 0, DC_DISP_BLEND_CURSOR_CONTROL); trace_display_enable(dc); tegra_dc_writel(dc, CURSOR_UPDATE, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_dsc_init(dc); if (dc->out->postpoweron) dc->out->postpoweron(&dc->ndev->dev); if (dc->out_ops && dc->out_ops->postpoweron) dc->out_ops->postpoweron(dc); tegra_log_resume_time(); tegra_dc_put(dc); return true; } static int _tegra_dc_set_default_videomode(struct tegra_dc *dc) { if (dc->mode.pclk == 0) { switch (dc->out->type) { case TEGRA_DC_OUT_HDMI: /* No fallback mode. If no mode info available * from bootloader or device tree, * mode will be set by userspace during unblank. */ break; case TEGRA_DC_OUT_DP: if (tegra_dc_is_nvdisplay()) break; case TEGRA_DC_OUT_FAKE_DP: case TEGRA_DC_OUT_NULL: return tegra_dc_set_fb_mode(dc, &tegra_dc_vga_mode, 0); /* Do nothing for other outputs for now */ case TEGRA_DC_OUT_RGB: case TEGRA_DC_OUT_DSI: default: return false; } } return false; } int tegra_dc_set_default_videomode(struct tegra_dc *dc) { return _tegra_dc_set_default_videomode(dc); } /* Preset sync point maxval for maximum range. */ static u32 tegra_dc_syncpt_preset_maxval(struct platform_device *dev, u32 id) { u32 old; old = nvhost_syncpt_read_minval(dev, id); nvhost_syncpt_set_maxval(dev, id, old - 2); return old; } /* Advance sync point to flush all waiters, return value before advancing. */ static u32 tegra_dc_syncpt_flush(struct platform_device *dev, u32 id) { u32 old; old = nvhost_syncpt_read_minval(dev, id); nvhost_syncpt_set_min_eq_max_ext(dev, id); return old; } static bool _tegra_dc_enable(struct tegra_dc *dc) { if (dc->mode.pclk == 0) return false; if (!dc->out) return false; if (dc->enabled) return true; dc->shutdown = false; if ((dc->out->type == TEGRA_DC_OUT_HDMI || dc->out->type == TEGRA_DC_OUT_DP || dc->out->type == TEGRA_DC_OUT_DSI) && !tegra_dc_hpd(dc)) return false; pm_runtime_get_sync(&dc->ndev->dev); if (tegra_dc_is_nvdisplay()) { if (tegra_nvdisp_head_enable(dc)) { pm_runtime_put_sync(&dc->ndev->dev); return false; } } else { if (!_tegra_dc_controller_enable(dc)) { pm_runtime_put_sync(&dc->ndev->dev); return false; } } if (tegra_dc_is_nvdisplay()) tegra_dc_crc_reset(dc); tegra_dc_client_handle_event(dc, NOTIFY_DC_ENABLED_EVENT); return true; } void tegra_dc_enable(struct tegra_dc *dc) { if (WARN_ON(!dc || !dc->out || !dc->out_ops)) return; mutex_lock(&dc->lock); if (!dc->enabled) { if (tegra_dc_reserve_common_channel(dc)) { dev_err(&dc->ndev->dev, "%s: DC %d enable failed due to timeout\n", __func__, dc->ctrl_num); mutex_unlock(&dc->lock); return; } tegra_dc_syncpt_preset_maxval(dc->ndev, dc->vblank_syncpt); dc->enabled = _tegra_dc_enable(dc); tegra_dc_release_common_channel(dc); } mutex_unlock(&dc->lock); trace_display_mode(dc, &dc->mode); } static void tegra_dc_flush_syncpts_window(struct tegra_dc *dc, unsigned win) { struct tegra_dc_win *w = tegra_dc_get_window(dc, win); u32 max; /* refuse to operate on invalid syncpts */ if (WARN_ON(w->syncpt.id == NVSYNCPT_INVALID)) return; /* flush any pending syncpt waits */ max = tegra_dc_incr_syncpt_max_locked(dc, win); while (w->syncpt.min < w->syncpt.max) { trace_display_syncpt_flush(dc, w->syncpt.id, w->syncpt.min, w->syncpt.max); w->syncpt.min++; nvhost_syncpt_cpu_incr_ext(dc->ndev, w->syncpt.id); } } void tegra_dc_disable_window(struct tegra_dc *dc, unsigned win) { struct tegra_dc_win *w = tegra_dc_get_window(dc, win); /* reset window bandwidth */ w->bandwidth = 0; w->new_bandwidth = 0; /* disable windows */ w->flags &= ~TEGRA_WIN_FLAG_ENABLED; /* flush pending syncpts */ tegra_dc_flush_syncpts_window(dc, win); } static void _tegra_dc_controller_disable(struct tegra_dc *dc) { unsigned i; tegra_dc_get(dc); if (atomic_read(&dc->holding)) { /* Force release all refs but the last one */ atomic_set(&dc->holding, 1); tegra_dc_release_dc_out(dc); } if (dc->out && dc->out->prepoweroff) dc->out->prepoweroff(); if (dc->out_ops && dc->out_ops->vrr_enable && dc->out && dc->out->vrr && dc->out->vrr->capability) { dc->out_ops->vrr_enable(dc, 0); /* TODO: Fix properly. Bug 1644102. */ tegra_dc_set_act_vfp(dc, dc->mode.v_front_porch); } if (dc->out_ops && dc->out_ops->disable) dc->out_ops->disable(dc); if (tegra_dc_is_powered(dc)) tegra_dc_writel(dc, 0, DC_CMD_INT_MASK); disable_irq_nosync(dc->irq); tegra_dc_clear_bandwidth(dc); if (dc->out && dc->out->disable) dc->out->disable(&dc->ndev->dev); for_each_set_bit(i, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { tegra_dc_disable_window(dc, i); } trace_display_disable(dc); if (dc->out_ops && dc->out_ops->postpoweroff) dc->out_ops->postpoweroff(dc); if (tegra_dc_is_nvdisplay()) /* clear the windows ownership from head*/ tegra_nvdisp_head_disable(dc); /* clean up tegra_dc_vsync_enable() */ while (dc->out->user_needs_vblank > 0) _tegra_dc_user_vsync_enable(dc, false); if (test_bit(V_BLANK_USER, &dc->vblank_ref_count)) { tegra_dc_release_dc_out(dc); clear_bit(V_BLANK_USER, &dc->vblank_ref_count); } tegra_dc_put(dc); if (tegra_dc_is_t21x()) { /* disable always on dc clk in continuous mode */ if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)) tegra_dc_clk_disable(dc); else #ifdef CONFIG_TEGRA_CORE_DVFS tegra_dvfs_set_rate(dc->clk, 0); #else ; #endif } } void tegra_dc_stats_enable(struct tegra_dc *dc, bool enable) { return; } bool tegra_dc_stats_get(struct tegra_dc *dc) { return true; } /* blank selected windows by disabling them */ void tegra_dc_blank_wins(struct tegra_dc *dc, unsigned windows) { int nwins = tegra_dc_get_numof_dispwindows(); struct tegra_dc_win *dcwins[nwins]; struct tegra_dc_win blank_win; unsigned i; unsigned long int blank_windows; int nr_win = 0; /* YUV420 10bpc variables */ bool yuv_420_10b_path = false; int fb_win_idx = -1; int fb_win_pos = -1; if (dc->yuv_bypass && tegra_dc_is_yuv420_10bpc(&dc->mode)) yuv_420_10b_path = true; if (tegra_dc_is_nvdisplay() && dc->shutdown) yuv_420_10b_path = false; if (yuv_420_10b_path) { u32 active_width = dc->mode.h_active; u32 active_height = dc->mode.v_active; blank_win = *tegra_fb_get_blank_win(dc->fb); /* * 420 10bpc blank frame statically * created for this pixel format */ blank_win.h.full = dfixed_const(1); blank_win.w.full = dfixed_const(active_width); blank_win.fmt = TEGRA_DC_EXT_FMT_T_A8R8G8B8; blank_win.out_w = active_width; blank_win.out_h = active_height; dcwins[0] = &blank_win; fb_win_idx = dcwins[0]->idx; nr_win++; } blank_windows = windows & dc->valid_windows; if (!blank_windows) return; for_each_set_bit(i, &blank_windows, tegra_dc_get_numof_dispwindows()) { dcwins[nr_win] = tegra_dc_get_window(dc, i); if (!dcwins[nr_win]) continue; /* * Prevent disabling the YUV410 10bpc window in case * it is also in blank_windows, additionally, prevent * adding it to the list twice. */ if (fb_win_idx == dcwins[nr_win]->idx) { fb_win_pos = i; continue; } dcwins[nr_win++]->flags &= ~TEGRA_WIN_FLAG_ENABLED; } tegra_dc_update_windows(dcwins, nr_win, NULL, true, false); tegra_dc_sync_windows(dcwins, nr_win); tegra_dc_program_bandwidth(dc, true); /* * Disable, reset bandwidth and advance pending syncpoints * of all windows. In case the statically created 420 10bpc * is also present in blank_windows, only advance syncpoints. */ for_each_set_bit(i, &blank_windows, tegra_dc_get_numof_dispwindows()) { if (fb_win_pos == i) { tegra_dc_flush_syncpts_window(dc, i); continue; } tegra_dc_disable_window(dc, i); } } int tegra_dc_restore(struct tegra_dc *dc) { return tegra_dc_ext_restore(dc->ext); } static void _tegra_dc_disable(struct tegra_dc *dc) { /* power down resets the registers, setting to true * causes CMU to be restored in tegra_dc_init(). */ if (tegra_dc_is_t21x()) dc->cmu_dirty = true; tegra_dc_crc_deinit(dc); tegra_dc_get(dc); _tegra_dc_controller_disable(dc); tegra_dc_put(dc); tegra_dc_powergate_locked(dc); pm_runtime_put(&dc->ndev->dev); tegra_log_suspend_entry_time(); tegra_dc_client_handle_event(dc, NOTIFY_DC_DISABLED_EVENT); } void tegra_dc_disable(struct tegra_dc *dc) { dc->shutdown = true; tegra_dc_disable_irq_ops(dc, false); tegra_dc_syncpt_flush(dc->ndev, dc->vblank_syncpt); } static inline void tegra_dc_disable_all_wins(struct tegra_dc *dc) { int blank_windows = tegra_dc_ext_disable(dc->ext); /* * The tegra_dc_ext_disable() call above will disable the windows on * this head that are owned by the given dc_ext owner. Any active * windows on this head that have no dc_ext owner will be left * untouched. The following tegra_dc_blank_wins() call will ensure that * any remaining windows are actually disabled. */ tegra_dc_blank_wins(dc, ~blank_windows); } static void tegra_dc_disable_irq_ops(struct tegra_dc *dc, bool from_irq) { if (WARN_ON(!dc || !dc->out || !dc->out_ops)) return; if (dc->shutdown) { if ((dc->out->type == TEGRA_DC_OUT_HDMI) || (dc->out->type == TEGRA_DC_OUT_DP)) if (dc->out_ops && dc->out_ops->shutdown_interface) dc->out_ops->shutdown_interface(dc); } if (tegra_dc_reserve_common_channel(dc)) { dev_err(&dc->ndev->dev, "%s: DC %d disable failed due to timeout\n", __func__, dc->ctrl_num); return; } tegra_dc_disable_all_wins(dc); if (dc->cursor.enabled) tegra_dc_cursor_suspend(dc); /* it's important that new underflow work isn't scheduled before the * lock is acquired. */ cancel_delayed_work_sync(&dc->underflow_work); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) { mutex_lock(&dc->one_shot_lock); cancel_delayed_work_sync(&dc->one_shot_work); } mutex_lock(&dc->lp_lock); mutex_lock(&dc->lock); if (dc->enabled) { dc->enabled = false; dc->blanked = false; if (!dc->suspended) _tegra_dc_disable(dc); } tegra_dc_release_common_channel(dc); #ifdef CONFIG_SWITCH if (dc->switchdev_registered) switch_set_state(&dc->modeset_switch, 0); #endif mutex_unlock(&dc->lock); mutex_unlock(&dc->lp_lock); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) mutex_unlock(&dc->one_shot_lock); if (!from_irq) synchronize_irq(dc->irq); trace_display_mode(dc, &dc->mode); /* disable pending clks due to uncompleted frames */ while (tegra_platform_is_silicon() && atomic_read(&dc->enable_count)) tegra_dc_put(dc); } static void tegra_dc_underflow_worker(struct work_struct *work) { struct tegra_dc *dc = container_of( to_delayed_work(work), struct tegra_dc, underflow_work); mutex_lock(&dc->lock); tegra_dc_get(dc); if (dc->enabled) tegra_dc_underflow_handler(dc); tegra_dc_put(dc); mutex_unlock(&dc->lock); } static void (*flip_callback)(void); static spinlock_t flip_callback_lock; static bool init_tegra_dc_flip_callback_called; static int __init init_tegra_dc_flip_callback(void) { spin_lock_init(&flip_callback_lock); init_tegra_dc_flip_callback_called = true; return 0; } pure_initcall(init_tegra_dc_flip_callback); int tegra_dc_set_flip_callback(void (*callback)(void)) { WARN_ON(!init_tegra_dc_flip_callback_called); spin_lock(&flip_callback_lock); flip_callback = callback; spin_unlock(&flip_callback_lock); return 0; } EXPORT_SYMBOL(tegra_dc_set_flip_callback); int tegra_dc_unset_flip_callback(void) { spin_lock(&flip_callback_lock); flip_callback = NULL; spin_unlock(&flip_callback_lock); return 0; } EXPORT_SYMBOL(tegra_dc_unset_flip_callback); void tegra_dc_call_flip_callback(void) { spin_lock(&flip_callback_lock); if (flip_callback) flip_callback(); spin_unlock(&flip_callback_lock); } EXPORT_SYMBOL(tegra_dc_call_flip_callback); #ifdef CONFIG_SWITCH static ssize_t switch_modeset_print_mode(struct switch_dev *sdev, char *buf) { struct tegra_dc *dc = container_of(sdev, struct tegra_dc, modeset_switch); if (!sdev->state) return sprintf(buf, "offline\n"); return sprintf(buf, "%dx%d\n", dc->mode.h_active, dc->mode.v_active); } #endif /* enables pads and clocks to perform DDC/I2C */ int tegra_dc_ddc_enable(struct tegra_dc *dc, bool enabled) { int ret = -ENOSYS; if (dc->out_ops) { if (enabled && dc->out_ops->ddc_enable) ret = dc->out_ops->ddc_enable(dc); else if (!enabled && dc->out_ops->ddc_disable) ret = dc->out_ops->ddc_disable(dc); } return ret; } #if IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS) int tegra_dc_slgc_disp0(struct notifier_block *nb, unsigned long unused0, void *unused1) { struct tegra_dc *dc = NULL; u32 val; if (tegra_dc_is_nvdisplay()) return NOTIFY_OK; dc = container_of(nb, struct tegra_dc, slgc_notifier); tegra_dc_get(dc); val = tegra_dc_readl(dc, DC_COM_DSC_TOP_CTL); val |= DSC_SLCG_OVERRIDE; tegra_dc_writel(dc, val, DC_COM_DSC_TOP_CTL); /* set */ /* flush the previous write */ (void)tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); val &= ~DSC_SLCG_OVERRIDE; tegra_dc_writel(dc, val, DC_COM_DSC_TOP_CTL); /* restore */ tegra_dc_put(dc); return NOTIFY_OK; } #endif int tegra_dc_update_winmask(struct tegra_dc *dc, unsigned long winmask) { struct tegra_dc *dc_other; struct tegra_dc_win *win; int i, j, ret = 0; int win_idx = -1; if (tegra_dc_is_t21x()) return -EINVAL; /* check that dc is not NULL and do range check */ if (!dc || (winmask >= (1 << tegra_dc_get_numof_dispwindows()))) return -EINVAL; mutex_lock(&dc->lock); if ((!dc->ndev) || (dc->enabled)) { ret = -EINVAL; goto exit; } /* check requested=enabled windows NOT owned by other dcs */ for_each_set_bit(i, &winmask, tegra_dc_get_numof_dispwindows()) { j = dc->ndev->id; win = tegra_dc_get_window(dc, i); /* is window already owned by this dc? */ if (win && win->dc && (win->dc == dc)) { /* get first valid window index for fb win index */ if (win_idx == -1) win_idx = i; continue; } /* is window already owned by other dc? */ for (j = 0; j < tegra_dc_get_numof_dispheads(); j++) { dc_other = tegra_dc_get_dc(j); if (!dc_other) continue; if (!dc_other->pdata) { ret = -EINVAL; goto exit; } /* found valid dc, does it own window=i? */ if ((dc_other->pdata->win_mask >> i) & 0x1) { dev_err(&dc->ndev->dev, "win[%d] already on fb%d\n", i, j); ret = -EINVAL; goto exit; } } /* get first valid window index for fb win index */ if (win_idx == -1) win_idx = i; } /* attach window happens on device enable call and * detach window happens on device disable call */ dc->pdata->win_mask = winmask; dc->valid_windows = winmask; tegra_fb_set_win_index(dc, winmask); dc->pdata->fb->win = win_idx; exit: mutex_unlock(&dc->lock); return ret; } struct clk *tegra_disp_of_clk_get_by_name(struct device_node *np, const char *name) { return of_clk_get_by_name(np, name); } struct clk *tegra_disp_clk_get(struct device *dev, const char *id) { struct clk *disp_clk; if (tegra_dc_is_t21x()) return devm_clk_get(dev, id); disp_clk = devm_clk_get(dev, id); if (IS_ERR_OR_NULL(disp_clk)) pr_err("Failed to get %s clk\n", id); return disp_clk; } void tegra_disp_clk_put(struct device *dev, struct clk *clk) { if (tegra_dc_is_nvdisplay()) { if (tegra_platform_is_silicon() && tegra_bpmp_running()) devm_clk_put(dev, clk); } else { return clk_put(clk); } } bool tegra_is_bl_display_initialized(int instance) { struct tegra_dc *dc = find_dc_by_ctrl_num(instance); if (!dc) { pr_err("could not find dc with ctrl number %d\n", instance); return false; } if (!dc->fb_mem) { pr_debug("dc->fb_mem not initialized\n"); return false; } return (dc->fb_mem->start != 0); } EXPORT_SYMBOL(tegra_is_bl_display_initialized); #if KERNEL_VERSION(4, 14, 0) <= LINUX_VERSION_CODE void tegra_get_fb_resource(struct resource *fb_res, int instance) { } #endif static int tegra_dc_probe(struct platform_device *ndev) { struct tegra_dc *dc; struct tegra_dc_mode *mode; struct tegra_dc_platform_data *dt_pdata = NULL; struct clk *clk; #ifndef CONFIG_TEGRA_ISOMGR struct clk *emc_clk; #else int isomgr_client_id = -1; #endif struct tegra_bwmgr_client *emc_la_handle; struct device_node *np = ndev->dev.of_node; struct resource *fb_mem = NULL; char clk_name[16]; int ret = 0; void __iomem *base; int irq; int i; #if IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS) int partition_id_disa, partition_id_disb; #endif struct resource of_fb_res; struct resource of_lut_res; int hotplug_init_status = -1; if (tegra_dc_is_nvdisplay() && !tegra_dc_common_probe_status()) return -EPROBE_DEFER; /* Serialize dc device registration */ mutex_lock(&tegra_dc_registration_lock); /* Specify parameters for the maximum physical segment size. */ ndev->dev.dma_parms = &tegra_dc_dma_parameters; dc = kzalloc(sizeof(struct tegra_dc), GFP_KERNEL); if (!dc) { dev_err(&ndev->dev, "can't allocate memory for tegra_dc\n"); mutex_unlock(&tegra_dc_registration_lock); return -ENOMEM; } irq = of_irq_to_resource(np, 0, NULL); if (!irq) goto err_free; ndev->id = tegra_dc_set(dc); if (ndev->id < 0) { dev_err(&ndev->dev, "can't add dc\n"); goto err_free; } dt_pdata = of_dc_parse_platform_data(ndev, dt_pdata); if (IS_ERR_OR_NULL(dt_pdata)) { ret = dt_pdata ? PTR_ERR(dt_pdata) : -EINVAL; goto err_free; } dc->ctrl_num = dt_pdata->ctrl_num; base = of_iomap(np, 0); if (!base) { dev_err(&ndev->dev, "registers can't be mapped\n"); ret = -EBUSY; goto err_release_resource_reg; } dev_info(&ndev->dev, "Display dc.%p registered with id=%d\n", base, ndev->id); if (tegra_dc_is_t21x()) { int i; char syncpt_name[25]; const char win_name[] = "abcd"; for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) dc->windows[i].syncpt.id = NVSYNCPT_INVALID; dc->valid_windows = dt_pdata->win_mask; for_each_set_bit(i, &dc->valid_windows, tegra_dc_get_numof_dispwindows()) { /* Get syncpt_name like disp0_a */ snprintf(syncpt_name, sizeof(syncpt_name), "disp%d_%c", dc->ctrl_num, win_name[i]); dc->windows[i].syncpt.id = nvhost_get_syncpt_client_managed(ndev, syncpt_name); /* Use first valid window as fb window */ if (dt_pdata->fb->win == TEGRA_FB_WIN_INVALID) dt_pdata->fb->win = i; } if (dc->ctrl_num == 0) { dc->vblank_syncpt = NVSYNCPT_VBLANK0; #if IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS) partition_id_disa = tegra_pd_get_powergate_id( tegra_disa_pd); if (partition_id_disa < 0) { ret = -EINVAL; goto err_iounmap_reg; } dc->powergate_id = partition_id_disa; #ifdef CONFIG_TEGRA_ISOMGR isomgr_client_id = TEGRA_ISO_CLIENT_DISP_0; #endif dc->slgc_notifier.notifier_call = tegra_dc_slgc_disp0; slcg_register_notifier(dc->powergate_id, &dc->slgc_notifier); #endif } else if (dc->ctrl_num == 1) { dc->vblank_syncpt = NVSYNCPT_VBLANK1; #if IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS) partition_id_disb = tegra_pd_get_powergate_id( tegra_disb_pd); if (partition_id_disb < 0) { ret = -EINVAL; goto err_iounmap_reg; } dc->powergate_id = partition_id_disb; #endif #ifdef CONFIG_TEGRA_ISOMGR isomgr_client_id = TEGRA_ISO_CLIENT_DISP_1; #endif } else { dev_err(&ndev->dev, "unknown dc number:%d\n", dc->ctrl_num); } } memset(&of_fb_res, 0, sizeof(struct resource)); if (tegra_dc_is_nvdisplay()) { /* on T186+ k4.4, k4.9, k4.14, fb is passed through the DT */ ret = of_tegra_get_fb_resource(np, &of_fb_res, "surface"); if (!ret) ret = of_tegra_get_fb_resource(np, &of_lut_res, "lut"); } else { #if KERNEL_VERSION(4, 14, 0) <= LINUX_VERSION_CODE /* on T210 k4.14, use the DT -FIXME: debug T210 lut passing */ (void) of_lut_res; ret = of_tegra_get_fb_resource(np, &of_fb_res, "surface"); #else /* FIXME: still use the command line for T210 k4.9 */ tegra_get_fb_resource(&of_fb_res, dc->ctrl_num); #endif } if (ret < 0) goto err_iounmap_reg; fb_mem = kzalloc(sizeof(struct resource), GFP_KERNEL); if (fb_mem == NULL) { ret = -ENOMEM; goto err_iounmap_reg; } fb_mem->name = "fbmem"; fb_mem->flags = IORESOURCE_MEM; fb_mem->start = (resource_size_t)of_fb_res.start; fb_mem->end = (resource_size_t)of_fb_res.end; if (tegra_dc_is_nvdisplay()) snprintf(clk_name, sizeof(clk_name), "nvdisplay_p%u", dc->ctrl_num); else snprintf(clk_name, sizeof(clk_name), "disp%u", dc->ctrl_num + 1); clk = tegra_disp_clk_get(&ndev->dev, clk_name); if (IS_ERR_OR_NULL(clk)) { dev_err(&ndev->dev, "can't get clock: %s\n", clk_name); ret = -ENOENT; goto err_iounmap_reg; } dc->clk = clk; dc->shift_clk_div.mul = dc->shift_clk_div.div = 1; /* Initialize one shot work delay, it will be assigned by dsi * according to refresh rate later. */ dc->one_shot_delay_ms = 40; dc->base = base; dc->irq = irq; dc->ndev = ndev; dc->fb_mem = fb_mem; dc->pdata = dt_pdata; dc->bw_kbps = 0; if (tegra_dc_is_nvdisplay()) { /* dc variables need to initialized before nvdisp init */ ret = tegra_nvdisp_init(dc); if (ret) goto err_iounmap_reg; } mutex_init(&dc->lock); mutex_init(&dc->one_shot_lock); mutex_init(&dc->lp_lock); mutex_init(&dc->msrmnt_info.lock); init_completion(&dc->frame_end_complete); init_completion(&dc->crc_complete); init_completion(&dc->hpd_complete); init_waitqueue_head(&dc->wq); init_waitqueue_head(&dc->timestamp_wq); INIT_WORK(&dc->vblank_work, tegra_dc_vblank); dc->vblank_ref_count = 0; INIT_WORK(&dc->frame_end_work, tegra_dc_frame_end); INIT_WORK(&dc->vpulse2_work, tegra_dc_vpulse2); dc->vpulse2_ref_count = 0; INIT_DELAYED_WORK(&dc->underflow_work, tegra_dc_underflow_worker); INIT_DELAYED_WORK(&dc->one_shot_work, tegra_dc_one_shot_worker); if (tegra_dc_is_nvdisplay()) INIT_DELAYED_WORK(&dc->vrr_work, tegra_nvdisp_vrr_work); if (tegra_dc_is_t21x()) tegra_dc_init_lut_defaults(&dc->fb_lut); if (tegra_dc_is_nvdisplay()) tegra_dc_init_nvdisp_lut_defaults(&dc->fb_nvdisp_lut); dc->n_windows = tegra_dc_get_numof_dispwindows(); if (tegra_dc_is_t21x()) { for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) { struct tegra_dc_win *tmp_win = &dc->tmp_wins[i]; struct tegra_dc_win *win = &dc->windows[i]; win->dc = dc; if (test_bit(i, &dc->valid_windows)) { win->idx = i; tmp_win->idx = i; tmp_win->dc = dc; tegra_dc_init_win_csc_defaults(&win->win_csc); tegra_dc_init_lut_defaults(&win->lut); } } } if (tegra_dc_is_nvdisplay()) { if (dc->pdata->nvdisp_cmu) dc->default_csc = dc->pdata->nvdisp_cmu->panel_csc; else tegra_nvdisp_init_win_csc_defaults(&dc->default_csc); } platform_set_drvdata(ndev, dc); #ifdef CONFIG_SWITCH dc->modeset_switch.name = dev_name(&ndev->dev); dc->modeset_switch.state = 0; dc->modeset_switch.print_state = switch_modeset_print_mode; ret = switch_dev_register(&dc->modeset_switch); if (ret < 0) { dev_err(&ndev->dev, "failed to register switch driver ret(%d)\n", ret); dc->switchdev_registered = false; } else dc->switchdev_registered = true; #endif tegra_dc_feature_register(dc); if (dc->pdata->default_out) { if (dc->pdata->default_out->hotplug_init) dc->pdata->default_out->hotplug_init(&dc->ndev->dev); ret = tegra_dc_set_out(dc, dc->pdata->default_out, false); if (ret < 0) { dev_err(&dc->ndev->dev, "failed to initialize DC out ops\n"); goto err_put_clk; } } else { dev_err(&ndev->dev, "No default output specified. Leaving output disabled.\n"); } dc->mode_dirty = false; /* ignore changes tegra_dc_set_out has done */ if (!dc->out) { ret = -EINVAL; goto err_put_clk; } if (tegra_dc_is_nvdisplay()) nvdisp_register_backlight_notifier(dc); dc->boot_topology.disp_id = dc->ndev->id; dc->boot_topology.protocol = dc->out->type; if (dc->out_ops->get_connector_instance) dc->boot_topology.conn_inst = dc->out_ops->get_connector_instance(dc); else dc->boot_topology.conn_inst = TEGRA_DC_TOPOLOGY_INVALID; dc->boot_topology.valid = true; dc->current_topology = dc->boot_topology; if ((dc->pdata->flags & TEGRA_DC_FLAG_ENABLED) && dc->out->type == TEGRA_DC_OUT_LVDS) { struct fb_monspecs specs; struct tegra_dc_lvds_data *lvds = tegra_dc_get_outdata(dc); if (!tegra_edid_get_monspecs(lvds->edid, &specs)) tegra_dc_set_fb_mode(dc, specs.modedb, false); } #ifndef CONFIG_TEGRA_ISOMGR /* * The emc is a shared clock, it will be set based on * the requirements for each user on the bus. */ snprintf(clk_name, sizeof(clk_name), "disp%u_emc", dc->ctrl_num + 1); emc_clk = tegra_disp_clk_get(&ndev->dev, clk_name); if (IS_ERR_OR_NULL(emc_clk)) { dev_err(&ndev->dev, "can't get %s clock\n", clk_name); ret = -ENOENT; goto err_put_clk; } dc->emc_clk = emc_clk; #endif /* * The emc_la clock is being added to set the floor value * for emc depending on the LA calculaions for each window */ if (!tegra_dc_is_nvdisplay()) { snprintf(clk_name, sizeof(clk_name), "disp%u_la_emc", dc->ctrl_num + 1); emc_la_handle = tegra_bwmgr_register( display_la_emc_client_id[dc->ctrl_num]); if (IS_ERR_OR_NULL(emc_la_handle)) { dev_err(&ndev->dev, "can't get handle %s\n", clk_name); ret = -ENOENT; goto err_disable_dc; } dc->emc_la_handle = emc_la_handle; ret = tegra_bwmgr_set_emc(dc->emc_la_handle, 0, TEGRA_BWMGR_SET_EMC_FLOOR); if (ret) { dev_err(&ndev->dev, "can't set emc clock: %d\n", ret); ret = -EINVAL; goto err_disable_dc; } } dc->ext = tegra_dc_ext_register(ndev, dc); if (IS_ERR_OR_NULL(dc->ext)) { dev_warn(&ndev->dev, "Failed to enable Tegra DC extensions.\n"); dc->ext = NULL; } /* interrupt handler must be registered before tegra_fb_register() */ if (request_threaded_irq(irq, NULL, tegra_dc_irq, IRQF_ONESHOT, dev_name(&ndev->dev), dc)) { dev_err(&ndev->dev, "request_irq %d failed\n", irq); ret = -EBUSY; goto err_disable_dc; } disable_dc_irq(dc); tegra_pd_add_device(&ndev->dev); pm_runtime_use_autosuspend(&ndev->dev); pm_runtime_set_autosuspend_delay(&ndev->dev, 100); pm_runtime_enable(&ndev->dev); /* Enable async suspend/resume to reduce LP0 latency */ device_enable_async_suspend(&ndev->dev); /* if bootloader leaves this head enabled, then skip CMU programming. */ dc->is_cmu_set_bl = (dc->pdata->flags & TEGRA_DC_FLAG_ENABLED) != 0; dc->cmu_enabled = dc->pdata->cmu_enable; #if defined(CONFIG_TEGRA_ISOMGR) if (tegra_dc_is_t21x()) { if (isomgr_client_id == -1) { dc->isomgr_handle = NULL; } else { dc->isomgr_handle = tegra_isomgr_register( isomgr_client_id, tegra_dc_calc_min_bandwidth(dc), tegra_dc_bandwidth_renegotiate, dc); if (IS_ERR(dc->isomgr_handle)) { dev_err(&dc->ndev->dev, "could not register isomgr. err=%ld\n", PTR_ERR(dc->isomgr_handle)); ret = -ENOENT; goto err_disable_dc; } dc->reserved_bw = tegra_dc_calc_min_bandwidth(dc); /* * Use maximum value so we can try to reserve as much as * needed until we are told by isomgr to backoff. */ dc->available_bw = UINT_MAX; } } #endif /* Initialize the flip stats to 0. */ atomic64_set(&dc->flip_stats.flips_queued, 0); atomic64_set(&dc->flip_stats.flips_skipped, 0); atomic64_set(&dc->flip_stats.flips_cmpltd, 0); tegra_dc_create_debugfs(dc); dc->frm_lck_info.frame_lock_enable = dc->pdata->frame_lock_enable; dc->frm_lck_info.job_pending = false; init_waitqueue_head(&(dc->frm_lck_info.win_upd_reqs)); dev_info(&ndev->dev, "probed\n"); if (dc->pdata->fb) { if (dc->enabled && dc->pdata->fb->bits_per_pixel == -1) { unsigned long fmt; tegra_dc_writel(dc, WINDOW_A_SELECT << dc->pdata->fb->win, DC_CMD_DISPLAY_WINDOW_HEADER); fmt = tegra_dc_readl(dc, DC_WIN_COLOR_DEPTH); dc->pdata->fb->bits_per_pixel = tegra_dc_fmt_bpp(fmt); } mode = tegra_dc_get_override_mode(dc); if (mode) { dc->pdata->fb->xres = mode->h_active; dc->pdata->fb->yres = mode->v_active; } /* if current mode is not set, add 640x480 to current mode * because fb_register always adds the current mode to the * modelist. */ if (tegra_dc_is_t21x() && (!dc->mode.pclk || !dc->mode.h_active || !dc->mode.v_active)) tegra_dc_set_fb_mode(dc, &tegra_dc_vga_mode, false); ret = tegra_dc_io_start(dc); dc->fb = tegra_fb_register(ndev, dc, dc->pdata->fb, fb_mem); if (!ret) tegra_dc_io_end(dc); if (IS_ERR_OR_NULL(dc->fb)) { dc->fb = NULL; dev_err(&ndev->dev, "failed to register fb\n"); ret = -EINVAL; goto err_remove_debugfs; } } if (dc->pdata->flags & TEGRA_DC_FLAG_ENABLED) { /* WAR: BL is putting DC in bad state for EDP configuration */ if (!tegra_platform_is_vdk() && (dc->out->type == TEGRA_DC_OUT_DP)) { if (tegra_dc_is_t21x()) { dc->rst = of_reset_control_get(np, "dc_rst"); if (IS_ERR_OR_NULL(dc->rst)) { dev_err(&dc->ndev->dev, "Unable to get dc_rst%u reset control\n", dc->ctrl_num); ret = dc->rst ? PTR_ERR(dc->rst) : -ENOENT; goto err_remove_debugfs; } if (dc->rst) { tegra_disp_clk_prepare_enable(dc->clk); reset_control_assert(dc->rst); udelay(10); reset_control_deassert(dc->rst); udelay(10); tegra_disp_clk_disable_unprepare( dc->clk); } } } } if (dc->out_ops && dc->out_ops->hotplug_init) hotplug_init_status = dc->out_ops->hotplug_init(dc); if (dc->out->type == TEGRA_DC_OUT_DP) { ret = tegra_dc_set_fbcon_boot_mode(dc, dc->edid); if (ret) dev_err(&dc->ndev->dev, "Failed to set fbcon mode for DC %d\n", dc->ctrl_num); } if (dc->pdata->flags & TEGRA_DC_FLAG_ENABLED) { _tegra_dc_set_default_videomode(dc); dc->enabled = _tegra_dc_enable(dc); #if IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS) /* BL or PG init will keep DISA unpowergated after booting. * Adding an extra powergate to balance the refcount * since _tegra_dc_enable() increases the refcount. */ if (tegra_dc_is_t21x()) { if (!tegra_platform_is_fpga()) if (dc->powergate_id == TEGRA_POWERGATE_DISA) tegra_dc_powergate_locked(dc); } #endif } if (dc->out_ops) { if (dc->out_ops->detect && hotplug_init_status >= 0) dc->connected = dc->out_ops->detect(dc); else dc->connected = true; } else dc->connected = false; /* Powergate display module when it's unconnected. */ /* detect() function, if presetns, responsible for the powergate */ if (!tegra_dc_get_connected(dc) && !(dc->out_ops && dc->out_ops->detect)) tegra_dc_powergate_locked(dc); tegra_dc_create_sysfs(&dc->ndev->dev); /* * Overriding the display mode only applies for modes set up during * boot. It should not apply for e.g. HDMI hotplug. */ dc->initialized = false; /* * Initialize vedid state. This is placed here * to allow persistence across sw HDMI hotplugs. */ dc->vedid = false; dc->vedid_data = NULL; mutex_unlock(&tegra_dc_registration_lock); return 0; err_remove_debugfs: tegra_dc_remove_debugfs(dc); free_irq(irq, dc); err_disable_dc: if (dc->ext) { tegra_dc_ext_disable(dc->ext); tegra_dc_ext_unregister(dc->ext); } mutex_lock(&dc->lock); if (dc->enabled) _tegra_dc_disable(dc); dc->enabled = false; mutex_unlock(&dc->lock); #if defined(CONFIG_TEGRA_ISOMGR) if (tegra_dc_is_t21x()) tegra_isomgr_unregister(dc->isomgr_handle); #elif !defined(CONFIG_TEGRA_ISOMGR) tegra_disp_clk_put(&ndev->dev, emc_clk); #endif if (!tegra_dc_is_nvdisplay()) tegra_bwmgr_unregister(dc->emc_la_handle); err_put_clk: #ifdef CONFIG_SWITCH if (dc->switchdev_registered) switch_dev_unregister(&dc->modeset_switch); #endif tegra_disp_clk_put(&ndev->dev, clk); err_iounmap_reg: iounmap(base); kfree(fb_mem); err_release_resource_reg: err_free: tegra_dc_clear(dc); kfree(dc); dc = NULL; mutex_unlock(&tegra_dc_registration_lock); return ret; } static int tegra_dc_remove(struct platform_device *ndev) { struct tegra_dc *dc = platform_get_drvdata(ndev); int i; if (!dc) return 0; if (dc->out->type == TEGRA_DC_OUT_HDMI) for (i = 0; i < ARRAY_SIZE(hdmi_extcon_cable_id); i++) if (extcon_hdmi_dc_map[i] == dc->ctrl_num) { extcon_hdmi_dc_map[i] = -1; break; } tegra_dc_remove_sysfs(&dc->ndev->dev); tegra_dc_remove_debugfs(dc); if (dc->fb) { tegra_fb_unregister(dc->fb); kfree(dc->fb_mem); } if (dc->ext) { tegra_dc_ext_disable(dc->ext); tegra_dc_ext_unregister(dc->ext); } kfree(dc->flip_buf.data); kfree(dc->crc_buf.data); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) { mutex_lock(&dc->one_shot_lock); cancel_delayed_work_sync(&dc->one_shot_work); } mutex_lock(&dc->lock); if (dc->enabled) _tegra_dc_disable(dc); dc->enabled = false; mutex_unlock(&dc->lock); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) mutex_unlock(&dc->one_shot_lock); synchronize_irq(dc->irq); /* wait for IRQ handlers to finish */ #ifdef CONFIG_SWITCH if (dc->switchdev_registered) switch_dev_unregister(&dc->modeset_switch); #endif free_irq(dc->irq, dc); #if defined(CONFIG_TEGRA_ISOMGR) if (tegra_dc_is_nvdisplay()) { if (!tegra_platform_is_vdk()) tegra_nvdisp_bandwidth_unregister(); } else { if (dc->isomgr_handle) { tegra_isomgr_unregister(dc->isomgr_handle); dc->isomgr_handle = NULL; } } #else tegra_disp_clk_put(&ndev->dev, dc->emc_clk); #endif if (!tegra_dc_is_nvdisplay()) tegra_bwmgr_unregister(dc->emc_la_handle); tegra_disp_clk_put(&ndev->dev, dc->clk); iounmap(dc->base); tegra_dc_clear(dc); kfree(dc); return 0; } #ifdef CONFIG_PM static int tegra_dc_suspend(struct platform_device *ndev, pm_message_t state) { struct tegra_dc *dc = platform_get_drvdata(ndev); int ret = 0; if (!dc) return ret; trace_display_suspend(dc); dev_info(&ndev->dev, "suspend\n"); ret = tegra_dc_reserve_common_channel(dc); if (ret) { dev_err(&dc->ndev->dev, "%s: DC %d suspend failed due to timeout\n", __func__, dc->ctrl_num); return ret; } tegra_dc_disable_all_wins(dc); tegra_dc_cursor_suspend(dc); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) { mutex_lock(&dc->one_shot_lock); cancel_delayed_work_sync(&dc->one_shot_work); } mutex_lock(&dc->lock); ret = tegra_dc_io_start(dc); if (dc->enabled) { _tegra_dc_disable(dc); dc->enabled = false; dc->reenable_on_resume = true; } dc->suspended = true; if (dc->out_ops && dc->out_ops->suspend) dc->out_ops->suspend(dc); tegra_dc_release_common_channel(dc); if (dc->out && dc->out->postsuspend) { dc->out->postsuspend(); /* avoid resume event due to voltage falling on interfaces that * support hotplug wake. And only do this if a panel is * connected, if we are already disconnected, then no phantom * hotplug can occur by disabling the voltage. */ if ((dc->out->flags & TEGRA_DC_OUT_HOTPLUG_WAKE_LP0) && tegra_dc_get_connected(dc)) msleep(100); } if (!ret) tegra_dc_io_end(dc); tegra_dc_syncpt_flush(dc->ndev, dc->vblank_syncpt); mutex_unlock(&dc->lock); if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) mutex_unlock(&dc->one_shot_lock); synchronize_irq(dc->irq); /* wait for IRQ handlers to finish */ return 0; } static int tegra_dc_resume(struct platform_device *ndev) { struct tegra_dc *dc = platform_get_drvdata(ndev); if (!dc) return 0; trace_display_resume(dc); dev_info(&ndev->dev, "resume\n"); tegra_dc_syncpt_preset_maxval(dc->ndev, dc->vblank_syncpt); /* To pan the fb on resume */ tegra_fb_pan_display_reset(dc->fb); if (dc->out && dc->out->hotplug_init) dc->out->hotplug_init(&ndev->dev); if (dc->out_ops && dc->out_ops->resume) dc->out_ops->resume(dc); mutex_lock(&dc->lock); if (!dc->enabled && dc->reenable_on_resume) dc->enabled = _tegra_dc_enable(dc); dc->reenable_on_resume = false; dc->suspended = false; mutex_unlock(&dc->lock); tegra_dc_cursor_resume(dc); return 0; } #endif /* CONFIG_PM */ void tegra_dc_shutdown(struct platform_device *ndev) { struct tegra_dc *dc = platform_get_drvdata(ndev); if (WARN_ON(!dc || !dc->out || !dc->out_ops)) return; if (!dc->enabled) return; kfree(dc->vedid_data); dc->vedid_data = NULL; dc->vedid = false; /* Let dc clients know about shutdown event before calling disable */ if (dc->out_ops && dc->out_ops->shutdown) dc->out_ops->shutdown(dc); tegra_dc_disable(dc); } static int suspend_set(const char *val, struct kernel_param *kp) { if (!strcmp(val, "dump")) dump_regs(tegra_dcs[0]); #ifdef CONFIG_PM else if (!strcmp(val, "suspend")) tegra_dc_suspend(tegra_dcs[0]->ndev, PMSG_SUSPEND); else if (!strcmp(val, "resume")) tegra_dc_resume(tegra_dcs[0]->ndev); #endif return 0; } static int suspend_get(char *buffer, struct kernel_param *kp) { return 0; } static int suspend; #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 135)) && (LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0))) module_param_call(suspend, (void *)suspend_set, (void *)suspend_get, &suspend, 0644); #else module_param_call(suspend, suspend_set, suspend_get, &suspend, 0644); #endif #ifndef MODULE static int __init parse_disp_params(char *options, struct tegra_dc_mode *mode) { int i, params[11]; char *p; memset(params, 0, ARRAY_SIZE(params) * sizeof(int)); for (i = 0; i < ARRAY_SIZE(params); i++) { if ((p = strsep(&options, ",")) != NULL) { if (*p) params[i] = simple_strtoul(p, &p, 10); } else return -EINVAL; } if ((mode->pclk = params[0]) == 0) return -EINVAL; mode->h_active = params[1]; mode->v_active = params[2]; mode->h_ref_to_sync = params[3]; mode->v_ref_to_sync = params[4]; mode->h_sync_width = params[5]; mode->v_sync_width = params[6]; mode->h_back_porch = params[7]; mode->v_back_porch = params[8]; mode->h_front_porch = params[9]; mode->v_front_porch = params[10]; return 0; } static int __init tegra_dc_mode_override(char *str) { char *p = str, *options; if (!p || !*p) return -EINVAL; p = strstr(str, "hdmi:"); if (p) { p += 5; options = strsep(&p, ";"); if (parse_disp_params(options, &override_disp_mode[TEGRA_DC_OUT_HDMI])) return -EINVAL; } p = strstr(str, "rgb:"); if (p) { p += 4; options = strsep(&p, ";"); if (parse_disp_params(options, &override_disp_mode[TEGRA_DC_OUT_RGB])) return -EINVAL; } p = strstr(str, "dsi:"); if (p) { p += 4; options = strsep(&p, ";"); if (parse_disp_params(options, &override_disp_mode[TEGRA_DC_OUT_DSI])) return -EINVAL; } p = strstr(str, "null:"); if (p) { p += 5; options = strsep(&p, ";"); if (parse_disp_params(options, &override_disp_mode[TEGRA_DC_OUT_NULL])) return -EINVAL; } return 0; } __setup("disp_params=", tegra_dc_mode_override); #endif #ifdef TEGRA_DC_USR_SHARED_IRQ static struct tegra_dc *tegra_dc_hwidx2dc(int dcid) { struct tegra_dc *dc; int i; for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) { dc = tegra_dc_get_dc(i); if (dc && (dcid == dc->ctrl_num)) return dc; } return NULL; } /* * get Tegra display head status * o inputs: * - dcid: display head HW index (0 to max_dc_heads-1) * - pSts: pointer to the head status structure to be returned * o outputs: * - return: error number * . 0: registration successful without an error * . !0: registration failed with an error * - *pSts: head status * o notes: */ int tegra_dc_get_disphead_sts(int dcid, struct tegra_dc_head_status *pSts) { struct tegra_dc *dc = tegra_dc_hwidx2dc(dcid); if (dc) { pSts->magic = TEGRA_DC_HEAD_STATUS_MAGIC1; pSts->irqnum = dc->irq; pSts->init = dc->initialized ? 1 : 0; pSts->connected = dc->connected ? 1 : 0; pSts->active = dc->enabled ? 1 : 0; return 0; } else { return -ENODEV; } } EXPORT_SYMBOL(tegra_dc_get_disphead_sts); /* * to register the Tegra display ISR user call-back routine * o inputs: * - dcid: display head HW index (0 to max_dc_heads-1) * - usr_isr_cb: function pointer to the user call-back routine * - usr_isr_pdt: user call-back private data * o outputs: * - return: error code * . 0: registration successful without an error * . !0: registration failed with an error * o notes: will overwrite the old CB always */ int tegra_dc_register_isr_usr_cb(int dcid, int (*usr_isr_cb)(int dcid, unsigned long irq_sts, void *usr_isr_pdt), void *usr_isr_pdt) { struct tegra_dc *dc = tegra_dc_hwidx2dc(dcid); /* register usr ISR */ if (dc && usr_isr_cb) { if (dc->isr_usr_cb) { dev_warn(&dc->ndev->dev, "%s DC%d: overwriting ISR USR CB:%p PDT:%p\n", __func__, dcid, dc->isr_usr_cb, dc->isr_usr_pdt); } mutex_lock(&dc->lock); /* always replace the old ISR */ dc->isr_usr_cb = usr_isr_cb; dc->isr_usr_pdt = usr_isr_pdt; mutex_unlock(&dc->lock); dev_info(&dc->ndev->dev, "DC%d: ISR USR CB:%p PDT:%p registered\n", dcid, usr_isr_cb, usr_isr_pdt); return 0; } else { return dc ? -EINVAL : -ENODEV; } } EXPORT_SYMBOL(tegra_dc_register_isr_usr_cb); /* * to unregister the Tegra display ISR user call-back routine * o inputs: * - dcid: display head HW index (0 to max_dc_heads-1) * - usr_isr_cb: registered user call-back. ignored. * - usr_isr_pdt: registered user call-back private data. ignored. * o outputs: * - return: error code * . 0: unregistration successful * . !0: unregistration failed with an error * o notes: will unregister the current CB always */ int tegra_dc_unregister_isr_usr_cb(int dcid, int (*usr_isr_cb)(int dcid, unsigned long irq_sts, void *usr_isr_pdt), void *usr_isr_pdt) { struct tegra_dc *dc = tegra_dc_hwidx2dc(dcid); /* unregister USR ISR CB */ if (dc) { mutex_lock(&dc->lock); dc->isr_usr_cb = NULL; dc->isr_usr_pdt = NULL; mutex_unlock(&dc->lock); dev_info(&dc->ndev->dev, "DC%d: USR ISR CB unregistered\n", dcid); return 0; } else { return -ENODEV; } } EXPORT_SYMBOL(tegra_dc_unregister_isr_usr_cb); #endif /* TEGRA_DC_USR_SHARED_IRQ */ /** * tegra_dc_enable_disable_frame_lock - enables/disables frame_lock in dc. * @dc: Pointer to tegra_dc struct. * @enable: Boolean value for enabling or disabling. * * The only call(entry point) to this function should be from dc_common. * dc_common makes sure that dc!=NULL before calling this API. * * Return: void */ void tegra_dc_enable_disable_frame_lock(struct tegra_dc *dc, bool enable) { mutex_lock(&dc->lock); dc->frm_lck_info.frame_lock_enable = enable; mutex_unlock(&dc->lock); } EXPORT_SYMBOL(tegra_dc_enable_disable_frame_lock); /** * tegra_dc_request_trigger_wins - enables/disables frame_lock in dc. * @dc: Pointer to tegra_dc struct. * @enable: Boolean value for status. * * Return: void */ void tegra_dc_request_trigger_wins(struct tegra_dc *dc) { mutex_lock(&dc->lock); tegra_dc_trigger_windows(dc); mutex_unlock(&dc->lock); } EXPORT_SYMBOL(tegra_dc_request_trigger_wins); int tegra_dc_get_numof_dispheads(void) { if (!hw_data || !hw_data->valid) return -ENODEV; return hw_data->nheads; } EXPORT_SYMBOL(tegra_dc_get_numof_dispheads); int tegra_dc_get_numof_dispwindows(void) { if (!hw_data || !hw_data->valid) return DC_N_WINDOWS; return hw_data->nwins; } EXPORT_SYMBOL(tegra_dc_get_numof_dispwindows); int tegra_dc_get_numof_dispsors(void) { if (!hw_data || !hw_data->valid) return -ENODEV; return hw_data->nsors; } EXPORT_SYMBOL(tegra_dc_get_numof_dispsors); struct tegra_dc_sor_info *tegra_dc_get_sor_cap(void) { if (!hw_data || !hw_data->valid) return NULL; return hw_data->sor_info; } /* tegra_dc_get_max_lines() - gets v_total for current mode * @disp_id : the display id of the concerned head. * * Return : v_total if successful else error value. */ int tegra_dc_get_max_lines(int disp_id) { int max_lines; struct tegra_dc *dc; struct tegra_dc_mode *m; dc = tegra_dc_get_dc(disp_id); if (!dc) return -ENODEV; m = &dc->mode; max_lines = m->v_back_porch + m->v_active + m->v_front_porch + m->v_sync_width; return max_lines; } EXPORT_SYMBOL(tegra_dc_get_max_lines); /* tegra_dc_get_addr_info() - gets the base address for a head * @disp_id : the display id of the concerned head. * @res : ptr to the resource from the caller. * * Return : 0 if successful else error value. */ int tegra_dc_get_addr_info(int disp_id, struct resource *res) { int ret; struct tegra_dc *dc; dc = tegra_dc_get_dc(disp_id); if (!dc || !res) return -ENODEV; ret = of_address_to_resource(dc->ndev->dev.of_node, 0, res); if (ret) return ret; return 0; } EXPORT_SYMBOL(tegra_dc_get_addr_info); /* * tegra_dc_get_vsync_timestamp() - Reads the vsync timestamp. * @dc : pointer to struct tegra_dc for the current head. * * Currently supported for one chip version. TODO for others. * * Return : The 64 bit timestamp value. */ uint64_t tegra_dc_get_vsync_timestamp(struct tegra_dc *dc) { if (!dc->enabled) return 0; if (tegra_dc_is_t21x() || tegra_dc_is_t18x()) return 0; else if (tegra_dc_is_t19x()) return tegra_dc_get_vsync_timestamp_t19x(dc); pr_warn("%s: Couldn't find the right chip version\n", __func__); return 0; } /* * tegra_dc_line2ns() - gets the time required to scan a given number of lines. * @dc : points to struct tegra_dc for the current head. * * Return : time in nanosecs */ static inline u64 tegra_dc_line2ns(struct tegra_dc *dc, int nr_lines) { return nr_lines * dc->mode_metadata.line_in_nsec; } /* * tegra_dc_get_scanline_timestamp() - gets the timestamp of a scanline * @dc : pointer to struct tegra_dc of the cuurent head. * @scanline : the scanline for which timestamp is needed. * * This helper function gives the timestamp of the @scanline when it occurred * last. * * Return : the timestamp value. */ static u64 tegra_dc_get_scanline_timestamp(struct tegra_dc *dc, const u32 scanline) { ktime_t ts; u64 timestamp; int curr_scanline; curr_scanline = tegra_dc_get_v_count(dc); ts = ktime_get(); timestamp = ktime_to_ns(ts); if (scanline < curr_scanline) timestamp -= tegra_dc_line2ns(dc, curr_scanline - scanline); else timestamp -= tegra_dc_line2ns(dc, curr_scanline + (dc->mode_metadata.vtotal_lines - scanline)); return timestamp; } /* * tegra_dc_collect_latency_data() - stores relevant info needed * for latency instrumentation. * @dc : pointer to struct tegra_dc of the current head. * * Currently supports nvdisplay only. Using the already stored * dma_buff_handle and the relevant offset, reads the first 2 pixels of * framebuffer. Returns if not nvdisplay, or the latency_measuring * functionality is disabled or the handle is NULL. * * Return : void */ static void tegra_dc_collect_latency_data(struct tegra_dc *dc) { int ret; struct dma_buf *handle; int page_num = 0; void *ptr; u64 value; if (tegra_dc_is_t21x() || !dc->enabled) return; mutex_lock(&dc->msrmnt_info.lock); handle = dc->msrmnt_info.buf_handle; if (!handle || !dc->msrmnt_info.enabled) { dev_dbg(&dc->ndev->dev, "dma_buff is NULL or latency collection is disabled\n"); mutex_unlock(&dc->msrmnt_info.lock); return; } ret = dma_buf_begin_cpu_access(handle, 0, handle->size, DMA_BIDIRECTIONAL); if (ret) { dev_err(&dc->ndev->dev, "dma_buf_begin_cpu_access failed\n"); mutex_unlock(&dc->msrmnt_info.lock); return; } ptr = dma_buf_kmap(handle, page_num); if (!ptr) { dev_err(&dc->ndev->dev, "dma_buf_kmap failed\n"); dma_buf_end_cpu_access(handle, 0, handle->size, DMA_BIDIRECTIONAL); mutex_unlock(&dc->msrmnt_info.lock); return; } value = *((u64 *)(ptr + dc->msrmnt_info.offset)); trace_display_embedded_latency(dc->ctrl_num, dc->msrmnt_info.line_num, be64_to_cpup(&value)); dma_buf_kunmap(handle, page_num, ptr); dma_buf_end_cpu_access(handle, 0, handle->size, DMA_BIDIRECTIONAL); mutex_unlock(&dc->msrmnt_info.lock); } /* * tegra_dc_en_dis_latency_msrmnt_mode() - enables/disables latency * measurement mode in dc. * @dc : pointer to struct tegra_dc of the current head. * @enable : tells if the latency_measuring functionality has to be enabled * or disabled. * * This wrapper functions calls into the pertinent enable/disable function based * on the chip type. * * Return : 0 if successful else relevant error number */ int tegra_dc_en_dis_latency_msrmnt_mode(struct tegra_dc *dc, int enable) { if (!dc) return -ENODEV; if (tegra_dc_is_nvdisplay()) tegra_nvdisp_set_msrmnt_mode(dc, enable); return 0; } struct tegra_dc_pd_table *tegra_dc_get_disp_pd_table(void) { if (!hw_data || !hw_data->valid) return ERR_PTR(-ENODEV); return hw_data->pd_table; } static int tegra_dc_assign_hw_data(void) { const struct of_device_id *match = NULL; if (hw_data) { pr_warn("%s: hw data already assigned\n", __func__); return 0; } of_find_matching_node_and_match(NULL, tegra_display_of_match, &match); if (!match) { pr_err("%s: no matching compatible node\n", __func__); return -ENODEV; } hw_data = (struct tegra_dc_hw_data *)match->data; if (!hw_data->valid) { hw_data = NULL; pr_err("%s: hw_data is not valid for %s\n", __func__, match->compatible); return -EINVAL; } return 0; } inline bool tegra_dc_is_t21x(void) { return hw_data && (hw_data->version == TEGRA_DC_HW_T210); } inline bool tegra_dc_is_t18x(void) { return hw_data && (hw_data->version == TEGRA_DC_HW_T18x); } inline bool tegra_dc_is_t19x(void) { return hw_data && (hw_data->version == TEGRA_DC_HW_T19x); } inline bool tegra_dc_is_nvdisplay(void) { return (tegra_dc_is_t18x() || tegra_dc_is_t19x()); } EXPORT_SYMBOL(tegra_dc_is_nvdisplay); static struct tegra_dc_sor_info t21x_sor_info[] = { { .hdcp_supported = false }, /* SOR0 */ { .hdcp_supported = true }, /* SOR1 */ }; static void tegra_dc_populate_t21x_hw_data(struct tegra_dc_hw_data *hw_data) { if (!hw_data) return; hw_data->nheads = 2; hw_data->nwins = 5; hw_data->nsors = 2; hw_data->sor_info = t21x_sor_info; /* unused */ hw_data->pd_table = NULL; hw_data->valid = true; hw_data->version = TEGRA_DC_HW_T210; } /** * tegra_dc_hw_init - Initializes hardware specific data for dc. * * The only call(entry point) to this function as of today should * be from dc_common. Since dc_common needs to be probed before * tegradc and the former also needs some hardware related info, * dc_common initializes hw data in its module_init(). The order * of calls to module_init() is kept as dc_common's followed by * tegra_dc's module_init(). * * Return: 0 if success else corresponding error number from * @tegra_dc_assign_hw_data(). */ int tegra_dc_hw_init(void) { int ret; tegra_dc_populate_t21x_hw_data(&t21x_hw_data); if (tegra_dc_populate_t18x_hw_data) tegra_dc_populate_t18x_hw_data(&t18x_hw_data); if (tegra_dc_populate_t19x_hw_data) tegra_dc_populate_t19x_hw_data(&t19x_hw_data); ret = tegra_dc_assign_hw_data(); return ret; } static struct platform_driver tegra_dc_driver = { .driver = { .name = "tegradc", .owner = THIS_MODULE, .of_match_table = of_match_ptr(tegra_display_of_match), .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .probe = tegra_dc_probe, .remove = tegra_dc_remove, #ifdef CONFIG_PM .suspend = tegra_dc_suspend, .resume = tegra_dc_resume, #endif .shutdown = tegra_dc_shutdown, }; static int __init tegra_dc_module_init(void) { int ret; int max_heads; #if defined(CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT) && defined(CONFIG_DEBUG_FS) int i; #endif max_heads = tegra_dc_get_numof_dispheads(); if (max_heads < 0) { printk(KERN_ERR "tegradc module_init failed\n"); return -ENOENT; } tegra_dcs = kzalloc(max_heads * sizeof(struct tegra_dc *), GFP_KERNEL); if (!tegra_dcs) return -ENOMEM; #if defined(CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT) && defined(CONFIG_DEBUG_FS) boot_out_type = kzalloc(max_heads * sizeof(int), GFP_KERNEL); if (!boot_out_type) { kfree(tegra_dcs); return -ENOMEM; } for (i = 0; i < max_heads; i++) boot_out_type[i] = -1; #endif ret = tegra_dc_ext_module_init(); if (ret) { #if defined(CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT) && defined(CONFIG_DEBUG_FS) kfree(boot_out_type); #endif kfree(tegra_dcs); return ret; } return platform_driver_register(&tegra_dc_driver); } static void __exit tegra_dc_module_exit(void) { #if defined(CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT) && defined(CONFIG_DEBUG_FS) kfree(boot_out_type); #endif kfree(tegra_dcs); platform_driver_unregister(&tegra_dc_driver); tegra_dc_ext_module_exit(); } module_exit(tegra_dc_module_exit); module_init(tegra_dc_module_init);