/* * Copyright (c) 2013-2018, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bpmp.h" static void *hv_virt_base; static struct device *device; char firmware_tag[sizeof(struct mrq_query_fw_tag_response)]; static int bpmp_get_fwtag(void) { const size_t sz = sizeof(struct mrq_query_fw_tag_response); struct mrq_query_abi_request abi_req = { .mrq = MRQ_QUERY_FW_TAG }; struct mrq_query_abi_response abi_resp; int r; char *virt; dma_addr_t phys; r = tegra_bpmp_send_receive(MRQ_QUERY_ABI, &abi_req, sizeof(abi_req), &abi_resp, sizeof(abi_resp)); if (r) { dev_err(device, "ABI query failed! %d\n", r); goto exit; } /* * If MRQ_QUERY_FW_TAG is not supported, read tag using * MRQ_QUERY_TAG */ if (abi_resp.status) { virt = tegra_bpmp_alloc_coherent(sz + 1, &phys, GFP_KERNEL); if (virt) r = tegra_bpmp_send_receive(MRQ_QUERY_TAG, &phys, sizeof(phys), NULL, 0); } else { virt = kmalloc(sz + 1, GFP_KERNEL); if (virt) r = tegra_bpmp_send_receive(MRQ_QUERY_FW_TAG, NULL, 0, virt, sz); } if (!virt) { r = -ENOMEM; goto exit; } if (!r) { /* Copy to global buffer */ memcpy(firmware_tag, virt, sz); virt[sz] = '\0'; dev_info(device, "firmware tag is %s\n", virt); } else { dev_err(device, "TAG query failed! %d\n", r); } if (abi_resp.status) tegra_bpmp_free_coherent(sz + 1, virt, phys); else kfree(virt); exit: return r; } static ssize_t bpmp_version(struct device *dev, char *data, size_t size) { return snprintf(data, size, "firmware tag %*.*s", (int)sizeof(firmware_tag), (int)sizeof(firmware_tag), firmware_tag); } static void *bpmp_get_virt_for_alloc(void *virt, dma_addr_t phys) { if (hv_virt_base) return hv_virt_base + phys; return virt; } void *tegra_bpmp_alloc_coherent(size_t size, dma_addr_t *phys, gfp_t flags) { void *virt; if (!device) return NULL; virt = dma_alloc_coherent(device, size, phys, flags); virt = bpmp_get_virt_for_alloc(virt, *phys); return virt; } EXPORT_SYMBOL(tegra_bpmp_alloc_coherent); static void *bpmp_get_virt_for_free(void *virt, dma_addr_t phys) { if (hv_virt_base) return (void *)(virt - hv_virt_base); return virt; } void tegra_bpmp_free_coherent(size_t size, void *vaddr, dma_addr_t phys) { if (!device) { pr_err("device not found\n"); return; } vaddr = bpmp_get_virt_for_free(vaddr, phys); dma_free_coherent(device, size, vaddr, phys); } EXPORT_SYMBOL(tegra_bpmp_free_coherent); static struct syscore_ops bpmp_syscore_ops = { .suspend = tegra_bpmp_suspend, .resume = tegra_bpmp_resume, }; int __bpmp_do_ping(void) { int ret; int challenge = 1; int reply; ret = tegra_bpmp_send_receive_atomic(MRQ_PING, &challenge, sizeof(challenge), &reply, sizeof(reply)); if (ret) return ret; if (reply != challenge * 2) return -EINVAL; return 0; } static int bpmp_do_ping(void) { unsigned long flags; int ret; local_irq_save(flags); ret = __bpmp_do_ping(); local_irq_restore(flags); pr_info("bpmp: ping status is %d\n", ret); return ret; } static struct platform_device bpmp_tty = { .name = "tegra-bpmp-tty", .id = -1, }; static int bpmp_init_powergate(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; u32 pd_cells; if (of_property_read_u32(np, "#power-domain-cells", &pd_cells)) return 0; if (pd_cells != 1) { dev_err(&pdev->dev, "%s #power-domain-cells must be 1\n", np->full_name); return -ENODEV; } return tegra_bpmp_init_powergate(pdev); } static int bpmp_setup_allocator(struct device *dev) { uint32_t mempool_id; int ret; struct tegra_hv_ivm_cookie *ivm; void *virt_base; int flags; ret = of_property_read_u32_index(dev->of_node, "mempool", 0, &mempool_id); if (ret) { dev_err(dev, "failed to read mempool id (%d)\n", ret); return ret; } ivm = tegra_hv_mempool_reserve(mempool_id); if (IS_ERR_OR_NULL(ivm)) { if (!IS_ERR(ivm)) dev_err(dev, "No mempool found\n"); return -ENOMEM; } dev_info(dev, "Found mempool with id %u\n", mempool_id); dev_info(dev, "ivm %pa\n", &ivm->ipa); virt_base = ioremap_cache(ivm->ipa, ivm->size); flags = DMA_MEMORY_EXCLUSIVE; #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) flags |= DMA_MEMORY_NOMAP; #endif ret = dma_declare_coherent_memory(dev, ivm->ipa, 0, ivm->size, flags); #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) if (!(ret & DMA_MEMORY_NOMAP)) { dev_err(dev, "dma_declare_coherent_memory failed (%x)\n", ret); return ret; } #endif hv_virt_base = virt_base; return 0; } static int bpmp_clk_init(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct clk *sclk; sclk = devm_clk_get(dev, "sclk"); if (IS_ERR(sclk)) { dev_err(dev, "cannot get avp sclk\n"); return -ENODEV; } clk_prepare_enable(sclk); return 0; } static int bpmp_linear_map_init(struct platform_device *pdev) { struct device_node *node; uint32_t of_start; uint32_t of_size; int ret; #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) DEFINE_DMA_ATTRS(attrs); #else unsigned long attrs; #endif node = pdev->dev.of_node; ret = of_property_read_u32(node, "carveout-start", &of_start); if (ret) return ret; ret = of_property_read_u32(node, "carveout-size", &of_size); if (ret) return ret; #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) dma_set_attr(DMA_ATTR_SKIP_IOVA_GAP, &attrs); dma_set_attr(DMA_ATTR_SKIP_CPU_SYNC, &attrs); ret = dma_map_linear_attrs(&pdev->dev, of_start, of_size, 0, &attrs); #else attrs = DMA_ATTR_SKIP_IOVA_GAP | DMA_ATTR_SKIP_CPU_SYNC; ret = dma_map_linear_attrs(&pdev->dev, of_start, of_size, 0, attrs); #endif if (ret == DMA_ERROR_CODE) return -ENOMEM; return 0; } struct dentry * __weak bpmp_init_debug(struct platform_device *pdev) { return NULL; } int __weak bpmp_init_cpuidle_debug(struct dentry *root) { return 0; } struct pconfig { const struct channel_cfg *chcfg; const struct mail_ops *ops; uint8_t clk; uint8_t cpuidle; uint8_t hv; uint8_t lin_map; }; static int bpmp_probe(struct platform_device *pdev) { const struct pconfig *cfg; struct dentry *root; int r; device = &pdev->dev; cfg = of_device_get_match_data(&pdev->dev); if (!cfg) { r = -ENODEV; goto err_out; } if (cfg->hv) { r = bpmp_setup_allocator(&pdev->dev); if (r) goto err_out; } if (cfg->lin_map) { r = bpmp_linear_map_init(pdev); if (r) goto err_out; } if (cfg->clk) { r = bpmp_clk_init(pdev); if (r) goto err_out; } root = bpmp_init_debug(pdev); if (root && cfg->cpuidle) { r = bpmp_init_cpuidle_debug(root); if (r) goto err_out; } bpmp_tty.dev.platform_data = root; r = bpmp_do_ping(); r = r ?: bpmp_get_fwtag(); r = r ?: of_platform_populate(device->of_node, NULL, NULL, device); r = r ?: platform_device_register(&bpmp_tty); if (r) goto err_out; register_syscore_ops(&bpmp_syscore_ops); devm_tegrafw_register(device, "bpmp", TFW_NORMAL, bpmp_version, NULL); r = bpmp_init_powergate(pdev); if (r) { dev_err(device, "powergating init failed (%d)\n", r); goto err_out; } dev_info(device, "probe ok\n"); return 0; err_out: dev_err(device, "probe failed (%d)\n", r); return r; } static const struct channel_cfg t210_chcfg = { .channel_mask = 0xfff, .per_cpu_ch_0 = 0, .per_cpu_ch_cnt = 4, .thread_ch_0 = 4, .thread_ch_cnt = 4, .ib_ch_0 = 8, .ib_ch_cnt = 4 }; static const struct channel_cfg t186_chcfg = { .channel_mask = 0x200f, .per_cpu_ch_0 = 3, .per_cpu_ch_cnt = 1, .thread_ch_0 = 0, .thread_ch_cnt = 3, .ib_ch_0 = 13, .ib_ch_cnt = 1 }; static const struct pconfig t210_cfg = { .chcfg = &t210_chcfg, .ops = &t210_mail_ops, .clk = 1, .cpuidle = 1, .lin_map = 1 }; static const struct pconfig t186_native_cfg = { .chcfg = &t186_chcfg, .ops = &t186_native_mail_ops }; static const struct pconfig t186_hv_cfg = { .chcfg = &t186_chcfg, .ops = &t186_hv_mail_ops, .hv = 1 }; const struct of_device_id bpmp_of_matches[] = { { .compatible = "nvidia,tegra186-bpmp", .data = &t186_native_cfg }, { .compatible = "nvidia,tegra186-bpmp-hv", .data = &t186_hv_cfg }, { .compatible = "nvidia,tegra210-bpmp", .data = &t210_cfg }, {} }; static struct platform_driver bpmp_driver = { .probe = bpmp_probe, .driver = { .owner = THIS_MODULE, .name = "bpmp", .of_match_table = of_match_ptr(bpmp_of_matches) } }; static __init int bpmp_init(void) { struct pconfig *cfg; const struct of_device_id *m; struct device_node *np; int r = -ENODEV; np = of_find_matching_node(NULL, bpmp_of_matches); if (!np || !of_device_is_available(np)) goto out; m = of_match_node(bpmp_of_matches, np); cfg = (struct pconfig *)m->data; r = bpmp_mail_init(cfg->chcfg, cfg->ops, np); if (r) goto out; r = platform_driver_register(&bpmp_driver); out: of_node_put(np); return r; } postcore_initcall(bpmp_init);