162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2019 NVIDIA CORPORATION. All rights reserved. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/clk.h> 762306a36Sopenharmony_ci#include <linux/debugfs.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1062306a36Sopenharmony_ci#include <linux/of_platform.h> 1162306a36Sopenharmony_ci#include <linux/platform_device.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <soc/tegra/bpmp.h> 1462306a36Sopenharmony_ci#include "mc.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistruct tegra186_emc_dvfs { 1762306a36Sopenharmony_ci unsigned long latency; 1862306a36Sopenharmony_ci unsigned long rate; 1962306a36Sopenharmony_ci}; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistruct tegra186_emc { 2262306a36Sopenharmony_ci struct tegra_bpmp *bpmp; 2362306a36Sopenharmony_ci struct device *dev; 2462306a36Sopenharmony_ci struct clk *clk; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci struct tegra186_emc_dvfs *dvfs; 2762306a36Sopenharmony_ci unsigned int num_dvfs; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci struct { 3062306a36Sopenharmony_ci struct dentry *root; 3162306a36Sopenharmony_ci unsigned long min_rate; 3262306a36Sopenharmony_ci unsigned long max_rate; 3362306a36Sopenharmony_ci } debugfs; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci struct icc_provider provider; 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic inline struct tegra186_emc *to_tegra186_emc(struct icc_provider *provider) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci return container_of(provider, struct tegra186_emc, provider); 4162306a36Sopenharmony_ci} 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci/* 4462306a36Sopenharmony_ci * debugfs interface 4562306a36Sopenharmony_ci * 4662306a36Sopenharmony_ci * The memory controller driver exposes some files in debugfs that can be used 4762306a36Sopenharmony_ci * to control the EMC frequency. The top-level directory can be found here: 4862306a36Sopenharmony_ci * 4962306a36Sopenharmony_ci * /sys/kernel/debug/emc 5062306a36Sopenharmony_ci * 5162306a36Sopenharmony_ci * It contains the following files: 5262306a36Sopenharmony_ci * 5362306a36Sopenharmony_ci * - available_rates: This file contains a list of valid, space-separated 5462306a36Sopenharmony_ci * EMC frequencies. 5562306a36Sopenharmony_ci * 5662306a36Sopenharmony_ci * - min_rate: Writing a value to this file sets the given frequency as the 5762306a36Sopenharmony_ci * floor of the permitted range. If this is higher than the currently 5862306a36Sopenharmony_ci * configured EMC frequency, this will cause the frequency to be 5962306a36Sopenharmony_ci * increased so that it stays within the valid range. 6062306a36Sopenharmony_ci * 6162306a36Sopenharmony_ci * - max_rate: Similarily to the min_rate file, writing a value to this file 6262306a36Sopenharmony_ci * sets the given frequency as the ceiling of the permitted range. If 6362306a36Sopenharmony_ci * the value is lower than the currently configured EMC frequency, this 6462306a36Sopenharmony_ci * will cause the frequency to be decreased so that it stays within the 6562306a36Sopenharmony_ci * valid range. 6662306a36Sopenharmony_ci */ 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic bool tegra186_emc_validate_rate(struct tegra186_emc *emc, 6962306a36Sopenharmony_ci unsigned long rate) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci unsigned int i; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci for (i = 0; i < emc->num_dvfs; i++) 7462306a36Sopenharmony_ci if (rate == emc->dvfs[i].rate) 7562306a36Sopenharmony_ci return true; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci return false; 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic int tegra186_emc_debug_available_rates_show(struct seq_file *s, 8162306a36Sopenharmony_ci void *data) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci struct tegra186_emc *emc = s->private; 8462306a36Sopenharmony_ci const char *prefix = ""; 8562306a36Sopenharmony_ci unsigned int i; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci for (i = 0; i < emc->num_dvfs; i++) { 8862306a36Sopenharmony_ci seq_printf(s, "%s%lu", prefix, emc->dvfs[i].rate); 8962306a36Sopenharmony_ci prefix = " "; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci seq_puts(s, "\n"); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return 0; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(tegra186_emc_debug_available_rates); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic int tegra186_emc_debug_min_rate_get(void *data, u64 *rate) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci struct tegra186_emc *emc = data; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci *rate = emc->debugfs.min_rate; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci return 0; 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic int tegra186_emc_debug_min_rate_set(void *data, u64 rate) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct tegra186_emc *emc = data; 11062306a36Sopenharmony_ci int err; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (!tegra186_emc_validate_rate(emc, rate)) 11362306a36Sopenharmony_ci return -EINVAL; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci err = clk_set_min_rate(emc->clk, rate); 11662306a36Sopenharmony_ci if (err < 0) 11762306a36Sopenharmony_ci return err; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci emc->debugfs.min_rate = rate; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci return 0; 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ciDEFINE_DEBUGFS_ATTRIBUTE(tegra186_emc_debug_min_rate_fops, 12562306a36Sopenharmony_ci tegra186_emc_debug_min_rate_get, 12662306a36Sopenharmony_ci tegra186_emc_debug_min_rate_set, "%llu\n"); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic int tegra186_emc_debug_max_rate_get(void *data, u64 *rate) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct tegra186_emc *emc = data; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci *rate = emc->debugfs.max_rate; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci return 0; 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic int tegra186_emc_debug_max_rate_set(void *data, u64 rate) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci struct tegra186_emc *emc = data; 14062306a36Sopenharmony_ci int err; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (!tegra186_emc_validate_rate(emc, rate)) 14362306a36Sopenharmony_ci return -EINVAL; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci err = clk_set_max_rate(emc->clk, rate); 14662306a36Sopenharmony_ci if (err < 0) 14762306a36Sopenharmony_ci return err; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci emc->debugfs.max_rate = rate; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return 0; 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ciDEFINE_DEBUGFS_ATTRIBUTE(tegra186_emc_debug_max_rate_fops, 15562306a36Sopenharmony_ci tegra186_emc_debug_max_rate_get, 15662306a36Sopenharmony_ci tegra186_emc_debug_max_rate_set, "%llu\n"); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic int tegra186_emc_get_emc_dvfs_latency(struct tegra186_emc *emc) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct mrq_emc_dvfs_latency_response response; 16162306a36Sopenharmony_ci struct tegra_bpmp_message msg; 16262306a36Sopenharmony_ci unsigned int i; 16362306a36Sopenharmony_ci int err; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci memset(&msg, 0, sizeof(msg)); 16662306a36Sopenharmony_ci msg.mrq = MRQ_EMC_DVFS_LATENCY; 16762306a36Sopenharmony_ci msg.tx.data = NULL; 16862306a36Sopenharmony_ci msg.tx.size = 0; 16962306a36Sopenharmony_ci msg.rx.data = &response; 17062306a36Sopenharmony_ci msg.rx.size = sizeof(response); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci err = tegra_bpmp_transfer(emc->bpmp, &msg); 17362306a36Sopenharmony_ci if (err < 0) { 17462306a36Sopenharmony_ci dev_err(emc->dev, "failed to EMC DVFS pairs: %d\n", err); 17562306a36Sopenharmony_ci return err; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci if (msg.rx.ret < 0) { 17862306a36Sopenharmony_ci dev_err(emc->dev, "EMC DVFS MRQ failed: %d (BPMP error code)\n", msg.rx.ret); 17962306a36Sopenharmony_ci return -EINVAL; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci emc->debugfs.min_rate = ULONG_MAX; 18362306a36Sopenharmony_ci emc->debugfs.max_rate = 0; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci emc->num_dvfs = response.num_pairs; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci emc->dvfs = devm_kmalloc_array(emc->dev, emc->num_dvfs, sizeof(*emc->dvfs), GFP_KERNEL); 18862306a36Sopenharmony_ci if (!emc->dvfs) 18962306a36Sopenharmony_ci return -ENOMEM; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci dev_dbg(emc->dev, "%u DVFS pairs:\n", emc->num_dvfs); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci for (i = 0; i < emc->num_dvfs; i++) { 19462306a36Sopenharmony_ci emc->dvfs[i].rate = response.pairs[i].freq * 1000; 19562306a36Sopenharmony_ci emc->dvfs[i].latency = response.pairs[i].latency; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (emc->dvfs[i].rate < emc->debugfs.min_rate) 19862306a36Sopenharmony_ci emc->debugfs.min_rate = emc->dvfs[i].rate; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (emc->dvfs[i].rate > emc->debugfs.max_rate) 20162306a36Sopenharmony_ci emc->debugfs.max_rate = emc->dvfs[i].rate; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci dev_dbg(emc->dev, " %2u: %lu Hz -> %lu us\n", i, 20462306a36Sopenharmony_ci emc->dvfs[i].rate, emc->dvfs[i].latency); 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci err = clk_set_rate_range(emc->clk, emc->debugfs.min_rate, emc->debugfs.max_rate); 20862306a36Sopenharmony_ci if (err < 0) { 20962306a36Sopenharmony_ci dev_err(emc->dev, "failed to set rate range [%lu-%lu] for %pC\n", 21062306a36Sopenharmony_ci emc->debugfs.min_rate, emc->debugfs.max_rate, emc->clk); 21162306a36Sopenharmony_ci return err; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci emc->debugfs.root = debugfs_create_dir("emc", NULL); 21562306a36Sopenharmony_ci debugfs_create_file("available_rates", 0444, emc->debugfs.root, emc, 21662306a36Sopenharmony_ci &tegra186_emc_debug_available_rates_fops); 21762306a36Sopenharmony_ci debugfs_create_file("min_rate", 0644, emc->debugfs.root, emc, 21862306a36Sopenharmony_ci &tegra186_emc_debug_min_rate_fops); 21962306a36Sopenharmony_ci debugfs_create_file("max_rate", 0644, emc->debugfs.root, emc, 22062306a36Sopenharmony_ci &tegra186_emc_debug_max_rate_fops); 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return 0; 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci/* 22662306a36Sopenharmony_ci * tegra_emc_icc_set_bw() - Set BW api for EMC provider 22762306a36Sopenharmony_ci * @src: ICC node for External Memory Controller (EMC) 22862306a36Sopenharmony_ci * @dst: ICC node for External Memory (DRAM) 22962306a36Sopenharmony_ci * 23062306a36Sopenharmony_ci * Do nothing here as info to BPMP-FW is now passed in the BW set function 23162306a36Sopenharmony_ci * of the MC driver. BPMP-FW sets the final Freq based on the passed values. 23262306a36Sopenharmony_ci */ 23362306a36Sopenharmony_cistatic int tegra_emc_icc_set_bw(struct icc_node *src, struct icc_node *dst) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci return 0; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic struct icc_node * 23962306a36Sopenharmony_citegra_emc_of_icc_xlate(struct of_phandle_args *spec, void *data) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci struct icc_provider *provider = data; 24262306a36Sopenharmony_ci struct icc_node *node; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci /* External Memory is the only possible ICC route */ 24562306a36Sopenharmony_ci list_for_each_entry(node, &provider->nodes, node_list) { 24662306a36Sopenharmony_ci if (node->id != TEGRA_ICC_EMEM) 24762306a36Sopenharmony_ci continue; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci return node; 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_cistatic int tegra_emc_icc_get_init_bw(struct icc_node *node, u32 *avg, u32 *peak) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci *avg = 0; 25862306a36Sopenharmony_ci *peak = 0; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci return 0; 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistatic int tegra_emc_interconnect_init(struct tegra186_emc *emc) 26462306a36Sopenharmony_ci{ 26562306a36Sopenharmony_ci struct tegra_mc *mc = dev_get_drvdata(emc->dev->parent); 26662306a36Sopenharmony_ci const struct tegra_mc_soc *soc = mc->soc; 26762306a36Sopenharmony_ci struct icc_node *node; 26862306a36Sopenharmony_ci int err; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci emc->provider.dev = emc->dev; 27162306a36Sopenharmony_ci emc->provider.set = tegra_emc_icc_set_bw; 27262306a36Sopenharmony_ci emc->provider.data = &emc->provider; 27362306a36Sopenharmony_ci emc->provider.aggregate = soc->icc_ops->aggregate; 27462306a36Sopenharmony_ci emc->provider.xlate = tegra_emc_of_icc_xlate; 27562306a36Sopenharmony_ci emc->provider.get_bw = tegra_emc_icc_get_init_bw; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci icc_provider_init(&emc->provider); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci /* create External Memory Controller node */ 28062306a36Sopenharmony_ci node = icc_node_create(TEGRA_ICC_EMC); 28162306a36Sopenharmony_ci if (IS_ERR(node)) { 28262306a36Sopenharmony_ci err = PTR_ERR(node); 28362306a36Sopenharmony_ci goto err_msg; 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci node->name = "External Memory Controller"; 28762306a36Sopenharmony_ci icc_node_add(node, &emc->provider); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci /* link External Memory Controller to External Memory (DRAM) */ 29062306a36Sopenharmony_ci err = icc_link_create(node, TEGRA_ICC_EMEM); 29162306a36Sopenharmony_ci if (err) 29262306a36Sopenharmony_ci goto remove_nodes; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci /* create External Memory node */ 29562306a36Sopenharmony_ci node = icc_node_create(TEGRA_ICC_EMEM); 29662306a36Sopenharmony_ci if (IS_ERR(node)) { 29762306a36Sopenharmony_ci err = PTR_ERR(node); 29862306a36Sopenharmony_ci goto remove_nodes; 29962306a36Sopenharmony_ci } 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci node->name = "External Memory (DRAM)"; 30262306a36Sopenharmony_ci icc_node_add(node, &emc->provider); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci err = icc_provider_register(&emc->provider); 30562306a36Sopenharmony_ci if (err) 30662306a36Sopenharmony_ci goto remove_nodes; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci return 0; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ciremove_nodes: 31162306a36Sopenharmony_ci icc_nodes_remove(&emc->provider); 31262306a36Sopenharmony_cierr_msg: 31362306a36Sopenharmony_ci dev_err(emc->dev, "failed to initialize ICC: %d\n", err); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci return err; 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_cistatic int tegra186_emc_probe(struct platform_device *pdev) 31962306a36Sopenharmony_ci{ 32062306a36Sopenharmony_ci struct tegra_mc *mc = dev_get_drvdata(pdev->dev.parent); 32162306a36Sopenharmony_ci struct tegra186_emc *emc; 32262306a36Sopenharmony_ci int err; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL); 32562306a36Sopenharmony_ci if (!emc) 32662306a36Sopenharmony_ci return -ENOMEM; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci emc->bpmp = tegra_bpmp_get(&pdev->dev); 32962306a36Sopenharmony_ci if (IS_ERR(emc->bpmp)) 33062306a36Sopenharmony_ci return dev_err_probe(&pdev->dev, PTR_ERR(emc->bpmp), "failed to get BPMP\n"); 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci emc->clk = devm_clk_get(&pdev->dev, "emc"); 33362306a36Sopenharmony_ci if (IS_ERR(emc->clk)) { 33462306a36Sopenharmony_ci err = PTR_ERR(emc->clk); 33562306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to get EMC clock: %d\n", err); 33662306a36Sopenharmony_ci goto put_bpmp; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci platform_set_drvdata(pdev, emc); 34062306a36Sopenharmony_ci emc->dev = &pdev->dev; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci if (tegra_bpmp_mrq_is_supported(emc->bpmp, MRQ_EMC_DVFS_LATENCY)) { 34362306a36Sopenharmony_ci err = tegra186_emc_get_emc_dvfs_latency(emc); 34462306a36Sopenharmony_ci if (err) 34562306a36Sopenharmony_ci goto put_bpmp; 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci if (mc && mc->soc->icc_ops) { 34962306a36Sopenharmony_ci if (tegra_bpmp_mrq_is_supported(emc->bpmp, MRQ_BWMGR_INT)) { 35062306a36Sopenharmony_ci mc->bwmgr_mrq_supported = true; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci /* 35362306a36Sopenharmony_ci * MC driver probe can't get BPMP reference as it gets probed 35462306a36Sopenharmony_ci * earlier than BPMP. So, save the BPMP ref got from the EMC 35562306a36Sopenharmony_ci * DT node in the mc->bpmp and use it in MC's icc_set hook. 35662306a36Sopenharmony_ci */ 35762306a36Sopenharmony_ci mc->bpmp = emc->bpmp; 35862306a36Sopenharmony_ci barrier(); 35962306a36Sopenharmony_ci } 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci /* 36262306a36Sopenharmony_ci * Initialize the ICC even if BPMP-FW doesn't support 'MRQ_BWMGR_INT'. 36362306a36Sopenharmony_ci * Use the flag 'mc->bwmgr_mrq_supported' within MC driver and return 36462306a36Sopenharmony_ci * EINVAL instead of passing the request to BPMP-FW later when the BW 36562306a36Sopenharmony_ci * request is made by client with 'icc_set_bw()' call. 36662306a36Sopenharmony_ci */ 36762306a36Sopenharmony_ci err = tegra_emc_interconnect_init(emc); 36862306a36Sopenharmony_ci if (err) { 36962306a36Sopenharmony_ci mc->bpmp = NULL; 37062306a36Sopenharmony_ci goto put_bpmp; 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci } 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci return 0; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ciput_bpmp: 37762306a36Sopenharmony_ci tegra_bpmp_put(emc->bpmp); 37862306a36Sopenharmony_ci return err; 37962306a36Sopenharmony_ci} 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_cistatic int tegra186_emc_remove(struct platform_device *pdev) 38262306a36Sopenharmony_ci{ 38362306a36Sopenharmony_ci struct tegra_mc *mc = dev_get_drvdata(pdev->dev.parent); 38462306a36Sopenharmony_ci struct tegra186_emc *emc = platform_get_drvdata(pdev); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci debugfs_remove_recursive(emc->debugfs.root); 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci mc->bpmp = NULL; 38962306a36Sopenharmony_ci tegra_bpmp_put(emc->bpmp); 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci return 0; 39262306a36Sopenharmony_ci} 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_cistatic const struct of_device_id tegra186_emc_of_match[] = { 39562306a36Sopenharmony_ci#if defined(CONFIG_ARCH_TEGRA_186_SOC) 39662306a36Sopenharmony_ci { .compatible = "nvidia,tegra186-emc" }, 39762306a36Sopenharmony_ci#endif 39862306a36Sopenharmony_ci#if defined(CONFIG_ARCH_TEGRA_194_SOC) 39962306a36Sopenharmony_ci { .compatible = "nvidia,tegra194-emc" }, 40062306a36Sopenharmony_ci#endif 40162306a36Sopenharmony_ci#if defined(CONFIG_ARCH_TEGRA_234_SOC) 40262306a36Sopenharmony_ci { .compatible = "nvidia,tegra234-emc" }, 40362306a36Sopenharmony_ci#endif 40462306a36Sopenharmony_ci { /* sentinel */ } 40562306a36Sopenharmony_ci}; 40662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, tegra186_emc_of_match); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_cistatic struct platform_driver tegra186_emc_driver = { 40962306a36Sopenharmony_ci .driver = { 41062306a36Sopenharmony_ci .name = "tegra186-emc", 41162306a36Sopenharmony_ci .of_match_table = tegra186_emc_of_match, 41262306a36Sopenharmony_ci .suppress_bind_attrs = true, 41362306a36Sopenharmony_ci .sync_state = icc_sync_state, 41462306a36Sopenharmony_ci }, 41562306a36Sopenharmony_ci .probe = tegra186_emc_probe, 41662306a36Sopenharmony_ci .remove = tegra186_emc_remove, 41762306a36Sopenharmony_ci}; 41862306a36Sopenharmony_cimodule_platform_driver(tegra186_emc_driver); 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ciMODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); 42162306a36Sopenharmony_ciMODULE_DESCRIPTION("NVIDIA Tegra186 External Memory Controller driver"); 422