/* * arch/arm/mach-tegra/latency_allowance.c * * Copyright (C) 2011-2020, NVIDIA CORPORATION. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "la_priv.h" #define TEST_LA_CODE 0 /* Bug 995270 */ #define HACK_LA_FIFO 1 static int default_set_la(enum tegra_la_id id, unsigned int bw_mbps); static struct la_chip_specific cs; module_param_named(disable_la, cs.disable_la, bool, S_IRUGO | S_IWUSR); module_param_named(disable_ptsa, cs.disable_ptsa, bool, S_IRUGO | S_IWUSR); module_param_named(disable_disp_ptsa, cs.disable_disp_ptsa, bool, S_IRUGO | S_IWUSR); module_param_named(disable_bbc_ptsa, cs.disable_bbc_ptsa, bool, S_IRUGO | S_IWUSR); #ifdef CONFIG_DEBUG_FS static int la_ptsa_debugfs_init(void); #endif static void init_chip_specific(void) { int cid; if (!tegra_platform_is_silicon()) return; cs.set_init_la = default_set_la; memset(&cs.id_to_index[0], 0xFF, sizeof(cs.id_to_index)); spin_lock_init(&cs.lock); cid = tegra_get_chip_id(); switch (cid) { case TEGRA194: tegra_la_get_t19x_specific(&cs); break; case TEGRA186: tegra_la_get_t18x_specific(&cs); break; case TEGRA210: tegra_la_get_t21x_specific(&cs); break; default: cs.set_init_la = NULL; } #ifdef CONFIG_DEBUG_FS la_ptsa_debugfs_init(); #endif } struct la_to_dc_params tegra_get_la_to_dc_params(void) { return cs.la_params; } static void set_la(struct la_client_info *ci, int la) { unsigned long reg_read; unsigned long reg_write; int idx = cs.id_to_index[ci->id]; spin_lock(&cs.lock); reg_read = mc_readl(ci->reg_addr); reg_write = (reg_read & ~ci->mask) | (la << ci->shift); mc_writel(reg_write, ci->reg_addr); cs.scaling_info[idx].la_set = la; ci->la_set = la; la_debug("name=%s, reg=0x%x, read=0x%x, write=0x%x\n", ci->name, (u32)ci->reg_addr, (u32)reg_read, (u32)reg_write); spin_unlock(&cs.lock); } static int default_set_la(enum tegra_la_id id, unsigned int bw_mbps) { int ideal_la; int la_to_set; unsigned int fifo_size_in_atoms; int bytes_per_atom = cs.atom_size; const int fifo_scale = 4; /* 25% of the FIFO */ struct la_client_info *ci; int idx = cs.id_to_index[id]; if (!tegra_platform_is_silicon()) return 0; VALIDATE_ID(id, &cs); VALIDATE_BW(bw_mbps); ci = &cs.la_info_array[idx]; fifo_size_in_atoms = ci->fifo_size_in_atoms; #ifdef CONFIG_TEGRA_MC_PTSA if (id >= TEGRA_LA_DISPLAY_0A && id <= TEGRA_LA_DISPLAY_HCB) { cs.disp_bw_array[id - TEGRA_LA_DISPLAY_0A] = bw_mbps; if (cs.update_display_ptsa_rate) cs.update_display_ptsa_rate(cs.disp_bw_array); } #endif #if HACK_LA_FIFO /* pretend that our FIFO is only as deep as the lowest fullness * we expect to see */ if (id >= ID(DISPLAY_0A) && id <= ID(DISPLAY_HCB)) fifo_size_in_atoms /= fifo_scale; #endif if (bw_mbps == 0) { la_to_set = cs.la_max_value; } else { ideal_la = (fifo_size_in_atoms * bytes_per_atom * 1000) / (bw_mbps * cs.ns_per_tick); la_to_set = ideal_la - (ci->expiration_in_ns / cs.ns_per_tick) - 1; } la_debug("\n%s:id=%d,idx=%d, bw=%dmbps, la_to_set=%d\n", __func__, id, idx, bw_mbps, la_to_set); la_to_set = (la_to_set < 0) ? 0 : la_to_set; cs.scaling_info[idx].actual_la_to_set = la_to_set; la_to_set = (la_to_set > cs.la_max_value) ? cs.la_max_value : la_to_set; set_la(ci, la_to_set); return 0; } static void program_scaled_la(struct la_client_info *ci, int la) { if (tegra_get_chip_id() == TEGRA210) program_scaled_la_t21x(ci, la); } void program_la(struct la_client_info *ci, int la) { u32 reg_read; u32 reg_write; if (la > cs.la_max_value) { pr_err("la > cs.la_max_value\n"); WARN_ON(1); return; } spin_lock(&cs.lock); reg_read = mc_readl(ci->reg_addr); reg_write = (reg_read & ~ci->mask) | (la << ci->shift); mc_writel(reg_write, ci->reg_addr); ci->la_set = la; la_debug("name=%s, reg_addr=0x%x, read=0x%x, write=0x%x\n", ci->name, (u32)(uintptr_t)ci->reg_addr, (u32)reg_read, (u32)reg_write); program_scaled_la(ci, la); spin_unlock(&cs.lock); } int la_suspend(void) { int i = 0; struct la_client_info *ci = NULL; /* stashing LA and PTSA from registers is necessary * in order to get latest values programmed by DVFS. */ for (i = 0; i < cs.la_info_array_size; i++) { ci = &cs.la_info_array[i]; ci->la_set = (mc_readl(ci->reg_addr) & ci->mask) >> ci->shift; } cs.save_ptsa(); if (cs.save_non_la_ptsa) cs.save_non_la_ptsa(); return 0; } void la_resume(void) { int i; for (i = 0; i < cs.la_info_array_size; i++) { if (cs.la_info_array[i].la_set) program_la(&cs.la_info_array[i], cs.la_info_array[i].la_set); } cs.program_ptsa(); if (cs.program_non_la_ptsa) cs.program_non_la_ptsa(); } int tegra_set_disp_latency_allowance(enum tegra_la_id id, unsigned long emc_freq_hz, unsigned int bw_mbps, struct dc_to_la_params disp_params) { if (cs.set_disp_la) return cs.set_disp_la(id, emc_freq_hz, bw_mbps, disp_params); else if (cs.set_dynamic_la) return cs.set_dynamic_la(id, bw_mbps); return 0; } /* * Check if the passed bandwidth is possible. * * Returns zero if there is a possible LA value that can satifsy @bw_mbps at * @emc_freq_hz. If no function has been defined for the active chip then this * this function returns true (i.e 0). */ int tegra_check_disp_latency_allowance(enum tegra_la_id id, unsigned long emc_freq_hz, unsigned int bw_mbps, struct dc_to_la_params disp_params) { if (cs.check_disp_la) return cs.check_disp_la(id, emc_freq_hz, bw_mbps, disp_params); return 0; } /* Sets latency allowance based on clients memory bandwitdh requirement. * Bandwidth passed is in mega bytes per second. */ int tegra_set_latency_allowance(enum tegra_la_id id, unsigned int bw_mbps) { if (cs.set_dynamic_la) return cs.set_dynamic_la(id, bw_mbps); return 0; } EXPORT_SYMBOL(tegra_set_latency_allowance); int tegra_set_camera_ptsa(enum tegra_la_id id, unsigned int bw_mbps, int is_hiso) { if (cs.update_camera_ptsa_rate) return cs.update_camera_ptsa_rate(id, bw_mbps, is_hiso); else if (cs.set_dynamic_la) return cs.set_dynamic_la(id, bw_mbps); return 0; } /* Thresholds for scaling are specified in % of fifo freeness. * If threshold_low is specified as 20%, it means when the fifo free * between 0 to 20%, use la as programmed_la. * If threshold_mid is specified as 50%, it means when the fifo free * between 20 to 50%, use la as programmed_la/2 . * If threshold_high is specified as 80%, it means when the fifo free * between 50 to 80%, use la as programmed_la/4. * When the fifo is free between 80 to 100%, use la as 0(highest priority). */ int tegra_enable_latency_scaling(enum tegra_la_id id, unsigned int threshold_low, unsigned int threshold_mid, unsigned int threshold_high) { if (cs.enable_la_scaling) return cs.enable_la_scaling(id, threshold_low, threshold_mid, threshold_high); return 0; } void tegra_disable_latency_scaling(enum tegra_la_id id) { if (cs.disable_la_scaling) { cs.disable_la_scaling(id); } } void tegra_latency_allowance_update_tick_length(unsigned int new_ns_per_tick) { int i = 0; int la; unsigned long reg_read; unsigned long reg_write; unsigned long scale_factor = new_ns_per_tick / cs.ns_per_tick; if (scale_factor > 1) { spin_lock(&cs.lock); cs.ns_per_tick = new_ns_per_tick; for (i = 0; i < cs.la_info_array_size - 1; i++) { reg_read = mc_readl(cs.la_info_array[i].reg_addr); la = ((reg_read & cs.la_info_array[i].mask) >> cs.la_info_array[i].shift) / scale_factor; reg_write = (reg_read & ~cs.la_info_array[i].mask) | (la << cs.la_info_array[i].shift); mc_writel(reg_write, cs.la_info_array[i].reg_addr); cs.scaling_info[i].la_set = la; } spin_unlock(&cs.lock); } } static int la_regs_show(struct seq_file *s, void *unused) { int i; unsigned long la; /* iterate the list, but don't print MAX_ID */ for (i = 0; i < cs.la_info_array_size - 1; i++) { la = (mc_readl(cs.la_info_array[i].reg_addr) & cs.la_info_array[i].mask) >> cs.la_info_array[i].shift; seq_printf(s, "%-16s: %4lu\n", cs.la_info_array[i].name, la); } return 0; } static int dbg_la_regs_open(struct inode *inode, struct file *file) { return single_open(file, la_regs_show, inode->i_private); } static const struct file_operations regs_fops = { .open = dbg_la_regs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int __init tegra_latency_allowance_debugfs_init(void) { if (cs.latency_debug_dir) return 0; cs.latency_debug_dir = debugfs_create_dir("tegra_latency", NULL); debugfs_create_file("la_info", S_IRUGO, cs.latency_debug_dir, NULL, ®s_fops); return 0; } static int tegra_la_suspend(void) { if (cs.suspend) return cs.suspend(); return 0; } static void tegra_la_resume(void) { int i; if (cs.resume) { cs.resume(); return; } for (i = 0; i < cs.la_info_array_size; i++) { if (cs.la_info_array[i].la_set) set_la(&cs.la_info_array[i], cs.la_info_array[i].la_set); } if (cs.init_ptsa) cs.init_ptsa(); } static struct syscore_ops tegra_la_syscore_ops = { .suspend = tegra_la_suspend, .resume = tegra_la_resume, }; static __init int tegra_la_syscore_init(void) { register_syscore_ops(&tegra_la_syscore_ops); return 0; } static int __init tegra_latency_allowance_init(void) { unsigned int i; int ret = 0; init_chip_specific(); for (i = 0; i < cs.la_info_array_size; i++) cs.id_to_index[cs.la_info_array[i].id] = i; for (i = 0; i < cs.la_info_array_size; i++) { if (cs.set_init_la) { ret = cs.set_init_la(cs.la_info_array[i].id, 0); if (ret < 0) { if (cs.la_cleanup) cs.la_cleanup(); return -1; } } else if (cs.la_info_array[i].init_la) { set_la(&cs.la_info_array[i], cs.la_info_array[i].init_la); } } if (cs.init_ptsa) cs.init_ptsa(); pr_info("la/ptsa driver initialized.\n"); return 0; } late_initcall(tegra_latency_allowance_debugfs_init); subsys_initcall(tegra_la_syscore_init); /* Must happen after MC init which is done by device tree. */ fs_initcall(tegra_latency_allowance_init); static void __exit tegra_latency_allowance_exit(void) { if (cs.la_cleanup) cs.la_cleanup(); } module_exit(tegra_latency_allowance_exit); /* Must be called after LA/PTSA init */ void mc_pcie_init(void) { if (cs.mc_pcie_init) cs.mc_pcie_init(); } EXPORT_SYMBOL(mc_pcie_init); #ifdef CONFIG_DEBUG_FS #include #include static unsigned long debugfs_display_emc_freq_hz; static unsigned int debugfs_display_bw_mbps; static int debugfs_camera_la_id; static unsigned int debugfs_camera_bw_mbps; static int debugfs_camera_is_hiso; static int debugfs_other_la_id; static unsigned int debugfs_other_bw_mbps; static int display_emc_freq_hz_get(void *data, u64 *val) { *val = (u64) debugfs_display_emc_freq_hz; return 0; } static int display_emc_freq_hz_set(void *data, u64 val) { debugfs_display_emc_freq_hz = val; return 0; } static int display_bw_mbps_get(void *data, u64 *val) { *val = (u64) debugfs_display_bw_mbps; return 0; } static int display_bw_mbps_set(void *data, u64 val) { debugfs_display_bw_mbps = (u32) val; return 0; } static int display_set_la_ptsa_set(void *data, u64 val) { struct dc_to_la_params disp_params = {0}; if (cs.set_disp_la(ID(NVDISPLAYR), debugfs_display_emc_freq_hz, debugfs_display_bw_mbps, disp_params)) return -1; return 0; } static int camera_la_id_get(void *data, u64 *val) { *val = (u64) debugfs_camera_la_id; return 0; } static int camera_la_id_set(void *data, u64 val) { debugfs_camera_la_id = (u32) val; return 0; } static int camera_bw_mbps_get(void *data, u64 *val) { *val = (u64) debugfs_camera_bw_mbps; return 0; } static int camera_bw_mbps_set(void *data, u64 val) { debugfs_camera_bw_mbps = (u32) val; return 0; } static int camera_is_hiso_get(void *data, u64 *val) { *val = (u64) debugfs_camera_is_hiso; return 0; } static int camera_is_hiso_set(void *data, u64 val) { debugfs_camera_is_hiso = (u32) val; return 0; } static int camera_set_ptsa_set(void *data, u64 val) { if (cs.update_camera_ptsa_rate(debugfs_camera_la_id, debugfs_camera_bw_mbps, debugfs_camera_is_hiso)) return -1; return 0; } static int other_la_id_get(void *data, u64 *val) { *val = (u64) debugfs_other_la_id; return 0; } static int other_la_id_set(void *data, u64 val) { debugfs_other_la_id = (u32) val; return 0; } static int other_bw_mbps_get(void *data, u64 *val) { *val = (u64) debugfs_other_bw_mbps; return 0; } static int other_bw_mbps_set(void *data, u64 val) { debugfs_other_bw_mbps = (u32) val; return 0; } static int other_set_la_ptsa_set(void *data, u64 val) { if (cs.set_dynamic_la(debugfs_other_la_id, debugfs_other_bw_mbps)) return -1; return 0; } DEFINE_SIMPLE_ATTRIBUTE(display_emc_freq_hz_fops, display_emc_freq_hz_get, display_emc_freq_hz_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(display_bw_mbps_fops, display_bw_mbps_get, display_bw_mbps_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(display_set_la_ptsa_fops, NULL, display_set_la_ptsa_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(camera_la_id_fops, camera_la_id_get, camera_la_id_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(camera_bw_mbps_fops, camera_bw_mbps_get, camera_bw_mbps_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(camera_is_hiso_fops, camera_is_hiso_get, camera_is_hiso_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(camera_set_ptsa_fops, NULL,\ camera_set_ptsa_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(other_la_id_fops, other_la_id_get, other_la_id_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(other_bw_mbps_fops, other_bw_mbps_get, other_bw_mbps_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(other_set_la_ptsa_fops, NULL, other_set_la_ptsa_set, "%llu\n"); static int la_ptsa_debugfs_init(void) { struct dentry *la_ptsa_debugfs_root; struct dentry *display_dir; struct dentry *camera_dir; struct dentry *other_dir; la_ptsa_debugfs_root = debugfs_create_dir("tegra_la_ptsa", NULL); if (!la_ptsa_debugfs_root) { pr_err("%s: Couldn't create the LA\\PTSA root debugfs node.\n", __func__); return -1; } /* Display nodes*/ display_dir = debugfs_create_dir("display", la_ptsa_debugfs_root); if (!display_dir) { pr_err("%s: Couldn't create the \"display\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("emc_freq_hz", S_IRUGO | S_IWUSR, display_dir, NULL, &display_emc_freq_hz_fops)) { pr_err("%s: Couldn't create the display \"emc_freq_hz\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("bw_mbps", S_IRUGO | S_IWUSR, display_dir, NULL, &display_bw_mbps_fops)) { pr_err("%s: Couldn't create the display \"bw_mbps\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("set_la_ptsa", S_IWUSR, display_dir, NULL, &display_set_la_ptsa_fops)) { pr_err("%s: Couldn't create the display \"set_la_ptsa\" debugfs node.\n", __func__); return -1; } /* Camera nodes*/ camera_dir = debugfs_create_dir("camera", la_ptsa_debugfs_root); if (!camera_dir) { pr_err("%s: Couldn't create the \"camera\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("la_id", S_IRUGO | S_IWUSR, camera_dir, NULL, &camera_la_id_fops)) { pr_err("%s: Couldn't create the camera \"la_id\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("bw_mbps", S_IRUGO | S_IWUSR, camera_dir, NULL, &camera_bw_mbps_fops)) { pr_err("%s: Couldn't create the camera \"bw_mbps\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("is_hiso", S_IRUGO | S_IWUSR, camera_dir, NULL, &camera_is_hiso_fops)) { pr_err("%s: Couldn't create the camera \"is_hiso\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("set_ptsa", S_IWUSR, camera_dir, NULL, &camera_set_ptsa_fops)) { pr_err("%s: Couldn't create the camera \"set_ptsa\" debugfs node.\n", __func__); return -1; } /* Non-display nodes */ other_dir = debugfs_create_dir("other", la_ptsa_debugfs_root); if (!other_dir) { pr_err("%s: Couldn't create the \"other\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("la_id", S_IRUGO | S_IWUSR, other_dir, NULL, &other_la_id_fops)) { pr_err("%s: Couldn't create the other \"la_id\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("bw_mbps", S_IRUGO | S_IWUSR, other_dir, NULL, &other_bw_mbps_fops)) { pr_err("%s: Couldn't create the other \"bw_mbps\" debugfs node.\n", __func__); return -1; } if (!debugfs_create_file("set_la_ptsa", S_IWUSR, other_dir, NULL, &other_set_la_ptsa_fops)) { pr_err("%s: Couldn't create the other \"set_la_ptsa\" debugfs node.\n", __func__); return -1; } return 0; } #endif // "CONFIG_DEBUG_FS" #if TEST_LA_CODE #define PRINT_ID_IDX_MAPPING 0 static int __init test_la(void) { int i; int err; enum tegra_la_id id = 0; int repeat_count = 5; #if PRINT_ID_IDX_MAPPING for (i = 0; i < ID(MAX_ID); i++) pr_info("ID=0x%x, Idx=0x%x", i, cs.id_to_index[i]); #endif do { for (id = 0; id < TEGRA_LA_MAX_ID; id++) { err = tegra_set_latency_allowance(id, 200); if (err) la_debug("\n***tegra_set_latency_allowance," " err=%d", err); } for (id = 0; id < TEGRA_LA_MAX_ID; id++) { if (id >= ID(DISPLAY_0AB) && id <= ID(DISPLAY_HCB)) continue; if (id >= ID(VI_WSB) && id <= ID(VI_WY)) continue; err = tegra_enable_latency_scaling(id, 20, 50, 80); if (err) la_debug("\n***tegra_enable_latency_scaling," " err=%d", err); } la_debug("la_scaling_enable_count =%d", cs.la_scaling_enable_count); for (id = 0; id < TEGRA_LA_MAX_ID; id++) { if (id >= ID(DISPLAY_0AB) && id <= ID(DISPLAY_HCB)) continue; if (id >= ID(VI_WSB) && id <= ID(VI_WY)) continue; tegra_disable_latency_scaling(id); } la_debug("la_scaling_enable_count=%d", cs.la_scaling_enable_count); } while (--repeat_count); return 0; } late_initcall(test_la); #endif