From aa0b664723219dae9eb8a7a395167ef258b01514 Mon Sep 17 00:00:00 2001 From: Puneet Saxena Date: Fri, 23 Sep 2022 10:59:03 +0000 Subject: [PATCH] drivers: tegra: make mc-hwpm module oot Make mc-hwpm module oot. Below are the reasons why making a fresh copy in nvdia-oot folder - - hwpm is introduced from T234 onwards - mc-hwpm is dependent on mc driver. nvidia-oot uses upstream mc driver. Hence mc-hwpm should also use upstream mc driver. - Thats why porting changes needed in mc-hwpm driver to be incompatible with upstream mc driver Bug 3728991 Change-Id: I2d7347d968895e080fa79d100b4c50c094ced1e3 Signed-off-by: Puneet Saxena Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2781118 Reviewed-by: Ketan Patil Reviewed-by: Bitan Biswas Reviewed-by: Sachin Nikam GVS: Gerrit_Virtual_Submit --- drivers/platform/tegra/Makefile | 1 + drivers/platform/tegra/mc-hwpm.c | 284 +++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 drivers/platform/tegra/mc-hwpm.c diff --git a/drivers/platform/tegra/Makefile b/drivers/platform/tegra/Makefile index 757eba16..53519f1c 100644 --- a/drivers/platform/tegra/Makefile +++ b/drivers/platform/tegra/Makefile @@ -20,3 +20,4 @@ obj-m += cvnas/ obj-m += hwpm/ obj-m += mce/ obj-m += uncore_pmu/ +obj-m += mc-hwpm.o diff --git a/drivers/platform/tegra/mc-hwpm.c b/drivers/platform/tegra/mc-hwpm.c new file mode 100644 index 00000000..976d4d42 --- /dev/null +++ b/drivers/platform/tegra/mc-hwpm.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#define pr_fmt(fmt) "mc-hwpm: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include "soc/tegra/mc.h" + +#include + +#define MC_EMEM_ADR_CFG_CHANNEL_ENABLE_0 0xdf8 +#define CH_MASK 0xFFFF /* Change bit counting if this mask changes */ +#define MC_MAX_CHANNELS 16 +/* Total channels = Broadcast channel + MC_MAX_CHANNELS */ +#define TOTAL_CHANNELS (1 + MC_MAX_CHANNELS) +static void __iomem *memctlr_regs[TOTAL_CHANNELS]; +static u32 platform_ch_nums; + +static bool is_tegra_safety_build(void) +{ +#ifdef CONFIG_OF + return of_property_read_bool(of_chosen, + "nvidia,tegra-safety-build"); +#else + return false; +#endif +} +/** + * Read from the MC. + * + * @idx The MC channel to read from. + * @reg The offset of the register to read. + * + * Read from the specified MC channel: 0 -> Broadcast channel/ Global channel + * 1 -> MC0, 16 -> MC16, etc. If @idx + * corresponds to a non-existent channel then 0 is returned. + */ +static u32 memctrl_readl(u32 chnl_no, u32 reg) +{ + static bool warned; + + if (is_tegra_safety_build()) { + if (!warned) { + pr_warn("WARNING: VM isn't allowed to read MC register space in Safety Build"); + warned = true; + } + return 0xffff; + } + + if (chnl_no > platform_ch_nums) + return 0; + + return readl(memctlr_regs[chnl_no] + reg); +} + +/** + * Write to the MC. + * + * @idx The MC channel to write to. + * @val Value to write. + * @reg The offset of the register to write. + * + * Write to the specified MC channel: 0 -> MC0, 1 -> MC1, etc. For writes there + * is a special channel, %MC_BROADCAST_CHANNEL, which writes to all channels. If + * @idx corresponds to a non-existent channel then the write is dropped. + */ +static void memctrl_writel(u32 chnl_no, u32 val, u32 reg) +{ + static bool warned; + + if (is_tegra_safety_build()) { + if (!warned) { + pr_warn("WARNING: VM isn't allowed to write into MC register space in Safety Build"); + warned = true; + } + return; + } + + if (chnl_no > platform_ch_nums) { + pr_err("Incorrect channel number: %u\n", chnl_no); + return; + } + + writel(val, memctlr_regs[chnl_no] + reg); +} + +static u32 get_platform_dram_num_channels(void) +{ + u32 plt_ch_nums = 0; + u32 ch; + + ch = memctrl_readl(0, MC_EMEM_ADR_CFG_CHANNEL_ENABLE_0) & CH_MASK; + while (ch) { + if (ch & 1) + plt_ch_nums++; + ch >>= 1; + } + + return plt_ch_nums; +} + +static int tegra_mc_hwpm_reg_op(void *ip_dev, + enum tegra_soc_hwpm_ip_reg_op reg_op, + u32 inst_element_index, u64 reg_offset, u32 *reg_data) +{ + if (reg_offset > 0x10000) { + pr_err("Incorrect reg offset: %llu\n", reg_offset); + return -EPERM; + } + + if (inst_element_index > platform_ch_nums) { + pr_err("Incorrect channel number: %u\n", inst_element_index); + return -EPERM; + } + + if (reg_op == TEGRA_SOC_HWPM_IP_REG_OP_READ) + *reg_data = memctrl_readl(inst_element_index, (u32)reg_offset); + else if (reg_op == TEGRA_SOC_HWPM_IP_REG_OP_WRITE) + memctrl_writel(inst_element_index, *reg_data, (u32)reg_offset); + + return 0; +} + +/* + * Map an MC register space. Each MC has a set of register ranges which must + * be parsed. The first starting address in the set of ranges is returned as + * it is expected that the DT file has the register ranges in ascending + * order. + * + * device 0 = global channel. + * device n = specific channel device-1, e.g device = 1 ==> channel 0. + */ +static void __iomem *tegra_mc_hwpm_map_regs(struct platform_device *pdev, u32 device) +{ + struct resource res; + const void *prop; + void __iomem *regs; + void __iomem *regs_start = NULL; + u32 reg_ranges; + u32 i, start; + + prop = of_get_property(pdev->dev.of_node, "reg-ranges", NULL); + if (!prop) { + pr_err("Failed to get MC MMIO region\n"); + pr_err(" device = %d: missing reg-ranges\n", device); + return NULL; + } + + reg_ranges = be32_to_cpup(prop); + start = device * reg_ranges; + + for (i = 0; i < reg_ranges; i++) { + regs = of_iomap(pdev->dev.of_node, start + i); + if (!regs) { + pr_err("Failed to get MC MMIO region\n"); + pr_err(" device = %d, range = %u\n", device, i); + return NULL; + } + + if (i == 0) + regs_start = regs; + } + + if (of_address_to_resource(pdev->dev.of_node, start, &res)) + return NULL; + + pr_debug("mapped MMIO address: 0x%p -> 0x%lx\n", + regs_start, (unsigned long)res.start); + + return regs_start; +} + +static struct tegra_soc_hwpm_ip_ops hwpm_ip_ops; + +static const struct of_device_id mc_hwpm_of_ids[] = { + { .compatible = "nvidia,tegra-t23x-mc-hwpm" }, + { } +}; + +/* + * MC driver init. + */ +static int tegra_mc_hwpm_hwpm_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + u32 dram_channels; + const void *prop; + int i; + + if (!pdev->dev.of_node) + return -EINVAL; + + match = of_match_device(mc_hwpm_of_ids, &pdev->dev); + if (!match) { + pr_err("Missing DT entry!\n"); + return -EINVAL; + } + + /* + * Channel count. + */ + prop = of_get_property(pdev->dev.of_node, "channels", NULL); + if (!prop) + dram_channels = 1; + else + dram_channels = be32_to_cpup(prop); + + platform_ch_nums = get_platform_dram_num_channels(); + if (platform_ch_nums > dram_channels) { + pr_err("channel mismatch error: max_soc_channels: %u platform_channels %u\n", + dram_channels, + platform_ch_nums); + return -EINVAL; + } + + /* + * Store reg mapping for broadcast channel + */ + memctlr_regs[0] = tegra_mc_hwpm_map_regs(pdev, 0); + if (!memctlr_regs[0]) + return -ENOMEM; + + /* Populate the rest of the channels... */ + if (platform_ch_nums > 1) { + for (i = 1; i <= platform_ch_nums; i++) { + memctlr_regs[i] = tegra_mc_hwpm_map_regs(pdev, i); + if (!memctlr_regs[i]) + return -ENOMEM; + } + } + + hwpm_ip_ops.ip_dev = (void *)pdev; + hwpm_ip_ops.resource_enum = TEGRA_SOC_HWPM_RESOURCE_MSS_CHANNEL; + hwpm_ip_ops.ip_base_address = pdev->resource[0].start; + hwpm_ip_ops.hwpm_ip_reg_op = &tegra_mc_hwpm_reg_op; + tegra_soc_hwpm_ip_register(&hwpm_ip_ops); + + return 0; +} + +static int tegra_mc_hwpm_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver mc_hwpm_driver = { + .driver = { + .name = "tegra-mc-hwpm", + .of_match_table = mc_hwpm_of_ids, + .owner = THIS_MODULE, + }, + + .probe = tegra_mc_hwpm_hwpm_probe, + .remove = tegra_mc_hwpm_remove, +}; + +static int __init tegra_mc_hwpm_init(void) +{ + int ret; + + ret = platform_driver_register(&mc_hwpm_driver); + if (ret) + return ret; + + return 0; +} +module_init(tegra_mc_hwpm_init); + +static void __exit tegra_mc_hwpm_fini(void) +{ +} +module_exit(tegra_mc_hwpm_fini); + +MODULE_DESCRIPTION("MC Hardware Performace Monitor driver"); +MODULE_AUTHOR("Puneet Saxena "); +MODULE_LICENSE("GPL v2");