tegrakernel/kernel/nvidia/drivers/video/tegra/dc/nvdisp/nvdisp.c

4706 lines
134 KiB
C
Raw Permalink Normal View History

2022-02-16 09:13:02 -06:00
/*
* drivers/video/tegra/dc/nvdisplay/nvdisp.c
*
* Copyright (c) 2014-2021, 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/interrupt.h>
#include <linux/of_irq.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/dma-mapping.h>
#include <linux/tegra_pm_domains.h>
#include <linux/tegra-pm.h>
#include <linux/platform/tegra/bwmgr_mc.h>
#include <linux/uaccess.h>
#include <linux/platform/tegra/isomgr.h>
#include <linux/debugfs.h>
#include <linux/ktime.h>
#include "dc.h"
#include "nvdisp.h"
#include "nvdisp_priv.h"
#include "dc_config.h"
#include "dc_priv.h"
#include "dp.h"
#include "hw_nvdisp_nvdisp.h"
#include "hw_win_nvdisp.h"
#include "dc_common.h"
DEFINE_MUTEX(tegra_nvdisp_lock);
static struct nvdisp_common_imp_data g_imp;
#define NVDISP_INPUT_LUT_SIZE 257
#define NVDISP_OUTPUT_LUT_SIZE 1025
#define NSEC_PER_MICROSEC 1000
#define MIN_FRAME_INTERVAL 1000
/* Global variables provided for clocks
* common to all heads
*/
struct clk *hubclk;
static struct clk *compclk;
static bool nvdisp_common_init_done;
static struct reset_control *nvdisp_common_rst[DC_N_WINDOWS+1];
static int tegra_nvdisp_set_color_control(struct tegra_dc *dc);
/*
* As per nvdisplay programming guidelines, only hubclk,dispclk,dscclk,
* pclk0 clocks should be enabled before accessing any display register
* in head0 power domain. But, as BL is unconditionally unpowergating
* all display powerdomains, enabling pclk1 and pclk2 clocks
* even here as WAR to avoid display register access issues in kernel
* Bug 200343370 tracks issue in BL.
*/
static struct tegra_dc_pd_clk_info t18x_disp_pd0_clk_info[] = {
{
.name = "nvdisplayhub",
.clk = NULL,
},
{
.name = "nvdisplay_disp",
.clk = NULL,
},
{
.name = "nvdisplay_p0",
.clk = NULL,
},
{
.name = "nvdisp_dsc",
.clk = NULL,
},
{
.name = "nvdisplay_p1",
.clk = NULL,
},
{
.name = "nvdisplay_p2",
.clk = NULL,
},
};
static struct tegra_dc_pd_clk_info t18x_disp_pd1_clk_info[] = {
{
.name = "nvdisplay_p1",
.clk = NULL,
},
};
static struct tegra_dc_pd_clk_info t18x_disp_pd2_clk_info[] = {
{
.name = "nvdisplay_p2",
.clk = NULL,
},
};
/*
* NOTE: Keep the following power domains ordered according to their head owner.
*/
static struct tegra_dc_pd_info t18x_disp_pd_info[] = {
/* Head0 power domain */
{
.of_id = {
{ .compatible = "nvidia,tegra186-disa-pd", },
{},
},
.pg_id = -1,
.head_owner = 0,
.head_mask = 0x1, /* Head(s): 0 */
.win_mask = 0x1, /* Window(s): 0 */
.domain_clks = t18x_disp_pd0_clk_info,
.nclks = ARRAY_SIZE(t18x_disp_pd0_clk_info),
.ref_cnt = 0,
},
/* Head1 power domain */
{
.of_id = {
{ .compatible = "nvidia,tegra186-disb-pd", },
{},
},
.pg_id = -1,
.head_owner = 1,
.head_mask = 0x2, /* Head(s): 1 */
.win_mask = 0x6, /* Window(s): 1,2 */
.domain_clks = t18x_disp_pd1_clk_info,
.nclks = ARRAY_SIZE(t18x_disp_pd1_clk_info),
.ref_cnt = 0,
},
/* Head2 power domain */
{
.of_id = {
{ .compatible = "nvidia,tegra186-disc-pd", },
{},
},
.pg_id = -1,
.head_owner = 2,
.head_mask = 0x4, /* Head(s): 2 */
.win_mask = 0x38, /* Window(s): 3,4,5 */
.domain_clks = t18x_disp_pd2_clk_info,
.nclks = ARRAY_SIZE(t18x_disp_pd2_clk_info),
.ref_cnt = 0,
},
};
static struct tegra_dc_pd_table t18x_disp_pd_table = {
.pd_entries = t18x_disp_pd_info,
.npd = ARRAY_SIZE(t18x_disp_pd_info),
};
static bool compclk_already_on = false, hubclk_already_on = false;
static int cur_clk_client_index;
static unsigned int default_srgb_regamma_lut[] = {
0x6000, 0x60CE, 0x619D, 0x626C, 0x632D, 0x63D4,
0x6469, 0x64F0, 0x656B, 0x65DF, 0x664A, 0x66B0,
0x6711, 0x676D, 0x67C4, 0x6819, 0x686A, 0x68B8,
0x6904, 0x694D, 0x6994, 0x69D8, 0x6A1B, 0x6A5D,
0x6A9C, 0x6ADA, 0x6B17, 0x6B52, 0x6B8C, 0x6BC5,
0x6BFD, 0x6C33, 0x6C69, 0x6C9E, 0x6CD1, 0x6D04,
0x6D36, 0x6D67, 0x6D98, 0x6DC7, 0x6DF6, 0x6E25,
0x6E52, 0x6E7F, 0x6EAC, 0x6ED7, 0x6F03, 0x6F2D,
0x6F58, 0x6F81, 0x6FAA, 0x6FD3, 0x6FFB, 0x7023,
0x704B, 0x7071, 0x7098, 0x70BE, 0x70E4, 0x7109,
0x712E, 0x7153, 0x7177, 0x719B, 0x71BF, 0x71E2,
0x7205, 0x7227, 0x724A, 0x726C, 0x728E, 0x72AF,
0x72D0, 0x72F1, 0x7312, 0x7333, 0x7353, 0x7373,
0x7392, 0x73B2, 0x73D1, 0x73F0, 0x740F, 0x742D,
0x744C, 0x746A, 0x7488, 0x74A6, 0x74C3, 0x74E0,
0x74FE, 0x751B, 0x7537, 0x7554, 0x7570, 0x758D,
0x75A9, 0x75C4, 0x75E0, 0x75FC, 0x7617, 0x7632,
0x764D, 0x7668, 0x7683, 0x769E, 0x76B8, 0x76D3,
0x76ED, 0x7707, 0x7721, 0x773B, 0x7754, 0x776E,
0x7787, 0x77A0, 0x77B9, 0x77D2, 0x77EB, 0x7804,
0x781D, 0x7835, 0x784E, 0x7866, 0x787E, 0x7896,
0x78AE, 0x78C6, 0x78DD, 0x78F5, 0x790D, 0x7924,
0x793B, 0x7952, 0x796A, 0x7981, 0x7997, 0x79AE,
0x79C5, 0x79DB, 0x79F2, 0x7A08, 0x7A1F, 0x7A35,
0x7A4B, 0x7A61, 0x7A77, 0x7A8D, 0x7AA3, 0x7AB8,
0x7ACE, 0x7AE3, 0x7AF9, 0x7B0E, 0x7B24, 0x7B39,
0x7B4E, 0x7B63, 0x7B78, 0x7B8D, 0x7BA2, 0x7BB6,
0x7BCB, 0x7BE0, 0x7BF4, 0x7C08, 0x7C1D, 0x7C31,
0x7C45, 0x7C59, 0x7C6E, 0x7C82, 0x7C96, 0x7CA9,
0x7CBD, 0x7CD1, 0x7CE5, 0x7CF8, 0x7D0C, 0x7D1F,
0x7D33, 0x7D46, 0x7D59, 0x7D6D, 0x7D80, 0x7D93,
0x7DA6, 0x7DB9, 0x7DCC, 0x7DDF, 0x7DF2, 0x7E04,
0x7E17, 0x7E2A, 0x7E3C, 0x7E4F, 0x7E61, 0x7E74,
0x7E86, 0x7E98, 0x7EAB, 0x7EBD, 0x7ECF, 0x7EE1,
0x7EF3, 0x7F05, 0x7F17, 0x7F29, 0x7F3B, 0x7F4D,
0x7F5E, 0x7F70, 0x7F82, 0x7F93, 0x7FA5, 0x7FB6,
0x7FC8, 0x7FD9, 0x7FEB, 0x7FFC, 0x800D, 0x801E,
0x8030, 0x8041, 0x8052, 0x8063, 0x8074, 0x8085,
0x8096, 0x80A7, 0x80B7, 0x80C8, 0x80D9, 0x80EA,
0x80FA, 0x810B, 0x811C, 0x812C, 0x813D, 0x814D,
0x815D, 0x816E, 0x817E, 0x818E, 0x819F, 0x81AF,
0x81BF, 0x81CF, 0x81DF, 0x81EF, 0x81FF, 0x820F,
0x821F, 0x822F, 0x823F, 0x824F, 0x825F, 0x826F,
0x827E, 0x828E, 0x829E, 0x82AD, 0x82BD, 0x82CC,
0x82DC, 0x82EB, 0x82FB, 0x830A, 0x831A, 0x8329,
0x8338, 0x8348, 0x8357, 0x8366, 0x8375, 0x8385,
0x8394, 0x83A3, 0x83B2, 0x83C1, 0x83D0, 0x83DF,
0x83EE, 0x83FD, 0x840C, 0x841A, 0x8429, 0x8438,
0x8447, 0x8455, 0x8464, 0x8473, 0x8481, 0x8490,
0x849F, 0x84AD, 0x84BC, 0x84CA, 0x84D9, 0x84E7,
0x84F5, 0x8504, 0x8512, 0x8521, 0x852F, 0x853D,
0x854B, 0x855A, 0x8568, 0x8576, 0x8584, 0x8592,
0x85A0, 0x85AE, 0x85BC, 0x85CA, 0x85D8, 0x85E6,
0x85F4, 0x8602, 0x8610, 0x861E, 0x862C, 0x8639,
0x8647, 0x8655, 0x8663, 0x8670, 0x867E, 0x868C,
0x8699, 0x86A7, 0x86B5, 0x86C2, 0x86D0, 0x86DD,
0x86EB, 0x86F8, 0x8705, 0x8713, 0x8720, 0x872E,
0x873B, 0x8748, 0x8756, 0x8763, 0x8770, 0x877D,
0x878B, 0x8798, 0x87A5, 0x87B2, 0x87BF, 0x87CC,
0x87D9, 0x87E6, 0x87F3, 0x8801, 0x880E, 0x881A,
0x8827, 0x8834, 0x8841, 0x884E, 0x885B, 0x8868,
0x8875, 0x8882, 0x888E, 0x889B, 0x88A8, 0x88B5,
0x88C1, 0x88CE, 0x88DB, 0x88E7, 0x88F4, 0x8900,
0x890D, 0x891A, 0x8926, 0x8933, 0x893F, 0x894C,
0x8958, 0x8965, 0x8971, 0x897D, 0x898A, 0x8996,
0x89A3, 0x89AF, 0x89BB, 0x89C8, 0x89D4, 0x89E0,
0x89EC, 0x89F9, 0x8A05, 0x8A11, 0x8A1D, 0x8A29,
0x8A36, 0x8A42, 0x8A4E, 0x8A5A, 0x8A66, 0x8A72,
0x8A7E, 0x8A8A, 0x8A96, 0x8AA2, 0x8AAE, 0x8ABA,
0x8AC6, 0x8AD2, 0x8ADE, 0x8AEA, 0x8AF5, 0x8B01,
0x8B0D, 0x8B19, 0x8B25, 0x8B31, 0x8B3C, 0x8B48,
0x8B54, 0x8B60, 0x8B6B, 0x8B77, 0x8B83, 0x8B8E,
0x8B9A, 0x8BA6, 0x8BB1, 0x8BBD, 0x8BC8, 0x8BD4,
0x8BDF, 0x8BEB, 0x8BF6, 0x8C02, 0x8C0D, 0x8C19,
0x8C24, 0x8C30, 0x8C3B, 0x8C47, 0x8C52, 0x8C5D,
0x8C69, 0x8C74, 0x8C80, 0x8C8B, 0x8C96, 0x8CA1,
0x8CAD, 0x8CB8, 0x8CC3, 0x8CCF, 0x8CDA, 0x8CE5,
0x8CF0, 0x8CFB, 0x8D06, 0x8D12, 0x8D1D, 0x8D28,
0x8D33, 0x8D3E, 0x8D49, 0x8D54, 0x8D5F, 0x8D6A,
0x8D75, 0x8D80, 0x8D8B, 0x8D96, 0x8DA1, 0x8DAC,
0x8DB7, 0x8DC2, 0x8DCD, 0x8DD8, 0x8DE3, 0x8DEE,
0x8DF9, 0x8E04, 0x8E0E, 0x8E19, 0x8E24, 0x8E2F,
0x8E3A, 0x8E44, 0x8E4F, 0x8E5A, 0x8E65, 0x8E6F,
0x8E7A, 0x8E85, 0x8E90, 0x8E9A, 0x8EA5, 0x8EB0,
0x8EBA, 0x8EC5, 0x8ECF, 0x8EDA, 0x8EE5, 0x8EEF,
0x8EFA, 0x8F04, 0x8F0F, 0x8F19, 0x8F24, 0x8F2E,
0x8F39, 0x8F43, 0x8F4E, 0x8F58, 0x8F63, 0x8F6D,
0x8F78, 0x8F82, 0x8F8C, 0x8F97, 0x8FA1, 0x8FAC,
0x8FB6, 0x8FC0, 0x8FCB, 0x8FD5, 0x8FDF, 0x8FEA,
0x8FF4, 0x8FFE, 0x9008, 0x9013, 0x901D, 0x9027,
0x9031, 0x903C, 0x9046, 0x9050, 0x905A, 0x9064,
0x906E, 0x9079, 0x9083, 0x908D, 0x9097, 0x90A1,
0x90AB, 0x90B5, 0x90BF, 0x90C9, 0x90D3, 0x90DD,
0x90E7, 0x90F1, 0x90FB, 0x9105, 0x910F, 0x9119,
0x9123, 0x912D, 0x9137, 0x9141, 0x914B, 0x9155,
0x915F, 0x9169, 0x9173, 0x917D, 0x9186, 0x9190,
0x919A, 0x91A4, 0x91AE, 0x91B8, 0x91C1, 0x91CB,
0x91D5, 0x91DF, 0x91E9, 0x91F2, 0x91FC, 0x9206,
0x9210, 0x9219, 0x9223, 0x922D, 0x9236, 0x9240,
0x924A, 0x9253, 0x925D, 0x9267, 0x9270, 0x927A,
0x9283, 0x928D, 0x9297, 0x92A0, 0x92AA, 0x92B3,
0x92BD, 0x92C6, 0x92D0, 0x92DA, 0x92E3, 0x92ED,
0x92F6, 0x9300, 0x9309, 0x9313, 0x931C, 0x9325,
0x932F, 0x9338, 0x9342, 0x934B, 0x9355, 0x935E,
0x9367, 0x9371, 0x937A, 0x9384, 0x938D, 0x9396,
0x93A0, 0x93A9, 0x93B2, 0x93BC, 0x93C5, 0x93CE,
0x93D7, 0x93E1, 0x93EA, 0x93F3, 0x93FC, 0x9406,
0x940F, 0x9418, 0x9421, 0x942B, 0x9434, 0x943D,
0x9446, 0x944F, 0x9459, 0x9462, 0x946B, 0x9474,
0x947D, 0x9486, 0x948F, 0x9499, 0x94A2, 0x94AB,
0x94B4, 0x94BD, 0x94C6, 0x94CF, 0x94D8, 0x94E1,
0x94EA, 0x94F3, 0x94FC, 0x9505, 0x950E, 0x9517,
0x9520, 0x9529, 0x9532, 0x953B, 0x9544, 0x954D,
0x9556, 0x955F, 0x9568, 0x9571, 0x957A, 0x9583,
0x958C, 0x9595, 0x959D, 0x95A6, 0x95AF, 0x95B8,
0x95C1, 0x95CA, 0x95D3, 0x95DB, 0x95E4, 0x95ED,
0x95F6, 0x95FF, 0x9608, 0x9610, 0x9619, 0x9622,
0x962B, 0x9633, 0x963C, 0x9645, 0x964E, 0x9656,
0x965F, 0x9668, 0x9671, 0x9679, 0x9682, 0x968B,
0x9693, 0x969C, 0x96A5, 0x96AD, 0x96B6, 0x96BF,
0x96C7, 0x96D0, 0x96D9, 0x96E1, 0x96EA, 0x96F2,
0x96FB, 0x9704, 0x970C, 0x9715, 0x971D, 0x9726,
0x972E, 0x9737, 0x9740, 0x9748, 0x9751, 0x9759,
0x9762, 0x976A, 0x9773, 0x977B, 0x9784, 0x978C,
0x9795, 0x979D, 0x97A6, 0x97AE, 0x97B6, 0x97BF,
0x97C7, 0x97D0, 0x97D8, 0x97E1, 0x97E9, 0x97F1,
0x97FA, 0x9802, 0x980B, 0x9813, 0x981B, 0x9824,
0x982C, 0x9834, 0x983D, 0x9845, 0x984D, 0x9856,
0x985E, 0x9866, 0x986F, 0x9877, 0x987F, 0x9888,
0x9890, 0x9898, 0x98A0, 0x98A9, 0x98B1, 0x98B9,
0x98C1, 0x98CA, 0x98D2, 0x98DA, 0x98E2, 0x98EB,
0x98F3, 0x98FB, 0x9903, 0x990B, 0x9914, 0x991C,
0x9924, 0x992C, 0x9934, 0x993C, 0x9945, 0x994D,
0x9955, 0x995D, 0x9965, 0x996D, 0x9975, 0x997D,
0x9986, 0x998E, 0x9996, 0x999E, 0x99A6, 0x99AE,
0x99B6, 0x99BE, 0x99C6, 0x99CE, 0x99D6, 0x99DE,
0x99E6, 0x99EE, 0x99F6, 0x99FE, 0x9A06, 0x9A0E,
0x9A16, 0x9A1E, 0x9A26, 0x9A2E, 0x9A36, 0x9A3E,
0x9A46, 0x9A4E, 0x9A56, 0x9A5E, 0x9A66, 0x9A6E,
0x9A76, 0x9A7E, 0x9A86, 0x9A8E, 0x9A96, 0x9A9D,
0x9AA5, 0x9AAD, 0x9AB5, 0x9ABD, 0x9AC5, 0x9ACD,
0x9AD5, 0x9ADC, 0x9AE4, 0x9AEC, 0x9AF4, 0x9AFC,
0x9B04, 0x9B0C, 0x9B13, 0x9B1B, 0x9B23, 0x9B2B,
0x9B33, 0x9B3A, 0x9B42, 0x9B4A, 0x9B52, 0x9B59,
0x9B61, 0x9B69, 0x9B71, 0x9B79, 0x9B80, 0x9B88,
0x9B90, 0x9B97, 0x9B9F, 0x9BA7, 0x9BAF, 0x9BB6,
0x9BBE, 0x9BC6, 0x9BCD, 0x9BD5, 0x9BDD, 0x9BE5,
0x9BEC, 0x9BF4, 0x9BFC, 0x9C03, 0x9C0B, 0x9C12,
0x9C1A, 0x9C22, 0x9C29, 0x9C31, 0x9C39, 0x9C40,
0x9C48, 0x9C50, 0x9C57, 0x9C5F, 0x9C66, 0x9C6E,
0x9C75, 0x9C7D, 0x9C85, 0x9C8C, 0x9C94, 0x9C9B,
0x9CA3, 0x9CAA, 0x9CB2, 0x9CBA, 0x9CC1, 0x9CC9,
0x9CD0, 0x9CD8, 0x9CDF, 0x9CE7, 0x9CEE, 0x9CF6,
0x9CFD, 0x9D05, 0x9D0C, 0x9D14, 0x9D1B, 0x9D23,
0x9D2A, 0x9D32, 0x9D39, 0x9D40, 0x9D48, 0x9D4F,
0x9D57, 0x9D5E, 0x9D66, 0x9D6D, 0x9D75, 0x9D7C,
0x9D83, 0x9D8B, 0x9D92, 0x9D9A, 0x9DA1, 0x9DA8,
0x9DB0, 0x9DB7, 0x9DBE, 0x9DC6, 0x9DCD, 0x9DD5,
0x9DDC, 0x9DE3, 0x9DEB, 0x9DF2, 0x9DF9, 0x9E01,
0x9E08, 0x9E0F, 0x9E17, 0x9E1E, 0x9E25, 0x9E2D,
0x9E34, 0x9E3B, 0x9E43, 0x9E4A, 0x9E51, 0x9E58,
0x9E60, 0x9E67, 0x9E6E, 0x9E75, 0x9E7D, 0x9E84,
0x9E8B, 0x9E92, 0x9E9A, 0x9EA1, 0x9EA8, 0x9EAF,
0x9EB7, 0x9EBE, 0x9EC5, 0x9ECC, 0x9ED4, 0x9EDB,
0x9EE2, 0x9EE9, 0x9EF0, 0x9EF7, 0x9EFF, 0x9F06,
0x9F0D, 0x9F14, 0x9F1B, 0x9F23, 0x9F2A, 0x9F31,
0x9F38, 0x9F3F, 0x9F46, 0x9F4D, 0x9F55, 0x9F5C,
0x9F63, 0x9F6A, 0x9F71, 0x9F78, 0x9F7F, 0x9F86,
0x9F8D, 0x9F95, 0x9F9C, 0x9FA3, 0x9FAA, 0x9FB1,
0x9FB8, 0x9FBF, 0x9FC6, 0x9FCD, 0x9FD4, 0x9FDB,
0x9FE2, 0x9FE9, 0x9FF0, 0x9FF7, 0x9FFF,
};
static unsigned int yuv8_10bpc_regamma_lut[] = {
0x6000, 0x6047, 0x608F, 0x60D7, 0x611F, 0x6167,
0x61AF, 0x61F7, 0x623F, 0x6287, 0x62CF, 0x6317,
0x635F, 0x63A7, 0x63EF, 0x6437, 0x647F, 0x64C7,
0x650F, 0x655B, 0x65A1, 0x65E5, 0x6627, 0x6668,
0x66A7, 0x66E5, 0x6721, 0x675C, 0x6796, 0x67CF,
0x6806, 0x683D, 0x6873, 0x68A7, 0x68DB, 0x690E,
0x6941, 0x6972, 0x69A3, 0x69D3, 0x6A02, 0x6A31,
0x6A5F, 0x6A8D, 0x6ABA, 0x6AE6, 0x6B12, 0x6B3D,
0x6B68, 0x6B93, 0x6BBD, 0x6BE6, 0x6C0F, 0x6C37,
0x6C60, 0x6C87, 0x6CAF, 0x6CD6, 0x6CFC, 0x6D22,
0x6D48, 0x6D6E, 0x6D93, 0x6DB8, 0x6DDC, 0x6E00,
0x6E24, 0x6E48, 0x6E6B, 0x6E8E, 0x6EB1, 0x6ED3,
0x6EF6, 0x6F18, 0x6F39, 0x6F5B, 0x6F7C, 0x6F9D,
0x6FBE, 0x6FDE, 0x6FFE, 0x701E, 0x703E, 0x705E,
0x707D, 0x709C, 0x70BB, 0x70DA, 0x70F9, 0x7117,
0x7135, 0x7154, 0x7171, 0x718F, 0x71AD, 0x71CA,
0x71E7, 0x7204, 0x7221, 0x723E, 0x725A, 0x7277,
0x7293, 0x72AF, 0x72CB, 0x72E6, 0x7302, 0x731E,
0x7339, 0x7354, 0x736F, 0x738A, 0x73A5, 0x73C0,
0x73DA, 0x73F4, 0x740F, 0x7429, 0x7443, 0x745D,
0x7477, 0x7490, 0x74AA, 0x74C3, 0x74DD, 0x74F6,
0x750F, 0x7528, 0x7541, 0x755A, 0x7572, 0x758B,
0x75A3, 0x75BC, 0x75D4, 0x75EC, 0x7604, 0x761C,
0x7634, 0x764C, 0x7663, 0x767B, 0x7693, 0x76AA,
0x76C1, 0x76D8, 0x76F0, 0x7707, 0x771E, 0x7735,
0x774B, 0x7762, 0x7779, 0x778F, 0x77A6, 0x77BC,
0x77D2, 0x77E9, 0x77FF, 0x7815, 0x782B, 0x7841,
0x7857, 0x786C, 0x7882, 0x7898, 0x78AD, 0x78C3,
0x78D8, 0x78EE, 0x7903, 0x7918, 0x792D, 0x7942,
0x7957, 0x796C, 0x7981, 0x7996, 0x79AB, 0x79BF,
0x79D4, 0x79E8, 0x79FD, 0x7A11, 0x7A26, 0x7A3A,
0x7A4E, 0x7A63, 0x7A77, 0x7A8B, 0x7A9F, 0x7AB3,
0x7AC7, 0x7ADA, 0x7AEE, 0x7B02, 0x7B16, 0x7B29,
0x7B3D, 0x7B50, 0x7B64, 0x7B77, 0x7B8A, 0x7B9E,
0x7BB1, 0x7BC4, 0x7BD7, 0x7BEA, 0x7BFD, 0x7C10,
0x7C23, 0x7C36, 0x7C49, 0x7C5C, 0x7C6F, 0x7C81,
0x7C94, 0x7CA7, 0x7CB9, 0x7CCC, 0x7CDE, 0x7CF1,
0x7D03, 0x7D15, 0x7D27, 0x7D3A, 0x7D4C, 0x7D5E,
0x7D70, 0x7D82, 0x7D94, 0x7DA6, 0x7DB8, 0x7DCA,
0x7DDC, 0x7DEE, 0x7DFF, 0x7E11, 0x7E23, 0x7E34,
0x7E46, 0x7E58, 0x7E69, 0x7E7B, 0x7E8C, 0x7E9D,
0x7EAF, 0x7EC0, 0x7ED1, 0x7EE3, 0x7EF4, 0x7F05,
0x7F16, 0x7F27, 0x7F38, 0x7F49, 0x7F5A, 0x7F6B,
0x7F7C, 0x7F8D, 0x7F9E, 0x7FAF, 0x7FBF, 0x7FD0,
0x7FE1, 0x7FF1, 0x8002, 0x8013, 0x8023, 0x8034,
0x8044, 0x8055, 0x8065, 0x8075, 0x8086, 0x8096,
0x80A6, 0x80B7, 0x80C7, 0x80D7, 0x80E7, 0x80F7,
0x8107, 0x8118, 0x8128, 0x8138, 0x8148, 0x8157,
0x8167, 0x8177, 0x8187, 0x8197, 0x81A7, 0x81B6,
0x81C6, 0x81D6, 0x81E6, 0x81F5, 0x8205, 0x8214,
0x8224, 0x8234, 0x8243, 0x8252, 0x8262, 0x8271,
0x8281, 0x8290, 0x829F, 0x82AF, 0x82BE, 0x82CD,
0x82DD, 0x82EC, 0x82FB, 0x830A, 0x8319, 0x8328,
0x8337, 0x8346, 0x8355, 0x8364, 0x8373, 0x8382,
0x8391, 0x83A0, 0x83AF, 0x83BE, 0x83CD, 0x83DB,
0x83EA, 0x83F9, 0x8408, 0x8416, 0x8425, 0x8434,
0x8442, 0x8451, 0x845F, 0x846E, 0x847C, 0x848B,
0x8499, 0x84A8, 0x84B6, 0x84C5, 0x84D3, 0x84E1,
0x84F0, 0x84FE, 0x850C, 0x851B, 0x8529, 0x8537,
0x8545, 0x8553, 0x8562, 0x8570, 0x857E, 0x858C,
0x859A, 0x85A8, 0x85B6, 0x85C4, 0x85D2, 0x85E0,
0x85EE, 0x85FC, 0x860A, 0x8618, 0x8626, 0x8633,
0x8641, 0x864F, 0x865D, 0x866B, 0x8678, 0x8686,
0x8694, 0x86A1, 0x86AF, 0x86BD, 0x86CA, 0x86D8,
0x86E5, 0x86F3, 0x8701, 0x870E, 0x871C, 0x8729,
0x8737, 0x8744, 0x8751, 0x875F, 0x876C, 0x877A,
0x8787, 0x8794, 0x87A2, 0x87AF, 0x87BC, 0x87C9,
0x87D7, 0x87E4, 0x87F1, 0x87FE, 0x880B, 0x8819,
0x8826, 0x8833, 0x8840, 0x884D, 0x885A, 0x8867,
0x8874, 0x8881, 0x888E, 0x889B, 0x88A8, 0x88B5,
0x88C2, 0x88CF, 0x88DC, 0x88E9, 0x88F6, 0x8902,
0x890F, 0x891C, 0x8929, 0x8936, 0x8942, 0x894F,
0x895C, 0x8969, 0x8975, 0x8982, 0x898F, 0x899B,
0x89A8, 0x89B4, 0x89C1, 0x89CE, 0x89DA, 0x89E7,
0x89F3, 0x8A00, 0x8A0C, 0x8A19, 0x8A25, 0x8A32,
0x8A3E, 0x8A4B, 0x8A57, 0x8A63, 0x8A70, 0x8A7C,
0x8A89, 0x8A95, 0x8AA1, 0x8AAE, 0x8ABA, 0x8AC6,
0x8AD2, 0x8ADF, 0x8AEB, 0x8AF7, 0x8B03, 0x8B0F,
0x8B1C, 0x8B28, 0x8B34, 0x8B40, 0x8B4C, 0x8B58,
0x8B64, 0x8B70, 0x8B7D, 0x8B89, 0x8B95, 0x8BA1,
0x8BAD, 0x8BB9, 0x8BC5, 0x8BD1, 0x8BDD, 0x8BE9,
0x8BF4, 0x8C00, 0x8C0C, 0x8C18, 0x8C24, 0x8C30,
0x8C3C, 0x8C48, 0x8C53, 0x8C5F, 0x8C6B, 0x8C77,
0x8C83, 0x8C8E, 0x8C9A, 0x8CA6, 0x8CB1, 0x8CBD,
0x8CC9, 0x8CD5, 0x8CE0, 0x8CEC, 0x8CF8, 0x8D03,
0x8D0F, 0x8D1A, 0x8D26, 0x8D32, 0x8D3D, 0x8D49,
0x8D54, 0x8D60, 0x8D6B, 0x8D77, 0x8D82, 0x8D8E,
0x8D99, 0x8DA5, 0x8DB0, 0x8DBC, 0x8DC7, 0x8DD2,
0x8DDE, 0x8DE9, 0x8DF4, 0x8E00, 0x8E0B, 0x8E17,
0x8E22, 0x8E2D, 0x8E38, 0x8E44, 0x8E4F, 0x8E5A,
0x8E66, 0x8E71, 0x8E7C, 0x8E87, 0x8E92, 0x8E9E,
0x8EA9, 0x8EB4, 0x8EBF, 0x8ECA, 0x8ED6, 0x8EE1,
0x8EEC, 0x8EF7, 0x8F02, 0x8F0D, 0x8F18, 0x8F23,
0x8F2E, 0x8F39, 0x8F44, 0x8F4F, 0x8F5A, 0x8F65,
0x8F70, 0x8F7B, 0x8F86, 0x8F91, 0x8F9C, 0x8FA7,
0x8FB2, 0x8FBD, 0x8FC8, 0x8FD3, 0x8FDE, 0x8FE9,
0x8FF3, 0x8FFE, 0x9009, 0x9014, 0x901F, 0x902A,
0x9034, 0x903F, 0x904A, 0x9055, 0x905F, 0x906A,
0x9075, 0x9080, 0x908A, 0x9095, 0x90A0, 0x90AA,
0x90B5, 0x90C0, 0x90CB, 0x90D5, 0x90E0, 0x90EA,
0x90F5, 0x9100, 0x910A, 0x9115, 0x911F, 0x912A,
0x9135, 0x913F, 0x914A, 0x9154, 0x915F, 0x9169,
0x9174, 0x917E, 0x9189, 0x9193, 0x919E, 0x91A8,
0x91B3, 0x91BD, 0x91C7, 0x91D2, 0x91DC, 0x91E7,
0x91F1, 0x91FB, 0x9206, 0x9210, 0x921B, 0x9225,
0x922F, 0x923A, 0x9244, 0x924E, 0x9259, 0x9263,
0x926D, 0x9277, 0x9282, 0x928C, 0x9296, 0x92A0,
0x92AB, 0x92B5, 0x92BF, 0x92C9, 0x92D4, 0x92DE,
0x92E8, 0x92F2, 0x92FC, 0x9306, 0x9311, 0x931B,
0x9325, 0x932F, 0x9339, 0x9343, 0x934D, 0x9357,
0x9361, 0x936B, 0x9376, 0x9380, 0x938A, 0x9394,
0x939E, 0x93A8, 0x93B2, 0x93BC, 0x93C6, 0x93D0,
0x93DA, 0x93E4, 0x93EE, 0x93F8, 0x9402, 0x940B,
0x9415, 0x941F, 0x9429, 0x9433, 0x943D, 0x9447,
0x9451, 0x945B, 0x9465, 0x946E, 0x9478, 0x9482,
0x948C, 0x9496, 0x94A0, 0x94A9, 0x94B3, 0x94BD,
0x94C7, 0x94D1, 0x94DA, 0x94E4, 0x94EE, 0x94F8,
0x9501, 0x950B, 0x9515, 0x951F, 0x9528, 0x9532,
0x953C, 0x9545, 0x954F, 0x9559, 0x9562, 0x956C,
0x9576, 0x957F, 0x9589, 0x9593, 0x959C, 0x95A6,
0x95AF, 0x95B9, 0x95C3, 0x95CC, 0x95D6, 0x95DF,
0x95E9, 0x95F2, 0x95FC, 0x9606, 0x960F, 0x9619,
0x9622, 0x962C, 0x9635, 0x963F, 0x9648, 0x9652,
0x965B, 0x9665, 0x966E, 0x9677, 0x9681, 0x968A,
0x9694, 0x969D, 0x96A7, 0x96B0, 0x96B9, 0x96C3,
0x96CC, 0x96D6, 0x96DF, 0x96E8, 0x96F2, 0x96FB,
0x9705, 0x970E, 0x9717, 0x9721, 0x972A, 0x9733,
0x973C, 0x9746, 0x974F, 0x9758, 0x9762, 0x976B,
0x9774, 0x977D, 0x9787, 0x9790, 0x9799, 0x97A2,
0x97AC, 0x97B5, 0x97BE, 0x97C7, 0x97D1, 0x97DA,
0x97E3, 0x97EC, 0x97F5, 0x97FE, 0x9808, 0x9811,
0x981A, 0x9823, 0x982C, 0x9835, 0x983F, 0x9848,
0x9851, 0x985A, 0x9863, 0x986C, 0x9875, 0x987E,
0x9887, 0x9890, 0x9899, 0x98A3, 0x98AC, 0x98B5,
0x98BE, 0x98C7, 0x98D0, 0x98D9, 0x98E2, 0x98EB,
0x98F4, 0x98FD, 0x9906, 0x990F, 0x9918, 0x9921,
0x992A, 0x9933, 0x993C, 0x9945, 0x994E, 0x9956,
0x995F, 0x9968, 0x9971, 0x997A, 0x9983, 0x998C,
0x9995, 0x999E, 0x99A7, 0x99AF, 0x99B8, 0x99C1,
0x99CA, 0x99D3, 0x99DC, 0x99E5, 0x99ED, 0x99F6,
0x99FF, 0x9A08, 0x9A11, 0x9A1A, 0x9A22, 0x9A2B,
0x9A34, 0x9A3D, 0x9A46, 0x9A4E, 0x9A57, 0x9A60,
0x9A69, 0x9A71, 0x9A7A, 0x9A83, 0x9A8C, 0x9A94,
0x9A9D, 0x9AA6, 0x9AAE, 0x9AB7, 0x9AC0, 0x9AC9,
0x9AD1, 0x9ADA, 0x9AE3, 0x9AEB, 0x9AF4, 0x9AFD,
0x9B05, 0x9B0E, 0x9B17, 0x9B1F, 0x9B28, 0x9B30,
0x9B39, 0x9B42, 0x9B4A, 0x9B53, 0x9B5C, 0x9B64,
0x9B6D, 0x9B75, 0x9B7E, 0x9B86, 0x9B8F, 0x9B98,
0x9BA0, 0x9BA9, 0x9BB1, 0x9BBA, 0x9BC2, 0x9BCB,
0x9BD3, 0x9BDC, 0x9BE4, 0x9BED, 0x9BF5, 0x9BFE,
0x9C06, 0x9C0F, 0x9C17, 0x9C20, 0x9C28, 0x9C31,
0x9C39, 0x9C42, 0x9C4A, 0x9C53, 0x9C5B, 0x9C64,
0x9C6C, 0x9C74, 0x9C7D, 0x9C85, 0x9C8E, 0x9C96,
0x9C9F, 0x9CA7, 0x9CAF, 0x9CB8, 0x9CC0, 0x9CC9,
0x9CD1, 0x9CD9, 0x9CE2, 0x9CEA, 0x9CF2, 0x9CFB,
0x9D03, 0x9D0B, 0x9D14, 0x9D1C, 0x9D24, 0x9D2D,
0x9D35, 0x9D3D, 0x9D46, 0x9D4E, 0x9D56, 0x9D5E,
0x9D67, 0x9D6F, 0x9D77, 0x9D80, 0x9D88, 0x9D90,
0x9D98, 0x9DA1, 0x9DA9, 0x9DB1, 0x9DB9, 0x9DC2,
0x9DCA, 0x9DD2, 0x9DDA, 0x9DE2, 0x9DEB, 0x9DF3,
0x9DFB, 0x9E03, 0x9E0C, 0x9E14, 0x9E1C, 0x9E24,
0x9E2C, 0x9E34, 0x9E3D, 0x9E45, 0x9E4D, 0x9E55,
0x9E5D, 0x9E65, 0x9E6D, 0x9E76, 0x9E7E, 0x9E86,
0x9E8E, 0x9E96, 0x9E9E, 0x9EA6, 0x9EAE, 0x9EB6,
0x9EBF, 0x9EC7, 0x9ECF, 0x9ED7, 0x9EDF, 0x9EE7,
0x9EEF, 0x9EF7, 0x9EFF, 0x9F07, 0x9F0F, 0x9F17,
0x9F1F, 0x9F27, 0x9F2F, 0x9F37, 0x9F3F, 0x9F47,
0x9F4F, 0x9F57, 0x9F5F, 0x9F67, 0x9F6F, 0x9F77,
0x9F7F, 0x9F87, 0x9F8F, 0x9F97, 0x9F9F, 0x9FA7,
0x9FAF, 0x9FB7, 0x9FBF, 0x9FC7, 0x9FCF, 0x9FD7,
0x9FDF, 0x9FE7, 0x9FEF, 0x9FF7, 0x9FFF,
};
static unsigned int yuv12bpc_regamma_lut[] = {
0x6000, 0x6047, 0x608F, 0x60D7, 0x611F, 0x6167,
0x61AF, 0x61F7, 0x623F, 0x6287, 0x62CF, 0x6317,
0x635F, 0x63A7, 0x63EF, 0x6437, 0x647F, 0x64C7,
0x650F, 0x6557, 0x659D, 0x65E1, 0x6623, 0x6664,
0x66A3, 0x66E1, 0x671D, 0x6758, 0x6792, 0x67CB,
0x6802, 0x6839, 0x686F, 0x68A4, 0x68D7, 0x690B,
0x693D, 0x696E, 0x699F, 0x69CF, 0x69FF, 0x6A2D,
0x6A5C, 0x6A89, 0x6AB6, 0x6AE3, 0x6B0E, 0x6B3A,
0x6B65, 0x6B8F, 0x6BB9, 0x6BE2, 0x6C0B, 0x6C34,
0x6C5C, 0x6C84, 0x6CAB, 0x6CD2, 0x6CF9, 0x6D1F,
0x6D45, 0x6D6A, 0x6D8F, 0x6DB4, 0x6DD9, 0x6DFD,
0x6E21, 0x6E44, 0x6E68, 0x6E8B, 0x6EAD, 0x6ED0,
0x6EF2, 0x6F14, 0x6F36, 0x6F57, 0x6F78, 0x6F99,
0x6FBA, 0x6FDB, 0x6FFB, 0x701B, 0x703B, 0x705A,
0x707A, 0x7099, 0x70B8, 0x70D7, 0x70F6, 0x7114,
0x7132, 0x7150, 0x716E, 0x718C, 0x71A9, 0x71C7,
0x71E4, 0x7201, 0x721E, 0x723A, 0x7257, 0x7273,
0x7290, 0x72AC, 0x72C8, 0x72E3, 0x72FF, 0x731A,
0x7336, 0x7351, 0x736C, 0x7387, 0x73A2, 0x73BC,
0x73D7, 0x73F1, 0x740C, 0x7426, 0x7440, 0x745A,
0x7474, 0x748D, 0x74A7, 0x74C0, 0x74DA, 0x74F3,
0x750C, 0x7525, 0x753E, 0x7557, 0x756F, 0x7588,
0x75A0, 0x75B9, 0x75D1, 0x75E9, 0x7601, 0x7619,
0x7631, 0x7649, 0x7661, 0x7678, 0x7690, 0x76A7,
0x76BE, 0x76D6, 0x76ED, 0x7704, 0x771B, 0x7732,
0x7748, 0x775F, 0x7776, 0x778C, 0x77A3, 0x77B9,
0x77D0, 0x77E6, 0x77FC, 0x7812, 0x7828, 0x783E,
0x7854, 0x786A, 0x787F, 0x7895, 0x78AB, 0x78C0,
0x78D5, 0x78EB, 0x7900, 0x7915, 0x792A, 0x7940,
0x7955, 0x796A, 0x797E, 0x7993, 0x79A8, 0x79BD,
0x79D1, 0x79E6, 0x79FA, 0x7A0F, 0x7A23, 0x7A37,
0x7A4C, 0x7A60, 0x7A74, 0x7A88, 0x7A9C, 0x7AB0,
0x7AC4, 0x7AD8, 0x7AEC, 0x7AFF, 0x7B13, 0x7B27,
0x7B3A, 0x7B4E, 0x7B61, 0x7B75, 0x7B88, 0x7B9B,
0x7BAE, 0x7BC2, 0x7BD5, 0x7BE8, 0x7BFB, 0x7C0E,
0x7C21, 0x7C34, 0x7C47, 0x7C59, 0x7C6C, 0x7C7F,
0x7C92, 0x7CA4, 0x7CB7, 0x7CC9, 0x7CDC, 0x7CEE,
0x7D00, 0x7D13, 0x7D25, 0x7D37, 0x7D49, 0x7D5C,
0x7D6E, 0x7D80, 0x7D92, 0x7DA4, 0x7DB6, 0x7DC8,
0x7DD9, 0x7DEB, 0x7DFD, 0x7E0F, 0x7E20, 0x7E32,
0x7E44, 0x7E55, 0x7E67, 0x7E78, 0x7E8A, 0x7E9B,
0x7EAC, 0x7EBE, 0x7ECF, 0x7EE0, 0x7EF1, 0x7F03,
0x7F14, 0x7F25, 0x7F36, 0x7F47, 0x7F58, 0x7F69,
0x7F7A, 0x7F8B, 0x7F9B, 0x7FAC, 0x7FBD, 0x7FCE,
0x7FDE, 0x7FEF, 0x8000, 0x8010, 0x8021, 0x8031,
0x8042, 0x8052, 0x8063, 0x8073, 0x8084, 0x8094,
0x80A4, 0x80B4, 0x80C5, 0x80D5, 0x80E5, 0x80F5,
0x8105, 0x8115, 0x8125, 0x8135, 0x8145, 0x8155,
0x8165, 0x8175, 0x8185, 0x8195, 0x81A5, 0x81B4,
0x81C4, 0x81D4, 0x81E3, 0x81F3, 0x8203, 0x8212,
0x8222, 0x8231, 0x8241, 0x8250, 0x8260, 0x826F,
0x827F, 0x828E, 0x829D, 0x82AD, 0x82BC, 0x82CB,
0x82DA, 0x82EA, 0x82F9, 0x8308, 0x8317, 0x8326,
0x8335, 0x8344, 0x8353, 0x8362, 0x8371, 0x8380,
0x838F, 0x839E, 0x83AD, 0x83BC, 0x83CB, 0x83D9,
0x83E8, 0x83F7, 0x8406, 0x8414, 0x8423, 0x8432,
0x8440, 0x844F, 0x845D, 0x846C, 0x847A, 0x8489,
0x8497, 0x84A6, 0x84B4, 0x84C3, 0x84D1, 0x84DF,
0x84EE, 0x84FC, 0x850A, 0x8519, 0x8527, 0x8535,
0x8543, 0x8552, 0x8560, 0x856E, 0x857C, 0x858A,
0x8598, 0x85A6, 0x85B4, 0x85C2, 0x85D0, 0x85DE,
0x85EC, 0x85FA, 0x8608, 0x8616, 0x8624, 0x8632,
0x863F, 0x864D, 0x865B, 0x8669, 0x8677, 0x8684,
0x8692, 0x86A0, 0x86AD, 0x86BB, 0x86C9, 0x86D6,
0x86E4, 0x86F1, 0x86FF, 0x870C, 0x871A, 0x8727,
0x8735, 0x8742, 0x8750, 0x875D, 0x876B, 0x8778,
0x8785, 0x8793, 0x87A0, 0x87AD, 0x87BA, 0x87C8,
0x87D5, 0x87E2, 0x87EF, 0x87FD, 0x880A, 0x8817,
0x8824, 0x8831, 0x883E, 0x884B, 0x8858, 0x8866,
0x8873, 0x8880, 0x888D, 0x889A, 0x88A7, 0x88B3,
0x88C0, 0x88CD, 0x88DA, 0x88E7, 0x88F4, 0x8901,
0x890E, 0x891A, 0x8927, 0x8934, 0x8941, 0x894E,
0x895A, 0x8967, 0x8974, 0x8980, 0x898D, 0x899A,
0x89A6, 0x89B3, 0x89BF, 0x89CC, 0x89D9, 0x89E5,
0x89F2, 0x89FE, 0x8A0B, 0x8A17, 0x8A24, 0x8A30,
0x8A3D, 0x8A49, 0x8A56, 0x8A62, 0x8A6E, 0x8A7B,
0x8A87, 0x8A93, 0x8AA0, 0x8AAC, 0x8AB8, 0x8AC5,
0x8AD1, 0x8ADD, 0x8AE9, 0x8AF6, 0x8B02, 0x8B0E,
0x8B1A, 0x8B26, 0x8B32, 0x8B3F, 0x8B4B, 0x8B57,
0x8B63, 0x8B6F, 0x8B7B, 0x8B87, 0x8B93, 0x8B9F,
0x8BAB, 0x8BB7, 0x8BC3, 0x8BCF, 0x8BDB, 0x8BE7,
0x8BF3, 0x8BFF, 0x8C0B, 0x8C17, 0x8C23, 0x8C2E,
0x8C3A, 0x8C46, 0x8C52, 0x8C5E, 0x8C6A, 0x8C75,
0x8C81, 0x8C8D, 0x8C99, 0x8CA4, 0x8CB0, 0x8CBC,
0x8CC8, 0x8CD3, 0x8CDF, 0x8CEB, 0x8CF6, 0x8D02,
0x8D0D, 0x8D19, 0x8D25, 0x8D30, 0x8D3C, 0x8D47,
0x8D53, 0x8D5E, 0x8D6A, 0x8D75, 0x8D81, 0x8D8C,
0x8D98, 0x8DA3, 0x8DAF, 0x8DBA, 0x8DC6, 0x8DD1,
0x8DDC, 0x8DE8, 0x8DF3, 0x8DFF, 0x8E0A, 0x8E15,
0x8E21, 0x8E2C, 0x8E37, 0x8E43, 0x8E4E, 0x8E59,
0x8E64, 0x8E70, 0x8E7B, 0x8E86, 0x8E91, 0x8E9C,
0x8EA8, 0x8EB3, 0x8EBE, 0x8EC9, 0x8ED4, 0x8EDF,
0x8EEB, 0x8EF6, 0x8F01, 0x8F0C, 0x8F17, 0x8F22,
0x8F2D, 0x8F38, 0x8F43, 0x8F4E, 0x8F59, 0x8F64,
0x8F6F, 0x8F7A, 0x8F85, 0x8F90, 0x8F9B, 0x8FA6,
0x8FB1, 0x8FBC, 0x8FC7, 0x8FD2, 0x8FDD, 0x8FE7,
0x8FF2, 0x8FFD, 0x9008, 0x9013, 0x901E, 0x9028,
0x9033, 0x903E, 0x9049, 0x9054, 0x905E, 0x9069,
0x9074, 0x907F, 0x9089, 0x9094, 0x909F, 0x90A9,
0x90B4, 0x90BF, 0x90C9, 0x90D4, 0x90DF, 0x90E9,
0x90F4, 0x90FF, 0x9109, 0x9114, 0x911E, 0x9129,
0x9134, 0x913E, 0x9149, 0x9153, 0x915E, 0x9168,
0x9173, 0x917D, 0x9188, 0x9192, 0x919D, 0x91A7,
0x91B2, 0x91BC, 0x91C6, 0x91D1, 0x91DB, 0x91E6,
0x91F0, 0x91FB, 0x9205, 0x920F, 0x921A, 0x9224,
0x922E, 0x9239, 0x9243, 0x924D, 0x9258, 0x9262,
0x926C, 0x9276, 0x9281, 0x928B, 0x9295, 0x929F,
0x92AA, 0x92B4, 0x92BE, 0x92C8, 0x92D3, 0x92DD,
0x92E7, 0x92F1, 0x92FB, 0x9305, 0x9310, 0x931A,
0x9324, 0x932E, 0x9338, 0x9342, 0x934C, 0x9356,
0x9360, 0x936B, 0x9375, 0x937F, 0x9389, 0x9393,
0x939D, 0x93A7, 0x93B1, 0x93BB, 0x93C5, 0x93CF,
0x93D9, 0x93E3, 0x93ED, 0x93F7, 0x9401, 0x940B,
0x9415, 0x941E, 0x9428, 0x9432, 0x943C, 0x9446,
0x9450, 0x945A, 0x9464, 0x946E, 0x9477, 0x9481,
0x948B, 0x9495, 0x949F, 0x94A9, 0x94B2, 0x94BC,
0x94C6, 0x94D0, 0x94DA, 0x94E3, 0x94ED, 0x94F7,
0x9501, 0x950A, 0x9514, 0x951E, 0x9527, 0x9531,
0x953B, 0x9545, 0x954E, 0x9558, 0x9562, 0x956B,
0x9575, 0x957F, 0x9588, 0x9592, 0x959B, 0x95A5,
0x95AF, 0x95B8, 0x95C2, 0x95CB, 0x95D5, 0x95DF,
0x95E8, 0x95F2, 0x95FB, 0x9605, 0x960E, 0x9618,
0x9621, 0x962B, 0x9634, 0x963E, 0x9647, 0x9651,
0x965A, 0x9664, 0x966D, 0x9677, 0x9680, 0x968A,
0x9693, 0x969D, 0x96A6, 0x96AF, 0x96B9, 0x96C2,
0x96CC, 0x96D5, 0x96DE, 0x96E8, 0x96F1, 0x96FB,
0x9704, 0x970D, 0x9717, 0x9720, 0x9729, 0x9733,
0x973C, 0x9745, 0x974E, 0x9758, 0x9761, 0x976A,
0x9774, 0x977D, 0x9786, 0x978F, 0x9799, 0x97A2,
0x97AB, 0x97B4, 0x97BE, 0x97C7, 0x97D0, 0x97D9,
0x97E2, 0x97EC, 0x97F5, 0x97FE, 0x9807, 0x9810,
0x9819, 0x9823, 0x982C, 0x9835, 0x983E, 0x9847,
0x9850, 0x9859, 0x9862, 0x986C, 0x9875, 0x987E,
0x9887, 0x9890, 0x9899, 0x98A2, 0x98AB, 0x98B4,
0x98BD, 0x98C6, 0x98CF, 0x98D8, 0x98E1, 0x98EA,
0x98F3, 0x98FC, 0x9905, 0x990E, 0x9917, 0x9920,
0x9929, 0x9932, 0x993B, 0x9944, 0x994D, 0x9956,
0x995F, 0x9968, 0x9971, 0x997A, 0x9983, 0x998B,
0x9994, 0x999D, 0x99A6, 0x99AF, 0x99B8, 0x99C1,
0x99CA, 0x99D3, 0x99DB, 0x99E4, 0x99ED, 0x99F6,
0x99FF, 0x9A08, 0x9A10, 0x9A19, 0x9A22, 0x9A2B,
0x9A34, 0x9A3C, 0x9A45, 0x9A4E, 0x9A57, 0x9A5F,
0x9A68, 0x9A71, 0x9A7A, 0x9A82, 0x9A8B, 0x9A94,
0x9A9D, 0x9AA5, 0x9AAE, 0x9AB7, 0x9ABF, 0x9AC8,
0x9AD1, 0x9ADA, 0x9AE2, 0x9AEB, 0x9AF4, 0x9AFC,
0x9B05, 0x9B0E, 0x9B16, 0x9B1F, 0x9B28, 0x9B30,
0x9B39, 0x9B41, 0x9B4A, 0x9B53, 0x9B5B, 0x9B64,
0x9B6C, 0x9B75, 0x9B7E, 0x9B86, 0x9B8F, 0x9B97,
0x9BA0, 0x9BA8, 0x9BB1, 0x9BBA, 0x9BC2, 0x9BCB,
0x9BD3, 0x9BDC, 0x9BE4, 0x9BED, 0x9BF5, 0x9BFE,
0x9C06, 0x9C0F, 0x9C17, 0x9C20, 0x9C28, 0x9C31,
0x9C39, 0x9C42, 0x9C4A, 0x9C52, 0x9C5B, 0x9C63,
0x9C6C, 0x9C74, 0x9C7D, 0x9C85, 0x9C8D, 0x9C96,
0x9C9E, 0x9CA7, 0x9CAF, 0x9CB8, 0x9CC0, 0x9CC8,
0x9CD1, 0x9CD9, 0x9CE1, 0x9CEA, 0x9CF2, 0x9CFA,
0x9D03, 0x9D0B, 0x9D13, 0x9D1C, 0x9D24, 0x9D2C,
0x9D35, 0x9D3D, 0x9D45, 0x9D4E, 0x9D56, 0x9D5E,
0x9D67, 0x9D6F, 0x9D77, 0x9D7F, 0x9D88, 0x9D90,
0x9D98, 0x9DA0, 0x9DA9, 0x9DB1, 0x9DB9, 0x9DC1,
0x9DCA, 0x9DD2, 0x9DDA, 0x9DE2, 0x9DEB, 0x9DF3,
0x9DFB, 0x9E03, 0x9E0B, 0x9E14, 0x9E1C, 0x9E24,
0x9E2C, 0x9E34, 0x9E3C, 0x9E45, 0x9E4D, 0x9E55,
0x9E5D, 0x9E65, 0x9E6D, 0x9E75, 0x9E7E, 0x9E86,
0x9E8E, 0x9E96, 0x9E9E, 0x9EA6, 0x9EAE, 0x9EB6,
0x9EBE, 0x9EC7, 0x9ECF, 0x9ED7, 0x9EDF, 0x9EE7,
0x9EEF, 0x9EF7, 0x9EFF, 0x9F07, 0x9F0F, 0x9F17,
0x9F1F, 0x9F27, 0x9F2F, 0x9F37, 0x9F3F, 0x9F47,
0x9F4F, 0x9F57, 0x9F5F, 0x9F67, 0x9F6F, 0x9F77,
0x9F7F, 0x9F87, 0x9F8F, 0x9F97, 0x9F9F, 0x9FA7,
0x9FAF, 0x9FB7, 0x9FBF, 0x9FC7, 0x9FCF, 0x9FD7,
0x9FDF, 0x9FE7, 0x9FEF, 0x9FF7, 0x9FFF,
};
void tegra_nvdisp_set_background_color(struct tegra_dc *dc,
u32 background_color)
{
tegra_dc_writel(dc, background_color, nvdisp_background_color_r());
}
static void tegra_nvdisp_mode_set_background_color(struct tegra_dc *dc)
{
u32 reg_val = nvdisp_background_color_reset_val_v();
if (dc->yuv_bypass) {
int yuv_flag = dc->mode.vmode & FB_VMODE_SET_YUV_MASK;
switch (yuv_flag) {
case FB_VMODE_Y420 | FB_VMODE_Y24:
reg_val = RGB_TO_YUV420_8BPC_BLACK_PIX;
break;
case FB_VMODE_Y420 | FB_VMODE_Y30:
reg_val = RGB_TO_YUV420_10BPC_BLACK_PIX;
break;
case FB_VMODE_Y422 | FB_VMODE_Y36:
reg_val = RGB_TO_YUV422_10BPC_BLACK_PIX;
break;
case FB_VMODE_Y444 | FB_VMODE_Y24:
reg_val = RGB_TO_YUV444_8BPC_BLACK_PIX;
break;
default:
dev_err(&dc->ndev->dev, "%s: unverified bypass mode = 0x%x\n",
__func__, dc->mode.vmode);
break;
}
}
tegra_nvdisp_set_background_color(dc, reg_val);
}
static inline void tegra_nvdisp_program_curs_dvfs_watermark(struct tegra_dc *dc,
u32 dvfs_watermark);
static inline void tegra_nvdisp_program_win_dvfs_watermark(
struct tegra_dc_win *win,
u32 dvfs_watermark);
static inline void tegra_nvdisp_program_common_fetch_meter(struct tegra_dc *dc,
u32 curs_slots,
u32 win_slots);
static void tegra_nvdisp_program_imp_curs_entries(struct tegra_dc *dc,
u32 fetch_slots,
u32 pipe_meter,
u32 dvfs_watermark,
u32 mempool_entries,
int owner_head);
static void tegra_nvdisp_program_imp_win_entries(struct tegra_dc *dc,
u32 thread_group,
u32 fetch_slots,
u32 pipe_meter,
u32 dvfs_watermark,
u32 mempool_entries,
u32 win_id,
int owner_head);
static inline bool tegra_nvdisp_is_dc_active(struct tegra_dc *dc)
{
u32 ctrl_mode = 0;
if (!dc || !dc->enabled)
return false;
ctrl_mode = tegra_dc_readl(dc, nvdisp_display_command_r());
return (ctrl_mode != nvdisp_display_command_control_mode_stop_f());
}
static int tegra_nvdisp_program_output_lut(struct tegra_dc *dc,
struct tegra_dc_nvdisp_lut *nvdisp_lut)
{
tegra_dc_writel(dc,
tegra_dc_reg_l32(nvdisp_lut->phy_addr),
nvdisp_output_lut_base_r());
tegra_dc_writel(dc,
tegra_dc_reg_h32(nvdisp_lut->phy_addr),
nvdisp_output_lut_base_hi_r());
tegra_dc_writel(dc, nvdisp_output_lut_ctl_size_1025_f() |
nvdisp_output_lut_ctl_mode_f(0x1), /* interpolate */
nvdisp_output_lut_ctl_r());
return 0;
}
static void nvdisp_copy_output_lut(u64 *dst, unsigned int *src, int size)
{
int i;
u64 r = 0;
for (i = 0; i < size; i++) {
r = src[i];
dst[i] = (r << 32) | (r << 16) | r;
}
}
void tegra_nvdisp_get_default_cmu(struct tegra_dc_nvdisp_cmu *default_cmu)
{
nvdisp_copy_output_lut(default_cmu->rgb, default_srgb_regamma_lut,
NVDISP_OUTPUT_LUT_SIZE);
}
static int nvdisp_alloc_output_lut(struct tegra_dc *dc)
{
struct tegra_dc_nvdisp_lut *nvdisp_lut = &dc->nvdisp_postcomp_lut;
if (!nvdisp_lut)
return -ENOMEM;
/* Allocate the memory for LUT */
nvdisp_lut->size = NVDISP_OUTPUT_LUT_SIZE * sizeof(u64);
nvdisp_lut->rgb = (u64 *)dma_zalloc_coherent(&dc->ndev->dev,
nvdisp_lut->size, &nvdisp_lut->phy_addr, GFP_KERNEL);
if (!nvdisp_lut->rgb)
return -ENOMEM;
/* Init LUT with cmu data provided from DT file */
if (dc->pdata->nvdisp_cmu && dc->pdata->cmu_enable) {
memcpy(nvdisp_lut->rgb, dc->pdata->nvdisp_cmu->rgb,
nvdisp_lut->size);
return 0;
}
/* Init the LUT table with default sRGB values */
nvdisp_copy_output_lut(nvdisp_lut->rgb, default_srgb_regamma_lut,
NVDISP_OUTPUT_LUT_SIZE);
return 0;
}
static int nvdisp_alloc_input_lut(struct tegra_dc *dc,
struct tegra_dc_win *win,
bool winlut)
{
struct tegra_dc_nvdisp_lut *nvdisp_lut;
if (winlut)
nvdisp_lut = &win->nvdisp_lut;
else
nvdisp_lut = &dc->fb_nvdisp_lut;
if (!nvdisp_lut)
return -ENOMEM;
/* Allocate the memory for LUT */
nvdisp_lut->size = NVDISP_INPUT_LUT_SIZE * sizeof(u64);
nvdisp_lut->rgb = (u64 *)dma_zalloc_coherent(&dc->ndev->dev,
nvdisp_lut->size, &nvdisp_lut->phy_addr, GFP_KERNEL);
if (!nvdisp_lut->rgb)
return -ENOMEM;
return 0;
}
/* Deassert all the common nvdisplay resets.
* Misc and all windows groups are placed in common.
*/
static int __maybe_unused
tegra_nvdisp_common_reset_deassert(struct tegra_dc *dc)
{
u8 i;
int err;
for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) {
if (!nvdisp_common_rst[i]) {
dev_err(&dc->ndev->dev, "No nvdisp resets available\n");
return -EINVAL;
}
err = reset_control_deassert(nvdisp_common_rst[i]);
if (err) {
dev_err(&dc->ndev->dev, "Unable to reset misc\n");
return err;
}
}
return 0;
}
static int __maybe_unused tegra_nvdisp_common_reset_assert(struct tegra_dc *dc)
{
u8 i;
int err;
for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) {
if (!nvdisp_common_rst[i]) {
dev_err(&dc->ndev->dev, "No Nvdisp resets available\n");
return -EINVAL;
}
err = reset_control_assert(nvdisp_common_rst[i]);
if (err) {
dev_err(&dc->ndev->dev, "Unable to reset misc\n");
return err;
}
}
return 0;
}
static int __maybe_unused tegra_nvdisp_wgrp_reset_assert(struct tegra_dc *dc)
{
int idx, err = 0;
for_each_set_bit(idx, &dc->valid_windows,
tegra_dc_get_numof_dispwindows()) {
err = reset_control_assert(nvdisp_common_rst[idx+1]);
if (err)
dev_err(&dc->ndev->dev, "Failed window %d rst\n", idx);
}
return err;
}
static int tegra_nvdisp_wgrp_reset_deassert(struct tegra_dc *dc)
{
int idx, err = 0;
/* Misc deassert is common for all windows */
err = reset_control_deassert(nvdisp_common_rst[0]);
if (err)
dev_err(&dc->ndev->dev, "Failed Misc deassert\n");
for_each_set_bit(idx, &dc->valid_windows,
tegra_dc_get_numof_dispwindows()) {
err = reset_control_deassert(nvdisp_common_rst[idx+1]);
if (err)
dev_err(&dc->ndev->dev, "Failed window deassert\n");
}
return err;
}
static int tegra_nvdisp_reset_prepare(struct tegra_dc *dc)
{
char rst_name[6];
int i;
/* Continue if bpmp is enabled or sim */
if (!tegra_bpmp_running()) {
if (tegra_platform_is_vdk())
dev_err(&dc->ndev->dev, "Continue without BPMP for sim\n");
else
return 0;
}
nvdisp_common_rst[0] =
devm_reset_control_get(&dc->ndev->dev, "misc");
if (IS_ERR(nvdisp_common_rst[0])) {
dev_err(&dc->ndev->dev, "Unable to get misc reset\n");
return PTR_ERR(nvdisp_common_rst[0]);
}
for (i = 0; i < tegra_dc_get_numof_dispwindows(); i++) {
snprintf(rst_name, sizeof(rst_name), "wgrp%u", i);
nvdisp_common_rst[i+1] =
devm_reset_control_get(&dc->ndev->dev, rst_name);
if (IS_ERR(nvdisp_common_rst[i+1])) {
dev_err(&dc->ndev->dev,"Unable to get %s reset\n",
rst_name);
return PTR_ERR(nvdisp_common_rst[i+1]);
}
}
return 0;
}
int tegra_nvdisp_set_compclk(struct tegra_dc *dc)
{
int i, index = 0;
unsigned long rate = 0;
struct tegra_dc *dc_other = NULL;
/* comp clk will be maximum of head0/1/2 */
mutex_lock(&tegra_nvdisp_lock);
for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) {
dc_other = tegra_dc_get_dc(i);
if (dc_other && dc_other->comp_clk_inuse &&
rate <= clk_get_rate(dc_other->clk)) {
rate = clk_get_rate(dc_other->clk);
index = i;
}
}
if (rate == 0) {
/* none of the pclks is on. disable compclk */
if (compclk_already_on) {
tegra_disp_clk_disable_unprepare(compclk);
compclk_already_on = false;
}
mutex_unlock(&tegra_nvdisp_lock);
return 0;
}
dc_other = tegra_dc_get_dc(index);
BUG_ON(!dc_other);
pr_debug(" rate get on compclk %ld\n", rate);
/* save current clock client index */
cur_clk_client_index = index;
/* Set parent for Display clock */
clk_set_parent(compclk, dc_other->clk);
/* Enable Display comp clock */
if (!compclk_already_on) {
tegra_disp_clk_prepare_enable(compclk);
compclk_already_on = true;
}
mutex_unlock(&tegra_nvdisp_lock);
return 0;
}
int tegra_nvdisp_test_and_set_compclk(unsigned long rate, struct tegra_dc *dc)
{
bool inuse_state;
mutex_lock(&tegra_nvdisp_lock);
/* check if current dc->clk is the source of compclk and whether
* rate to which dc->clk is going to be changed is less than current
* compclk rate. If yes, it is necessary to switch compclk source to
* next highest dc->clk. Otherwise current dc->clk acting as parent to
* compclk will lower its rate causing compclk rate to get lowered
* and head will underflow for brief period before compclk is again
* set to highest of all dc->clks
*/
if (cur_clk_client_index == dc->ctrl_num &&
rate < clk_get_rate(compclk)) {
inuse_state = dc->comp_clk_inuse;
/* set inuse flag to false to make sure current
* dc->clk will not be used as source for compclk
*/
dc->comp_clk_inuse = false;
mutex_unlock(&tegra_nvdisp_lock);
tegra_nvdisp_set_compclk(dc);
dc->comp_clk_inuse = inuse_state;
return 0;
}
mutex_unlock(&tegra_nvdisp_lock);
return 0;
}
static int _tegra_nvdisp_init_pd_table(struct tegra_dc *dc)
{
struct tegra_dc_pd_table *pd_table = tegra_dc_get_disp_pd_table();
struct tegra_dc_pd_info *pds = pd_table->pd_entries;
int npower_domains = pd_table->npd;
int i;
mutex_init(&pd_table->pd_lock);
for (i = 0; i < npower_domains; i++) {
struct tegra_dc_pd_info *pd = &pds[i];
struct tegra_dc_pd_clk_info *domain_clks = pd->domain_clks;
int nclks = pd->nclks;
int j;
/* Fill in the powergate id for this power domain. */
pd->pg_id = tegra_pd_get_powergate_id(pd->of_id);
/* Query all the required clocks for this power domain. */
for (j = 0; j < nclks; j++) {
struct tegra_dc_pd_clk_info *domain_clk;
domain_clk = &domain_clks[j];
domain_clk->clk =
tegra_disp_clk_get(&dc->ndev->dev, domain_clk->name);
if (IS_ERR_OR_NULL(domain_clk->clk)) {
dev_err(&dc->ndev->dev, "can't get %s clock\n",
domain_clk->name);
return -ENOENT;
}
}
}
return 0;
}
static void _tegra_nvdisp_destroy_pd_table(struct tegra_dc *dc)
{
struct tegra_dc_pd_table *pd_table = tegra_dc_get_disp_pd_table();
struct tegra_dc_pd_info *pds = pd_table->pd_entries;
int npower_domains = pd_table->npd;
int i;
for (i = 0; i < npower_domains; i++) {
struct tegra_dc_pd_info *pd = &pds[i];
struct tegra_dc_pd_clk_info *domain_clks = pd->domain_clks;
int nclks = pd->nclks;
int j;
/* Release all the required clocks for this power domain. */
for (j = 0; j < nclks; j++) {
struct tegra_dc_pd_clk_info *domain_clk;
domain_clk = &domain_clks[j];
if (IS_ERR_OR_NULL(domain_clk->clk))
continue;
tegra_disp_clk_put(&dc->ndev->dev, domain_clk->clk);
}
}
}
static void tegra_nvdisp_init_imp_wqs(void)
{
struct nvdisp_request_wq *reservation_wq;
struct nvdisp_request_wq *promotion_wq;
int num_heads = tegra_dc_get_numof_dispheads();
int timeout;
reservation_wq = &g_imp.common_channel_reservation_wq;
promotion_wq = &g_imp.common_channel_promotion_wq;
/*
* Initialize the promotion workqueue:
* - Use 1 HZ to represent the worst-case time for a pending ACT_REQ
* to promote.
* - The "nr_pending" field will be ignored for this workqueue, but go
* ahead and initialize it to 0.
*/
init_waitqueue_head(&promotion_wq->wq);
atomic_set(&promotion_wq->nr_pending, 0);
promotion_wq->timeout_per_entry = HZ;
/* Initialize the reservation workqueue. */
init_waitqueue_head(&reservation_wq->wq);
atomic_set(&reservation_wq->nr_pending, 0);
/*
* We can quickly calculate the worst-case time for an IMP flip by doing
* the following:
* 1) Use 1 HZ as the base value. This roughly corresponds to an 1 fps
* refresh rate.
* 2) For a single IMP flip on head X, we may need to promote BOTH
* decreasing and increasing mempool requests for all the other
* heads. This is why there's a factor of 2. Keep using the
* worst-case promotion time of 1 HZ.
*/
timeout = HZ;
timeout += max(num_heads - 1, 0) * 2 * HZ;
/*
* The COMMON channel barrier can be held during head enable/disable as
* well. Each of these operations should complete within a few
* microseconds, but we still need to consider them to approximate our
* upper bound.
*/
timeout = max(timeout, NVDISP_HEAD_ENABLE_DISABLE_TIMEOUT_HZ);
reservation_wq->timeout_per_entry = timeout;
}
static void tegra_nvdisp_init_imp_mc_caps(void)
{
struct tegra_dc_ext_imp_mc_caps *mc_caps = &g_imp.mc_caps;
mc_caps->peak_hubclk_hz = clk_round_rate(hubclk, ULONG_MAX);
mc_caps->num_dram_channels = tegra_bwmgr_get_dram_num_channels();
if (tegra_dc_is_t19x())
mc_caps->request_batch_size = 8;
else
mc_caps->request_batch_size = 32;
}
static void tegra_nvdisp_init_common_imp_data(void)
{
struct mrq_emc_dvfs_latency_response *emc_dvfs_table =
&g_imp.emc_dvfs_table;
uint32_t cur_max_latency = 0;
int i;
INIT_LIST_HEAD(&g_imp.imp_settings_queue);
tegra_nvdisp_init_imp_wqs();
tegra_bpmp_send_receive(MRQ_EMC_DVFS_LATENCY, NULL, 0,
emc_dvfs_table,
sizeof(*emc_dvfs_table));
for (i = emc_dvfs_table->num_pairs - 1; i >= 0; i--) {
struct emc_dvfs_latency *dvfs_pair = &emc_dvfs_table->pairs[i];
cur_max_latency = max(dvfs_pair->latency, cur_max_latency);
dvfs_pair->latency = cur_max_latency;
}
tegra_nvdisp_init_imp_mc_caps();
}
static u32 _tegra_nvdisp_get_total_mempool_size(struct tegra_dc *dc)
{
#define MEMPOOL_WIDTH (32)
u32 mempool_entries, bytes_per_entry;
u32 ihub_capa;
ihub_capa = tegra_dc_readl(dc, nvdisp_ihub_capa_r());
mempool_entries = nvdisp_ihub_capa_mempool_entries_v(ihub_capa);
bytes_per_entry =
(MEMPOOL_WIDTH << nvdisp_ihub_capa_mempool_width_v(ihub_capa));
return mempool_entries * bytes_per_entry;
#undef MEMPOOL_WIDTH
}
static void tegra_nvdisp_init_common_imp_reg_caps(struct tegra_dc *dc)
{
mutex_lock(&tegra_nvdisp_lock);
if (unlikely(!g_imp.reg_caps_initialized)) {
g_imp.mc_caps.total_mempool_size_bytes =
_tegra_nvdisp_get_total_mempool_size(dc);
g_imp.reg_caps_initialized = true;
}
mutex_unlock(&tegra_nvdisp_lock);
}
static int _tegra_nvdisp_init_once(struct tegra_dc *dc)
{
int ret = 0;
int i;
char syncpt_name[] = "disp_a";
ret = tegra_nvdisp_reset_prepare(dc);
if (ret)
return ret;
/* Get the nvdisplay_hub and nvdisplay_disp clock and enable
* it by default. Change the rates based on requirement later
*/
hubclk = tegra_disp_clk_get(&dc->ndev->dev, "nvdisplayhub");
if (IS_ERR_OR_NULL(hubclk)) {
dev_err(&dc->ndev->dev, "can't get display hub clock\n");
ret = -ENOENT;
goto INIT_EXIT;
}
compclk = tegra_disp_clk_get(&dc->ndev->dev, "nvdisplay_disp");
if (IS_ERR_OR_NULL(compclk)) {
dev_err(&dc->ndev->dev, "can't get display comp clock\n");
ret = -ENOENT;
goto INIT_CLK_ERR;
}
/* Init sycpt ids */
dc->valid_windows = 0x3f; /* Assign all windows to this head */
for (i = 0; i < tegra_dc_get_numof_dispwindows();
++i, ++syncpt_name[5]) {
struct tegra_dc_win *win = tegra_dc_get_window(dc, i);
win->idx = i;
win->syncpt.id = nvhost_get_syncpt_client_managed(dc->ndev,
syncpt_name);
/* allocate input LUT memory and assign to HW */
ret = nvdisp_alloc_input_lut(dc, win, true);
if (ret)
goto INIT_LUT_ERR;
tegra_dc_init_nvdisp_lut_defaults(&win->nvdisp_lut);
win->color_expand_enable = true;
win->clamp_before_blend = true;
win->force_user_csc = false;
win->force_user_degamma = false;
}
ret = _tegra_nvdisp_init_pd_table(dc);
if (ret)
goto INIT_PD_ERR;
#ifdef CONFIG_TEGRA_ISOMGR
/*
* Register the IHUB bw client. The bwmgr id that we pass in
* must be different from the one that's internally mapped to
* the ISO client id.
*/
ret = tegra_nvdisp_bandwidth_register(TEGRA_ISO_CLIENT_DISP_0,
TEGRA_BWMGR_CLIENT_DISP1);
if (ret) {
dev_err(&dc->ndev->dev,
"failed to register ihub bw client\n");
goto BW_REG_ERR;
}
#endif
tegra_nvdisp_init_common_imp_data();
tegra_nvdisp_crc_region_init();
dc->valid_windows = 0;
nvdisp_common_init_done = true;
goto INIT_EXIT;
#ifdef CONFIG_TEGRA_ISOMGR
BW_REG_ERR:
_tegra_nvdisp_bandwidth_unregister();
#endif
INIT_PD_ERR:
_tegra_nvdisp_destroy_pd_table(dc);
INIT_LUT_ERR:
for (i = 0; i < tegra_dc_get_numof_dispwindows(); ++i) {
struct tegra_dc_nvdisp_lut *nvdisp_lut;
struct tegra_dc_win *win = tegra_dc_get_window(dc, i);
/* Free the memory for Input LUT & fb LUT*/
nvdisp_lut = &win->nvdisp_lut;
if (nvdisp_lut->rgb)
dma_free_coherent(&dc->ndev->dev, nvdisp_lut->size,
(void *)nvdisp_lut->rgb, nvdisp_lut->phy_addr);
}
INIT_CLK_ERR:
if (!IS_ERR_OR_NULL(hubclk))
tegra_disp_clk_put(&dc->ndev->dev, hubclk);
if (!IS_ERR_OR_NULL(compclk))
tegra_disp_clk_put(&dc->ndev->dev, compclk);
INIT_EXIT:
return ret;
}
static inline bool tegra_nvdisp_is_lpf_required(struct tegra_dc *dc)
{
if (dc->mode.vmode & FB_VMODE_BYPASS)
return false;
return ((dc->mode.vmode & FB_VMODE_Y422) ||
tegra_dc_is_yuv420_8bpc(&dc->mode));
}
void tegra_nvdisp_set_chroma_lpf(struct tegra_dc *dc)
{
/* if color fmt is yuv_422 and postcomp support yuv422
* enable chroma lpf by default
*/
u32 postcomp_capa = tegra_dc_readl(dc, nvdisp_postcomp_capa_r());
u32 chroma_lpf = tegra_dc_readl(dc, nvdisp_procamp_r());
chroma_lpf &= ~nvdisp_procamp_chroma_lpf_enable_f();
if (tegra_nvdisp_is_lpf_required(dc) &&
nvdisp_postcomp_capa_is_yuv422_enable_v(postcomp_capa))
chroma_lpf |= nvdisp_procamp_chroma_lpf_enable_f();
tegra_dc_writel(dc, chroma_lpf, nvdisp_procamp_r());
}
static int _tegra_nvdisp_set_ec_output_lut(struct tegra_dc *dc,
struct tegra_dc_mode *mode)
{
struct tegra_dc_nvdisp_lut *nvdisp_lut = &dc->nvdisp_postcomp_lut;
if (!nvdisp_lut)
return -ENOMEM;
if (mode->vmode & FB_VMODE_SET_YUV_MASK) {
if (mode->vmode & FB_VMODE_Y24 ||
mode->vmode & FB_VMODE_Y30) {
nvdisp_copy_output_lut(nvdisp_lut->rgb,
yuv8_10bpc_regamma_lut,
NVDISP_OUTPUT_LUT_SIZE);
} else if (mode->vmode & FB_VMODE_Y36) {
nvdisp_copy_output_lut(nvdisp_lut->rgb,
yuv12bpc_regamma_lut,
NVDISP_OUTPUT_LUT_SIZE);
}
}
return 0;
}
void tegra_nvdisp_set_ocsc(struct tegra_dc *dc, struct tegra_dc_mode *mode)
{
u32 csc2_control;
/* If mode is not dirty, then set user cached csc2 values */
if (!dc->mode_dirty) {
csc2_control = dc->cached_settings.csc2_control;
goto write_reg;
}
if (mode->vmode & FB_VMODE_BYPASS) {
/* Use rgb ocsc and disable range scaling for yuv bypass. */
csc2_control = nvdisp_csc2_control_output_color_sel_rgb_f() |
nvdisp_csc2_control_limit_rgb_disable_f();
goto update_cache;
}
if (!IS_RGB(mode->vmode)) {
u32 ec = mode->vmode & (FB_VMODE_EC_MASK & ~FB_VMODE_EC_ENABLE);
switch (ec) {
case FB_VMODE_EC_ADOBE_YCC601:
case FB_VMODE_EC_SYCC601:
case FB_VMODE_EC_XVYCC601:
csc2_control =
nvdisp_csc2_control_output_color_sel_y601_f();
break;
case FB_VMODE_EC_XVYCC709:
default:
csc2_control =
nvdisp_csc2_control_output_color_sel_y709_f();
break;
case FB_VMODE_EC_BT2020_CYCC:
case FB_VMODE_EC_BT2020_YCC_RGB:
csc2_control =
nvdisp_csc2_control_output_color_sel_y2020_f();
break;
}
} else {
csc2_control = nvdisp_csc2_control_output_color_sel_rgb_f();
}
if (dc->mode.vmode & FB_VMODE_LIMITED_RANGE)
csc2_control |= nvdisp_csc2_control_limit_rgb_enable_f();
else
csc2_control |= nvdisp_csc2_control_limit_rgb_disable_f();
update_cache:
/* Set user data cache */
dc->cached_settings.csc2_control = csc2_control;
write_reg:
tegra_dc_writel(dc, csc2_control, nvdisp_csc2_control_r());
}
static inline void tegra_nvdisp_set_rg_unstall(struct tegra_dc *dc)
{
if (tegra_dc_is_t19x())
tegra_nvdisp_set_rg_unstall_t19x(dc);
}
int tegra_nvdisp_program_mode(struct tegra_dc *dc, struct tegra_dc_mode
*mode)
{
unsigned long v_back_porch;
unsigned long v_front_porch;
unsigned long v_sync_width;
unsigned long v_active;
if (!dc->mode.pclk)
return 0;
if (dc->out_ops && dc->out_ops->modeset_notifier)
dc->out_ops->modeset_notifier(dc);
v_back_porch = mode->v_back_porch;
v_front_porch = mode->v_front_porch;
v_sync_width = mode->v_sync_width;
v_active = mode->v_active;
if (mode->vmode == FB_VMODE_INTERLACED) {
v_back_porch /= 2;
v_front_porch /= 2;
v_sync_width /= 2;
v_active /= 2;
}
tegra_dc_get(dc);
tegra_dc_program_bandwidth(dc, true);
tegra_dc_writel(dc,
nvdisp_sync_width_h_f(mode->h_sync_width) |
nvdisp_sync_width_v_f(v_sync_width),
nvdisp_sync_width_r());
tegra_dc_writel(dc,
nvdisp_back_porch_h_f(mode->h_back_porch) |
nvdisp_back_porch_v_f(v_back_porch),
nvdisp_back_porch_r());
tegra_dc_writel(dc,
nvdisp_front_porch_h_f(mode->h_front_porch) |
nvdisp_front_porch_v_f(v_front_porch),
nvdisp_front_porch_r());
tegra_dc_writel(dc,
nvdisp_active_h_f(mode->h_active) |
nvdisp_active_v_f(v_active),
nvdisp_active_r());
if (mode->vmode == FB_VMODE_INTERLACED)
tegra_dc_writel(dc, INTERLACE_MODE_ENABLE |
INTERLACE_START_FIELD_1
| INTERLACE_STATUS_FIELD_1,
nvdisp_interlace_ctl_r());
else
tegra_dc_writel(dc, INTERLACE_MODE_DISABLE,
nvdisp_interlace_ctl_r());
if (mode->vmode == FB_VMODE_INTERLACED) {
tegra_dc_writel(dc,
nvdisp_interlace_fld2_width_v_f(v_sync_width),
nvdisp_interlace_fld2_width_r());
tegra_dc_writel(dc,
nvdisp_interlace_fld2_bporch_v_f(v_back_porch + 1),
nvdisp_interlace_fld2_bporch_r());
tegra_dc_writel(dc,
nvdisp_interlace_fld2_active_v_f(v_active),
nvdisp_interlace_fld2_active_r());
tegra_dc_writel(dc,
nvdisp_interlace_fld2_fporch_v_f(v_front_porch),
nvdisp_interlace_fld2_fporch_r());
}
/* TODO: MIPI/CRT/HDMI clock cals */
/* TODO: confirm shift clock still exists in T186 */
if (dc->mode.pclk != mode->pclk)
pr_info("Redo Clock pclk 0x%x != dc-pclk 0x%x\n",
mode->pclk, dc->mode.pclk);
tegra_nvdisp_mode_set_background_color(dc);
tegra_nvdisp_set_ocsc(dc, mode);
_tegra_nvdisp_set_ec_output_lut(dc, mode);
tegra_nvdisp_set_chroma_lpf(dc);
tegra_nvdisp_set_rg_unstall(dc);
/* general-update */
tegra_nvdisp_activate_general_channel(dc);
#ifdef CONFIG_SWITCH
switch_set_state(&dc->modeset_switch,
(mode->h_active << 16) | mode->v_active);
#endif
if (dc->mode_dirty)
memcpy(&dc->cached_mode, &dc->mode, sizeof(dc->mode));
tegra_dc_put(dc);
dc->mode_dirty = false;
trace_display_mode(dc, &dc->mode);
tegra_dc_ext_process_modechange(dc->ndev->id);
tegra_dc_client_handle_event(dc, NOTIFY_MODESET_EVENT);
return 0;
}
int tegra_nvdisp_init(struct tegra_dc *dc)
{
char rst_name[6];
int err = 0;
char syncpt_name[32];
mutex_lock(&tegra_nvdisp_lock);
/* Only need init once no matter how many dc objects */
if (!nvdisp_common_init_done) {
err = _tegra_nvdisp_init_once(dc);
if (err) {
mutex_unlock(&tegra_nvdisp_lock);
return err;
}
}
mutex_unlock(&tegra_nvdisp_lock);
/*Lut alloc is needed per dc */
if (!dc->fb_nvdisp_lut.rgb) {
if (nvdisp_alloc_input_lut(dc, NULL, false))
return -ENOMEM;
}
/* Output LUT is needed per dc */
if (!(dc->nvdisp_postcomp_lut.rgb)) {
if (nvdisp_alloc_output_lut(dc))
return -ENOMEM;
}
/* Set the valid windows as per mask */
dc->valid_windows = dc->pdata->win_mask;
/* Assign first valid window as fb window */
if (dc->pdata->fb->win == TEGRA_FB_WIN_INVALID) {
int i;
for_each_set_bit(i, &dc->valid_windows,
tegra_dc_get_numof_dispwindows()) {
dc->pdata->fb->win = i;
break;
}
}
/* Allocate a syncpoint for vblank on each head */
snprintf(syncpt_name, sizeof(syncpt_name), "vblank%u", dc->ctrl_num);
dc->vblank_syncpt = nvhost_get_syncpt_client_managed(dc->ndev,
syncpt_name);
dev_info(&dc->ndev->dev, "vblank syncpt # %d for dc %d\n",
dc->vblank_syncpt, dc->ctrl_num);
/* Allocate a syncpoint for vpulse3 on each head */
snprintf(syncpt_name, sizeof(syncpt_name), "disp%u.vpulse3",
dc->ctrl_num);
dc->vpulse3_syncpt = nvhost_get_syncpt_client_managed(dc->ndev,
syncpt_name);
dev_info(&dc->ndev->dev, "vpulse3 syncpt # %d for dc %d\n",
dc->vpulse3_syncpt, dc->ctrl_num);
#ifdef CONFIG_TEGRA_ISOMGR
/* Save reference to isohub bw info */
tegra_nvdisp_bandwidth_attach(dc);
#endif
if (tegra_bpmp_running()) {
snprintf(rst_name, sizeof(rst_name), "head%u", dc->ctrl_num);
dc->rst = devm_reset_control_get(&dc->ndev->dev, rst_name);
if (IS_ERR(dc->rst)) {
dev_err(&dc->ndev->dev,"Unable to get %s reset\n",
rst_name);
return PTR_ERR(dc->rst);
}
}
dc->parent_clk_safe = tegra_disp_clk_get(&dc->ndev->dev,
"pllp_display");
if (IS_ERR_OR_NULL(dc->parent_clk_safe)) {
dev_err(&dc->ndev->dev, "can't get pllp_display\n");
err = -ENOENT;
}
return err;
}
static int tegra_nvdisp_set_control_t18x(struct tegra_dc *dc)
{
u32 reg, protocol;
bool use_sor = false;
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)) {
protocol = nvdisp_dsi_control_protocol_dsia_f();
reg = nvdisp_dsi_control_r();
} else if (dc->out->type == TEGRA_DC_OUT_HDMI) {
/* sor1 in the function name is irrelevant */
protocol = nvdisp_sor1_control_protocol_tmdsa_f();
use_sor = true;
} else if ((dc->out->type == TEGRA_DC_OUT_DP) ||
(dc->out->type == TEGRA_DC_OUT_FAKE_DP)) {
/* sor in the function name is irrelevant */
protocol = nvdisp_sor_control_protocol_dpa_f();
use_sor = true;
} else {
dev_err(&dc->ndev->dev, "%s: unsupported out_type=%d\n",
__func__, dc->out->type);
return -EINVAL;
}
if (use_sor) {
switch (dc->out_ops->get_connector_instance(dc)) {
case 0:
reg = nvdisp_sor_control_r();
break;
case 1:
reg = nvdisp_sor1_control_r();
break;
default:
pr_err("%s: invalid sor_num:%d\n", __func__,
dc->out_ops->get_connector_instance(dc));
return -ENODEV;
}
}
tegra_dc_writel(dc, protocol, reg);
tegra_dc_enable_general_act(dc);
return 0;
}
static int tegra_nvdisp_head_init(struct tegra_dc *dc)
{
int ret = 0;
u32 int_enable;
u32 int_mask;
u32 i, val;
/* Init syncpt */
tegra_dc_writel(dc, nvdisp_incr_syncpt_cntrl_no_stall_f(1),
nvdisp_incr_syncpt_cntrl_r());
tegra_dc_writel(dc, nvdisp_cont_syncpt_vsync_en_enable_f() |
nvdisp_cont_syncpt_vsync_indx_f(dc->vblank_syncpt),
nvdisp_cont_syncpt_vsync_r());
/* Init interrupts */
/* Setting Int type. EDGE for most, LEVEL for UF related */
tegra_dc_writel(dc, 0x3C000000, nvdisp_int_type_r());
/* Setting all the Int polarity to high */
tegra_dc_writel(dc, 0x3D8010F6, nvdisp_int_polarity_r());
/* enable interrupts for vblank, frame_end and underflows */
int_enable = nvdisp_cmd_int_status_frame_end_f(1) |
nvdisp_cmd_int_status_v_blank_f(1) |
nvdisp_cmd_int_status_uf_f(1) |
nvdisp_cmd_int_status_sd3_f(1);
/* for panels with one-shot mode enable tearing effect interrupt */
if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE)
int_enable |= MSF_INT;
/* Todo: also need to enable interrupts for SD3, DSC etc */
tegra_dc_writel(dc, int_enable, nvdisp_cmd_int_enable_r());
int_mask = nvdisp_cmd_int_status_uf_f(1);
tegra_dc_writel(dc, int_mask, nvdisp_cmd_int_mask_r());
tegra_dc_writel(dc, nvdisp_state_access_write_mux_assembly_f() |
nvdisp_state_access_read_mux_active_f(),
nvdisp_state_access_r());
/* YUV bypass flag is set during flips, but we need to check bypass
* flag here for YUV modes so that the output CSC and chroma LPF blocks
* are correctly programmed during modeset for YUV bypass modes.
* Else, momentary screen corruption can be observed during modeset.
*/
if (dc->mode.pclk)
dc->yuv_bypass = (dc->mode.vmode & FB_VMODE_SET_YUV_MASK) &&
(dc->mode.vmode & FB_VMODE_BYPASS);
for_each_set_bit(i, &dc->valid_windows,
tegra_dc_get_numof_dispwindows()) {
struct tegra_dc_win *win = tegra_dc_get_window(dc, i);
/* cache window specific default user data */
win->cached_settings.clamp_before_blend = true;
win->cached_settings.color_expand_enable = true;
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;
/* set mode */
tegra_nvdisp_program_mode(dc, &dc->mode);
if (tegra_dc_is_t18x())
ret = tegra_nvdisp_set_control_t18x(dc);
else if (tegra_dc_is_t19x())
ret = tegra_nvdisp_set_control_t19x(dc);
if (ret) {
dev_err(&dc->ndev->dev, "%s: error:%d in set_control\n",
__func__, ret);
goto exit;
}
tegra_nvdisp_set_color_control(dc);
/* Enable Vpulse3 scanline signal */
tegra_dc_writel(dc, nvdisp_disp_signal_option_v_pulse3_enable_f(),
nvdisp_disp_signal_option_r());
tegra_dc_enable_general_act(dc);
exit:
return ret;
}
static struct tegra_nvdisp_imp_head_settings *find_imp_head_by_ctrl_num(
struct tegra_nvdisp_imp_settings *imp_settings,
struct tegra_dc *dc)
{
int i;
for (i = 0; i < imp_settings->num_heads; i++) {
struct tegra_nvdisp_imp_head_settings *head_settings;
head_settings = &imp_settings->head_settings[i];
if (head_settings->entries.ctrl_num == dc->ctrl_num)
return head_settings;
}
return NULL;
}
static inline void tegra_nvdisp_program_common_win_batch_size(
struct tegra_dc *dc)
{
if (tegra_dc_is_t19x())
tegra_nvdisp_program_common_win_batch_size_t19x(dc);
}
static void tegra_nvdisp_postcomp_imp_init(struct tegra_dc *dc)
{
struct nvdisp_imp_table *imp_table;
struct tegra_nvdisp_imp_settings *boot_setting;
struct tegra_nvdisp_imp_head_settings *head_settings;
struct tegra_dc_ext_nvdisp_imp_head_entries *head_entries;
bool any_dc_enabled = false;
int i;
for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) {
struct tegra_dc *other_dc = tegra_dc_get_dc(i);
if (other_dc && other_dc->enabled) {
any_dc_enabled = true;
break;
}
}
/*
* Use any_dc_enabled to ensure that the common win batch request size
* is only programmed by the first head that comes up.
*/
if (!any_dc_enabled)
tegra_nvdisp_program_common_win_batch_size(dc);
if (!tegra_platform_is_silicon())
return;
imp_table = tegra_dc_common_get_imp_table();
if (!imp_table)
return;
/*
* Use any dc_enabled to ensure that the common fetch metering is only
* initialized by the first head that comes up.
*/
boot_setting = imp_table->boot_setting;
if (!any_dc_enabled)
tegra_nvdisp_program_common_fetch_meter(dc,
boot_setting->global_entries.total_curs_fetch_slots,
boot_setting->global_entries.total_win_fetch_slots);
/*
* Program default ihub values for the cursor on this head. This should
* be done before enabling the output LUT since it also uses those same
* values. There shouldn't be any need to disable ihub latency events
* since the cursor pipe isn't active at this point in time.
*/
head_settings = find_imp_head_by_ctrl_num(boot_setting, dc);
head_entries = &head_settings->entries;
tegra_nvdisp_program_imp_curs_entries(dc,
head_entries->curs_fetch_slots,
head_entries->curs_pipe_meter,
head_entries->curs_dvfs_watermark,
head_entries->curs_mempool_entries,
dc->ctrl_num);
}
static int tegra_nvdisp_postcomp_init(struct tegra_dc *dc)
{
struct tegra_dc_nvdisp_lut *nvdisp_lut = NULL;
u32 update_mask = 0;
u32 act_req_mask = 0;
tegra_nvdisp_postcomp_imp_init(dc);
update_mask |=
nvdisp_cmd_state_ctrl_common_act_update_enable_f();
act_req_mask |=
nvdisp_cmd_state_ctrl_common_act_req_enable_f();
/*
* Set the LUT address in the HW register. Enable the default sRGB_LUT.
* Replace this with the LUT derived from panel characterization through
* DT.
*/
nvdisp_lut = &dc->nvdisp_postcomp_lut;
if (dc->cmu_enabled) {
tegra_nvdisp_program_output_lut(dc, nvdisp_lut);
tegra_nvdisp_set_color_control(dc);
act_req_mask |=
nvdisp_cmd_state_ctrl_general_act_req_enable_f();
}
if (tegra_dc_enable_update_and_act(dc, update_mask, act_req_mask))
dev_err(&dc->ndev->dev,
"timeout waiting for postcomp init state to promote\n");
return 0;
}
static int tegra_nvdisp_rg_init(struct tegra_dc *dc)
{
return 0;
}
static int tegra_nvdisp_cursor_init(struct tegra_dc *dc)
{
return 0;
}
/*
* Moves all the win entries specified in the winmask to the front of the
* head_settings->win_entries array.
*/
static u8 swap_imp_boot_wins(
struct tegra_nvdisp_imp_head_settings *head_settings,
unsigned long winmask)
{
u8 num_wins = 0;
int i;
for (i = 0; i < head_settings->num_wins; i++) {
struct tegra_dc_ext_nvdisp_imp_win_entries *win_entry;
win_entry = &head_settings->win_entries[i];
if ((1 << win_entry->id) & winmask) {
struct tegra_dc_ext_nvdisp_imp_win_entries tmp;
tmp = head_settings->win_entries[num_wins];
head_settings->win_entries[num_wins++] = *win_entry;
head_settings->win_entries[i] = tmp;
}
}
return num_wins;
}
static void tegra_nvdisp_dc_wins_imp_init(struct tegra_dc *dc)
{
struct nvdisp_imp_table *imp_table;
struct tegra_dc_win *win;
int max_num_wins = tegra_dc_get_numof_dispwindows();
int i;
/*
* For non-silicon platforms, assign a 1:1 thread group mapping and get
* out. Dynamic IMP won't be enabled on these platforms, which means
* that the reset values for the other IHUB registers should be fine.
*/
if (!tegra_platform_is_silicon()) {
for_each_set_bit(i, &dc->pdata->win_mask, max_num_wins) {
win = tegra_dc_get_window(dc, i);
if (!win)
continue;
nvdisp_win_write(win, win_ihub_thread_group_num_f(i) |
win_ihub_thread_group_enable_yes_f(),
win_ihub_thread_group_r());
}
return;
}
imp_table = tegra_dc_common_get_imp_table();
if (imp_table) {
struct tegra_nvdisp_imp_settings *boot_setting;
struct tegra_nvdisp_imp_head_settings *head_settings;
u8 cur_win_num;
boot_setting = imp_table->boot_setting;
head_settings = find_imp_head_by_ctrl_num(boot_setting, dc);
cur_win_num = head_settings->num_wins;
head_settings->num_wins =
swap_imp_boot_wins(head_settings, dc->pdata->win_mask);
for (i = 0; i < head_settings->num_wins; i++) {
struct tegra_dc_ext_nvdisp_imp_win_entries *win_entry;
u32 tg = win_ihub_thread_group_enable_no_f();
win_entry = &head_settings->win_entries[i];
tegra_nvdisp_program_imp_win_entries(dc,
win_entry->thread_group,
win_entry->fetch_slots,
win_entry->pipe_meter,
win_entry->dvfs_watermark,
win_entry->mempool_entries,
win_entry->id,
dc->ctrl_num);
win = tegra_dc_get_window(dc, win_entry->id);
if (win_entry->thread_group >= 0)
tg = win_ihub_thread_group_enable_yes_f() |
win_ihub_thread_group_num_f(
win_entry->thread_group);
nvdisp_win_write(win, tg, win_ihub_thread_group_r());
}
head_settings->num_wins = cur_win_num;
}
}
static int tegra_nvdisp_assign_dc_wins(struct tegra_dc *dc)
{
u32 update_mask = nvdisp_cmd_state_ctrl_common_act_update_enable_f();
u32 act_req_mask = nvdisp_cmd_state_ctrl_common_act_req_enable_f();
int num_wins = tegra_dc_get_numof_dispwindows();
int idx = 0, ret = 0;
int i = -1;
mutex_lock(&tegra_nvdisp_lock);
/* Assign windows to this head. */
for_each_set_bit(idx, &dc->pdata->win_mask, num_wins) {
if (tegra_nvdisp_assign_win(dc, idx)) {
dev_err(&dc->ndev->dev,
"failed to assign window %d\n", idx);
} else {
dev_dbg(&dc->ndev->dev,
"Window %d assigned to head %d\n", idx,
dc->ctrl_num);
update_mask |=
nvdisp_cmd_state_ctrl_win_a_update_enable_f() << idx;
act_req_mask |=
nvdisp_cmd_state_ctrl_a_act_req_enable_f() << idx;
if (i == -1)
i = idx;
}
}
tegra_nvdisp_dc_wins_imp_init(dc);
/* Wait for COMMON_ACT_REQ to complete or time out. */
if (tegra_dc_enable_update_and_act(dc, update_mask, act_req_mask)) {
dev_err(&dc->ndev->dev,
"timeout waiting for win assignments to promote\n");
ret = -EINVAL;
}
mutex_unlock(&tegra_nvdisp_lock);
/* Set the fb_index on changing from a zero winmask to a valid one. */
if ((dc->pdata->fb->win == -1) && dc->pdata->win_mask) {
tegra_fb_set_win_index(dc, dc->pdata->win_mask);
dc->pdata->fb->win = i;
}
return ret;
}
int tegra_nvdisp_head_disable(struct tegra_dc *dc)
{
int idx, ret = 0;
struct tegra_dc *dc_other = NULL;
/*
* Disable the DVFS watermarks for the cursor and windows owned by this
* head.
*
* The relevant cursor and windows should have already been disabled
* prior to tegra_nvdisp_head_disable() being called, so it should be
* safe to disable their watermarks, even though the watermark registers
* aren't buffered at all.
*/
tegra_nvdisp_program_curs_dvfs_watermark(dc, 0x0);
for_each_set_bit(idx, &dc->pdata->win_mask,
tegra_dc_get_numof_dispwindows()) {
struct tegra_dc_win *win = tegra_dc_get_window(dc, idx);
if (!win || win->dc != dc)
continue;
/*
* The window owner might have already been set to NONE, so
* program the assembly state accordingly so that we can
* actually write the register.
*
* The window owner will be categorically set to NONE during the
* tegra_nvdisp_detach_win() call right below.
*/
nvdisp_win_write(win, dc->ctrl_num, win_set_control_r());
tegra_nvdisp_program_win_dvfs_watermark(win, 0x0);
}
/* Detach windows from the head */
for_each_set_bit(idx, &dc->pdata->win_mask,
tegra_dc_get_numof_dispwindows()) {
if (tegra_nvdisp_detach_win(dc, idx))
dev_err(&dc->ndev->dev,
"failed to detach window %d\n", idx);
else
dev_dbg(&dc->ndev->dev,
"Window %d detached from head %d\n", idx,
dc->ctrl_num);
}
/* Set comp clock to different pclk since dc->clk will be disabled */
mutex_lock(&tegra_nvdisp_lock);
dc->comp_clk_inuse = false;
mutex_unlock(&tegra_nvdisp_lock);
tegra_nvdisp_set_compclk(dc);
if (dc->out->dsc_en)
tegra_dc_en_dis_dsc(dc, false);
/* Disable DC clock */
tegra_disp_clk_disable_unprepare(dc->clk);
ret = clk_set_parent(dc->clk, dc->parent_clk_safe);
if (ret)
dev_err(&dc->ndev->dev,
"can't set parent_clk_safe for dc->clk\n");
/* check if any of head is using hub clock */
mutex_lock(&tegra_nvdisp_lock);
for (idx = 0; idx < tegra_dc_get_numof_dispheads(); idx++) {
dc_other = tegra_dc_get_dc(idx);
if (dc_other && dc_other->comp_clk_inuse)
break;
}
/* disable hub clock if none of the heads is using it and clear bw */
if (idx == tegra_dc_get_numof_dispheads() && hubclk_already_on) {
tegra_nvdisp_clear_bandwidth(dc);
tegra_disp_clk_disable_unprepare(hubclk);
hubclk_already_on = false;
}
mutex_unlock(&tegra_nvdisp_lock);
return ret;
}
int tegra_nvdisp_head_enable(struct tegra_dc *dc)
{
int i;
int res;
int pclk = 0, ret = 0;
struct clk *parent_clk = NULL;
if (WARN_ON(!dc || !dc->out || !dc->out_ops))
return false;
tegra_dc_unpowergate_locked(dc);
/* set clock status to inuse */
mutex_lock(&tegra_nvdisp_lock);
dc->comp_clk_inuse = true;
/* turn on hub clock and init bw */
if (!hubclk_already_on) {
tegra_nvdisp_init_bandwidth(dc);
tegra_disp_clk_prepare_enable(hubclk);
hubclk_already_on = true;
}
mutex_unlock(&tegra_nvdisp_lock);
pr_debug(" rate get on hub %ld\n", clk_get_rate(hubclk));
/* Enable OR -- need to enable the connection first */
if (dc->out->enable)
dc->out->enable(&dc->ndev->dev);
/* Setting DC clocks, DC, COMPCLK
* Set maximum of DC clock for COMPCLK
*/
if (dc->out->type == TEGRA_DC_OUT_DSI) {
parent_clk = tegra_disp_clk_get(&dc->ndev->dev,
"pll_d_out1");
} else {
parent_clk = tegra_disp_clk_get(&dc->ndev->dev,
dc->out->parent_clk);
pr_info("Parent Clock set for DC %s\n",
dc->out->parent_clk);
}
if (IS_ERR_OR_NULL(parent_clk)) {
dev_err(&dc->ndev->dev,
"Failed to get DC Parent clock\n");
ret = -ENOENT;
return ret; /*TODO: Add proper cleanup later */
}
/* Set parent for DC clock */
clk_set_parent(dc->clk, parent_clk);
/* Set rate on DC same as pclk */
if (!dc->initialized)
clk_set_rate(dc->clk, dc->mode.pclk);
if (dc->out_ops->setup_clk)
pclk = dc->out_ops->setup_clk(dc, dc->clk);
/* Enable DC clock */
tegra_disp_clk_prepare_enable(dc->clk);
pr_debug(" dc clk %ld\n", clk_get_rate(dc->clk));
tegra_nvdisp_set_compclk(dc);
tegra_dc_get(dc);
/* Deassert the dc reset */
if (tegra_bpmp_running()) {
res = reset_control_deassert(dc->rst);
if (res) {
dev_err(&dc->ndev->dev, "Unable to deassert dc %d\n",
dc->ctrl_num);
return res;
}
tegra_nvdisp_wgrp_reset_deassert(dc);
}
/* Mask interrupts during init */
tegra_dc_writel(dc, 0, DC_CMD_INT_MASK);
enable_irq(dc->irq);
/*
* These IMP caps only need to be initialized once. However, we're
* putting this function call here during head enable in order to avoid
* separate powergate/rst handling during init.
*/
tegra_nvdisp_init_common_imp_reg_caps(dc);
res = tegra_nvdisp_head_init(dc);
res |= tegra_nvdisp_postcomp_init(dc);
res |= tegra_nvdisp_rg_init(dc);
res |= tegra_nvdisp_cursor_init(dc);
res |= tegra_nvdisp_assign_dc_wins(dc);
if (res) {
dev_err(&dc->ndev->dev, "%s, failed head enable\n", __func__);
goto failed_enable;
}
tegra_dc_dsc_init(dc);
if (dc->out->dsc_en)
tegra_dc_en_dis_dsc(dc, true);
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);
trace_display_enable(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();
/*
* We will need to reinitialize the display the next time panel
* is enabled.
*/
dc->out->flags &= ~TEGRA_DC_OUT_INITIALIZED_MODE;
/* Enable RG underflow logging */
tegra_dc_writel(dc, nvdisp_rg_underflow_enable_enable_f() |
nvdisp_rg_underflow_mode_red_f(),
nvdisp_rg_underflow_r());
tegra_dc_put(dc);
return 0;
failed_enable:
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);
/* TODO: disable DC clock */
return -EINVAL;
}
void tegra_nvdisp_set_vrr_mode(struct tegra_dc *dc)
{
tegra_dc_writel(dc,
nvdisp_display_rate_min_refresh_enable_f(1) |
nvdisp_display_rate_min_refresh_interval_f(
(u32)(div_s64(dc->frametime_ns, NSEC_PER_MICROSEC))),
nvdisp_display_rate_r());
tegra_dc_writel(dc, nvdisp_display_command_control_mode_nc_display_f(),
nvdisp_display_command_r());
tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_host_trig_enable_f()
, nvdisp_cmd_state_ctrl_r());
}
EXPORT_SYMBOL(tegra_nvdisp_set_vrr_mode);
void tegra_nvdisp_vrr_work(struct work_struct *work)
{
int reg_val;
int frame_time_elapsed;
struct timespec time_now;
s64 time_now_us;
struct tegra_dc *dc = container_of(
to_delayed_work(work), struct tegra_dc, vrr_work);
struct tegra_vrr *vrr = dc->out->vrr;
mutex_lock(&dc->lock);
tegra_dc_get(dc);
getnstimeofday(&time_now);
time_now_us = (s64)time_now.tv_sec * 1000000 +
time_now.tv_nsec / 1000;
frame_time_elapsed = time_now_us - vrr->curr_frame_us;
if (frame_time_elapsed < (vrr->frame_len_max - MIN_FRAME_INTERVAL)) {
reg_val = tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r());
reg_val |= nvdisp_cmd_state_ctrl_host_trig_enable_f();
reg_val |= nvdisp_cmd_state_ctrl_general_act_req_enable_f();
tegra_dc_writel(dc, reg_val, nvdisp_cmd_state_ctrl_r());
tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); /* flush */
}
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
return;
}
EXPORT_SYMBOL(tegra_nvdisp_vrr_work);
void tegra_nvdisp_stop_display(struct tegra_dc *dc)
{
tegra_dc_writel(dc, nvdisp_display_command_control_mode_stop_f(),
nvdisp_display_command_r());
}
EXPORT_SYMBOL(tegra_nvdisp_stop_display);
void tegra_nvdisp_sysfs_enable_crc(struct tegra_dc *dc)
{
mutex_lock(&dc->lock);
tegra_dc_get(dc);
tegra_dc_writel(dc, nvdisp_crc_control_enable_enable_f() |
nvdisp_crc_control_input_data_active_data_f(),
nvdisp_crc_control_r());
tegra_dc_enable_general_act(dc);
dc->crc_ref_cnt.legacy = true;
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_nvdisp_sysfs_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, nvdisp_crc_control_r());
tegra_dc_enable_general_act(dc);
dc->crc_ref_cnt.legacy = false;
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
}
u32 tegra_nvdisp_sysfs_read_rg_crc(struct tegra_dc *dc)
{
int crc = 0;
int val = 0;
if (!dc) {
pr_err("Failed to get dc: NULL parameter.\n");
goto crc_error;
}
/* If gated quitely return */
if (tegra_bpmp_running() && !tegra_dc_is_powered(dc))
return 0;
#ifdef INIT_COMPLETION
INIT_COMPLETION(dc->crc_complete);
#else
reinit_completion(&dc->crc_complete);
#endif
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);
val = tegra_dc_readl(dc, nvdisp_rg_crca_r());
if (val & nvdisp_rg_crca_valid_true_f())
crc = tegra_dc_readl(dc, nvdisp_rg_crcb_r());
/* clear the error bit if set */
if (val & nvdisp_rg_crca_error_true_f())
tegra_dc_writel(dc, nvdisp_rg_crca_error_true_f(),
nvdisp_rg_crca_r());
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
crc_error:
return crc;
}
void tegra_nvdisp_underflow_handler(struct tegra_dc *dc)
{
u32 reg = tegra_dc_readl(dc, nvdisp_rg_underflow_r());
dc->stats.underflows++;
if (dc->underflow_mask & NVDISP_UF_INT)
dc->stats.underflow_frames +=
nvdisp_rg_underflow_frames_uflowed_v(reg);
/* Clear the sticky bit and counter */
tegra_dc_writel(dc,
nvdisp_rg_underflow_frames_uflowed_rst_trigger_f() |
nvdisp_rg_underflow_uflowed_clr_f(),
nvdisp_rg_underflow_r());
/* Check whether the reset is done */
reg = tegra_dc_readl(dc, nvdisp_rg_underflow_r());
if (reg & nvdisp_rg_underflow_frames_uflowed_rst_pending_f()) {
pr_err("nvdisp_rg_underflow_frames_uflowed_rst is pending\n");
} else {
/* Enable RG underflow logging */
tegra_dc_writel(dc, nvdisp_rg_underflow_enable_enable_f() |
nvdisp_rg_underflow_mode_red_f(),
nvdisp_rg_underflow_r());
}
}
int tegra_nvdisp_is_powered(struct tegra_dc *dc)
{
struct tegra_dc_pd_table *pd_table = tegra_dc_get_disp_pd_table();
struct tegra_dc_pd_info *pds = pd_table->pd_entries;
u32 cur_head_mask = (1 << dc->ctrl_num);
int npower_domains = pd_table->npd;
int i, ret = 0;
mutex_lock(&pd_table->pd_lock);
for (i = 0; i < npower_domains; i++) {
struct tegra_dc_pd_info *pd = &pds[i];
if (cur_head_mask & pd->head_mask) {
ret = pd->ref_cnt;
break;
}
}
mutex_unlock(&pd_table->pd_lock);
return ret;
}
static inline int tegra_nvdisp_handle_pd_enable(struct tegra_dc_pd_info *pd,
int ref_cnt_update)
{
int ret = 0;
if (pd->ref_cnt == 0) {
struct tegra_dc_pd_clk_info *domain_clks = pd->domain_clks;
int nclks = pd->nclks;
int i;
ret = tegra_unpowergate_partition(pd->pg_id);
if (ret) {
pr_err("%s: Failed to unpowergate Head%u pd\n",
__func__, pd->head_owner);
return -EINVAL;
}
for (i = 0; i < nclks; i++)
tegra_disp_clk_prepare_enable(domain_clks[i].clk);
pr_info("%s: Unpowergated Head%u pd\n", __func__,
pd->head_owner);
}
pd->ref_cnt += ref_cnt_update;
return ret;
}
static inline int tegra_nvdisp_handle_pd_disable(struct tegra_dc_pd_info *pd,
int ref_cnt_update)
{
int remaining_ref_cnt = pd->ref_cnt - ref_cnt_update;
int ret = 0;
if (remaining_ref_cnt < 0) {
pr_err("%s: Unbalanced ref count for Head%u pd=%d\n",
__func__, pd->head_owner, pd->ref_cnt);
return -EINVAL;
} else if (remaining_ref_cnt == 0) {
struct tegra_dc_pd_clk_info *domain_clks = pd->domain_clks;
int nclks = pd->nclks;
int i;
ret = tegra_powergate_partition(pd->pg_id);
if (ret) {
pr_err("%s: Failed to powergate Head%u pd\n",
__func__, pd->head_owner);
return -EINVAL;
}
for (i = 0; i < nclks; i++)
tegra_disp_clk_disable_unprepare(domain_clks[i].clk);
pr_info("%s: Powergated Head%u pd\n", __func__, pd->head_owner);
}
pd->ref_cnt = remaining_ref_cnt;
return ret;
}
static int tegra_nvdisp_update_pd_ref_cnts(struct tegra_dc *dc, bool enable)
{
struct tegra_dc_pd_table *pd_table = tegra_dc_get_disp_pd_table();
struct tegra_dc_pd_info *pds = pd_table->pd_entries;
struct tegra_dc_pd_info *pd;
u32 cur_head_mask = (1 << dc->ctrl_num);
u32 pd_owner;
int npower_domains = pd_table->npd;
int ret = 0, i;
int pd_ref_cnt_updates[npower_domains];
memset(pd_ref_cnt_updates, 0,
sizeof(pd_ref_cnt_updates[0]) * npower_domains);
for (i = 0; i < npower_domains; i++) {
pd = &pds[i];
pd_owner = pd->head_owner;
/*
* Check whether the given head and/or ANY of its assigned
* windows reside in this power domain. Take/release an extra
* refcount to the DISP (Head0) power domain if necessary.
* There's an implicit dependency here since DISP houses common
* logic, such as PFE, IHUB, ORs, etc.
*/
if ((cur_head_mask & pd->head_mask) ||
(dc->valid_windows & pd->win_mask)) {
pd_ref_cnt_updates[pd_owner]++;
if (pd_owner != 0)
pd_ref_cnt_updates[0]++;
}
}
mutex_lock(&pd_table->pd_lock);
for (i = 0; i < npower_domains; i++) {
int ref_cnt_update;
int pd_idx = i;
/*
* If this is a powerGATE request, start iterating backwards
* and save DISP for last.
*/
if (!enable)
pd_idx = npower_domains - i - 1;
pd = &pds[pd_idx];
pd_owner = pd->head_owner;
ref_cnt_update = pd_ref_cnt_updates[pd_owner];
if (ref_cnt_update == 0)
continue;
if (enable)
ret = tegra_nvdisp_handle_pd_enable(pd, ref_cnt_update);
else
ret = tegra_nvdisp_handle_pd_disable(pd,
ref_cnt_update);
if (ret)
break;
}
mutex_unlock(&pd_table->pd_lock);
return ret;
}
int tegra_nvdisp_unpowergate_dc(struct tegra_dc *dc)
{
if (!dc) {
pr_err("%s: DC is NULL\n", __func__);
return -ENODEV;
}
return tegra_nvdisp_update_pd_ref_cnts(dc, true);
}
int tegra_nvdisp_powergate_dc(struct tegra_dc *dc)
{
if (!dc) {
pr_err("%s: DC is NULL\n", __func__);
return -ENODEV;
}
return tegra_nvdisp_update_pd_ref_cnts(dc, false);
}
static int tegra_nvdisp_set_color_control(struct tegra_dc *dc)
{
u32 color_control;
switch (dc->out->depth) {
case 36:
color_control = nvdisp_color_ctl_base_color_size_36bits_f();
break;
case 30:
color_control = nvdisp_color_ctl_base_color_size_30bits_f();
break;
case 24:
color_control = nvdisp_color_ctl_base_color_size_24bits_f();
break;
case 18:
color_control = nvdisp_color_ctl_base_color_size_18bits_f();
break;
default:
color_control = nvdisp_color_ctl_base_color_size_24bits_f();
break;
}
switch (dc->out->dither) {
case TEGRA_DC_UNDEFINED_DITHER:
case TEGRA_DC_DISABLE_DITHER:
color_control |= nvdisp_color_ctl_dither_ctl_disable_f();
break;
case TEGRA_DC_ORDERED_DITHER:
color_control |= nvdisp_color_ctl_dither_ctl_ordered_f();
break;
case TEGRA_DC_TEMPORAL_DITHER:
color_control |= nvdisp_color_ctl_dither_ctl_temporal_f();
break;
case TEGRA_DC_ERRACC_DITHER:
color_control |= nvdisp_color_ctl_dither_ctl_err_acc_f();
break;
default:
dev_err(&dc->ndev->dev, "Error: Unsupported dithering mode\n");
}
if (dc->cmu_enabled)
color_control |= nvdisp_color_ctl_cmu_enable_f();
/* TO DO - dither rotation, dither offset, dither phase */
tegra_dc_writel(dc, color_control,
nvdisp_color_ctl_r());
return 0;
}
void tegra_dc_cache_nvdisp_cmu(struct tegra_dc *dc,
struct tegra_dc_nvdisp_cmu *src_cmu)
{
/* copy the data to DC lut */
memcpy(dc->nvdisp_postcomp_lut.rgb, src_cmu->rgb,
dc->nvdisp_postcomp_lut.size);
dc->cmu_dirty = true;
}
static void _tegra_nvdisp_update_cmu(struct tegra_dc *dc,
struct tegra_dc_nvdisp_lut *cmu)
{
dc->cmu_enabled = dc->pdata->cmu_enable;
if (!dc->cmu_enabled)
return;
/* Not disabling the cmu here - will
* consider it if there is any corruption on
* updating cmu while it is running
*/
tegra_nvdisp_program_output_lut(dc, cmu);
dc->cmu_dirty = false;
}
int tegra_nvdisp_update_cmu(struct tegra_dc *dc,
struct tegra_dc_nvdisp_lut *cmu)
{
u32 act_req_mask = nvdisp_cmd_state_ctrl_general_act_req_enable_f();
mutex_lock(&dc->lock);
if (!dc->enabled) {
mutex_unlock(&dc->lock);
return 0;
}
tegra_dc_get(dc);
_tegra_nvdisp_update_cmu(dc, cmu);
tegra_nvdisp_set_color_control(dc);
tegra_dc_writel(dc, act_req_mask, nvdisp_cmd_state_ctrl_r());
tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r());
if (tegra_dc_in_cmode(dc)) {
/* wait for ACT_REQ to complete or time out */
if (tegra_dc_poll_register(dc, nvdisp_cmd_state_ctrl_r(),
act_req_mask, 0, 1,
NVDISP_TEGRA_POLL_TIMEOUT_MS))
dev_err(&dc->ndev->dev,
"dc timeout waiting to clear ACT_REQ, mask:0x%x\n",
act_req_mask);
}
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
return 0;
}
EXPORT_SYMBOL(tegra_nvdisp_update_cmu);
static inline void tegra_nvdisp_enable_ihub_latency_events(struct tegra_dc *dc)
{
tegra_dc_writel(dc,
tegra_dc_readl(dc,
nvdisp_ihub_misc_ctl_r()) |
nvdisp_ihub_misc_ctl_latency_event_enable_f(),
nvdisp_ihub_misc_ctl_r());
}
static inline void tegra_nvdisp_disable_ihub_latency_events(struct tegra_dc *dc)
{
tegra_dc_writel(dc,
tegra_dc_readl(dc, nvdisp_ihub_misc_ctl_r()) &
~nvdisp_ihub_misc_ctl_latency_event_enable_f(),
nvdisp_ihub_misc_ctl_r());
}
static inline void tegra_nvdisp_program_curs_dvfs_watermark(struct tegra_dc *dc,
u32 dvfs_watermark)
{
if (dvfs_watermark) {
tegra_dc_writel(dc,
nvdisp_ihub_cursor_latency_ctla_ctl_mode_enable_f() |
nvdisp_ihub_cursor_latency_ctla_submode_watermark_f(),
nvdisp_ihub_cursor_latency_ctla_r());
tegra_dc_writel(dc,
nvdisp_ihub_cursor_latency_ctlb_watermark_f(dvfs_watermark),
nvdisp_ihub_cursor_latency_ctlb_r());
} else { /* disable watermark */
tegra_dc_writel(dc, 0x0, nvdisp_ihub_cursor_latency_ctla_r());
tegra_dc_writel(dc,
nvdisp_ihub_cursor_latency_ctlb_watermark_f(0x1fffffff),
nvdisp_ihub_cursor_latency_ctlb_r());
}
}
static inline void tegra_nvdisp_program_win_dvfs_watermark(
struct tegra_dc_win *win,
u32 dvfs_watermark)
{
if (dvfs_watermark) {
nvdisp_win_write(win,
win_ihub_latency_ctla_ctl_mode_enable_f() |
win_ihub_latency_ctla_submode_watermark_f(),
win_ihub_latency_ctla_r());
nvdisp_win_write(win,
win_ihub_latency_ctlb_watermark_f(dvfs_watermark),
win_ihub_latency_ctlb_r());
} else { /* disable watermark */
nvdisp_win_write(win, 0x0, win_ihub_latency_ctla_r());
nvdisp_win_write(win,
win_ihub_latency_ctlb_watermark_f(0x1fffffff),
win_ihub_latency_ctlb_r());
}
}
static inline void tegra_nvdisp_program_common_fetch_meter(struct tegra_dc *dc,
u32 curs_slots,
u32 win_slots)
{
tegra_dc_writel(dc,
nvdisp_ihub_common_fetch_meter_cursor_slots_f(curs_slots) |
nvdisp_ihub_common_fetch_meter_wgrp_slots_f(win_slots),
nvdisp_ihub_common_fetch_meter_r());
trace_display_imp_program_global(dc->ctrl_num, curs_slots, win_slots);
}
static u32 tegra_nvdisp_get_active_curs_pool(struct tegra_dc *dc)
{
u32 cur_val = 0;
if (!dc || !dc->enabled)
return cur_val;
/*
* If either the cursor surface or output LUT are currently enabled on
* this head, take the active mempool value into account. Else, assume
* there are no mempool entries assigned.
*
* The reason why output LUT matters is because it shares both the same
* precomp pipe and FB request port as the corresponding cursor pipe.
* As such, the cursor ihub settings would still be programmed in a case
* where cursor is disabled, but output LUT is enabled.
*/
if (dc->cursor.enabled || dc->cmu_enabled)
cur_val = nvdisp_ihub_cursor_pool_config_entries_f(
tegra_dc_readl(dc,
nvdisp_ihub_cursor_pool_config_r()));
return cur_val;
}
static u32 tegra_nvdisp_get_active_win_pool(struct tegra_dc_win *win)
{
u32 cur_val = 0;
if (!win || !win->dc)
return cur_val;
/*
* If this window is currently enabled, take the active mempool value
* into account. Else, treat this window as having no mempool entries.
*/
if (WIN_IS_ENABLED(win))
cur_val = win_ihub_pool_config_entries_f(
nvdisp_win_read(win,
win_ihub_pool_config_r()));
return cur_val;
}
static int tegra_nvdisp_get_v_taps_user_info(
struct tegra_dc_ext_imp_user_info *info)
{
u32 num_wins;
u32 *win_ids;
u32 *in_widths;
u32 *out_widths;
u32 *v_taps;
size_t i, j, win_arr_size;
int ret = 0;
num_wins = info->num_windows;
win_arr_size = num_wins * sizeof(u32);
win_ids = kzalloc(win_arr_size, GFP_KERNEL);
in_widths = kzalloc(win_arr_size, GFP_KERNEL);
out_widths = kzalloc(win_arr_size, GFP_KERNEL);
v_taps = kzalloc(win_arr_size, GFP_KERNEL);
if (!win_ids || !in_widths || !out_widths || !v_taps) {
ret = -ENOMEM;
goto v_taps_free_and_ret;
}
if (copy_from_user(win_ids, info->win_ids, win_arr_size)) {
ret = -EFAULT;
goto v_taps_free_and_ret;
}
if (copy_from_user(in_widths, info->in_widths, win_arr_size)) {
ret = -EFAULT;
goto v_taps_free_and_ret;
}
if (copy_from_user(out_widths, info->out_widths, win_arr_size)) {
ret = -EFAULT;
goto v_taps_free_and_ret;
}
for (i = 0; i < num_wins; i++) {
int win_capc, win_cape;
int min_width;
struct tegra_dc *owner_dc = NULL;
struct tegra_dc_win *win = NULL;
for (j = 0; j < tegra_dc_get_numof_dispheads(); j++) {
owner_dc = tegra_dc_get_dc(j);
if (!owner_dc)
continue;
win = tegra_dc_get_window(owner_dc, win_ids[i]);
if (win)
break;
}
if (!win)
continue;
/*
* in_widths[i] has 20 bits integer (MSB) and 12 bits fractional
* (LSB)
*/
win_capc = win->precomp_capc;
win_cape = win->precomp_cape;
min_width = ((in_widths[i] >> 12) < out_widths[i]) ?
in_widths[i] >> 12 : out_widths[i];
if (min_width <
win_precomp_wgrp_capc_max_pixels_5tap444_v(win_capc))
v_taps[i] = 5;
else /* IMP only accepts 2 or 5 for v taps */
v_taps[i] = 2;
}
if (copy_to_user(info->v_taps, v_taps, win_arr_size)) {
ret = -EFAULT;
goto v_taps_free_and_ret;
}
v_taps_free_and_ret:
kfree(win_ids);
kfree(in_widths);
kfree(out_widths);
kfree(v_taps);
return ret;
}
static int cpy_dvfs_pairs_to_user(void __user *ext_dvfs_ptr,
u32 num_pairs_requested, u32 *num_pairs_returned)
{
struct mrq_emc_dvfs_latency_response *dvfs_table =
&g_imp.emc_dvfs_table;
struct tegra_dc_ext_imp_emc_dvfs_pair ext_pairs[dvfs_table->num_pairs];
u32 num_pairs_to_cpy = 0;
size_t i;
int emc_to_dram_factor = bwmgr_get_emc_to_dram_freq_factor();
int ret = 0;
num_pairs_to_cpy = min(dvfs_table->num_pairs, num_pairs_requested);
for (i = 0; i < num_pairs_to_cpy; i++) {
ext_pairs[i].freq = dvfs_table->pairs[i].freq /
emc_to_dram_factor;
ext_pairs[i].latency = dvfs_table->pairs[i].latency;
}
/*
* If lock mode is enabled, the EMC frequency may have been locked by an
* external client. As such, filter the EMC DVFS table based on the
* current max EMC rate.
*/
if (unlikely(g_imp.lock_mode_enabled)) {
unsigned long max_emc_khz = tegra_bwmgr_round_rate(ULONG_MAX) /
emc_to_dram_factor / 1000;
for (i = num_pairs_to_cpy - 1; i >= 0; i--) {
if (ext_pairs[i].freq > max_emc_khz)
num_pairs_to_cpy--;
else if (ext_pairs[i].freq <= max_emc_khz)
break;
}
}
if (copy_to_user(ext_dvfs_ptr, ext_pairs,
num_pairs_to_cpy * sizeof(*ext_pairs))) {
pr_err("%s: Can't copy DVFS pairs to user\n", __func__);
ret = -EFAULT;
} else {
*num_pairs_returned = num_pairs_to_cpy;
}
return ret;
}
int tegra_nvdisp_get_imp_user_info(struct tegra_dc_ext_imp_user_info *info)
{
int ret = 0;
info->mempool_size = g_imp.mc_caps.total_mempool_size_bytes;
ret = tegra_nvdisp_get_v_taps_user_info(info);
if (ret)
return ret;
return cpy_dvfs_pairs_to_user(info->emc_dvfs_pairs,
info->emc_dvfs_pairs_requested,
&info->emc_dvfs_pairs_returned);
}
EXPORT_SYMBOL(tegra_nvdisp_get_imp_user_info);
static int cpy_thread_info_to_user(struct tegra_dc_ext_imp_caps *imp_caps)
{
int max_wins = tegra_dc_get_numof_dispwindows();
struct tegra_dc_ext_imp_thread_info *ext_info_arr;
struct tegra_dc_ext_imp_thread_info *ext_info;
struct tegra_dc_ext_imp_thread_info *thread_info_map[max_wins];
u32 num_info = imp_caps->num_thread_info;
struct nvdisp_imp_table *imp_table;
struct tegra_nvdisp_imp_settings *boot_setting;
int i, ret = 0;
imp_table = tegra_dc_common_get_imp_table();
if (!imp_table || !imp_table->boot_setting) {
pr_err("%s: No IMP boot setting found\n", __func__);
return -ENODEV;
}
boot_setting = imp_table->boot_setting;
if (num_info > max_wins) {
pr_err("%s: Num thread info (%u) > max wins (%d)\n",
__func__, num_info, max_wins);
return -E2BIG;
}
ext_info_arr = kcalloc(num_info, sizeof(*ext_info_arr), GFP_KERNEL);
if (!ext_info_arr)
return -ENOMEM;
if (copy_from_user(ext_info_arr, imp_caps->thread_info,
num_info * sizeof(*ext_info_arr))) {
pr_err("%s: Can't copy thread info from user\n", __func__);
ret = -EFAULT;
goto free_thread_info_ret;
}
for (i = 0; i < max_wins; i++)
thread_info_map[i] = NULL;
for (i = 0; i < num_info; i++) {
ext_info = &ext_info_arr[i];
if (ext_info->win_id >= max_wins) {
pr_err("%s: Win id (%u) >= max wins (%d)\n",
__func__, ext_info->win_id, max_wins);
ret = -EINVAL;
goto free_thread_info_ret;
}
thread_info_map[ext_info->win_id] = ext_info;
}
for (i = 0; i < boot_setting->num_heads; i++) {
struct tegra_nvdisp_imp_head_settings *head_settings;
int j;
head_settings = &boot_setting->head_settings[i];
for (j = 0; j < head_settings->num_wins; j++) {
struct tegra_dc_ext_nvdisp_imp_win_entries *win_entries;
win_entries = &head_settings->win_entries[j];
ext_info = thread_info_map[win_entries->id];
if (!ext_info)
continue;
ext_info->thread_group = win_entries->thread_group;
}
}
if (copy_to_user(imp_caps->thread_info, ext_info_arr,
num_info * sizeof(*ext_info_arr))) {
pr_err("%s: Failed to copy thread info to user\n", __func__);
ret = -EFAULT;
}
free_thread_info_ret:
kfree(ext_info_arr);
return ret;
}
int tegra_nvdisp_get_imp_caps(struct tegra_dc_ext_imp_caps *imp_caps)
{
int ret = 0;
imp_caps->mc_caps = g_imp.mc_caps;
/*
* If lock mode is enabled, the max hubclk frequency may have been
* locked by an external client. Re-query the max hubclk rate
* accordingly.
*/
if (unlikely(g_imp.lock_mode_enabled))
imp_caps->mc_caps.peak_hubclk_hz =
clk_round_rate(hubclk, ULONG_MAX);
ret = cpy_dvfs_pairs_to_user(imp_caps->dvfs_pairs,
imp_caps->num_dvfs_requested,
&imp_caps->num_dvfs_returned);
if (ret)
return ret;
return cpy_thread_info_to_user(imp_caps);
}
EXPORT_SYMBOL(tegra_nvdisp_get_imp_caps);
struct tegra_nvdisp_imp_settings *tegra_nvdisp_get_current_imp_settings(void)
{
return list_first_entry_or_null(&g_imp.imp_settings_queue,
struct tegra_nvdisp_imp_settings,
imp_node);
}
u32 tegra_nvdisp_get_max_pending_bw(struct tegra_dc *dc)
{
struct tegra_nvdisp_imp_settings *settings;
u32 max_pending_bw = 0;
list_for_each_entry(settings, &g_imp.imp_settings_queue, imp_node) {
u32 pending_bw =
settings->global_entries.total_iso_bw_with_catchup_kBps;
if (pending_bw > max_pending_bw)
max_pending_bw = pending_bw;
}
return max_pending_bw;
}
static inline bool tegra_nvdisp_common_channel_is_clean(struct tegra_dc *dc)
{
return !dc->common_channel_pending;
}
static void tegra_nvdisp_enable_common_channel_intr(struct tegra_dc *dc)
{
if (!tegra_nvdisp_is_dc_active(dc))
return;
/*
* WIN_X_ACT_REQs are checked at VBLANK in NC_DISPLAY mode, and at
* FRAME_END in C_DISPLAY mode. Be consistent and check COMMON_ACT_REQ
* in the same spot.
*/
if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) {
mutex_lock(&dc->lock);
set_bit(V_BLANK_IMP, &dc->vblank_ref_count);
tegra_dc_unmask_interrupt(dc, V_BLANK_INT);
mutex_unlock(&dc->lock);
} else {
tegra_dc_config_frame_end_intr(dc, true);
}
dc->common_channel_intr_enabled = true;
}
void tegra_nvdisp_set_common_channel_pending(struct tegra_dc *dc)
{
if (tegra_nvdisp_is_dc_active(dc))
dc->common_channel_pending = true;
}
static void tegra_nvdisp_activate_common_channel(struct tegra_dc *dc)
{
tegra_nvdisp_enable_common_channel_intr(dc);
tegra_dc_writel(dc,
nvdisp_cmd_state_ctrl_common_act_update_enable_f(),
nvdisp_cmd_state_ctrl_r());
tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); /* flush */
tegra_dc_writel(dc,
nvdisp_cmd_state_ctrl_common_act_req_enable_f(),
nvdisp_cmd_state_ctrl_r());
tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); /* flush */
mutex_lock(&tegra_nvdisp_lock);
tegra_nvdisp_set_common_channel_pending(dc);
mutex_unlock(&tegra_nvdisp_lock);
}
static void tegra_nvdisp_wait_for_common_channel_to_promote(struct tegra_dc *dc)
{
struct nvdisp_request_wq *promotion_wq;
int timeout = 0;
promotion_wq = &g_imp.common_channel_promotion_wq;
mutex_lock(&tegra_nvdisp_lock);
if (tegra_nvdisp_common_channel_is_clean(dc)) {
mutex_unlock(&tegra_nvdisp_lock);
return;
}
/*
* Use an exclusive wait since only one HEAD can program COMMON channel
* state at any given time.
*/
timeout = promotion_wq->timeout_per_entry;
___wait_event(promotion_wq->wq,
___wait_cond_timeout(tegra_nvdisp_common_channel_is_clean(dc)),
TASK_UNINTERRUPTIBLE, WQ_FLAG_EXCLUSIVE, timeout,
mutex_unlock(&tegra_nvdisp_lock);
__ret = schedule_timeout(__ret);
mutex_lock(&tegra_nvdisp_lock));
if (!tegra_nvdisp_common_channel_is_clean(dc))
dev_err(&dc->ndev->dev,
"%s: DC %d timed out waiting for COMMON promotion\n",
__func__, dc->ctrl_num);
mutex_unlock(&tegra_nvdisp_lock);
}
static void tegra_nvdisp_notify_common_channel_promoted(struct tegra_dc *dc)
{
/* Wake up one exclusive waiter. There should only be one. */
dc->common_channel_pending = false;
dc->common_channel_intr_enabled = false;
wake_up_nr(&g_imp.common_channel_promotion_wq.wq, 1);
}
static bool tegra_nvdisp_common_channel_is_free(void)
{
int i;
for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) {
struct tegra_dc *dc = tegra_dc_get_dc(i);
if (dc && dc->common_channel_reserved)
return false;
}
return true;
}
int tegra_dc_reserve_common_channel(struct tegra_dc *dc)
{
struct nvdisp_request_wq *reservation_wq;
int cur_nr_pending = 0;
int timeout = 0;
int ret = 0;
if (!tegra_dc_is_nvdisplay())
return 0;
reservation_wq = &g_imp.common_channel_reservation_wq;
mutex_lock(&tegra_nvdisp_lock);
cur_nr_pending = atomic_read(&reservation_wq->nr_pending);
atomic_inc(&reservation_wq->nr_pending);
if (tegra_nvdisp_common_channel_is_free()) {
dc->common_channel_reserved = true;
mutex_unlock(&tegra_nvdisp_lock);
return ret;
}
/*
* Scale the per-entry timeout value by the number of outstanding
* requests that need to be serviced before this one.
*/
timeout = reservation_wq->timeout_per_entry;
timeout *= max(cur_nr_pending, 1);
/*
* Use an exclusive wait to queue and wake up processes in a FIFO order.
* This ensures that the list of pending waiters is always aligned with
* the global queue of IMP settings.
*/
ret = ___wait_event(reservation_wq->wq,
___wait_cond_timeout(tegra_nvdisp_common_channel_is_free()),
TASK_UNINTERRUPTIBLE, WQ_FLAG_EXCLUSIVE, timeout,
mutex_unlock(&tegra_nvdisp_lock);
__ret = schedule_timeout(__ret);
mutex_lock(&tegra_nvdisp_lock));
/*
* If we timed out, manually restore "nr_pending" and return an error.
*/
if (!tegra_nvdisp_common_channel_is_free()) {
atomic_dec(&reservation_wq->nr_pending);
mutex_unlock(&tegra_nvdisp_lock);
dev_err(&dc->ndev->dev,
"%s: DC %d timed out waiting for the COMMON channel\n",
__func__, dc->ctrl_num);
ret = -EINVAL;
return ret;
}
ret = 0;
dc->common_channel_reserved = true;
mutex_unlock(&tegra_nvdisp_lock);
return ret;
}
EXPORT_SYMBOL(tegra_dc_reserve_common_channel);
void tegra_dc_release_common_channel(struct tegra_dc *dc)
{
struct nvdisp_request_wq *reservation_wq;
if (!tegra_dc_is_nvdisplay())
return;
reservation_wq = &g_imp.common_channel_reservation_wq;
mutex_lock(&tegra_nvdisp_lock);
if (!dc->common_channel_reserved) {
mutex_unlock(&tegra_nvdisp_lock);
return;
}
/* Wake up the next exclusive waiter. */
dc->common_channel_reserved = false;
atomic_dec(&reservation_wq->nr_pending);
wake_up_nr(&reservation_wq->wq, 1);
mutex_unlock(&tegra_nvdisp_lock);
}
EXPORT_SYMBOL(tegra_dc_release_common_channel);
bool tegra_dc_handle_common_channel_promotion(struct tegra_dc *dc)
{
/*
* COMMON channel state is promoted on the very next loadv boundary for
* whichever HEAD set COMMON_ACT_REQ. Notify whichever HEAD is waiting
* if this condition has been met.
*/
u32 val;
u32 dirty;
bool clear_intr = false;
if (!tegra_dc_is_nvdisplay())
return false;
val = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
dirty = !!(val & COMMON_ACT_REQ);
mutex_lock(&tegra_nvdisp_lock);
if (dc->common_channel_pending && !dirty) {
clear_intr = dc->common_channel_intr_enabled;
tegra_nvdisp_notify_common_channel_promoted(dc);
}
mutex_unlock(&tegra_nvdisp_lock);
return clear_intr;
}
EXPORT_SYMBOL(tegra_dc_handle_common_channel_promotion);
static void dealloc_imp_settings(
struct tegra_nvdisp_imp_settings *imp_settings)
{
struct tegra_nvdisp_mempool_req *req;
int i;
if (!imp_settings)
return;
for (i = 0; i < imp_settings->num_heads; i++) {
struct tegra_nvdisp_imp_head_settings *head_settings;
head_settings = &imp_settings->head_settings[i];
if (head_settings->num_wins > 0)
kfree(head_settings->win_entries);
}
if (imp_settings->decreasing_pool_reqs) {
for (i = 0; i < imp_settings->num_heads; i++) {
req = &imp_settings->decreasing_pool_reqs[i];
kfree(req->win_ids);
kfree(req->win_entries);
}
}
if (imp_settings->increasing_pool_reqs) {
for (i = 0; i < imp_settings->num_heads; i++) {
req = &imp_settings->increasing_pool_reqs[i];
kfree(req->win_ids);
kfree(req->win_entries);
}
}
kfree(imp_settings->decreasing_pool_reqs);
kfree(imp_settings->increasing_pool_reqs);
if (imp_settings->num_heads > 0)
kfree(imp_settings->head_settings);
kfree(imp_settings);
}
static int alloc_pool_reqs(struct tegra_nvdisp_imp_settings *imp_settings)
{
size_t pool_reqs_size;
int i;
pool_reqs_size = sizeof(*(imp_settings->decreasing_pool_reqs)) *
imp_settings->num_heads;
imp_settings->decreasing_pool_reqs =
kzalloc(pool_reqs_size, GFP_KERNEL);
if (!imp_settings->decreasing_pool_reqs) {
pr_err("%s: Failed to alloc mem for dec mempool\n", __func__);
return -ENOMEM;
}
imp_settings->increasing_pool_reqs =
kzalloc(pool_reqs_size, GFP_KERNEL);
if (!imp_settings->increasing_pool_reqs) {
pr_err("%s: Failed to alloc mem for inc mempool\n", __func__);
return -ENOMEM;
}
for (i = 0; i < imp_settings->num_heads; i++) {
struct tegra_nvdisp_mempool_req *dec_pool, *inc_pool;
size_t win_ids_size, win_entries_size;
dec_pool = &imp_settings->decreasing_pool_reqs[i];
inc_pool = &imp_settings->increasing_pool_reqs[i];
win_ids_size = sizeof(*(dec_pool->win_ids)) *
tegra_dc_get_numof_dispwindows();
win_entries_size = sizeof(*(dec_pool->win_entries)) *
tegra_dc_get_numof_dispwindows();
dec_pool->win_ids = kzalloc(win_ids_size, GFP_KERNEL);
dec_pool->win_entries = kzalloc(win_entries_size, GFP_KERNEL);
inc_pool->win_ids = kzalloc(win_ids_size, GFP_KERNEL);
inc_pool->win_entries = kzalloc(win_entries_size, GFP_KERNEL);
if (!dec_pool->win_ids || !dec_pool->win_entries ||
!inc_pool->win_ids || !inc_pool->win_entries) {
pr_err("%s: No mem for mempool win\n", __func__);
return -ENOMEM;
}
}
return 0;
}
static struct tegra_nvdisp_imp_settings *alloc_imp_settings(
u8 num_heads, u8 *num_wins_per_head)
{
struct tegra_nvdisp_imp_settings *imp_settings;
int i;
imp_settings = kzalloc(sizeof(*imp_settings), GFP_KERNEL);
if (!imp_settings) {
pr_err("%s: Failed to alloc mem for settings\n", __func__);
return imp_settings;
}
imp_settings->head_settings =
kcalloc(num_heads,
sizeof(*(imp_settings->head_settings)),
GFP_KERNEL);
if (!imp_settings->head_settings) {
pr_err("%s: Failed to alloc mem for heads\n", __func__);
kfree(imp_settings);
return NULL;
}
imp_settings->num_heads = num_heads;
for (i = 0; i < num_heads; i++) {
struct tegra_nvdisp_imp_head_settings *head_settings;
head_settings = &imp_settings->head_settings[i];
head_settings->win_entries =
kcalloc(num_wins_per_head[i],
sizeof(*(head_settings->win_entries)),
GFP_KERNEL);
if (!head_settings->win_entries) {
pr_err("%s: Failed to alloc mem for wins\n", __func__);
dealloc_imp_settings(imp_settings);
return NULL;
}
head_settings->num_wins = num_wins_per_head[i];
}
if (alloc_pool_reqs(imp_settings)) {
dealloc_imp_settings(imp_settings);
imp_settings = NULL;
}
return imp_settings;
}
static void cpy_from_ext_imp_head_v1(
struct tegra_dc_ext_imp_head_results *head_results,
struct tegra_nvdisp_imp_head_settings *nvdisp_head,
u8 ctrl_num)
{
struct tegra_dc_ext_nvdisp_imp_head_entries *head_entries;
u8 num_wins = head_results->num_windows;
int i;
head_entries = &nvdisp_head->entries;
head_entries->ctrl_num = ctrl_num;
head_entries->curs_fetch_slots =
head_results->metering_slots_value_cursor;
head_entries->curs_pipe_meter = head_results->pipe_meter_value_cursor;
head_entries->curs_dvfs_watermark =
head_results->thresh_lwm_dvfs_cursor;
head_entries->curs_mempool_entries =
head_results->pool_config_entries_cursor;
for (i = 0; i < num_wins; i++) {
struct tegra_dc_ext_nvdisp_imp_win_entries *win_entries;
win_entries = &nvdisp_head->win_entries[i];
win_entries->id = head_results->win_ids[i];
win_entries->thread_group = head_results->thread_group_win[i];
win_entries->fetch_slots =
head_results->metering_slots_value_win[i];
win_entries->pipe_meter = head_results->pipe_meter_value_win[i];
win_entries->dvfs_watermark =
head_results->thresh_lwm_dvfs_win[i];
win_entries->mempool_entries =
head_results->pool_config_entries_win[i];
}
}
static struct tegra_nvdisp_imp_settings *cpy_from_ext_imp_settings_v1(
struct tegra_dc_ext_imp_settings *ext_settings)
{
struct tegra_nvdisp_imp_settings *nvdisp_settings;
struct tegra_dc_ext_nvdisp_imp_global_entries *global_entries;
u8 active_heads = 0, max_heads = tegra_dc_get_numof_dispheads();
u8 num_wins_per_head[max_heads];
int i;
for (i = 0; i < max_heads; i++) {
struct tegra_dc_ext_imp_head_results *head_results;
head_results = &ext_settings->imp_results[i];
num_wins_per_head[i] = 0;
if (head_results->head_active)
num_wins_per_head[active_heads++] =
head_results->num_windows;
}
nvdisp_settings = alloc_imp_settings(active_heads, num_wins_per_head);
if (!nvdisp_settings)
return NULL;
global_entries = &nvdisp_settings->global_entries;
global_entries->total_win_fetch_slots =
ext_settings->window_slots_value;
global_entries->total_curs_fetch_slots =
ext_settings->cursor_slots_value;
global_entries->emc_floor_hz = ext_settings->proposed_emc_hz;
global_entries->min_hubclk_hz = ext_settings->hubclk;
global_entries->total_iso_bw_with_catchup_kBps =
ext_settings->total_display_iso_bw_kbps;
global_entries->total_iso_bw_without_catchup_kBps =
ext_settings->required_total_bw_kbps;
active_heads = 0;
for (i = 0; i < max_heads; i++) {
struct tegra_dc_ext_imp_head_results *head_results;
struct tegra_nvdisp_imp_head_settings *nvdisp_head;
head_results = &ext_settings->imp_results[i];
if (!head_results->head_active)
continue;
nvdisp_head = &nvdisp_settings->head_settings[active_heads++];
cpy_from_ext_imp_head_v1(head_results, nvdisp_head, i);
}
return nvdisp_settings;
}
static int cpy_from_ext_imp_head_v2(
struct tegra_dc_ext_nvdisp_imp_head_settings *ext_head,
struct tegra_nvdisp_imp_head_settings *nvdisp_head)
{
struct tegra_dc_ext_nvdisp_imp_win_settings *ext_wins;
u8 num_wins = ext_head->num_wins;
int i, ret = 0;
ext_wins = kcalloc(num_wins, sizeof(*ext_wins), GFP_KERNEL);
if (!ext_wins) {
pr_err("%s: Failed to alloc mem for dc_ext wins\n", __func__);
return -ENOMEM;
}
if (copy_from_user(ext_wins, (void __user *)ext_head->win_settings,
sizeof(*ext_wins) * num_wins)) {
pr_err("%s: Failed to copy dc_ext wins from user\n", __func__);
ret = -EFAULT;
goto cpy_imp_wins_ret;
}
nvdisp_head->entries = ext_head->entries;
for (i = 0; i < num_wins; i++)
nvdisp_head->win_entries[i] = ext_wins[i].entries;
cpy_imp_wins_ret:
kfree(ext_wins);
return ret;
}
static struct tegra_nvdisp_imp_settings *cpy_from_ext_imp_settings_v2(
struct tegra_dc_ext_nvdisp_imp_settings *ext_settings)
{
struct tegra_nvdisp_imp_settings *nvdisp_settings = NULL;
struct tegra_dc_ext_nvdisp_imp_head_settings *ext_heads;
u8 num_heads = ext_settings->num_heads;
u8 max_heads = tegra_dc_get_numof_dispheads();
u8 num_wins_per_head[max_heads];
int i, ret = 0;
ext_heads = kcalloc(num_heads, sizeof(*ext_heads), GFP_KERNEL);
if (!ext_heads) {
pr_err("%s: Failed to alloc mem for dc_ext heads\n", __func__);
return NULL;
}
if (copy_from_user(ext_heads,
(void __user *)ext_settings->head_settings,
sizeof(*ext_heads) * num_heads)) {
pr_err("%s: Failed to copy dc_ext heads from user\n", __func__);
goto cpy_imp_v2_ret;
}
for (i = 0; i < num_heads; i++)
num_wins_per_head[i] = ext_heads[i].num_wins;
/*
* Zero out the remaining entries individually. We can't use memset()
* since this is a variable-length array.
*/
for (; i < max_heads; i++)
num_wins_per_head[i] = 0;
nvdisp_settings = alloc_imp_settings(num_heads, num_wins_per_head);
if (!nvdisp_settings)
goto cpy_imp_v2_ret;
nvdisp_settings->global_entries = ext_settings->global_settings.entries;
for (i = 0; i < num_heads; i++) {
if (ext_heads[i].num_wins < 1 || ext_heads[i].num_wins > DC_N_WINDOWS) {
pr_err("Wrong number of displays");
goto cpy_imp_v2_ret;
}
ret = cpy_from_ext_imp_head_v2(&ext_heads[i],
&nvdisp_settings->head_settings[i]);
if (ret) {
dealloc_imp_settings(nvdisp_settings);
nvdisp_settings = NULL;
goto cpy_imp_v2_ret;
}
}
cpy_imp_v2_ret:
kfree(ext_heads);
return nvdisp_settings;
}
static struct tegra_nvdisp_imp_settings *convert_dc_ext_to_nvdisp_imp(
struct tegra_dc_ext_flip_user_data *flip_user_data,
void __user *ext_settings_ptr,
struct tegra_dc_ext_imp_settings *ext_settings_v1,
struct tegra_dc_ext_nvdisp_imp_settings *ext_settings_v2)
{
struct tegra_nvdisp_imp_settings *nvdisp_settings;
bool use_v2 = flip_user_data->flags & TEGRA_DC_EXT_FLIP_FLAG_IMP_V2;
int ret = 0;
if (use_v2)
ret = copy_from_user(ext_settings_v2, ext_settings_ptr,
sizeof(*ext_settings_v2));
else
ret = copy_from_user(ext_settings_v1, ext_settings_ptr,
sizeof(*ext_settings_v1));
if (ret) {
pr_err("%s: Failed to copy dc_ext settings\n", __func__);
return NULL;
}
if (use_v2)
nvdisp_settings =
cpy_from_ext_imp_settings_v2(ext_settings_v2);
else
nvdisp_settings =
cpy_from_ext_imp_settings_v1(ext_settings_v1);
if (!nvdisp_settings)
pr_err("%s: Failed to create nvdisp IMP settings\n", __func__);
return nvdisp_settings;
}
int tegra_dc_queue_imp_propose(struct tegra_dc *dc,
struct tegra_dc_ext_flip_user_data *flip_user_data)
{
struct tegra_dc_ext_imp_settings ext_settings_v1;
struct tegra_dc_ext_nvdisp_imp_settings ext_settings_v2;
struct tegra_nvdisp_imp_settings *nvdisp_settings;
struct tegra_dc_ext_nvdisp_imp_global_entries *global_entries;
void __user *ext_session_id_ptr;
void __user *ext_settings_ptr;
bool use_v2 = flip_user_data->flags & TEGRA_DC_EXT_FLIP_FLAG_IMP_V2;
int ret = 0;
if (!tegra_dc_is_nvdisplay())
return -EINVAL;
ext_settings_ptr = (void __user *)flip_user_data->imp_ptr.settings;
nvdisp_settings = convert_dc_ext_to_nvdisp_imp(flip_user_data,
ext_settings_ptr,
&ext_settings_v1,
&ext_settings_v2);
if (!nvdisp_settings)
return -EINVAL;
mutex_lock(&tegra_nvdisp_lock);
global_entries = &nvdisp_settings->global_entries;
ret = tegra_nvdisp_negotiate_reserved_bw(dc,
(u32)global_entries->total_iso_bw_with_catchup_kBps,
(u32)global_entries->total_iso_bw_without_catchup_kBps,
(u32)global_entries->emc_floor_hz,
(u32)global_entries->min_hubclk_hz);
if (ret) {
mutex_unlock(&tegra_nvdisp_lock);
dealloc_imp_settings(nvdisp_settings);
return ret;
}
if (use_v2) {
u32 offset = offsetof(struct tegra_dc_ext_nvdisp_imp_settings,
session_id);
ext_session_id_ptr = ext_settings_ptr + offset;
} else {
ext_session_id_ptr =
(void __user *)ext_settings_v1.session_id_ptr;
}
if (copy_to_user(ext_session_id_ptr, &dc->imp_session_id_cntr,
sizeof(dc->imp_session_id_cntr))) {
dev_err(&dc->ndev->dev,
"Failed to copy IMP session id back to user\n");
mutex_unlock(&tegra_nvdisp_lock);
if (nvdisp_settings->num_heads > 0)
dealloc_imp_settings(nvdisp_settings);
else
dev_err(&dc->ndev->dev, "num heads is not a positive integer\n");
return -EFAULT;
}
nvdisp_settings->session_id = dc->imp_session_id_cntr++;
nvdisp_settings->owner_ctrl_num = dc->ctrl_num;
INIT_LIST_HEAD(&nvdisp_settings->imp_node);
list_add_tail(&nvdisp_settings->imp_node, &g_imp.imp_settings_queue);
mutex_unlock(&tegra_nvdisp_lock);
trace_display_imp_propose_queued(dc->ctrl_num,
nvdisp_settings->session_id);
return ret;
}
EXPORT_SYMBOL(tegra_dc_queue_imp_propose);
static void tegra_nvdisp_flush_imp_queue(void)
{
struct tegra_nvdisp_imp_settings *cur = NULL, *next = NULL;
mutex_lock(&tegra_nvdisp_lock);
list_for_each_entry_safe(cur,
next,
&g_imp.imp_settings_queue,
imp_node) {
list_del(&cur->imp_node);
dealloc_imp_settings(cur);
}
mutex_unlock(&tegra_nvdisp_lock);
}
int tegra_dc_validate_imp_queue(struct tegra_dc *dc, u64 session_id)
{
struct tegra_nvdisp_imp_settings *cur = NULL, *next = NULL;
int ret = -EINVAL;
if (!tegra_dc_is_nvdisplay())
return ret;
mutex_lock(&tegra_nvdisp_lock);
/*
* Since IMP flips are issued in the same respective order as their
* corresponding PROPOSEs, and only one IMP flip can be in-flight at any
* moment in time, any entries that are in front of the one we're
* looking for must be stale. These entries can be safely removed.
*
* If none of the entries in the queue match the one we're looking for,
* return an error.
*/
list_for_each_entry_safe(cur,
next,
&g_imp.imp_settings_queue,
imp_node) {
if (cur->owner_ctrl_num != dc->ctrl_num ||
cur->session_id != session_id) {
list_del(&cur->imp_node);
dealloc_imp_settings(cur);
} else {
ret = 0;
break;
}
}
mutex_unlock(&tegra_nvdisp_lock);
return ret;
}
EXPORT_SYMBOL(tegra_dc_validate_imp_queue);
struct tegra_dc *find_dc_by_ctrl_num(u32 ctrl_num)
{
int i;
for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) {
struct tegra_dc *dc = tegra_dc_get_dc(i);
if (dc && dc->ctrl_num == ctrl_num)
return dc;
}
return NULL;
}
static void tegra_nvdisp_generate_mempool_reqs(struct tegra_dc *dc,
struct tegra_nvdisp_imp_settings *imp_settings)
{
int i;
for (i = 0; i < imp_settings->num_heads; i++) {
struct tegra_nvdisp_imp_head_settings *head_settings;
struct tegra_nvdisp_mempool_req *dec_pool, *inc_pool;
struct tegra_nvdisp_mempool_req *cur_pool = NULL;
struct tegra_dc *other_dc;
u32 old_val, new_val;
u8 num_dec, num_inc;
int j;
head_settings = &imp_settings->head_settings[i];
other_dc =
find_dc_by_ctrl_num(head_settings->entries.ctrl_num);
if (!other_dc || !other_dc->enabled || other_dc == dc)
continue;
num_dec = imp_settings->num_decreasing_pool;
num_inc = imp_settings->num_increasing_pool;
dec_pool = &imp_settings->decreasing_pool_reqs[num_dec];
inc_pool = &imp_settings->increasing_pool_reqs[num_inc];
old_val = tegra_nvdisp_get_active_curs_pool(other_dc);
new_val = head_settings->entries.curs_mempool_entries;
if (new_val < old_val)
cur_pool = dec_pool;
else if (new_val > old_val)
cur_pool = inc_pool;
if (cur_pool) {
cur_pool->program_cursor = true;
cur_pool->cursor_entry = new_val;
cur_pool = NULL;
}
for (j = 0; j < head_settings->num_wins; j++) {
struct tegra_dc_ext_nvdisp_imp_win_entries *win_entries;
struct tegra_dc_win *win;
win_entries = &head_settings->win_entries[j];
win = tegra_dc_get_window(other_dc, win_entries->id);
if (!win)
continue;
old_val = tegra_nvdisp_get_active_win_pool(win);
new_val = win_entries->mempool_entries;
if (new_val < old_val)
cur_pool = dec_pool;
else if (new_val > old_val)
cur_pool = inc_pool;
if (cur_pool) {
u8 num_wins = cur_pool->num_wins;
cur_pool->win_ids[num_wins] = win_entries->id;
cur_pool->win_entries[num_wins] = new_val;
cur_pool->num_wins++;
cur_pool = NULL;
}
}
if (dec_pool->program_cursor || dec_pool->num_wins > 0) {
dec_pool->ctrl_num = other_dc->ctrl_num;
imp_settings->num_decreasing_pool++;
}
if (inc_pool->program_cursor || inc_pool->num_wins > 0) {
inc_pool->ctrl_num = other_dc->ctrl_num;
imp_settings->num_increasing_pool++;
}
}
}
static void tegra_nvdisp_program_dc_mempool(struct tegra_dc *dc,
struct tegra_nvdisp_mempool_req *mempool_req)
{
u32 entries = 0;
int i;
if (!dc || !dc->enabled)
return;
if (mempool_req->program_cursor) {
entries = mempool_req->cursor_entry;
tegra_dc_writel(dc,
nvdisp_ihub_cursor_pool_config_entries_f(entries),
nvdisp_ihub_cursor_pool_config_r());
trace_display_imp_cursor_mempool_programmed(dc->ctrl_num,
entries);
}
for (i = 0; i < mempool_req->num_wins; i++) {
struct tegra_dc_win *win;
win = tegra_dc_get_window(dc, mempool_req->win_ids[i]);
if (!win)
continue;
entries = mempool_req->win_entries[i];
nvdisp_win_write(win,
win_ihub_pool_config_entries_f(entries),
win_ihub_pool_config_r());
trace_display_imp_win_mempool_programmed(dc->ctrl_num, win->idx,
entries);
}
tegra_nvdisp_activate_common_channel(dc);
tegra_nvdisp_wait_for_common_channel_to_promote(dc);
}
static void tegra_nvdisp_program_other_mempool(struct tegra_dc *dc,
struct tegra_nvdisp_imp_settings *imp_settings,
bool before_win_update)
{
struct tegra_nvdisp_mempool_req *mempool_reqs;
int i, num_reqs;
if (before_win_update) {
mempool_reqs = imp_settings->decreasing_pool_reqs;
num_reqs = imp_settings->num_decreasing_pool;
} else {
mempool_reqs = imp_settings->increasing_pool_reqs;
num_reqs = imp_settings->num_increasing_pool;
}
for (i = 0; i < num_reqs; i++) {
struct tegra_nvdisp_mempool_req *req;
struct tegra_dc *other_dc;
req = &mempool_reqs[i];
other_dc = find_dc_by_ctrl_num(req->ctrl_num);
if (!other_dc)
continue;
tegra_nvdisp_program_dc_mempool(other_dc, req);
}
}
void tegra_dc_adjust_imp(struct tegra_dc *dc, bool before_win_update)
{
struct tegra_nvdisp_imp_settings *imp_settings;
struct tegra_dc_ext_nvdisp_imp_global_entries *global_entries;
if (!tegra_dc_is_nvdisplay())
return;
if (!dc) {
pr_err("%s: DC is NULL\n", __func__);
return;
}
if (!dc->enabled) {
pr_err("%s: DC %d is NOT enabled\n", __func__, dc->ctrl_num);
return;
}
mutex_lock(&tegra_nvdisp_lock);
imp_settings = tegra_nvdisp_get_current_imp_settings();
if (!imp_settings) {
mutex_unlock(&tegra_nvdisp_lock);
return;
}
/*
* - The cursor and wgrp latency registers take effect immediately.
* As such, disable latency events for now and re-enable them after
* the rest of the window channel state has promoted.
* - Make sure our ISO bandwidth and hubclk requirements are met before
* changing the current isohub settings.
*/
global_entries = &imp_settings->global_entries;
if (before_win_update) {
tegra_nvdisp_disable_ihub_latency_events(dc);
tegra_nvdisp_program_bandwidth(dc,
(u32)global_entries->total_iso_bw_with_catchup_kBps,
(u32)global_entries->total_iso_bw_without_catchup_kBps,
(u32)global_entries->emc_floor_hz,
(u32)global_entries->min_hubclk_hz,
before_win_update);
}
mutex_unlock(&tegra_nvdisp_lock);
/* Make sure there's no pending change on the current head. */
tegra_nvdisp_wait_for_common_channel_to_promote(dc);
if (before_win_update) {
mutex_lock(&tegra_nvdisp_lock);
tegra_nvdisp_generate_mempool_reqs(dc, imp_settings);
mutex_unlock(&tegra_nvdisp_lock);
}
tegra_nvdisp_program_other_mempool(dc, imp_settings, before_win_update);
if (!before_win_update) {
mutex_lock(&tegra_nvdisp_lock);
/*
* Update our bandwidth requirements after the new window state
* and isohub settings have promoted.
*/
tegra_nvdisp_program_bandwidth(dc,
(u32)global_entries->total_iso_bw_with_catchup_kBps,
(u32)global_entries->total_iso_bw_without_catchup_kBps,
(u32)global_entries->emc_floor_hz,
(u32)global_entries->min_hubclk_hz,
before_win_update);
/* Re-enable ihub latency events. */
tegra_nvdisp_enable_ihub_latency_events(dc);
/*
* These IMP settings are no longer pending. Remove them from
* the global queue and free the associated memory.
*/
list_del(&imp_settings->imp_node);
dealloc_imp_settings(imp_settings);
mutex_unlock(&tegra_nvdisp_lock);
}
}
EXPORT_SYMBOL(tegra_dc_adjust_imp);
static void tegra_nvdisp_program_imp_curs_entries(struct tegra_dc *dc,
u32 fetch_slots,
u32 pipe_meter,
u32 dvfs_watermark,
u32 mempool_entries,
int owner_head)
{
tegra_dc_writel(dc,
nvdisp_ihub_cursor_fetch_meter_slots_f(fetch_slots),
nvdisp_ihub_cursor_fetch_meter_r());
tegra_nvdisp_program_curs_dvfs_watermark(dc, dvfs_watermark);
tegra_dc_writel(dc,
nvdisp_cursor_pipe_meter_val_f(pipe_meter),
nvdisp_cursor_pipe_meter_r());
/*
* Only program cursor mempool for the HEAD that's initiating the IMP
* state change. The cursor mempool for the other HEADs is taken care of
* elsewhere.
*/
if (dc->ctrl_num == owner_head) {
tegra_dc_writel(dc,
nvdisp_ihub_cursor_pool_config_entries_f(mempool_entries),
nvdisp_ihub_cursor_pool_config_r());
}
trace_display_imp_program_cursor(dc->ctrl_num,
fetch_slots,
pipe_meter,
mempool_entries,
dvfs_watermark);
}
static void tegra_nvdisp_program_imp_win_entries(struct tegra_dc *dc,
u32 thread_group,
u32 fetch_meter,
u32 pipe_meter,
u32 dvfs_watermark,
u32 mempool_entries,
u32 win_id,
int owner_head)
{
struct tegra_dc_win *win = tegra_dc_get_window(dc, win_id);
if (!win)
return;
nvdisp_win_write(win,
win_ihub_fetch_meter_slots_f(fetch_meter),
win_ihub_fetch_meter_r());
tegra_nvdisp_program_win_dvfs_watermark(win, dvfs_watermark);
nvdisp_win_write(win,
win_precomp_pipe_meter_val_f(pipe_meter),
win_precomp_pipe_meter_r());
/*
* Only program wgrp mempool for the HEAD that's initiating the IMP
* state change. The wgrp mempool for the other HEADs is taken care of
* elsewhere.
*/
if (dc->ctrl_num == owner_head) {
nvdisp_win_write(win,
win_ihub_pool_config_entries_f(mempool_entries),
win_ihub_pool_config_r());
}
trace_display_imp_program_win(dc->ctrl_num, win_id,
fetch_meter,
pipe_meter,
mempool_entries,
dvfs_watermark,
thread_group);
}
static void tegra_nvdisp_program_imp_head_settings(struct tegra_dc *dc,
struct tegra_nvdisp_imp_head_settings *head_settings,
u32 owner_head)
{
struct tegra_dc_ext_nvdisp_imp_head_entries *head_entries;
int i;
if (!dc || !dc->enabled)
return;
head_entries = &head_settings->entries;
tegra_nvdisp_program_imp_curs_entries(dc,
head_entries->curs_fetch_slots,
head_entries->curs_pipe_meter,
head_entries->curs_dvfs_watermark,
head_entries->curs_mempool_entries,
owner_head);
for (i = 0; i < head_settings->num_wins; i++) {
struct tegra_dc_ext_nvdisp_imp_win_entries *win_entries;
win_entries = &head_settings->win_entries[i];
tegra_nvdisp_program_imp_win_entries(dc,
win_entries->thread_group,
win_entries->fetch_slots,
win_entries->pipe_meter,
win_entries->dvfs_watermark,
win_entries->mempool_entries,
win_entries->id,
owner_head);
}
}
void tegra_nvdisp_program_imp_settings(struct tegra_dc *dc)
{
struct tegra_nvdisp_imp_settings *imp_settings;
int i;
if (!dc || !dc->enabled)
return;
imp_settings = tegra_nvdisp_get_current_imp_settings();
if (!imp_settings)
return;
for (i = 0; i < imp_settings->num_heads; i++) {
struct tegra_nvdisp_imp_head_settings *head_settings;
struct tegra_dc *other_dc;
head_settings = &imp_settings->head_settings[i];
other_dc =
find_dc_by_ctrl_num(head_settings->entries.ctrl_num);
if (!other_dc || !other_dc->enabled)
continue;
tegra_nvdisp_program_imp_head_settings(other_dc, head_settings,
dc->ctrl_num);
}
tegra_nvdisp_program_common_fetch_meter(dc,
imp_settings->global_entries.total_curs_fetch_slots,
imp_settings->global_entries.total_win_fetch_slots);
}
static struct tegra_nvdisp_imp_settings *cpy_imp_entries(
struct tegra_nvdisp_imp_settings *src_settings)
{
struct tegra_nvdisp_imp_settings *dst_settings;
u32 max_heads = tegra_dc_get_numof_dispheads();
u8 num_wins_per_head[max_heads];
u8 num_heads = src_settings->num_heads;
int i;
for (i = 0; i < num_heads; i++) {
struct tegra_nvdisp_imp_head_settings *head_settings;
head_settings = &src_settings->head_settings[i];
num_wins_per_head[i] = head_settings->num_wins;
}
/*
* Zero out the remaining entries individually. We can't use memset()
* since this is a variable-length array.
*/
for (; i < max_heads; i++)
num_wins_per_head[i] = 0;
dst_settings = alloc_imp_settings(num_heads, num_wins_per_head);
if (!dst_settings)
return NULL;
memcpy(&dst_settings->global_entries, &src_settings->global_entries,
sizeof(dst_settings->global_entries));
for (i = 0; i < num_heads; i++) {
struct tegra_nvdisp_imp_head_settings *src_head, *dst_head;
src_head = &src_settings->head_settings[i];
dst_head = &dst_settings->head_settings[i];
memcpy(&dst_head->entries, &src_head->entries,
sizeof(dst_head->entries));
memcpy(dst_head->win_entries, src_head->win_entries,
sizeof(*(dst_head->win_entries)) * dst_head->num_wins);
}
return dst_settings;
}
static void program_imp_win_owners(bool attach)
{
int num_wins = tegra_dc_get_numof_dispwindows();
int i;
for (i = 0; i < tegra_dc_get_numof_dispheads(); i++) {
struct tegra_dc *dc = tegra_dc_get_dc(i);
unsigned long winmask;
int win_id = 0;
if (!dc || !dc->enabled)
continue;
winmask = dc->pdata->win_mask;
for_each_set_bit(win_id, &winmask, num_wins) {
struct tegra_dc_win *win;
win = tegra_dc_get_window(dc, win_id);
if (!win || !win->dc)
continue;
if (!WIN_IS_ENABLED(win)) {
u32 val = win_set_control_owner_none_f();
if (attach)
val = dc->ctrl_num;
nvdisp_win_write(win, val, win_set_control_r());
}
}
}
}
void tegra_dc_reset_imp_state(void)
{
struct nvdisp_imp_table *imp_table;
struct tegra_nvdisp_imp_settings *reset_settings;
struct tegra_dc_ext_nvdisp_imp_global_entries *global_entries;
struct tegra_dc *reserve_dc = NULL, *master_dc = NULL;
struct nvdisp_bandwidth_config max_bw_cfg = {0};
int num_heads = tegra_dc_get_numof_dispheads();
int i;
if (!tegra_platform_is_silicon())
return;
if (!tegra_dc_is_nvdisplay())
return;
/*
* Try to find any registered DC instance. It doesn't matter which one
* since we just need a non-NULL instance to reserve the COMMON channel.
* Although this should never happen, immediately return if there are
* none found.
*/
for (i = 0; i < num_heads; i++) {
reserve_dc = tegra_dc_get_dc(i);
if (reserve_dc)
break;
}
if (!reserve_dc || tegra_dc_reserve_common_channel(reserve_dc))
return;
/*
* This function should only be called when all DC handles have been
* closed. No userspace clients should be active at this point, and all
* pending IMP requests should have already been processed or dropped.
* However, we should explicitly flush the queue just to be sure.
*/
tegra_nvdisp_flush_imp_queue();
/*
* No heads should be undergoing any state changes at this point, so
* pick any head that's still enabled and pretend that it's triggering
* an IMP state change. If there are no active heads, release the COMMON
* channel and return.
*/
for (i = 0; i < num_heads; i++) {
master_dc = tegra_dc_get_dc(i);
if (master_dc && master_dc->enabled)
break;
}
if (!master_dc || !master_dc->enabled)
goto reset_imp_release_common_channel;
imp_table = tegra_dc_common_get_imp_table();
if (!imp_table)
goto reset_imp_release_common_channel;
/* Copy the relevant settings over. */
reset_settings = cpy_imp_entries(imp_table->boot_setting);
if (!reset_settings)
goto reset_imp_release_common_channel;
tegra_nvdisp_get_max_bw_cfg(&max_bw_cfg);
global_entries = &reset_settings->global_entries;
global_entries->total_iso_bw_with_catchup_kBps = max_bw_cfg.iso_bw;
global_entries->total_iso_bw_without_catchup_kBps = max_bw_cfg.total_bw;
global_entries->emc_floor_hz = max_bw_cfg.emc_la_floor;
global_entries->min_hubclk_hz = max_bw_cfg.hubclk;
/* Add the default settings to the IMP queue. */
INIT_LIST_HEAD(&reset_settings->imp_node);
list_add_tail(&reset_settings->imp_node, &g_imp.imp_settings_queue);
/* Follow the same basic flow as an actual IMP flip. */
if (tegra_nvdisp_negotiate_reserved_bw(master_dc,
max_bw_cfg.iso_bw,
max_bw_cfg.total_bw,
max_bw_cfg.emc_la_floor,
max_bw_cfg.hubclk)) {
list_del(&reset_settings->imp_node);
dealloc_imp_settings(reset_settings);
goto reset_imp_release_common_channel;
}
program_imp_win_owners(true);
for (i = 0; i < reset_settings->num_heads; i++) {
struct tegra_nvdisp_imp_head_settings *head_settings;
struct tegra_dc *dc;
head_settings = &reset_settings->head_settings[i];
dc = find_dc_by_ctrl_num(head_settings->entries.ctrl_num);
if (!dc)
continue;
swap_imp_boot_wins(head_settings, dc->pdata->win_mask);
}
tegra_dc_adjust_imp(master_dc, true);
tegra_nvdisp_program_imp_settings(master_dc);
if (tegra_dc_enable_update_and_act(master_dc,
nvdisp_cmd_state_ctrl_common_act_update_enable_f(),
nvdisp_cmd_state_ctrl_common_act_req_enable_f()))
dev_err(&master_dc->ndev->dev,
"timeout waiting for master dc state to promote\n");
tegra_dc_adjust_imp(master_dc, false);
program_imp_win_owners(false);
/*
* The second adjust IMP call should have already detached the IMP
* settings from the global queue and freed them.
*/
tegra_dc_release_common_channel(reserve_dc);
return;
reset_imp_release_common_channel:
tegra_dc_release_common_channel(reserve_dc);
}
EXPORT_SYMBOL(tegra_dc_reset_imp_state);
void tegra_nvdisp_reg_dump(struct tegra_dc *dc, void *data,
void (* print)(void *data, const char *str))
{
int i;
char buff[256];
const char winname[] = "ABCDEFT";
/* If gated, quietly return. */
if (!tegra_dc_is_powered(dc))
return;
mutex_lock(&dc->lock);
tegra_dc_get(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)
#include "hw_nvdisp_nvdisp_regdump.c"
#undef DUMP_REG
#define DUMP_REG(a) do { \
snprintf(buff, sizeof(buff), "%-32s\t%03x\t%08x\n", \
#a, a, nvdisp_win_read(win, a)); \
print(data, buff); \
} while (0)
for (i = 0; i < tegra_dc_get_numof_dispwindows(); ++i) {
struct tegra_dc_win *win = tegra_dc_get_window(dc, i);
if (!win || !win->dc)
continue;
print(data, "\n");
snprintf(buff, sizeof(buff), "WINDOW %c:\n", winname[i]);
print(data, buff);
#include "hw_nvdisp_win_regdump.c"
#undef DUMP_REG
}
tegra_dc_put(dc);
mutex_unlock(&dc->lock);
}
void tegra_dc_enable_sor_t18x(struct tegra_dc *dc, int sor_num, bool enable)
{
u32 enb;
u32 reg_val = tegra_dc_readl(dc, nvdisp_win_options_r());
switch (sor_num) {
case 0:
enb = nvdisp_win_options_sor_set_sor_enable_f();
break;
case 1:
enb = nvdisp_win_options_sor1_set_sor1_enable_f();
break;
default:
pr_err("%s: invalid sor_num:%d\n", __func__, sor_num);
return;
}
reg_val = enable ? reg_val | enb : reg_val & ~enb;
tegra_dc_writel(dc, reg_val, nvdisp_win_options_r());
}
void tegra_nvdisp_set_output_lut(struct tegra_dc *dc,
struct tegra_dc_ext_nvdisp_cmu *user_nvdisp_cmu, bool new_cmu_values)
{
struct tegra_dc_nvdisp_lut *nvdisp_lut;
u32 reg_val = 0;
dc->cmu_enabled = user_nvdisp_cmu->cmu_enable ? true : false;
reg_val = tegra_dc_readl(dc, nvdisp_color_ctl_r());
if (user_nvdisp_cmu->cmu_enable) {
reg_val |= nvdisp_color_ctl_cmu_enable_f();
if (new_cmu_values) {
nvdisp_lut = &dc->nvdisp_postcomp_lut;
nvdisp_copy_output_lut(nvdisp_lut->rgb,
(unsigned int *)&user_nvdisp_cmu->rgb,
NVDISP_OUTPUT_LUT_SIZE);
}
} else {
reg_val &= ~nvdisp_color_ctl_cmu_enable_f();
}
tegra_dc_writel(dc, reg_val, nvdisp_color_ctl_r());
}
void tegra_nvdisp_set_output_colorspace(struct tegra_dc *dc,
u16 output_colorspace)
{
u32 csc2_control = dc->cached_settings.csc2_control;
/* Reset the csc2 control colorspace */
csc2_control &= ~(nvdisp_csc2_control_output_color_sel_y2020_f());
if (output_colorspace == TEGRA_DC_EXT_FLIP_FLAG_CS_NONE) {
csc2_control |= nvdisp_csc2_control_output_color_sel_rgb_f();
} else if (output_colorspace == TEGRA_DC_EXT_FLIP_FLAG_CS_REC601) {
csc2_control |= nvdisp_csc2_control_output_color_sel_y601_f();
} else if (output_colorspace == TEGRA_DC_EXT_FLIP_FLAG_CS_REC709) {
csc2_control |= nvdisp_csc2_control_output_color_sel_y709_f();
} else if (output_colorspace == TEGRA_DC_EXT_FLIP_FLAG_CS_REC2020) {
csc2_control |= nvdisp_csc2_control_output_color_sel_y2020_f();
} else {
dev_err(&dc->ndev->dev, "%s: unsupported colorspace=%u\n",
__func__, output_colorspace);
return;
}
dc->cached_settings.csc2_control = csc2_control;
}
void tegra_nvdisp_set_output_range(struct tegra_dc *dc, u8 limited_range_enable)
{
u32 csc2_control = dc->cached_settings.csc2_control;
/* Reset the csc2 control color range */
csc2_control &= ~(nvdisp_csc2_control_limit_rgb_enable_f());
if (limited_range_enable)
csc2_control |= nvdisp_csc2_control_limit_rgb_enable_f();
else
csc2_control |= nvdisp_csc2_control_limit_rgb_disable_f();
dc->cached_settings.csc2_control = csc2_control;
}
void tegra_nvdisp_set_csc2(struct tegra_dc *dc)
{
tegra_dc_writel(dc, dc->cached_settings.csc2_control,
nvdisp_csc2_control_r());
}
void tegra_nvdisp_activate_general_channel(struct tegra_dc *dc)
{
/* Send general ack update and request enable */
tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_general_update_enable_f(),
nvdisp_cmd_state_ctrl_r());
tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r());
tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_general_act_req_enable_f(),
nvdisp_cmd_state_ctrl_r());
tegra_dc_readl(dc, nvdisp_cmd_state_ctrl_r()); /* flush */
}
static struct tegra_dc_sor_info t18x_sor_info[] = {
{ .hdcp_supported = true }, /* SOR0 */
{ .hdcp_supported = true }, /* SOR1 */
};
void tegra_dc_populate_t18x_hw_data(struct tegra_dc_hw_data *hw_data)
{
if (!hw_data)
return;
hw_data->nheads = 3;
hw_data->nwins = 6;
hw_data->nsors = 2;
hw_data->sor_info = t18x_sor_info;
hw_data->pd_table = &t18x_disp_pd_table;
hw_data->valid = true;
hw_data->version = TEGRA_DC_HW_T18x;
}
/* _tegra_nvdisp_enable_vpulse2_int() - initializes and enables vpulse2
* interrupt
* @dc : pointer to struct tegra_dc of the current head.
* @start_pos : line number at which interrupt is expected.
*
* Return : 0 if successful else the relevant error number.
*/
static int _tegra_nvdisp_enable_vpulse2_int(struct tegra_dc *dc, u32 start_pos)
{
u32 val;
if (!dc || !dc->enabled)
return -ENODEV;
if (start_pos > dc->mode_metadata.vtotal_lines)
return -EINVAL;
tegra_dc_get(dc);
tegra_dc_writel(dc, start_pos, nvdisp_v_pulse2_position_a_r());
val = tegra_dc_readl(dc, nvdisp_disp_signal_option_r());
tegra_dc_writel(dc, val | nvdisp_disp_signal_option_v_pulse2_enable_f(),
nvdisp_disp_signal_option_r());
tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_general_act_req_enable_f(),
nvdisp_cmd_state_ctrl_r());
val = tegra_dc_readl(dc, nvdisp_cmd_int_enable_r());
tegra_dc_writel(dc, val | V_PULSE2_INT, nvdisp_cmd_int_enable_r());
tegra_dc_put(dc);
return 0;
}
/* _tegra_nvdisp_disable_vpulse2_int() - disables vpulse2 interrupt
* @dc : pointer to struct tegra_dc of the current head.
*
* Return : -ENODEV if dc isn't present or disabled. 0 if
* successful.
*/
static int _tegra_nvdisp_disable_vpulse2_int(struct tegra_dc *dc)
{
u32 val;
if (!dc || !dc->enabled)
return -ENODEV;
tegra_dc_get(dc);
val = tegra_dc_readl(dc, nvdisp_disp_signal_option_r());
tegra_dc_writel(dc, val & ~nvdisp_disp_signal_option_v_pulse2_enable_f()
, nvdisp_disp_signal_option_r());
tegra_dc_writel(dc, nvdisp_cmd_state_ctrl_general_act_req_enable_f(),
nvdisp_cmd_state_ctrl_r());
val = tegra_dc_readl(dc, nvdisp_cmd_int_enable_r());
tegra_dc_writel(dc, val & ~V_PULSE2_INT, nvdisp_cmd_int_enable_r());
tegra_dc_put(dc);
return 0;
}
/*
* tegra_nvdisp_set_msrmnt_mode() - enables latency measurement
* mode in dc.
* @dc : pointer to struct tegra_dc of the current head.
* @enable : tells whether to enable/disable measurement mode.
*
* Based on the enable value, configures and resets the vpulse2
* interrupt which is used for reading the timestamps.
*
* Return : void
*/
void tegra_nvdisp_set_msrmnt_mode(struct tegra_dc *dc, bool enable)
{
int ret = 0;
if (!dc)
return;
mutex_lock(&dc->msrmnt_info.lock);
if (enable && !dc->msrmnt_info.enabled) {
dc->msrmnt_info.line_num = dc->mode_metadata.vtotal_lines/2;
ret = _tegra_nvdisp_enable_vpulse2_int(dc,
dc->msrmnt_info.line_num);
if (ret) {
mutex_unlock(&dc->msrmnt_info.lock);
return;
}
/*
* Wait for the frame_end for v_pulse2 programming above to
* take effect.
*/
_tegra_dc_wait_for_frame_end(dc,
div_s64(dc->frametime_ns, 1000000ll) * 2);
set_bit(V_PULSE2_LATENCY_MSRMNT, &dc->vpulse2_ref_count);
tegra_dc_get(dc);
tegra_dc_unmask_interrupt(dc, V_PULSE2_INT);
tegra_dc_put(dc);
} else if (!enable && dc->msrmnt_info.enabled) {
ret = _tegra_nvdisp_disable_vpulse2_int(dc);
if (ret) {
mutex_unlock(&dc->msrmnt_info.lock);
return;
}
/*
* Wait for the frame_end for v_pulse2 programming above to
* take effect.
*/
_tegra_dc_wait_for_frame_end(dc,
div_s64(dc->frametime_ns, 1000000ll) * 2);
clear_bit(V_PULSE2_LATENCY_MSRMNT, &dc->vpulse2_ref_count);
tegra_dc_get(dc);
tegra_dc_mask_interrupt(dc, V_PULSE2_INT);
tegra_dc_put(dc);
}
dc->msrmnt_info.enabled = enable;
mutex_unlock(&dc->msrmnt_info.lock);
}
#ifdef CONFIG_DEBUG_FS
inline struct dentry *tegra_nvdisp_create_imp_lock_debugfs(struct tegra_dc *dc)
{
return debugfs_create_bool("imp_lock_mode", 0644, dc->debug_common_dir,
&g_imp.lock_mode_enabled);
}
#endif