/** * Copyright (c) 2015-2019, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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 #define CREATE_TRACE_POINTS #include u8 bwmgr_dram_efficiency; u8 bwmgr_dram_num_channels; /* flag to determine supported memory and channel configuration */ u8 bwmgr_dram_config_supported; u32 *bwmgr_dram_iso_eff_table; u32 *bwmgr_dram_noniso_eff_table; u32 *bwmgr_max_nvdis_bw_reqd; u32 *bwmgr_max_vi_bw_reqd; int *bwmgr_slope; u32 *bwmgr_vi_bw_reqd_offset; int bwmgr_iso_bw_percentage; enum bwmgr_dram_types bwmgr_dram_type; int emc_to_dram_freq_factor; #define IS_HANDLE_VALID(x) ((x >= bwmgr.bwmgr_client) && \ (x < bwmgr.bwmgr_client + TEGRA_BWMGR_CLIENT_COUNT)) struct tegra_bwmgr_client { unsigned long bw; unsigned long iso_bw; unsigned long cap; unsigned long iso_cap; unsigned long floor; int refcount; }; /* TODO: Manage client state in a dynamic list */ static struct { struct tegra_bwmgr_client bwmgr_client[TEGRA_BWMGR_CLIENT_COUNT]; struct mutex lock; unsigned long emc_min_rate; unsigned long emc_max_rate; struct clk *emc_clk; struct task_struct *task; bool status; struct bwmgr_ops *ops; bool override; } bwmgr; static struct dram_refresh_alrt { unsigned long cur_state; u32 max_cooling_state; struct thermal_cooling_device *cdev; } *galert_data; static bool clk_update_disabled; static struct { unsigned long bw; unsigned long iso_bw; unsigned long non_iso_cap; unsigned long iso_cap; unsigned long floor; unsigned long total_bw_aftr_eff; unsigned long iso_bw_aftr_eff; unsigned long calc_freq; unsigned long req_freq; } debug_info; static void bwmgr_debugfs_init(void); /* keep in sync with tegra_bwmgr_client_id */ static const char * const tegra_bwmgr_client_names[] = { "cpu_cluster_0", "cpu_cluster_1", "cpu_cluster_2", "cpu_cluster_3", "disp_0", "disp_1", "disp_2", "disp1_la_emc", "disp2_la_emc", "usbd", "xhci", "sdmmc1", "sdmmc2", "sdmmc3", "sdmmc4", "mon", "gpu", "msenc", "nvenc1", "nvjpg", "nvdec", "nvdec1", "tsec", "tsecb", "vi", "ispa", "ispb", "camera", "camera_non_iso", "camrtc", "isomgr", "thermal", "vic", "adsp", "adma", "pcie", "pcie_1", "pcie_2", "pcie_3", "pcie_4", "pcie_5", "bbc_0", "eqos", "se0", "se1", "se2", "se3", "se4", "pmqos", "nvpmodel", "debug", "nvdla0", "nvdla1", "null", }; #if defined(CONFIG_DEBUG_FS) && defined(CONFIG_TRACEPOINTS) static const char *bwmgr_req_to_name(enum tegra_bwmgr_request_type req) { /* Keep in sync with enum tegra_bwmgr_request_type. */ switch (req) { case TEGRA_BWMGR_SET_EMC_FLOOR: return "TEGRA_BWMGR_SET_EMC_FLOOR"; case TEGRA_BWMGR_SET_EMC_CAP: return "TEGRA_BWMGR_SET_EMC_CAP"; case TEGRA_BWMGR_SET_EMC_ISO_CAP: return "TEGRA_BWMGR_SET_EMC_ISO_CAP"; case TEGRA_BWMGR_SET_EMC_SHARED_BW: return "TEGRA_BWMGR_SET_EMC_SHARED_BW"; case TEGRA_BWMGR_SET_EMC_SHARED_BW_ISO: return "TEGRA_BWMGR_SET_EMC_SHARED_BW_ISO"; default: return "INVALID_REQUEST"; } } #endif /* defined(CONFIG_DEBUG_FS) && defined(CONFIG_TRACEPOINTS) */ static inline bool bwmgr_lock(void) { /* disallow rentrance, avoid deadlock */ if (unlikely(bwmgr.task == current)) { pr_err("bwmgr: %s deadlock ?\n", __func__); dump_stack(); return false; } mutex_lock(&bwmgr.lock); bwmgr.task = current; return true; } static inline bool bwmgr_unlock(void) { /* detect mismatched calls */ if (unlikely(bwmgr.task != current)) { pr_err("bwmgr: %s mismatch ?\n", __func__); dump_stack(); return false; } bwmgr.task = NULL; mutex_unlock(&bwmgr.lock); return true; } /* call with bwmgr lock held except during init*/ static void purge_client(struct tegra_bwmgr_client *handle) { handle->bw = 0; handle->iso_bw = 0; handle->cap = bwmgr.emc_max_rate; handle->iso_cap = bwmgr.emc_max_rate; handle->floor = 0; handle->refcount = 0; } static unsigned long tegra_bwmgr_apply_efficiency( unsigned long total_bw, unsigned long iso_bw, unsigned long max_rate, u64 usage_flags, unsigned long *iso_bw_min, unsigned long iso_bw_nvdis, unsigned long iso_bw_vi) { return bwmgr.ops->bwmgr_apply_efficiency(total_bw, iso_bw, max_rate, usage_flags, iso_bw_min, iso_bw_nvdis, iso_bw_vi); } /* call with bwmgr lock held */ static int bwmgr_update_clk(void) { int i; unsigned long bw = 0; unsigned long iso_bw = 0; // iso_bw_guarantee unsigned long iso_bw_nvdis = 0; //DISP0 + DISP1 + DISP2 unsigned long iso_bw_vi = 0; //CAMERA unsigned long iso_bw_other_clients = 0; //Other ISO clients unsigned long non_iso_cap = bwmgr.emc_max_rate; unsigned long iso_cap = bwmgr.emc_max_rate; unsigned long floor = 0; unsigned long iso_bw_min; u64 iso_client_flags = 0; int ret = 0; /* sizeof(iso_client_flags) */ BUILD_BUG_ON(TEGRA_BWMGR_CLIENT_COUNT > 64); /* check that lock is held */ if (unlikely(bwmgr.task != current)) { pr_err("bwmgr: %s called without lock\n", __func__); return -EINVAL; } if (bwmgr.override) return 0; for (i = 0; i < TEGRA_BWMGR_CLIENT_COUNT; i++) { bw += bwmgr.bwmgr_client[i].bw; bw = min(bw, bwmgr.emc_max_rate); if (bwmgr.bwmgr_client[i].iso_bw > 0) { iso_client_flags |= BIT(i); if ((i == TEGRA_BWMGR_CLIENT_DISP0) || (i == TEGRA_BWMGR_CLIENT_DISP1) || (i == TEGRA_BWMGR_CLIENT_DISP2)) { iso_bw_nvdis += bwmgr.bwmgr_client[i].iso_bw; iso_bw_nvdis = min(iso_bw_nvdis, bwmgr.emc_max_rate); } else if (i == TEGRA_BWMGR_CLIENT_CAMERA) { iso_bw_vi += bwmgr.bwmgr_client[i].iso_bw; iso_bw_vi = min(iso_bw_vi, bwmgr.emc_max_rate); } else { iso_bw_other_clients += bwmgr.bwmgr_client[i].iso_bw; iso_bw_other_clients = min(iso_bw_other_clients, bwmgr.emc_max_rate); } iso_bw = iso_bw_nvdis + iso_bw_vi + iso_bw_other_clients; iso_bw = min(iso_bw, bwmgr.emc_max_rate); } non_iso_cap = min(non_iso_cap, bwmgr.bwmgr_client[i].cap); iso_cap = min(iso_cap, bwmgr.bwmgr_client[i].iso_cap); floor = max(floor, bwmgr.bwmgr_client[i].floor); } debug_info.bw = bw; debug_info.iso_bw = iso_bw; debug_info.floor = floor; debug_info.iso_cap = iso_cap; debug_info.non_iso_cap = non_iso_cap; bw += iso_bw; bw = tegra_bwmgr_apply_efficiency( bw, iso_bw, bwmgr.emc_max_rate, iso_client_flags, &iso_bw_min, iso_bw_nvdis, iso_bw_vi); debug_info.total_bw_aftr_eff = bw; debug_info.iso_bw_aftr_eff = iso_bw_min; floor = min(floor, bwmgr.emc_max_rate); bw = max(bw, floor); bw = min(bw, min(iso_cap, max(non_iso_cap, iso_bw_min))); debug_info.calc_freq = bw; debug_info.req_freq = bw; ret = clk_set_rate(bwmgr.emc_clk, bw); if (ret) pr_err ("bwmgr: clk_set_rate failed for freq %lu Hz with errno %d\n", bw, ret); return ret; } struct tegra_bwmgr_client *tegra_bwmgr_register( enum tegra_bwmgr_client_id client) { if (!bwmgr_dram_config_supported) { pr_err("bwmgr: ddr config not supported\n"); WARN_ON(true); return ERR_PTR(-EINVAL); } if ((client >= TEGRA_BWMGR_CLIENT_COUNT) || (client < 0)) { pr_err("bwmgr: invalid client id %d tried to register", client); WARN_ON(true); return ERR_PTR(-EINVAL); } if (!bwmgr_lock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[client]); return ERR_PTR(-EINVAL); } (bwmgr.bwmgr_client + client)->refcount++; if (!bwmgr_unlock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[client]); return ERR_PTR(-EINVAL); } return (bwmgr.bwmgr_client + client); } EXPORT_SYMBOL_GPL(tegra_bwmgr_register); void tegra_bwmgr_unregister(struct tegra_bwmgr_client *handle) { if (!IS_HANDLE_VALID(handle)) { WARN_ON(true); return; } if (!bwmgr_lock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[handle - bwmgr.bwmgr_client]); return; } handle->refcount--; if (handle->refcount <= 0) { if (handle->refcount < 0) { pr_err("bwmgr: Mismatched unregister call, client %ld\n", handle - bwmgr.bwmgr_client); WARN_ON(true); } purge_client(handle); } if (!bwmgr_unlock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[handle - bwmgr.bwmgr_client]); return; } } EXPORT_SYMBOL_GPL(tegra_bwmgr_unregister); u8 tegra_bwmgr_get_dram_num_channels(void) { return bwmgr_dram_num_channels; } EXPORT_SYMBOL_GPL(tegra_bwmgr_get_dram_num_channels); unsigned long tegra_bwmgr_get_max_emc_rate(void) { return bwmgr.emc_max_rate; } EXPORT_SYMBOL_GPL(tegra_bwmgr_get_max_emc_rate); /* Returns the ratio between dram and emc freq based on the type of dram */ int bwmgr_get_emc_to_dram_freq_factor(void) { return emc_to_dram_freq_factor; } EXPORT_SYMBOL_GPL(bwmgr_get_emc_to_dram_freq_factor); /* Returns the actual emc frequency calculated using the dram * frequency and emc_to_dram conversion factor */ unsigned long tegra_bwmgr_get_core_emc_rate(void) { return (unsigned long)(tegra_bwmgr_get_emc_rate() / bwmgr_get_emc_to_dram_freq_factor()); } EXPORT_SYMBOL_GPL(tegra_bwmgr_get_core_emc_rate); unsigned long tegra_bwmgr_round_rate(unsigned long bw) { if (bwmgr.emc_clk) return clk_round_rate(bwmgr.emc_clk, bw); return 0; } EXPORT_SYMBOL_GPL(tegra_bwmgr_round_rate); int tegra_bwmgr_set_emc(struct tegra_bwmgr_client *handle, unsigned long val, enum tegra_bwmgr_request_type req) { int ret = 0; bool update_clk = false; if (!bwmgr.emc_clk) return 0; if (!bwmgr.status) return 0; if (!bwmgr_dram_config_supported) { pr_err("bwmgr: ddr config not supported\n"); WARN_ON(true); return -EINVAL; } if (!IS_HANDLE_VALID(handle)) { pr_err("bwmgr: client sent bad handle %p\n", handle); WARN_ON(true); return -EINVAL; } if (req >= TEGRA_BWMGR_SET_EMC_REQ_COUNT) { pr_err("bwmgr: client %ld sent bad request type %d\n", handle - bwmgr.bwmgr_client, req); WARN_ON(true); return -EINVAL; } if (!bwmgr_lock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[handle - bwmgr.bwmgr_client]); return -EINVAL; } #ifdef CONFIG_TRACEPOINTS trace_tegra_bwmgr_set_emc( tegra_bwmgr_client_names[handle - bwmgr.bwmgr_client], val, bwmgr_req_to_name(req)); #endif /* CONFIG_TRACEPOINTS */ switch (req) { case TEGRA_BWMGR_SET_EMC_FLOOR: if (handle->floor != val) { handle->floor = val; update_clk = true; } break; case TEGRA_BWMGR_SET_EMC_CAP: if (val == 0) val = bwmgr.emc_max_rate; if (handle->cap != val) { handle->cap = val; update_clk = true; } break; case TEGRA_BWMGR_SET_EMC_ISO_CAP: if (val == 0) val = bwmgr.emc_max_rate; if (handle->iso_cap != val) { handle->iso_cap = val; update_clk = true; } break; case TEGRA_BWMGR_SET_EMC_SHARED_BW: if (handle->bw != val) { handle->bw = val; update_clk = true; } break; case TEGRA_BWMGR_SET_EMC_SHARED_BW_ISO: if (handle->iso_bw != val) { handle->iso_bw = val; update_clk = true; } break; default: WARN_ON(true); if (!bwmgr_unlock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[handle - bwmgr.bwmgr_client]); return -EINVAL; } return -EINVAL; } if (update_clk && !clk_update_disabled) ret = bwmgr_update_clk(); if (!bwmgr_unlock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[handle - bwmgr.bwmgr_client]); return -EINVAL; } return ret; } EXPORT_SYMBOL_GPL(tegra_bwmgr_set_emc); int tegra_bwmgr_get_client_info(struct tegra_bwmgr_client *handle, unsigned long *out_val, enum tegra_bwmgr_request_type req) { if (!bwmgr.emc_clk) return 0; if (!bwmgr.status) return 0; if (!bwmgr_dram_config_supported) { pr_err("bwmgr: ddr config not supported\n"); WARN_ON(true); return -EINVAL; } if (!IS_HANDLE_VALID(handle)) { pr_err("bwmgr: client sent bad handle %p\n", handle); WARN_ON(true); return -EINVAL; } if (req >= TEGRA_BWMGR_SET_EMC_REQ_COUNT) { pr_err("bwmgr: client %ld sent bad request type %d\n", handle - bwmgr.bwmgr_client, req); WARN_ON(true); return -EINVAL; } if (!bwmgr_lock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[handle - bwmgr.bwmgr_client]); return -EINVAL; } switch (req) { case TEGRA_BWMGR_SET_EMC_FLOOR: *out_val = handle->floor; break; case TEGRA_BWMGR_SET_EMC_CAP: *out_val = handle->cap; break; case TEGRA_BWMGR_SET_EMC_ISO_CAP: *out_val = handle->iso_cap; break; case TEGRA_BWMGR_SET_EMC_SHARED_BW: *out_val = handle->bw; break; case TEGRA_BWMGR_SET_EMC_SHARED_BW_ISO: *out_val = handle->iso_bw; break; default: WARN_ON(true); if (!bwmgr_unlock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[handle - bwmgr.bwmgr_client]); return -EINVAL; } return -EINVAL; } if (!bwmgr_unlock()) { pr_err("bwmgr: %s failed for client %s\n", __func__, tegra_bwmgr_client_names[handle - bwmgr.bwmgr_client]); return -EINVAL; } return 0; } EXPORT_SYMBOL_GPL(tegra_bwmgr_get_client_info); int tegra_bwmgr_notifier_register(struct notifier_block *nb) { if (!nb) return -EINVAL; if (bwmgr.emc_clk) return clk_notifier_register(bwmgr.emc_clk, nb); else return -ENODEV; } EXPORT_SYMBOL_GPL(tegra_bwmgr_notifier_register); int tegra_bwmgr_notifier_unregister(struct notifier_block *nb) { if (!nb) return -EINVAL; if (bwmgr.emc_clk) return clk_notifier_unregister(bwmgr.emc_clk, nb); else return -ENODEV; } EXPORT_SYMBOL_GPL(tegra_bwmgr_notifier_unregister); unsigned long tegra_bwmgr_get_emc_rate(void) { if (bwmgr.emc_clk) return clk_get_rate(bwmgr.emc_clk); return 0; } EXPORT_SYMBOL_GPL(tegra_bwmgr_get_emc_rate); /* bwmgr_get_lowest_iso_emc_freq * bwmgr_apply_efficiency function will use this api to calculate * the lowest emc frequency that satisfies the requests of ISO clients. * * A return value of 0 indicates that the requested * iso bandwidth cannot be supported. */ unsigned long bwmgr_get_lowest_iso_emc_freq(long iso_bw, long iso_bw_nvdis, long iso_bw_vi) { return bwmgr.ops->get_best_iso_freq(iso_bw, iso_bw_nvdis, iso_bw_vi); } EXPORT_SYMBOL_GPL(bwmgr_get_lowest_iso_emc_freq); /* tegra_bwmgr_get_max_iso_bw * This function returns the max iso bw. * This is applicable from t19x onwards, where max_iso is different * based on clients requesting. * This should not be called on pre t19x and returns 0 in those cases */ u32 tegra_bwmgr_get_max_iso_bw(enum tegra_iso_client client) { if (bwmgr.ops->get_max_iso_bw) return bwmgr.ops->get_max_iso_bw(client); return 0; } EXPORT_SYMBOL_GPL(tegra_bwmgr_get_max_iso_bw); int bwmgr_iso_bw_percentage_max(void) { return bwmgr_iso_bw_percentage; } EXPORT_SYMBOL_GPL(bwmgr_iso_bw_percentage_max); unsigned long bwmgr_freq_to_bw(unsigned long freq) { return bwmgr.ops->freq_to_bw(freq); } EXPORT_SYMBOL_GPL(bwmgr_freq_to_bw); unsigned long bwmgr_bw_to_freq(unsigned long bw) { return bwmgr.ops->bw_to_freq(bw); } EXPORT_SYMBOL_GPL(bwmgr_bw_to_freq); u32 bwmgr_dvfs_latency(u32 ufreq) { return bwmgr.ops->dvfs_latency(ufreq); } EXPORT_SYMBOL_GPL(bwmgr_dvfs_latency); /* Get maximum throttle state supported by bwmgr cooling device. */ static int dram_ref_alert_cdev_max_state(struct thermal_cooling_device *tcd, unsigned long *state) { struct dram_refresh_alrt *alert_data = tcd->devdata; if (!alert_data) return -EINVAL; *state = (unsigned long)alert_data->max_cooling_state; return 0; } /* Get current throttle state of the bwmgr cooling device. */ static int dram_ref_alert_cdev_cur_state(struct thermal_cooling_device *tcd, unsigned long *state) { struct dram_refresh_alrt *alert_data = tcd->devdata; if (!alert_data) return -EINVAL; *state = alert_data->cur_state; return 0; } static int tegra_bwmgr_update_efficiency(unsigned long cur_state, unsigned long prev_state) { int ret = 0; #ifdef CONFIG_TRACEPOINTS trace_tegra_bwmgr_update_efficiency( cur_state, prev_state); #endif /* CONFIG_TRACEPOINTS */ /* At this point, the thermal framework has indicated that * the trip point has been crossed for AO-therm zone. However, * this does not guarantee dram refresh rate has been changed. * talk to bpmp at this point to find out if dram refresh rate * has changed or not. This will be implemented once the bpmp * IPC's are ready.For now update efficiency. */ if (!bwmgr_lock()) { pr_err("bwmgr: %s failed\n", __func__); return -EINVAL; } if (bwmgr.override) { bwmgr_unlock(); return 0; } if (bwmgr.ops->update_efficiency) bwmgr.ops->update_efficiency(cur_state); if (!clk_update_disabled) ret = bwmgr_update_clk(); if (!bwmgr_unlock()) { pr_err("bwmgr: %s failed.\n", __func__); return -EINVAL; } return ret; } /* Set current throttle state of the bwmgr cooling device. */ static int dram_ref_alert_cdev_set_state(struct thermal_cooling_device *tcd, unsigned long state) { int ret = 0; unsigned long prev_state; struct dram_refresh_alrt *alert_data = tcd->devdata; if (!alert_data) return -EINVAL; prev_state = alert_data->cur_state; alert_data->cur_state = state; ret = tegra_bwmgr_update_efficiency(state, prev_state); return ret; } /* * Cooling device operations. */ static struct thermal_cooling_device_ops dram_ref_alert_cdev_ops = { .get_max_state = dram_ref_alert_cdev_max_state, .get_cur_state = dram_ref_alert_cdev_cur_state, .set_cur_state = dram_ref_alert_cdev_set_state, }; int __init bwmgr_init(void) { int i; struct device_node *dn; long round_rate; struct clk *emc_master_clk; mutex_init(&bwmgr.lock); if (tegra_get_chip_id() == TEGRA210) bwmgr.ops = bwmgr_eff_init_t21x(); else if (tegra_get_chip_id() == TEGRA186) bwmgr.ops = bwmgr_eff_init_t18x(); else if (tegra_get_chip_id() == TEGRA194) bwmgr.ops = bwmgr_eff_init_t19x(); else /* * Fall back to t19x if we are running on a new chip. */ bwmgr.ops = bwmgr_eff_init_t19x(); dn = of_find_compatible_node(NULL, NULL, "nvidia,bwmgr"); if (dn == NULL) { pr_err("bwmgr: dt node not found.\n"); return -ENODEV; } bwmgr.emc_clk = of_clk_get(dn, 0); if (IS_ERR_OR_NULL(bwmgr.emc_clk)) { pr_err("bwmgr: couldn't find emc clock.\n"); bwmgr.emc_clk = NULL; WARN_ON(true); return -ENODEV; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) clk_prepare_enable(bwmgr.emc_clk); #endif emc_master_clk = bwmgr.emc_clk; if (of_property_read_bool(dn, "nvidia,bwmgr-use-shared-master")) emc_master_clk = clk_get_parent(emc_master_clk); round_rate = clk_round_rate(emc_master_clk, 0); if (round_rate < 0) { bwmgr.emc_min_rate = 0; pr_err("bwmgr: couldn't get emc clock min rate.\n"); } else bwmgr.emc_min_rate = (unsigned long)round_rate; /* Use LONG_MAX as downstream functions treats rate arg as signed */ round_rate = clk_round_rate(emc_master_clk, LONG_MAX); if (round_rate < 0) { bwmgr.emc_max_rate = 0; pr_err("bwmgr: couldn't get emc clock max rate.\n"); } else bwmgr.emc_max_rate = (unsigned long)round_rate; /* On some pre-si platforms max rate is acquired via DT */ if (tegra_platform_is_sim() || tegra_platform_is_fpga()) { if (of_property_read_u64(dn, "max_rate_Hz", (u64 *) &round_rate) == 0) { pr_err("bwmgr: using max rate from device tree.\n"); bwmgr.emc_max_rate = round_rate; } } for (i = 0; i < TEGRA_BWMGR_CLIENT_COUNT; i++) purge_client(bwmgr.bwmgr_client + i); bwmgr_debugfs_init(); pmqos_bwmgr_init(); /* Check status property is okay or not. */ if (of_device_is_available(dn)) bwmgr.status = true; else bwmgr.status = false; return 0; } subsys_initcall_sync(bwmgr_init); void __exit bwmgr_exit(void) { int i; for (i = 0; i < TEGRA_BWMGR_CLIENT_COUNT; i++) purge_client(bwmgr.bwmgr_client + i); bwmgr.emc_clk = NULL; mutex_destroy(&bwmgr.lock); if (galert_data) thermal_cooling_device_unregister(galert_data->cdev); kfree(galert_data); } module_exit(bwmgr_exit); static int __init bwmgr_cooling_init(void) { struct device_node *dn; char *cdev_type; const char *str; int ret = 0; galert_data = kzalloc(sizeof(struct dram_refresh_alrt), GFP_KERNEL); if (!galert_data) return -ENOMEM; dn = of_find_compatible_node(NULL, NULL, "nvidia,bwmgr"); if (dn == NULL) { pr_err("bwmgr: dt node not found.\n"); ret = -ENODEV; goto error; } if (of_property_read_string(dn, "cdev-type", &str) == 0) { cdev_type = (char *)str; } else { pr_info("bwmgr: missing cdev-type property\n"); goto error; } if (of_property_read_u32(dn, "cooling-max-state", &(galert_data->max_cooling_state))) { pr_err("bwmgr: missing max_cooling_state property\n"); ret = -EINVAL; goto error; } galert_data->cur_state = 0; galert_data->cdev = thermal_of_cooling_device_register(dn, cdev_type, galert_data, &dram_ref_alert_cdev_ops); if (IS_ERR(galert_data->cdev)) { ret = PTR_ERR(galert_data->cdev); pr_err("bwmgr: failed to register to thermal framework\n"); goto error; } return 0; error: kfree(galert_data); return ret; } late_initcall(bwmgr_cooling_init); /* thermal_init is fs_init, make bwmgr_cooling_init late_init as it * depends on thermal_init */ #ifdef CONFIG_DEBUG_FS static struct tegra_bwmgr_client *bwmgr_debugfs_client_handle; static struct dentry *debugfs_dir; static struct dentry *debugfs_node_floor; static struct dentry *debugfs_node_cap; static struct dentry *debugfs_node_iso_cap; static struct dentry *debugfs_node_bw; static struct dentry *debugfs_node_iso_bw; static struct dentry *debugfs_node_emc_rate; static struct dentry *debugfs_node_emc_min; static struct dentry *debugfs_node_emc_max; static struct dentry *debugfs_node_core_emc_rate; static struct dentry *debugfs_node_clients_info; static struct dentry *debugfs_node_dram_channels; static int bwmgr_debugfs_emc_rate_set(void *data, u64 val) { int ret = 0; if (!bwmgr_lock()) return -EPERM; if (val == 0) { bwmgr.override = false; bwmgr_update_clk(); } else if (bwmgr.emc_clk) { bwmgr.override = true; ret = clk_set_rate(bwmgr.emc_clk, val); } if (!bwmgr_unlock()) return -EPERM; return ret; } static int bwmgr_debugfs_emc_rate_get(void *data, u64 *val) { *val = tegra_bwmgr_get_emc_rate(); return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_debugfs_emc_rate, bwmgr_debugfs_emc_rate_get, bwmgr_debugfs_emc_rate_set, "%llu\n"); static int bwmgr_debugfs_core_emc_rate_get(void *data, u64 *val) { *val = tegra_bwmgr_get_core_emc_rate(); return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_debugfs_core_emc_rate, bwmgr_debugfs_core_emc_rate_get, NULL, "%llu\n"); static int bwmgr_debugfs_dram_channels_get(void *data, u64 *val) { *val = bwmgr_dram_num_channels; return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_debugfs_dram_channels, bwmgr_debugfs_dram_channels_get, NULL, "%llu\n"); static int bwmgr_debugfs_floor_set(void *data, u64 val) { tegra_bwmgr_set_emc(bwmgr_debugfs_client_handle, val, TEGRA_BWMGR_SET_EMC_FLOOR); return 0; } static int bwmgr_debugfs_floor_get(void *data, u64 *val) { *val = bwmgr_debugfs_client_handle->floor; return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_debugfs_floor, bwmgr_debugfs_floor_get, bwmgr_debugfs_floor_set, "%llu\n"); static int bwmgr_debugfs_cap_set(void *data, u64 val) { tegra_bwmgr_set_emc(bwmgr_debugfs_client_handle, val, TEGRA_BWMGR_SET_EMC_CAP); return 0; } static int bwmgr_debugfs_cap_get(void *data, u64 *val) { *val = bwmgr_debugfs_client_handle->cap; return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_debugfs_cap, bwmgr_debugfs_cap_get, bwmgr_debugfs_cap_set, "%llu\n"); static int bwmgr_debugfs_iso_cap_set(void *data, u64 val) { tegra_bwmgr_set_emc(bwmgr_debugfs_client_handle, val, TEGRA_BWMGR_SET_EMC_ISO_CAP); return 0; } static int bwmgr_debugfs_iso_cap_get(void *data, u64 *val) { *val = bwmgr_debugfs_client_handle->iso_cap; return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_debugfs_iso_cap, bwmgr_debugfs_iso_cap_get, bwmgr_debugfs_iso_cap_set, "%llu\n"); static int bwmgr_debugfs_bw_set(void *data, u64 val) { tegra_bwmgr_set_emc(bwmgr_debugfs_client_handle, val, TEGRA_BWMGR_SET_EMC_SHARED_BW); return 0; } static int bwmgr_debugfs_bw_get(void *data, u64 *val) { *val = bwmgr_debugfs_client_handle->bw; return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_debugfs_bw, bwmgr_debugfs_bw_get, bwmgr_debugfs_bw_set, "%llu\n"); static int bwmgr_debugfs_iso_bw_set(void *data, u64 val) { tegra_bwmgr_set_emc(bwmgr_debugfs_client_handle, val, TEGRA_BWMGR_SET_EMC_SHARED_BW_ISO); return 0; } static int bwmgr_debugfs_iso_bw_get(void *data, u64 *val) { *val = bwmgr_debugfs_client_handle->iso_bw; return 0; } static int bwmgr_clients_info_show(struct seq_file *s, void *data) { int i; if (!bwmgr_lock()) { pr_err("bwmgr: %s failed\n", __func__); return -EINVAL; } seq_printf(s, "%15s%15s%15s%15s%15s%15s (Khz)\n", "Client", "Floor", "SharedBw", "SharedIsoBw", "Cap", "IsoCap"); for (i = 0; i < TEGRA_BWMGR_CLIENT_COUNT; i++) { seq_printf(s, "%14s%s%15lu%15lu%15lu%15lu%15lu\n", tegra_bwmgr_client_names[i], bwmgr.bwmgr_client[i].refcount ? "*" : " ", bwmgr.bwmgr_client[i].floor / 1000, bwmgr.bwmgr_client[i].bw / 1000, bwmgr.bwmgr_client[i].iso_bw / 1000, bwmgr.bwmgr_client[i].cap / 1000, bwmgr.bwmgr_client[i].iso_cap / 1000); } seq_printf(s, "Total BW requested : %lu (Khz)\n", debug_info.bw / 1000); seq_printf(s, "Total ISO_BW requested : %lu (Khz)\n", debug_info.iso_bw / 1000); seq_printf(s, "Effective floor request : %lu (Khz)\n", debug_info.floor / 1000); seq_printf(s, "Effective NON_ISO_CAP : %lu (Khz)\n", debug_info.non_iso_cap / 1000); seq_printf(s, "Effective ISO_CAP : %lu (Khz)\n", debug_info.iso_cap / 1000); seq_printf(s, "Total BW + ISO_BW : %lu (Khz)\n", (debug_info.bw + debug_info.iso_bw) / 1000); seq_printf(s, "Total BW+ISO_BW after applying efficieny numbers: %lu (Khz)\n", debug_info.total_bw_aftr_eff / 1000); seq_printf(s, "Total ISO_BW after applying efficiency : %lu (Khz)\n", debug_info.iso_bw_aftr_eff / 1000); seq_printf(s, "EMC calculated rate : %lu (Khz)\n", debug_info.calc_freq / 1000); seq_printf(s, "EMC requested(rounded) rate : %lu (Khz)\n", debug_info.req_freq / 1000); seq_printf(s, "EMC current rate : %lu (Khz)\n", tegra_bwmgr_get_emc_rate() / 1000); if (!bwmgr_unlock()) { pr_err("bwmgr: %s failed\n", __func__); return -EINVAL; } return 0; } DEFINE_SIMPLE_ATTRIBUTE(fops_debugfs_iso_bw, bwmgr_debugfs_iso_bw_get, bwmgr_debugfs_iso_bw_set, "%llu\n"); static int bwmgr_clients_info_open(struct inode *inode, struct file *file) { return single_open(file, bwmgr_clients_info_show, inode->i_private); } static const struct file_operations fops_bwmgr_clients_info = { .open = bwmgr_clients_info_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static void bwmgr_debugfs_init(void) { bwmgr_debugfs_client_handle = tegra_bwmgr_register(TEGRA_BWMGR_CLIENT_DEBUG); if (IS_ERR_OR_NULL(bwmgr_debugfs_client_handle)) { bwmgr_debugfs_client_handle = NULL; pr_err("bwmgr: could not register bwmgr debugfs client\n"); return; } debugfs_dir = debugfs_create_dir("tegra_bwmgr", NULL); if (debugfs_dir) { debugfs_create_bool( "clk_update_disabled", S_IRWXU, debugfs_dir, &clk_update_disabled); debugfs_node_emc_min = debugfs_create_u64( "emc_min_rate", S_IRUSR, debugfs_dir, (u64 *) &bwmgr.emc_min_rate); debugfs_node_emc_max = debugfs_create_u64( "emc_max_rate", S_IRUSR, debugfs_dir, (u64 *) &bwmgr.emc_max_rate); debugfs_node_core_emc_rate = debugfs_create_file( "core_emc_rate", S_IRUSR, debugfs_dir, NULL, &fops_debugfs_core_emc_rate); debugfs_node_emc_rate = debugfs_create_file ("emc_rate", S_IRUSR, debugfs_dir, NULL, &fops_debugfs_emc_rate); debugfs_node_floor = debugfs_create_file ("debug_client_floor", S_IRWXU, debugfs_dir, NULL, &fops_debugfs_floor); debugfs_node_cap = debugfs_create_file ("debug_client_cap", S_IRWXU, debugfs_dir, NULL, &fops_debugfs_cap); debugfs_node_iso_cap = debugfs_create_file ("debug_client_iso_cap", S_IRWXU, debugfs_dir, NULL, &fops_debugfs_iso_cap); debugfs_node_bw = debugfs_create_file ("debug_client_bw", S_IRWXU, debugfs_dir, NULL, &fops_debugfs_bw); debugfs_node_iso_bw = debugfs_create_file ("debug_client_iso_bw", S_IRWXU, debugfs_dir, NULL, &fops_debugfs_iso_bw); debugfs_node_clients_info = debugfs_create_file ("bwmgr_clients_info", S_IRUGO, debugfs_dir, NULL, &fops_bwmgr_clients_info); debugfs_node_dram_channels = debugfs_create_file( "num_dram_channels", S_IRUSR, debugfs_dir, NULL, &fops_debugfs_dram_channels); } else pr_err("bwmgr: error creating bwmgr debugfs dir.\n"); if (!bwmgr_lock()) { pr_err("bwmgr: %s failed\n", __func__); return; } debug_info.non_iso_cap = bwmgr.emc_max_rate; debug_info.iso_cap = bwmgr.emc_max_rate; if (!bwmgr_unlock()) { pr_err("bwmgr: %s failed\n", __func__); return; } } #else static void bwmgr_debugfs_init(void) {}; #endif /* CONFIG_DEBUG_FS */