3100 lines
81 KiB
C
3100 lines
81 KiB
C
/*
|
|
* drivers/video/tegra/dc/dc.c
|
|
*
|
|
* Copyright (C) 2010 Google, Inc.
|
|
* Author: Erik Gilling <konkers@android.com>
|
|
*
|
|
* Copyright (c) 2010-2013, 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 <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/err.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/backlight.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/nvhost.h>
|
|
#include <video/tegrafb.h>
|
|
#include <drm/drm_fixed.h>
|
|
#include <linux/clk-provider.h>
|
|
|
|
#define CREATE_TRACE_POINTS
|
|
#include <trace/events/display.h>
|
|
|
|
#include <linux/nvhost.h>
|
|
|
|
#include "dc_reg.h"
|
|
#include "dc_config.h"
|
|
#include "dc_priv.h"
|
|
#include "dev.h"
|
|
#include "nvsd.h"
|
|
#include "dp.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
|
|
|
|
static struct fb_videomode tegra_dc_hdmi_fallback_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,
|
|
};
|
|
|
|
static struct tegra_dc_mode override_disp_mode[3];
|
|
|
|
static void _tegra_dc_controller_disable(struct tegra_dc *dc);
|
|
|
|
struct tegra_dc *tegra_dcs[TEGRA_MAX_DC];
|
|
|
|
DEFINE_MUTEX(tegra_dc_lock);
|
|
DEFINE_MUTEX(shared_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 },
|
|
};
|
|
|
|
#ifdef CONFIG_TEGRA_DC_CMU
|
|
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, 17, 18, 19, 19, 20, 21,
|
|
22, 22, 23, 24, 24, 25, 26, 26,
|
|
27, 27, 28, 29, 29, 30, 30, 31,
|
|
31, 32, 32, 32, 33, 33, 34, 34,
|
|
35, 35, 35, 36, 36, 36, 37, 37,
|
|
38, 38, 38, 39, 39, 39, 40, 40,
|
|
40, 41, 41, 41, 41, 42, 42, 42,
|
|
43, 43, 43, 43, 44, 44, 44, 45,
|
|
45, 45, 45, 46, 46, 46, 46, 47,
|
|
47, 47, 47, 48, 48, 48, 48, 49,
|
|
49, 49, 49, 49, 50, 50, 50, 50,
|
|
51, 51, 51, 51, 51, 52, 52, 52,
|
|
52, 53, 53, 53, 53, 53, 54, 54,
|
|
54, 54, 54, 55, 55, 55, 55, 55,
|
|
56, 56, 56, 56, 56, 56, 57, 57,
|
|
57, 57, 57, 58, 58, 58, 58, 58,
|
|
58, 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, 63, 64,
|
|
64, 64, 64, 64, 64, 65, 65, 65,
|
|
65, 65, 65, 65, 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, 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, 73, 74, 74,
|
|
74, 74, 74, 74, 74, 74, 75, 75,
|
|
75, 75, 75, 75, 75, 75, 76, 76,
|
|
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,
|
|
80, 81, 81, 81, 81, 81, 81, 81,
|
|
81, 81, 81, 82, 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, 84, 85,
|
|
85, 85, 85, 85, 85, 85, 85, 85,
|
|
85, 86, 86, 86, 86, 86, 86, 86,
|
|
86, 86, 86, 87, 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,
|
|
89, 89, 90, 90, 90, 90, 90, 90,
|
|
90, 90, 90, 90, 91, 91, 91, 91,
|
|
91, 91, 91, 91, 91, 91, 91, 92,
|
|
92, 92, 92, 92, 92, 92, 92, 92,
|
|
92, 92, 93, 93, 93, 93, 93, 93,
|
|
93, 93, 93, 93, 93, 94, 94, 94,
|
|
94, 94, 94, 94, 94, 94, 94, 94,
|
|
94, 95, 95, 95, 95, 95, 95, 95,
|
|
95, 95, 95, 95, 96, 96, 96, 96,
|
|
96, 96, 96, 96, 96, 96, 96, 96,
|
|
97, 97, 97, 97, 97, 97, 97, 97,
|
|
97, 97, 97, 97, 98, 98, 98, 98,
|
|
98, 98, 98, 98, 98, 98, 98, 98,
|
|
99, 99, 99, 99, 99, 99, 99, 99,
|
|
99, 99, 99, 99, 100, 100, 100, 100,
|
|
100, 100, 100, 100, 100, 100, 100, 100,
|
|
100, 101, 101, 101, 101, 101, 101, 101,
|
|
102, 102, 103, 104, 104, 105, 105, 106,
|
|
107, 107, 108, 108, 109, 109, 110, 111,
|
|
111, 112, 112, 113, 113, 114, 114, 115,
|
|
115, 116, 116, 117, 117, 118, 118, 119,
|
|
119, 120, 120, 121, 121, 122, 122, 123,
|
|
123, 124, 124, 125, 125, 126, 126, 127,
|
|
127, 127, 128, 128, 129, 129, 130, 130,
|
|
131, 131, 131, 132, 132, 133, 133, 134,
|
|
134, 134, 135, 135, 136, 136, 136, 137,
|
|
137, 138, 138, 139, 139, 139, 140, 140,
|
|
141, 141, 141, 142, 142, 142, 143, 143,
|
|
144, 144, 144, 145, 145, 145, 146, 146,
|
|
147, 147, 147, 148, 148, 148, 149, 149,
|
|
150, 150, 150, 151, 151, 151, 152, 152,
|
|
152, 153, 153, 153, 154, 154, 154, 155,
|
|
155, 155, 156, 156, 156, 157, 157, 157,
|
|
158, 158, 158, 159, 159, 159, 160, 160,
|
|
160, 161, 161, 161, 162, 162, 162, 163,
|
|
163, 163, 164, 164, 164, 165, 165, 165,
|
|
166, 166, 166, 166, 167, 167, 167, 168,
|
|
168, 168, 169, 169, 169, 169, 170, 170,
|
|
170, 171, 171, 171, 172, 172, 172, 172,
|
|
173, 173, 173, 174, 174, 174, 174, 175,
|
|
175, 175, 176, 176, 176, 176, 177, 177,
|
|
177, 178, 178, 178, 178, 179, 179, 179,
|
|
180, 180, 180, 180, 181, 181, 181, 181,
|
|
182, 182, 182, 183, 183, 183, 183, 184,
|
|
184, 184, 184, 185, 185, 185, 185, 186,
|
|
186, 186, 187, 187, 187, 187, 188, 188,
|
|
188, 188, 189, 189, 189, 189, 190, 190,
|
|
190, 190, 191, 191, 191, 191, 192, 192,
|
|
192, 192, 193, 193, 193, 193, 194, 194,
|
|
194, 194, 195, 195, 195, 195, 196, 196,
|
|
196, 196, 197, 197, 197, 197, 198, 198,
|
|
198, 198, 199, 199, 199, 199, 199, 200,
|
|
200, 200, 200, 201, 201, 201, 201, 202,
|
|
202, 202, 202, 203, 203, 203, 203, 203,
|
|
204, 204, 204, 204, 205, 205, 205, 205,
|
|
206, 206, 206, 206, 206, 207, 207, 207,
|
|
207, 208, 208, 208, 208, 208, 209, 209,
|
|
209, 209, 210, 210, 210, 210, 210, 211,
|
|
211, 211, 211, 212, 212, 212, 212, 212,
|
|
213, 213, 213, 213, 213, 214, 214, 214,
|
|
214, 215, 215, 215, 215, 215, 216, 216,
|
|
216, 216, 216, 217, 217, 217, 217, 218,
|
|
218, 218, 218, 218, 219, 219, 219, 219,
|
|
219, 220, 220, 220, 220, 220, 221, 221,
|
|
221, 221, 221, 222, 222, 222, 222, 222,
|
|
223, 223, 223, 223, 224, 224, 224, 224,
|
|
224, 225, 225, 225, 225, 225, 226, 226,
|
|
226, 226, 226, 227, 227, 227, 227, 227,
|
|
227, 228, 228, 228, 228, 228, 229, 229,
|
|
229, 229, 229, 230, 230, 230, 230, 230,
|
|
231, 231, 231, 231, 231, 232, 232, 232,
|
|
232, 232, 233, 233, 233, 233, 233, 233,
|
|
234, 234, 234, 234, 234, 235, 235, 235,
|
|
},
|
|
};
|
|
#endif
|
|
|
|
void tegra_dc_clk_enable(struct tegra_dc *dc)
|
|
{
|
|
clk_prepare_enable(dc->clk);
|
|
}
|
|
|
|
void tegra_dc_clk_disable(struct tegra_dc *dc)
|
|
{
|
|
clk_disable_unprepare(dc->clk);
|
|
}
|
|
|
|
void tegra_dc_get(struct tegra_dc *dc)
|
|
{
|
|
tegra_dc_io_start(dc);
|
|
|
|
/* extra reference to dc clk */
|
|
clk_prepare_enable(dc->clk);
|
|
}
|
|
|
|
void tegra_dc_put(struct tegra_dc *dc)
|
|
{
|
|
/* balance extra dc clk reference */
|
|
clk_disable_unprepare(dc->clk);
|
|
|
|
tegra_dc_io_end(dc);
|
|
}
|
|
|
|
void tegra_dc_hold_dc_out(struct tegra_dc *dc)
|
|
{
|
|
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 (dc->out_ops && dc->out_ops->release)
|
|
dc->out_ops->release(dc);
|
|
tegra_dc_put(dc);
|
|
}
|
|
|
|
#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)
|
|
|
|
static void _dump_regs(struct tegra_dc *dc, void *data,
|
|
void (* print)(void *data, const char *str))
|
|
{
|
|
int i;
|
|
char buff[256];
|
|
|
|
mutex_lock(&dc->lock);
|
|
tegra_dc_get(dc);
|
|
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);
|
|
#if !defined(CONFIG_TEGRA_DC_BLENDER_GEN2)
|
|
DUMP_REG(DC_DISP_BORDER_COLOR);
|
|
#endif
|
|
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);
|
|
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);
|
|
#if defined(CONFIG_TEGRA_DC_INTERLACE)
|
|
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);
|
|
#endif
|
|
for (i = 0; i < 3; i++) {
|
|
print(data, "\n");
|
|
snprintf(buff, sizeof(buff), "WINDOW %c:\n", 'A' + 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_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);
|
|
|
|
#if defined(CONFIG_ARCH_TEGRA_124_SOC)
|
|
if (is_tegra124()) {
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
DUMP_REG(DC_WIN_BUFFER_SURFACE_KIND);
|
|
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);
|
|
}
|
|
|
|
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);
|
|
#ifdef CONFIG_TEGRA_DC_CMU
|
|
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_KBR);
|
|
DUMP_REG(DC_COM_CMU_CSC_KRB);
|
|
DUMP_REG(DC_COM_CMU_CSC_KGB);
|
|
DUMP_REG(DC_COM_CMU_CSC_KBB);
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
static void dump_regs(struct tegra_dc *dc)
|
|
{
|
|
_dump_regs(dc, dc, dump_regs_print);
|
|
}
|
|
#else /* !DEBUG */
|
|
|
|
static void dump_regs(struct tegra_dc *dc) {}
|
|
|
|
#endif /* DEBUG */
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
|
|
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;
|
|
|
|
_dump_regs(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"
|
|
"stereo_mode: %d\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->stereo_mode);
|
|
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"
|
|
"underflows_a: %llu\n"
|
|
"underflows_b: %llu\n"
|
|
"underflows_c: %llu\n",
|
|
dc->stats.underflows,
|
|
dc->stats.underflows_a,
|
|
dc->stats.underflows_b,
|
|
dc->stats.underflows_c);
|
|
|
|
#if defined(CONFIG_ARCH_TEGRA_124_SOC)
|
|
if (is_tegra124()) {
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
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 int 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;
|
|
|
|
if (event == 0x1) /* TEGRA_DC_EXT_EVENT_HOTPLUG */
|
|
tegra_dc_ext_process_hotplug(dc->ndev->id);
|
|
else if (event == 0x2) /* TEGRA_DC_EXT_EVENT_BANDWIDTH */
|
|
tegra_dc_ext_process_bandwidth_renegotiate(dc->ndev->id);
|
|
else {
|
|
dev_err(&dc->ndev->dev, "Unknown event 0x%lx\n", event);
|
|
return -EINVAL; /* unknown event number */
|
|
}
|
|
return len;
|
|
}
|
|
|
|
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 void tegra_dc_remove_debugfs(struct tegra_dc *dc)
|
|
{
|
|
debugfs_remove_recursive(dc->debugdir);
|
|
}
|
|
|
|
static void tegra_dc_create_debugfs(struct tegra_dc *dc)
|
|
{
|
|
struct dentry *retval;
|
|
|
|
dc->debugdir = debugfs_create_dir(dev_name(&dc->ndev->dev), NULL);
|
|
if (!dc->debugdir)
|
|
goto remove_out;
|
|
|
|
retval = debugfs_create_file("regs", S_IRUGO, dc->debugdir, dc,
|
|
®s_fops);
|
|
if (!retval)
|
|
goto remove_out;
|
|
|
|
retval = debugfs_create_file("mode", S_IRUGO, dc->debugdir, dc,
|
|
&mode_fops);
|
|
if (!retval)
|
|
goto remove_out;
|
|
|
|
retval = debugfs_create_file("stats", S_IRUGO, dc->debugdir, dc,
|
|
&stats_fops);
|
|
if (!retval)
|
|
goto remove_out;
|
|
|
|
retval = debugfs_create_file("event_inject", S_IRUGO, dc->debugdir, dc,
|
|
&event_inject_fops);
|
|
if (!retval)
|
|
goto remove_out;
|
|
|
|
return;
|
|
remove_out:
|
|
dev_err(&dc->ndev->dev, "could not create debugfs\n");
|
|
tegra_dc_remove_debugfs(dc);
|
|
}
|
|
|
|
#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 */
|
|
|
|
static int tegra_dc_set(struct tegra_dc *dc, int index)
|
|
{
|
|
int ret = 0;
|
|
|
|
mutex_lock(&tegra_dc_lock);
|
|
if (index >= TEGRA_MAX_DC) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (dc != NULL && tegra_dcs[index] != NULL) {
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
tegra_dcs[index] = dc;
|
|
|
|
out:
|
|
mutex_unlock(&tegra_dc_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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_MAX_DC; idx++)
|
|
cnt += ((dc = tegra_dcs[idx]) != NULL && dc->enabled) ? 1 : 0;
|
|
mutex_unlock(&tegra_dc_lock);
|
|
|
|
return (cnt > 1);
|
|
}
|
|
|
|
/* 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;
|
|
BUG_ON(win > dc->n_windows);
|
|
mutex_lock(&dc->lock);
|
|
tegra_dc_get(dc);
|
|
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_MAX_DC)
|
|
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 >= dc->n_windows)
|
|
return NULL;
|
|
|
|
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 sense;
|
|
int level;
|
|
int hpd;
|
|
|
|
if (WARN_ON(!dc || !dc->out))
|
|
return false;
|
|
|
|
if (dc->out->hotplug_state != 0) {
|
|
if (dc->out->hotplug_state == 1) /* force on */
|
|
return true;
|
|
if (dc->out->hotplug_state == -1) /* force off */
|
|
return false;
|
|
}
|
|
level = gpio_get_value_cansleep(dc->out->hotplug_gpio);
|
|
|
|
sense = dc->out->flags & TEGRA_DC_OUT_HOTPLUG_MASK;
|
|
|
|
hpd = (sense == TEGRA_DC_OUT_HOTPLUG_HIGH && level) ||
|
|
(sense == TEGRA_DC_OUT_HOTPLUG_LOW && !level);
|
|
|
|
if (dc->out->hotplug_report)
|
|
dc->out->hotplug_report(hpd);
|
|
|
|
return hpd;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_hpd);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_TEGRA_DC_CMU
|
|
static void tegra_dc_cache_cmu(struct tegra_dc_cmu *dst_cmu,
|
|
struct tegra_dc_cmu *src_cmu)
|
|
{
|
|
memcpy(dst_cmu, src_cmu, sizeof(struct tegra_dc_cmu));
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void tegra_dc_get_cmu(struct tegra_dc *dc, struct tegra_dc_cmu *cmu)
|
|
{
|
|
u32 val;
|
|
u32 i;
|
|
bool flags;
|
|
|
|
val = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL);
|
|
if (val & CMU_ENABLE)
|
|
flags = true;
|
|
|
|
val &= ~CMU_ENABLE;
|
|
tegra_dc_writel(dc, val, DC_DISP_DISP_COLOR_CONTROL);
|
|
tegra_dc_writel(dc, GENERAL_UPDATE, DC_CMD_STATE_CONTROL);
|
|
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
|
|
|
/*TODO: Sync up with frame end */
|
|
mdelay(20);
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
val = LUT1_READ_EN | LUT1_READ_ADDR(i);
|
|
tegra_dc_writel(dc, val, DC_COM_CMU_LUT1_READ);
|
|
val = tegra_dc_readl(dc, DC_COM_CMU_LUT1);
|
|
cmu->lut1[i] = LUT1_READ_DATA(val);
|
|
}
|
|
|
|
cmu->csc.krr = tegra_dc_readl(dc, DC_COM_CMU_CSC_KRR);
|
|
cmu->csc.kgr = tegra_dc_readl(dc, DC_COM_CMU_CSC_KGR);
|
|
cmu->csc.kbr = tegra_dc_readl(dc, DC_COM_CMU_CSC_KBR);
|
|
cmu->csc.krg = tegra_dc_readl(dc, DC_COM_CMU_CSC_KRG);
|
|
cmu->csc.kgg = tegra_dc_readl(dc, DC_COM_CMU_CSC_KGG);
|
|
cmu->csc.kbg = tegra_dc_readl(dc, DC_COM_CMU_CSC_KBG);
|
|
cmu->csc.krb = tegra_dc_readl(dc, DC_COM_CMU_CSC_KRB);
|
|
cmu->csc.kgb = tegra_dc_readl(dc, DC_COM_CMU_CSC_KGB);
|
|
cmu->csc.kbb = tegra_dc_readl(dc, DC_COM_CMU_CSC_KBB);
|
|
|
|
for (i = 0; i < 960; i++) {
|
|
val = LUT2_READ_EN | LUT2_READ_ADDR(i);
|
|
tegra_dc_writel(dc, val, DC_COM_CMU_LUT2_READ);
|
|
val = tegra_dc_readl(dc, DC_COM_CMU_LUT2);
|
|
cmu->lut2[i] = LUT2_READ_DATA(val);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_get_cmu);
|
|
|
|
int _tegra_dc_update_cmu(struct tegra_dc *dc, struct tegra_dc_cmu *cmu)
|
|
{
|
|
u32 val;
|
|
|
|
if (dc->pdata->cmu_enable) {
|
|
dc->pdata->flags |= TEGRA_DC_FLAG_CMU_ENABLE;
|
|
} else {
|
|
dc->pdata->flags &= ~TEGRA_DC_FLAG_CMU_ENABLE;
|
|
return 0;
|
|
}
|
|
|
|
if (cmu != &dc->cmu) {
|
|
tegra_dc_cache_cmu(&dc->cmu, cmu);
|
|
|
|
/* Disable CMU */
|
|
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_UPDATE;
|
|
tegra_dc_writel(dc, val, DC_CMD_STATE_CONTROL);
|
|
val = GENERAL_ACT_REQ;
|
|
tegra_dc_writel(dc, val, DC_CMD_STATE_CONTROL);
|
|
/*TODO: Sync up with vsync */
|
|
mdelay(20);
|
|
}
|
|
|
|
tegra_dc_set_cmu(dc, &dc->cmu);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tegra_dc_update_cmu(struct tegra_dc *dc, struct tegra_dc_cmu *cmu)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&dc->lock);
|
|
if (!dc->enabled) {
|
|
mutex_unlock(&dc->lock);
|
|
return 0;
|
|
}
|
|
|
|
tegra_dc_get(dc);
|
|
|
|
ret = _tegra_dc_update_cmu(dc, cmu);
|
|
tegra_dc_set_color_control(dc);
|
|
|
|
tegra_dc_put(dc);
|
|
mutex_unlock(&dc->lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_update_cmu);
|
|
|
|
void tegra_dc_cmu_enable(struct tegra_dc *dc, bool cmu_enable)
|
|
{
|
|
dc->pdata->cmu_enable = cmu_enable;
|
|
if (dc->pdata->cmu) {
|
|
tegra_dc_update_cmu(dc, dc->pdata->cmu);
|
|
} else {
|
|
if (dc->out->type == TEGRA_DC_OUT_HDMI)
|
|
tegra_dc_update_cmu(dc, &default_limited_cmu);
|
|
else
|
|
tegra_dc_update_cmu(dc, &default_cmu);
|
|
}
|
|
}
|
|
#else
|
|
#define tegra_dc_cache_cmu(dst_cmu, src_cmu)
|
|
#define tegra_dc_set_cmu(dc, cmu)
|
|
#define tegra_dc_update_cmu(dc, cmu)
|
|
#endif
|
|
|
|
/* 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(const struct tegra_dc *dc, int i)
|
|
{
|
|
return dc->syncpt[i].id;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_get_syncpt_id);
|
|
|
|
u32 tegra_dc_incr_syncpt_max(struct tegra_dc *dc, int i)
|
|
{
|
|
u32 max;
|
|
|
|
mutex_lock(&dc->lock);
|
|
tegra_dc_get(dc);
|
|
max = nvhost_syncpt_incr_max_ext(dc->ndev,
|
|
dc->syncpt[i].id, ((dc->enabled) ? 1 : 0));
|
|
dc->syncpt[i].max = max;
|
|
tegra_dc_put(dc);
|
|
mutex_unlock(&dc->lock);
|
|
|
|
return max;
|
|
}
|
|
|
|
void tegra_dc_incr_syncpt_min(struct tegra_dc *dc, int i, u32 val)
|
|
{
|
|
mutex_lock(&dc->lock);
|
|
if (dc->enabled) {
|
|
tegra_dc_get(dc);
|
|
while (dc->syncpt[i].min < val) {
|
|
dc->syncpt[i].min++;
|
|
nvhost_syncpt_cpu_incr_ext(dc->ndev, dc->syncpt[i].id);
|
|
}
|
|
tegra_dc_put(dc);
|
|
}
|
|
mutex_unlock(&dc->lock);
|
|
}
|
|
|
|
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:
|
|
pr_err("Invalid argument in function %s\n", __func__);
|
|
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)
|
|
{
|
|
if (dc->out->type == TEGRA_DC_OUT_RGB ||
|
|
dc->out->type == TEGRA_DC_OUT_HDMI ||
|
|
dc->out->type == TEGRA_DC_OUT_DSI)
|
|
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)
|
|
{
|
|
struct tegra_dc_mode *mode;
|
|
int err = 0;
|
|
|
|
dc->out = out;
|
|
mode = tegra_dc_get_override_mode(dc);
|
|
|
|
if (mode)
|
|
tegra_dc_set_mode(dc, mode);
|
|
else if (out->n_modes > 0)
|
|
tegra_dc_set_mode(dc, &dc->out->modes[0]);
|
|
|
|
switch (out->type) {
|
|
case TEGRA_DC_OUT_RGB:
|
|
dc->out_ops = &tegra_dc_rgb_ops;
|
|
break;
|
|
|
|
case TEGRA_DC_OUT_HDMI:
|
|
dc->out_ops = &tegra_dc_hdmi_ops;
|
|
break;
|
|
|
|
#ifdef CONFIG_TEGRA_DSI
|
|
case TEGRA_DC_OUT_DSI:
|
|
dc->out_ops = &tegra_dc_dsi_ops;
|
|
break;
|
|
#endif
|
|
|
|
#ifdef CONFIG_TEGRA_DP
|
|
case TEGRA_DC_OUT_DP:
|
|
dc->out_ops = &tegra_dc_dp_ops;
|
|
break;
|
|
#endif
|
|
|
|
#ifdef CONFIG_TEGRA_LVDS
|
|
case TEGRA_DC_OUT_LVDS:
|
|
dc->out_ops = &tegra_dc_lvds_ops;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
dc->out_ops = NULL;
|
|
break;
|
|
}
|
|
|
|
if (dc->out_ops && dc->out_ops->init) {
|
|
err = dc->out_ops->init(dc);
|
|
if (err < 0) {
|
|
dc->out = NULL;
|
|
dc->out_ops = NULL;
|
|
return err;
|
|
}
|
|
}
|
|
/* If there is no predefined mode, try early_enable()
|
|
to check mode from panel directly */
|
|
if (!mode && !out->n_modes && dc->out_ops &&
|
|
dc->out_ops->early_enable) {
|
|
if (dc->out_ops->early_enable(dc))
|
|
dev_info(&dc->ndev->dev,
|
|
"Detected mode: %dx%d pclk=%d\n",
|
|
dc->mode.h_active, dc->mode.v_active,
|
|
dc->mode.pclk);
|
|
else {
|
|
dev_err(&dc->ndev->dev,
|
|
"Error: failed to check panel mode\n");
|
|
err = -EINVAL;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* returns on error: -EINVAL
|
|
* on success: TEGRA_DC_OUT_RGB, TEGRA_DC_OUT_HDMI, or TEGRA_DC_OUT_DSI. */
|
|
int tegra_dc_get_out(const struct tegra_dc *dc)
|
|
{
|
|
if (dc && dc->out)
|
|
return dc->out->type;
|
|
return -EINVAL;
|
|
}
|
|
|
|
unsigned tegra_dc_get_out_height(const struct tegra_dc *dc)
|
|
{
|
|
if (dc->out)
|
|
return dc->out->height;
|
|
else
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_get_out_height);
|
|
|
|
unsigned tegra_dc_get_out_width(const struct tegra_dc *dc)
|
|
{
|
|
if (dc->out)
|
|
return dc->out->width;
|
|
else
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_get_out_width);
|
|
|
|
unsigned tegra_dc_get_out_max_pixclock(const struct tegra_dc *dc)
|
|
{
|
|
if (dc->out && dc->out->max_pixclock)
|
|
return dc->out->max_pixclock;
|
|
else
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_get_out_max_pixclock);
|
|
|
|
int tegra_dc_is_enabled(struct platform_device *ndev)
|
|
{
|
|
struct tegra_dc *dc = platform_get_drvdata(ndev);
|
|
|
|
if (dc)
|
|
return dc->enabled ? 1 : 0;
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
EXPORT_SYMBOL(tegra_dc_is_enabled);
|
|
|
|
void tegra_dc_enable_crc(struct tegra_dc *dc)
|
|
{
|
|
u32 val;
|
|
|
|
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_UPDATE, DC_CMD_STATE_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_disable_crc(struct tegra_dc *dc)
|
|
{
|
|
/* 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_UPDATE, DC_CMD_STATE_CONTROL);
|
|
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
|
|
|
tegra_dc_put(dc);
|
|
mutex_unlock(&dc->lock);
|
|
}
|
|
|
|
u32 tegra_dc_read_checksum_latched(struct tegra_dc *dc)
|
|
{
|
|
int crc = 0;
|
|
|
|
if (!dc) {
|
|
pr_err("Failed to get dc: NULL parameter.\n");
|
|
goto crc_error;
|
|
}
|
|
|
|
INIT_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;
|
|
}
|
|
|
|
static bool tegra_dc_windows_are_dirty(struct tegra_dc *dc)
|
|
{
|
|
u32 val;
|
|
|
|
val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
|
|
if (val & WIN_ALL_ACT_REQ)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline void enable_dc_irq(const struct tegra_dc *dc)
|
|
{
|
|
enable_irq(dc->irq);
|
|
}
|
|
|
|
void tegra_dc_get_fbvblank(struct tegra_dc *dc, struct fb_vblank *vblank)
|
|
{
|
|
if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
|
|
vblank->flags = FB_VBLANK_HAVE_VSYNC;
|
|
}
|
|
|
|
int tegra_dc_wait_for_vsync(struct tegra_dc *dc)
|
|
{
|
|
int ret = -ENOTTY;
|
|
|
|
if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) || !dc->enabled)
|
|
return ret;
|
|
|
|
/*
|
|
* Logic is as follows
|
|
* a) Indicate we need a vblank.
|
|
* b) Wait for completion to be signalled from isr.
|
|
* c) Initialize completion for next iteration.
|
|
*/
|
|
|
|
mutex_lock(&dc->one_shot_lp_lock);
|
|
tegra_dc_get(dc);
|
|
dc->out->user_needs_vblank = true;
|
|
tegra_dc_unmask_interrupt(dc, MSF_INT);
|
|
|
|
ret = wait_for_completion_interruptible(&dc->out->user_vblank_comp);
|
|
init_completion(&dc->out->user_vblank_comp);
|
|
tegra_dc_mask_interrupt(dc, MSF_INT);
|
|
tegra_dc_put(dc);
|
|
mutex_unlock(&dc->one_shot_lp_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void tegra_dc_prism_update_backlight(struct tegra_dc *dc)
|
|
{
|
|
struct device_node *dn = NULL;
|
|
|
|
/* Do the actual brightness update outside of the mutex dc->lock */
|
|
if (dc->out->sd_settings && !dc->out->sd_settings->bl_device &&
|
|
dc->out->sd_settings->bl_device_name) {
|
|
char *bl_device_name =
|
|
dc->out->sd_settings->bl_device_name;
|
|
dn = of_find_node_by_name(NULL, bl_device_name);
|
|
if (dn) {
|
|
dc->out->sd_settings->bl_device =
|
|
of_find_backlight_by_node(dn);
|
|
if (!dc->out->sd_settings->bl_device)
|
|
dev_warn(&dc->ndev->dev,
|
|
"PRISM: Get backlight dev failed.\n");
|
|
else
|
|
dev_info(&dc->ndev->dev,
|
|
"PRISM enabled: Got backlight dev\n");
|
|
of_node_put(dn);
|
|
} else
|
|
dev_warn(&dc->ndev->dev,
|
|
"PRISM: Get backlight node failed.\n");
|
|
}
|
|
|
|
if (dc->out->sd_settings && dc->out->sd_settings->bl_device) {
|
|
struct backlight_device *bl = dc->out->sd_settings->bl_device;
|
|
backlight_update_status(bl);
|
|
}
|
|
}
|
|
|
|
static void tegra_dc_vblank(struct work_struct *work)
|
|
{
|
|
struct tegra_dc *dc = container_of(work, struct tegra_dc, vblank_work);
|
|
bool nvsd_updated = false;
|
|
|
|
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))
|
|
clear_bit(V_BLANK_FLIP, &dc->vblank_ref_count);
|
|
|
|
/* Update the SD brightness */
|
|
if (dc->out->sd_settings && !dc->out->sd_settings->use_vpulse2) {
|
|
nvsd_updated = nvsd_update_brightness(dc);
|
|
/* Ref-count vblank if nvsd is on-going. Otherwise, clean the
|
|
* V_BLANK_NVSD bit of vblank ref-count. */
|
|
if (nvsd_updated) {
|
|
set_bit(V_BLANK_NVSD, &dc->vblank_ref_count);
|
|
tegra_dc_unmask_interrupt(dc, V_BLANK_INT);
|
|
} else {
|
|
clear_bit(V_BLANK_NVSD, &dc->vblank_ref_count);
|
|
}
|
|
}
|
|
|
|
/* use the new frame's bandwidth setting instead of max(current, new),
|
|
* skip this if we're using tegra_dc_one_shot_worker() */
|
|
if (!(dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE))
|
|
tegra_dc_program_bandwidth(dc, true);
|
|
|
|
/* Mask vblank interrupt if ref-count is zero. */
|
|
if (!dc->vblank_ref_count)
|
|
tegra_dc_mask_interrupt(dc, V_BLANK_INT);
|
|
|
|
tegra_dc_put(dc);
|
|
mutex_unlock(&dc->lock);
|
|
|
|
/* Do the actual brightness update outside of the mutex dc->lock */
|
|
if (nvsd_updated)
|
|
tegra_dc_prism_update_backlight(dc);
|
|
}
|
|
|
|
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)
|
|
{
|
|
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 defined(CONFIG_ARCH_TEGRA_124_SOC)
|
|
if (is_tegra124()) {
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
/* Check for any underflow reset conditions */
|
|
for (i = 0; i < dc->n_windows; i++) {
|
|
u32 masks[] = {
|
|
WIN_A_UF_INT,
|
|
WIN_B_UF_INT,
|
|
WIN_C_UF_INT,
|
|
#ifdef CONFIG_ARCH_TEGRA_124_SOC
|
|
WIN_D_UF_INT,
|
|
HC_UF_INT,
|
|
WIN_T_UF_INT,
|
|
#endif
|
|
};
|
|
|
|
if (WARN_ONCE(i >= ARRAY_SIZE(masks),
|
|
"underflow stats unsupported"))
|
|
break; /* bail if the table above is missing entries */
|
|
if (!masks[i])
|
|
continue; /* skip empty entries */
|
|
|
|
if (dc->underflow_mask & masks[i])
|
|
dc->windows[i].underflows++;
|
|
else
|
|
dc->windows[i].underflows = 0;
|
|
}
|
|
|
|
/* 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);
|
|
bool nvsd_updated = false;
|
|
|
|
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))
|
|
clear_bit(V_PULSE2_FLIP, &dc->vpulse2_ref_count);
|
|
|
|
/* Update the SD brightness */
|
|
if (dc->out->sd_settings && dc->out->sd_settings->use_vpulse2) {
|
|
nvsd_updated = nvsd_update_brightness(dc);
|
|
if (nvsd_updated) {
|
|
set_bit(V_PULSE2_NVSD, &dc->vpulse2_ref_count);
|
|
tegra_dc_unmask_interrupt(dc, V_PULSE2_INT);
|
|
} else {
|
|
clear_bit(V_PULSE2_NVSD, &dc->vpulse2_ref_count);
|
|
}
|
|
}
|
|
|
|
/* Mask vpulse2 interrupt if ref-count is zero. */
|
|
if (!dc->vpulse2_ref_count)
|
|
tegra_dc_mask_interrupt(dc, V_PULSE2_INT);
|
|
|
|
tegra_dc_put(dc);
|
|
mutex_unlock(&dc->lock);
|
|
|
|
/* Do the actual brightness update outside of the mutex dc->lock */
|
|
if (nvsd_updated)
|
|
tegra_dc_prism_update_backlight(dc);
|
|
}
|
|
|
|
static void tegra_dc_one_shot_irq(struct tegra_dc *dc, unsigned long status)
|
|
{
|
|
/* pending user vblank, so wakeup */
|
|
if ((status & (V_BLANK_INT | MSF_INT)) &&
|
|
(dc->out->user_needs_vblank)) {
|
|
dc->out->user_needs_vblank = false;
|
|
complete(&dc->out->user_vblank_comp);
|
|
}
|
|
|
|
if (status & V_BLANK_INT) {
|
|
/* Sync up windows. */
|
|
tegra_dc_trigger_windows(dc);
|
|
|
|
/* Schedule any additional bottom-half vblank actvities. */
|
|
queue_work(system_freezable_wq, &dc->vblank_work);
|
|
}
|
|
|
|
if (status & FRAME_END_INT) {
|
|
/* 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 (status & V_PULSE2_INT)
|
|
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 & FRAME_END_INT) {
|
|
struct timespec tm = CURRENT_TIME;
|
|
dc->frame_end_timestamp = timespec_to_ns(&tm);
|
|
wake_up(&dc->timestamp_wq);
|
|
|
|
/* 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);
|
|
|
|
tegra_dc_trigger_windows(dc);
|
|
}
|
|
|
|
if (status & V_PULSE2_INT)
|
|
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)));
|
|
}
|
|
|
|
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;
|
|
|
|
mutex_lock(&dc->lock);
|
|
if (!dc->enabled || !tegra_dc_is_powered(dc)) {
|
|
mutex_unlock(&dc->lock);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
tegra_dc_get(dc);
|
|
|
|
if (!nvhost_module_powered_ext(dc->ndev)) {
|
|
WARN(1, "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);
|
|
|
|
/* 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 */
|
|
|
|
tegra_dc_put(dc);
|
|
mutex_unlock(&dc->lock);
|
|
|
|
if (need_disable)
|
|
tegra_dc_disable(dc);
|
|
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");
|
|
}
|
|
|
|
#ifdef CONFIG_TEGRA_DC_CMU
|
|
if (dc->pdata->flags & TEGRA_DC_FLAG_CMU_ENABLE)
|
|
color_control |= CMU_ENABLE;
|
|
#endif
|
|
|
|
tegra_dc_writel(dc, color_control, DC_DISP_DISP_COLOR_CONTROL);
|
|
}
|
|
|
|
static u32 get_syncpt(struct tegra_dc *dc, int idx)
|
|
{
|
|
if (idx >= 0 && idx < dc->n_windows)
|
|
return dc->win_syncpt[idx];
|
|
BUG();
|
|
}
|
|
|
|
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);
|
|
tegra_dc_writel(dc, V_PULSE_2_ENABLE, DC_DISP_DISP_SIGNAL_OPTIONS0);
|
|
}
|
|
|
|
static int tegra_dc_init(struct tegra_dc *dc)
|
|
{
|
|
int i;
|
|
int int_enable;
|
|
int win_mask = WINDOW_A_SELECT | WINDOW_B_SELECT | WINDOW_C_SELECT;
|
|
|
|
tegra_dc_io_start(dc);
|
|
|
|
tegra_dc_writel(dc, WRITE_MUX_ASSEMBLY | READ_MUX_ASSEMBLY,
|
|
DC_CMD_STATE_ACCESS);
|
|
tegra_dc_writel(dc, win_mask, DC_CMD_DISPLAY_WINDOW_HEADER);
|
|
tegra_dc_writel(dc, 0, DC_WIN_WIN_OPTIONS);
|
|
|
|
tegra_dc_writel(dc, 0x00000100, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
|
|
/* TODO: Add MC priority setting here. */
|
|
|
|
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, 0x0001c700, 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);
|
|
|
|
#if !defined(CONFIG_TEGRA_DC_BLENDER_GEN2)
|
|
tegra_dc_writel(dc, 0x00000000, DC_DISP_BORDER_COLOR);
|
|
#else
|
|
tegra_dc_writel(dc, 0x00000000, DC_DISP_BLEND_BACKGROUND_COLOR);
|
|
#endif
|
|
|
|
#ifdef CONFIG_TEGRA_DC_CMU
|
|
if (dc->pdata->cmu) {
|
|
_tegra_dc_update_cmu(dc, dc->pdata->cmu);
|
|
} else {
|
|
if (dc->out->type == TEGRA_DC_OUT_HDMI)
|
|
_tegra_dc_update_cmu(dc, &default_limited_cmu);
|
|
else
|
|
_tegra_dc_update_cmu(dc, &default_cmu);
|
|
}
|
|
#endif
|
|
tegra_dc_set_color_control(dc);
|
|
for (i = 0; i < dc->n_windows; i++) {
|
|
struct tegra_dc_win *win = &dc->windows[i];
|
|
tegra_dc_writel(dc, WINDOW_A_SELECT << i,
|
|
DC_CMD_DISPLAY_WINDOW_HEADER);
|
|
tegra_dc_set_csc(dc, &win->csc);
|
|
tegra_dc_set_lut(dc, win);
|
|
tegra_dc_set_scaling_filter(dc);
|
|
}
|
|
|
|
for (i = 0; i < dc->n_windows; i++) {
|
|
u32 syncpt = get_syncpt(dc, i);
|
|
|
|
dc->syncpt[i].id = syncpt;
|
|
|
|
dc->syncpt[i].min = dc->syncpt[i].max =
|
|
nvhost_syncpt_read_ext(dc->ndev, syncpt);
|
|
}
|
|
|
|
dc->crc_pending = false;
|
|
|
|
trace_display_mode(dc, &dc->mode);
|
|
|
|
if (dc->mode.pclk) {
|
|
if (tegra_dc_program_mode(dc, &dc->mode)) {
|
|
tegra_dc_io_end(dc);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* Initialize SD AFTER the modeset.
|
|
nvsd_init handles the sd_settings = NULL case. */
|
|
nvsd_init(dc, dc->out->sd_settings);
|
|
|
|
tegra_dc_io_end(dc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
do {
|
|
usleep_range(poll_interval_us, poll_interval_us << 1);
|
|
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;
|
|
}
|
|
|
|
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);
|
|
tegra_dc_clk_enable(dc);
|
|
tegra_dc_io_start(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);
|
|
tegra_dc_clk_disable(dc);
|
|
if (dc->out && dc->out->disable)
|
|
dc->out->disable();
|
|
tegra_dc_io_end(dc);
|
|
return false;
|
|
}
|
|
|
|
if (dc->out->type == TEGRA_DC_OUT_DP)
|
|
tegra_dp_aux_pad_on_off(dc, false);
|
|
|
|
if (dc->out_ops && dc->out_ops->enable)
|
|
dc->out_ops->enable(dc);
|
|
|
|
/* force a full blending update */
|
|
for (i = 0; i < dc->n_windows; 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_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 */
|
|
|
|
trace_display_enable(dc);
|
|
|
|
tegra_dc_writel(dc, GENERAL_UPDATE, DC_CMD_STATE_CONTROL);
|
|
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
|
|
|
|
if (dc->out->postpoweron)
|
|
dc->out->postpoweron(&dc->ndev->dev);
|
|
|
|
if (dc->out_ops && dc->out_ops->postpoweron)
|
|
dc->out_ops->postpoweron(dc);
|
|
|
|
/*
|
|
* We will need to reinitialize the display the next time panel
|
|
* is enabled.
|
|
*/
|
|
dc->out->flags &= ~TEGRA_DC_OUT_INITIALIZED_MODE;
|
|
|
|
tegra_dc_io_end(dc);
|
|
|
|
if (dc->bl_device) {
|
|
struct tegra_dc_edid *edid = tegra_dc_get_edid(dc);
|
|
unsigned char *block = edid->buf + 8;
|
|
char manuf_id[4];
|
|
unsigned short model;
|
|
unsigned int serial;
|
|
char panel_identifier[20];
|
|
|
|
if (!edid) {
|
|
dev_err(&dc->ndev->dev, "Failed getting edid\n");
|
|
return true;
|
|
}
|
|
|
|
manuf_id[0] = ((block[0] & 0x7c) >> 2) + '@';
|
|
manuf_id[1] = ((block[0] & 0x03) << 3) +
|
|
((block[1] & 0xe0) >> 5) + '@';
|
|
manuf_id[2] = (block[1] & 0x1f) + '@';
|
|
manuf_id[3] = 0;
|
|
model = block[2] + (block[3] << 8);
|
|
serial = block[4] + (block[5] << 8) +
|
|
(block[6] << 16) + (block[7] << 24);
|
|
snprintf(panel_identifier, sizeof(panel_identifier), "%s-%x-%u",
|
|
manuf_id, model, serial);
|
|
|
|
backlight_choose(dc->bl_device, panel_identifier);
|
|
}
|
|
|
|
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:
|
|
/* DC enable called but no videomode is loaded.
|
|
Check if HDMI is connected, then set fallback mdoe */
|
|
if (tegra_dc_hpd(dc)) {
|
|
return tegra_dc_set_fb_mode(dc,
|
|
&tegra_dc_hdmi_fallback_mode, 0);
|
|
} else
|
|
return false;
|
|
|
|
break;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
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;
|
|
|
|
pm_runtime_get_sync(&dc->ndev->dev);
|
|
|
|
if (!_tegra_dc_controller_enable(dc)) {
|
|
pm_runtime_put_sync(&dc->ndev->dev);
|
|
return false;
|
|
}
|
|
|
|
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)
|
|
dc->enabled = _tegra_dc_enable(dc);
|
|
|
|
mutex_unlock(&dc->lock);
|
|
trace_display_mode(dc, &dc->mode);
|
|
}
|
|
|
|
static void _tegra_dc_controller_disable(struct tegra_dc *dc)
|
|
{
|
|
unsigned i;
|
|
|
|
tegra_dc_get(dc);
|
|
|
|
if (dc->out && dc->out->prepoweroff)
|
|
dc->out->prepoweroff();
|
|
|
|
if (dc->out_ops && dc->out_ops->disable)
|
|
dc->out_ops->disable(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();
|
|
|
|
for (i = 0; i < dc->n_windows; i++) {
|
|
struct tegra_dc_win *w = &dc->windows[i];
|
|
|
|
/* reset window bandwidth */
|
|
w->bandwidth = 0;
|
|
w->new_bandwidth = 0;
|
|
|
|
/* disable windows */
|
|
w->flags &= ~TEGRA_WIN_FLAG_ENABLED;
|
|
|
|
/* flush any pending syncpt waits. Add 1 for sync framework. */
|
|
while (dc->syncpt[i].min < (dc->syncpt[i].max + 1)) {
|
|
trace_display_syncpt_flush(dc, dc->syncpt[i].id,
|
|
dc->syncpt[i].min, dc->syncpt[i].max);
|
|
dc->syncpt[i].min++;
|
|
nvhost_syncpt_cpu_incr_ext(dc->ndev, dc->syncpt[i].id);
|
|
}
|
|
}
|
|
trace_display_disable(dc);
|
|
|
|
if (dc->out_ops && dc->out_ops->postpoweroff)
|
|
dc->out_ops->postpoweroff(dc);
|
|
|
|
tegra_dc_clk_disable(dc);
|
|
tegra_dc_put(dc);
|
|
}
|
|
|
|
void tegra_dc_stats_enable(struct tegra_dc *dc, bool enable)
|
|
{
|
|
u32 val;
|
|
if (dc->enabled) {
|
|
val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
|
|
if (enable)
|
|
val |= (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT);
|
|
else
|
|
val &= ~(WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT);
|
|
tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE);
|
|
}
|
|
}
|
|
|
|
bool tegra_dc_stats_get(struct tegra_dc *dc)
|
|
{
|
|
u32 val;
|
|
bool res;
|
|
|
|
if (dc->enabled) {
|
|
val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE);
|
|
res = !!(val & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT));
|
|
} else {
|
|
res = false;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* make the screen blank by disabling all windows */
|
|
void tegra_dc_blank(struct tegra_dc *dc)
|
|
{
|
|
struct tegra_dc_win **dcwins;
|
|
unsigned i;
|
|
|
|
dcwins = kzalloc(sizeof(struct tegra_dc_win *) * dc->n_windows,
|
|
GFP_KERNEL);
|
|
for (i = 0; i < dc->n_windows; i++) {
|
|
dcwins[i] = tegra_dc_get_window(dc, i);
|
|
dcwins[i]->flags &= ~TEGRA_WIN_FLAG_ENABLED;
|
|
}
|
|
|
|
tegra_dc_update_windows(dcwins, dc->n_windows);
|
|
tegra_dc_sync_windows(dcwins, dc->n_windows);
|
|
|
|
kfree(dcwins);
|
|
}
|
|
|
|
static void _tegra_dc_disable(struct tegra_dc *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);
|
|
}
|
|
|
|
tegra_dc_io_start(dc);
|
|
_tegra_dc_controller_disable(dc);
|
|
tegra_dc_io_end(dc);
|
|
|
|
tegra_dc_powergate_locked(dc);
|
|
|
|
if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
|
|
mutex_unlock(&dc->one_shot_lock);
|
|
pm_runtime_put(&dc->ndev->dev);
|
|
}
|
|
|
|
void tegra_dc_disable(struct tegra_dc *dc)
|
|
{
|
|
if (WARN_ON(!dc || !dc->out || !dc->out_ops))
|
|
return;
|
|
|
|
tegra_dc_ext_disable(dc->ext);
|
|
|
|
/* it's important that new underflow work isn't scheduled before the
|
|
* lock is acquired. */
|
|
cancel_delayed_work_sync(&dc->underflow_work);
|
|
|
|
mutex_lock(&dc->lock);
|
|
|
|
if (dc->enabled) {
|
|
dc->enabled = false;
|
|
|
|
if (!dc->suspended)
|
|
_tegra_dc_disable(dc);
|
|
}
|
|
|
|
mutex_unlock(&dc->lock);
|
|
synchronize_irq(dc->irq);
|
|
trace_display_mode(dc, &dc->mode);
|
|
}
|
|
|
|
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 tegra_dc_add_modes(struct tegra_dc *dc)
|
|
{
|
|
struct fb_monspecs specs;
|
|
int i;
|
|
|
|
memset(&specs, 0, sizeof(specs));
|
|
specs.max_x = dc->mode.h_active * 1000;
|
|
specs.max_y = dc->mode.v_active * 1000;
|
|
specs.modedb_len = dc->out->n_modes;
|
|
specs.modedb = kzalloc(specs.modedb_len *
|
|
sizeof(struct fb_videomode), GFP_KERNEL);
|
|
for (i = 0; i < dc->out->n_modes; i++)
|
|
tegra_dc_to_fb_videomode(&specs.modedb[i],
|
|
&dc->out->modes[i]);
|
|
tegra_fb_update_monspecs(dc->fb, &specs, NULL);
|
|
kfree(specs.modedb);
|
|
}
|
|
|
|
static int tegra_dc_alloc_members(struct tegra_dc *dc)
|
|
{
|
|
dc->blend.alpha = kzalloc(sizeof(*(dc->blend.alpha)) *
|
|
dc->n_windows, GFP_KERNEL);
|
|
if (!dc->blend.alpha)
|
|
return -ENOMEM;
|
|
|
|
dc->blend.flags = kzalloc(sizeof(*(dc->blend.flags)) *
|
|
dc->n_windows, GFP_KERNEL);
|
|
if (!dc->blend.flags)
|
|
return -ENOMEM;
|
|
|
|
dc->blend.z = kzalloc(sizeof(*(dc->blend.z)) *
|
|
dc->n_windows, GFP_KERNEL);
|
|
if (!dc->blend.z)
|
|
return -ENOMEM;
|
|
|
|
dc->windows = kzalloc(sizeof(struct tegra_dc_win) *
|
|
dc->n_windows, GFP_KERNEL);
|
|
if (!dc->windows)
|
|
return -ENOMEM;
|
|
|
|
dc->syncpt = kzalloc(sizeof(*(dc->syncpt)) * dc->n_windows, GFP_KERNEL);
|
|
if (!dc->syncpt)
|
|
return -ENOMEM;
|
|
|
|
dc->win_syncpt = kzalloc(sizeof(*(dc->win_syncpt)) *
|
|
dc->n_windows, GFP_KERNEL);
|
|
if (!dc->win_syncpt)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_dc_free_members(struct tegra_dc *dc)
|
|
{
|
|
kfree(dc->blend.alpha);
|
|
kfree(dc->blend.flags);
|
|
kfree(dc->blend.z);
|
|
kfree(dc->windows);
|
|
kfree(dc->syncpt);
|
|
kfree(dc->win_syncpt);
|
|
}
|
|
|
|
static int tegra_dc_create_fb(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
struct device *dev = data;
|
|
struct platform_device *pdc0 = NULL;
|
|
struct platform_device *pdc1 = NULL;
|
|
struct tegra_dc *dc = NULL;
|
|
void *fb_cpuva = NULL;
|
|
dma_addr_t fb_iova;
|
|
size_t fb_size;
|
|
struct tegra_dc_mode *mode;
|
|
int bpp;
|
|
unsigned stride;
|
|
|
|
if (event != BUS_NOTIFY_BOUND_DRIVER)
|
|
return NOTIFY_DONE;
|
|
|
|
pdc0 = to_platform_device(bus_find_device_by_name(
|
|
&platform_bus_type, NULL, "tegradc.0"));
|
|
pdc1 = to_platform_device(bus_find_device_by_name(
|
|
&platform_bus_type, NULL, "tegradc.1"));
|
|
|
|
if (dev == &pdc0->dev)
|
|
dc = platform_get_drvdata(pdc0);
|
|
else if (dev == &pdc1->dev)
|
|
dc = platform_get_drvdata(pdc1);
|
|
else
|
|
return NOTIFY_DONE;
|
|
|
|
if (dc->fb)
|
|
return NOTIFY_DONE;
|
|
|
|
mode = &dc->mode;
|
|
|
|
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);
|
|
}
|
|
bpp = DIV_ROUND_UP(dc->pdata->fb->bits_per_pixel, 8);
|
|
|
|
if (dev == &pdc0->dev) {
|
|
stride = round_up(mode->h_active * bpp,
|
|
TEGRA_LINEAR_PITCH_ALIGNMENT);
|
|
fb_size = round_up(stride * mode->v_active, PAGE_SIZE);
|
|
} else {
|
|
/*
|
|
* On ChromeOS, dc.1 doesn't need framebuffer at all because
|
|
* HDMI is managed by X server and we don't have framebuffer
|
|
* console on dc.1 as well.
|
|
*
|
|
* There are only 2 user cases which requires framebuffer:
|
|
* 1. FB blank is used to enable/disable corresponding dc
|
|
* 2. Userspace uses FB ioctls to retrieve display infos
|
|
*
|
|
* So here we create a dummy framebuffer for dc.1 to save mem.
|
|
*/
|
|
stride = round_up(640 * bpp, TEGRA_LINEAR_PITCH_ALIGNMENT);
|
|
fb_size = round_up(stride * 480, PAGE_SIZE);
|
|
dc->pdata->fb->xres = 640;
|
|
dc->pdata->fb->yres = 480;
|
|
}
|
|
|
|
fb_cpuva = dma_alloc_writecombine(dev, fb_size, &fb_iova, GFP_KERNEL);
|
|
if (!fb_cpuva) {
|
|
dev_err(dev, "Allocate framebuffer failed.\n");
|
|
goto failed;
|
|
}
|
|
dev_info(dev,
|
|
"Framebuffer created: cpuva: 0x%p@%d, iova: 0x%llx@%d\n",
|
|
fb_cpuva, fb_size, (u64)fb_iova, fb_size);
|
|
|
|
if (dev == &pdc0->dev)
|
|
dc->pdata->fbmem.name = "fbmem.0";
|
|
else
|
|
dc->pdata->fbmem.name = "fbmem.1";
|
|
dc->pdata->fbmem.start = fb_iova;
|
|
dc->pdata->fbmem.end = fb_iova + fb_size - 1;
|
|
dc->pdata->fbmem.flags = IORESOURCE_MEM;
|
|
|
|
/* If we didn't set the fb's xres and yres, get it from DC's
|
|
mode which is set as part of edid parsing. */
|
|
if (!dc->pdata->fb->xres && dc->mode.h_active)
|
|
dc->pdata->fb->xres = dc->mode.h_active;
|
|
if (!dc->pdata->fb->yres && dc->mode.v_active)
|
|
dc->pdata->fb->yres = dc->mode.v_active;
|
|
|
|
tegra_dc_io_start(dc);
|
|
dc->fb = tegra_fb_register(dc->ndev, dc, dc->pdata->fb,
|
|
&dc->pdata->fbmem, fb_cpuva);
|
|
tegra_dc_io_end(dc);
|
|
if (IS_ERR_OR_NULL(dc->fb)) {
|
|
dc->fb = NULL;
|
|
dev_err(dev, "failed to register fb\n");
|
|
goto freefb;
|
|
}
|
|
|
|
if (dc->out && dc->out->n_modes)
|
|
tegra_dc_add_modes(dc);
|
|
|
|
/* Notify hdmi state machine */
|
|
if (dev == &pdc1->dev && !completion_done(&dc->fb_ready))
|
|
complete(&dc->fb_ready);
|
|
|
|
return NOTIFY_OK;
|
|
|
|
freefb:
|
|
dma_free_writecombine(dev, fb_size, fb_cpuva, fb_iova);
|
|
failed:
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block tegra_dc_bus_notifier = {
|
|
.notifier_call = tegra_dc_create_fb,
|
|
};
|
|
|
|
static int tegra_dc_probe(struct platform_device *ndev)
|
|
{
|
|
struct tegra_dc *dc;
|
|
struct clk *clk;
|
|
struct clk *emc_clk;
|
|
struct resource *res;
|
|
int ret = 0;
|
|
void __iomem *base;
|
|
int irq;
|
|
int i;
|
|
struct tegra_dc_platform_data *pdata = NULL;
|
|
struct device_node *bl_node;
|
|
|
|
if (!ndev->dev.platform_data) {
|
|
dev_err(&ndev->dev, "no platform data\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* We can't finish the probing unless the regulators are ready. */
|
|
pdata = ndev->dev.platform_data;
|
|
ret = pdata->default_out->regulator_probe(&ndev->dev);
|
|
if (ret) {
|
|
dev_err(&ndev->dev, "Regulators are not ready: %d\n", ret);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
/* 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");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dc->ndev = ndev;
|
|
dc->n_windows = get_dc_n_windows(dc);
|
|
ret = tegra_dc_alloc_members(dc);
|
|
if (ret) {
|
|
dev_err(&ndev->dev, "can't alloc memory for dc members.\n");
|
|
goto err_free;
|
|
}
|
|
|
|
irq = platform_get_irq_byname(ndev, "dc");
|
|
if (irq <= 0) {
|
|
dev_err(&ndev->dev, "no irq\n");
|
|
ret = -ENOENT;
|
|
goto err_free;
|
|
}
|
|
|
|
res = platform_get_resource_byname(ndev, IORESOURCE_MEM, "dc");
|
|
if (!res) {
|
|
dev_err(&ndev->dev, "no mem resource\n");
|
|
ret = -ENOENT;
|
|
goto err_free;
|
|
}
|
|
|
|
base = devm_ioremap_resource(&ndev->dev, res);
|
|
if (IS_ERR(base)) {
|
|
dev_err(&ndev->dev, "remap dc regs failed.\n");
|
|
ret = PTR_ERR(base);
|
|
goto err_free;
|
|
}
|
|
|
|
if (strcmp(dev_name(&ndev->dev), "tegradc.0") == 0) {
|
|
dc->vblank_syncpt = NVSYNCPT_VBLANK0;
|
|
dc->win_syncpt[0] = NVSYNCPT_DISP0_A;
|
|
dc->win_syncpt[1] = NVSYNCPT_DISP0_B;
|
|
dc->win_syncpt[2] = NVSYNCPT_DISP0_C;
|
|
dc->valid_windows = 0x07;
|
|
#if defined(CONFIG_ARCH_TEGRA_124_SOC)
|
|
if (is_tegra124()) {
|
|
dc->win_syncpt[3] = NVSYNCPT_DISP0_D;
|
|
dc->valid_windows |= 0x08;
|
|
}
|
|
#endif
|
|
dc->powergate_id = TEGRA_POWERGATE_DIS;
|
|
ndev->id = 0;
|
|
} else if (strcmp(dev_name(&ndev->dev), "tegradc.1") == 0) {
|
|
dc->vblank_syncpt = NVSYNCPT_VBLANK1;
|
|
dc->win_syncpt[0] = NVSYNCPT_DISP1_A;
|
|
dc->win_syncpt[1] = NVSYNCPT_DISP1_B;
|
|
dc->win_syncpt[2] = NVSYNCPT_DISP1_C;
|
|
dc->valid_windows = 0x07;
|
|
dc->powergate_id = TEGRA_POWERGATE_DISB;
|
|
ndev->id = 1;
|
|
} else {
|
|
dev_err(&ndev->dev,
|
|
"Unknown base address %llx: unable to assign syncpt\n",
|
|
(u64)res->start);
|
|
}
|
|
|
|
clk = devm_clk_get(&ndev->dev, "dc");
|
|
if (IS_ERR(clk)) {
|
|
dev_err(&ndev->dev, "can't get clock\n");
|
|
ret = -ENOENT;
|
|
goto err_free;
|
|
}
|
|
|
|
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->pdata = ndev->dev.platform_data;
|
|
dc->bw_kbps = 0;
|
|
|
|
mutex_init(&dc->lock);
|
|
mutex_init(&dc->one_shot_lock);
|
|
mutex_init(&dc->one_shot_lp_lock);
|
|
init_completion(&dc->frame_end_complete);
|
|
init_completion(&dc->crc_complete);
|
|
init_completion(&dc->fb_ready);
|
|
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->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);
|
|
|
|
tegra_dc_init_lut_defaults(&dc->fb_lut);
|
|
|
|
for (i = 0; i < dc->n_windows; i++) {
|
|
struct tegra_dc_win *win = &dc->windows[i];
|
|
win->idx = i;
|
|
win->dc = dc;
|
|
tegra_dc_init_csc_defaults(&win->csc);
|
|
tegra_dc_init_lut_defaults(&win->lut);
|
|
}
|
|
|
|
ret = tegra_dc_set(dc, ndev->id);
|
|
if (ret < 0) {
|
|
dev_err(&ndev->dev, "can't add dc\n");
|
|
goto err_free;
|
|
}
|
|
|
|
platform_set_drvdata(ndev, dc);
|
|
|
|
tegra_dc_feature_register(dc);
|
|
|
|
if (dc->pdata->default_out) {
|
|
ret = tegra_dc_set_out(dc, dc->pdata->default_out);
|
|
if (ret < 0) {
|
|
dev_err(&dc->ndev->dev, "failed to initialize DC out ops\n");
|
|
goto err_free;
|
|
}
|
|
} 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 */
|
|
|
|
/*
|
|
* The emc is a shared clock, it will be set based on
|
|
* the requirements for each user on the bus.
|
|
*/
|
|
emc_clk = devm_clk_get(&ndev->dev, "emc");
|
|
if (IS_ERR(emc_clk)) {
|
|
dev_err(&ndev->dev, "can't get emc clock\n");
|
|
ret = -ENOENT;
|
|
goto err_free;
|
|
}
|
|
dc->emc_clk = emc_clk;
|
|
|
|
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);
|
|
|
|
/* Get the backlight device, if one is specified in dc's device tree. */
|
|
bl_node = of_parse_phandle(ndev->dev.of_node, "bl-device", 0);
|
|
if (bl_node)
|
|
dc->bl_device = of_find_backlight_by_node(bl_node);
|
|
|
|
pm_runtime_use_autosuspend(&ndev->dev);
|
|
pm_runtime_set_autosuspend_delay(&ndev->dev, 100);
|
|
pm_runtime_enable(&ndev->dev);
|
|
device_enable_async_suspend(&ndev->dev);
|
|
|
|
if (dc->pdata->flags & TEGRA_DC_FLAG_ENABLED) {
|
|
_tegra_dc_set_default_videomode(dc);
|
|
dc->enabled = _tegra_dc_enable(dc);
|
|
}
|
|
|
|
tegra_dc_create_debugfs(dc);
|
|
|
|
if (dc->out && dc->out->hotplug_init)
|
|
dc->out->hotplug_init(&ndev->dev);
|
|
|
|
if (dc->out_ops && dc->out_ops->detect)
|
|
dc->out_ops->detect(dc);
|
|
else
|
|
dc->connected = true;
|
|
|
|
/* Powergate display module when it's unconnected. */
|
|
/* detect() function, if presetns, responsible for the powergate */
|
|
if (!tegra_dc_get_connected(dc) && !dc->out_ops->detect)
|
|
tegra_dc_powergate_locked(dc);
|
|
|
|
if (strcmp(dev_name(&ndev->dev), "tegradc.0") == 0) {
|
|
/*
|
|
* Register a notifier to register framebuffer.
|
|
* We use IOMMU to create the framebuffer so we need to
|
|
* make sure the IOMMU has bound with our driver.
|
|
*/
|
|
bus_register_notifier(&platform_bus_type,
|
|
&tegra_dc_bus_notifier);
|
|
}
|
|
|
|
tegra_dc_create_sysfs(&dc->ndev->dev);
|
|
|
|
dev_info(&ndev->dev, "probed\n");
|
|
return 0;
|
|
|
|
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);
|
|
err_free:
|
|
tegra_dc_free_members(dc);
|
|
kfree(dc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_dc_remove(struct platform_device *ndev)
|
|
{
|
|
struct tegra_dc *dc = platform_get_drvdata(ndev);
|
|
|
|
bus_unregister_notifier(&platform_bus_type, &tegra_dc_bus_notifier);
|
|
|
|
tegra_dc_remove_sysfs(&dc->ndev->dev);
|
|
tegra_dc_remove_debugfs(dc);
|
|
|
|
if (dc->fb)
|
|
tegra_fb_unregister(dc->fb);
|
|
|
|
tegra_dc_ext_disable(dc->ext);
|
|
|
|
if (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);
|
|
synchronize_irq(dc->irq); /* wait for IRQ handlers to finish */
|
|
|
|
free_irq(dc->irq, dc);
|
|
clk_put(dc->emc_clk);
|
|
clk_put(dc->clk);
|
|
iounmap(dc->base);
|
|
|
|
tegra_dc_free_members(dc);
|
|
kfree(dc);
|
|
tegra_dc_set(NULL, ndev->id);
|
|
|
|
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);
|
|
|
|
trace_display_suspend(dc);
|
|
dev_info(&ndev->dev, "suspend\n");
|
|
|
|
tegra_dc_ext_disable(dc->ext);
|
|
|
|
mutex_lock(&dc->lock);
|
|
tegra_dc_io_start(dc);
|
|
|
|
if (dc->out_ops && dc->out_ops->suspend)
|
|
dc->out_ops->suspend(dc);
|
|
|
|
if (dc->enabled) {
|
|
_tegra_dc_disable(dc);
|
|
|
|
dc->suspended = true;
|
|
}
|
|
|
|
if (dc->out && dc->out->postsuspend) {
|
|
dc->out->postsuspend();
|
|
if (dc->out->type && dc->out->type == TEGRA_DC_OUT_HDMI)
|
|
/*
|
|
* avoid resume event due to voltage falling
|
|
*/
|
|
msleep(100);
|
|
}
|
|
|
|
tegra_dc_io_end(dc);
|
|
mutex_unlock(&dc->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);
|
|
|
|
trace_display_resume(dc);
|
|
dev_info(&ndev->dev, "resume\n");
|
|
|
|
mutex_lock(&dc->lock);
|
|
dc->suspended = false;
|
|
|
|
/* To pan the fb on resume */
|
|
tegra_fb_pan_display_reset(dc->fb);
|
|
|
|
if (dc->enabled) {
|
|
dc->enabled = false;
|
|
_tegra_dc_set_default_videomode(dc);
|
|
dc->enabled = _tegra_dc_enable(dc);
|
|
}
|
|
|
|
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_unlock(&dc->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
static 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;
|
|
|
|
tegra_dc_blank(dc);
|
|
tegra_dc_disable(dc);
|
|
}
|
|
|
|
extern 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;
|
|
}
|
|
|
|
extern int suspend_get(char *buffer, struct kernel_param *kp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int suspend;
|
|
|
|
module_param_call(suspend, suspend_set, suspend_get, &suspend, 0644);
|
|
|
|
static struct of_device_id tegra_dc_of_match[] = {
|
|
{ .compatible = "nvidia,tegra124-dc", },
|
|
{ },
|
|
};
|
|
|
|
struct platform_driver tegra_dc_driver = {
|
|
.driver = {
|
|
.name = "tegradc",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = tegra_dc_of_match,
|
|
},
|
|
.probe = tegra_dc_probe,
|
|
.remove = tegra_dc_remove,
|
|
#ifdef CONFIG_PM
|
|
.suspend = tegra_dc_suspend,
|
|
.resume = tegra_dc_resume,
|
|
#endif
|
|
.shutdown = tegra_dc_shutdown,
|
|
};
|
|
|
|
#ifndef MODULE
|
|
static int __init parse_disp_params(char *options, struct tegra_dc_mode *mode)
|
|
{
|
|
int i, params[11];
|
|
char *p;
|
|
int ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(params); i++) {
|
|
p = strsep(&options, ",");
|
|
if (p != NULL) {
|
|
if (*p) {
|
|
long unsigned int res;
|
|
ret = kstrtoul(p, 10, &res);
|
|
params[i] = res;
|
|
if (!ret)
|
|
pr_err("Error parsing disp param %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
} else
|
|
return -EINVAL;
|
|
}
|
|
|
|
mode->pclk = params[0];
|
|
if (mode->pclk == 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;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
__setup("disp_params=", tegra_dc_mode_override);
|
|
#endif
|
|
|
|
static int __init tegra_dc_module_init(void)
|
|
{
|
|
int ret = tegra_dc_ext_module_init();
|
|
if (ret)
|
|
return ret;
|
|
return platform_driver_register(&tegra_dc_driver);
|
|
}
|
|
|
|
static void __exit tegra_dc_module_exit(void)
|
|
{
|
|
platform_driver_unregister(&tegra_dc_driver);
|
|
tegra_dc_ext_module_exit();
|
|
}
|
|
|
|
module_exit(tegra_dc_module_exit);
|
|
module_init(tegra_dc_module_init);
|