platform: tegra: Fix MC-HWPM driver

Simplify the MC-HWPM driver and fix the hang seen during probe of it.
These changes are made to scale the driver for future SOCs as well.
The hang is seen because MC registers are accessed in function
get_platform_dram_num_channels() before the MC aperture is io-mapped.

Bug 4235766

Change-Id: I3c8f9229898ac459c616aca0ef400a8b4c16e66a
Signed-off-by: Ashish Mhetre <amhetre@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2963195
Reviewed-by: Ketan Patil <ketanp@nvidia.com>
Reviewed-by: svcacv <svcacv@nvidia.com>
Reviewed-by: Sachin Nikam <snikam@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
This commit is contained in:
Ashish Mhetre
2023-08-17 09:48:16 +00:00
committed by mobile promotions
parent 804feada07
commit 4a11641e79

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
*/
#define pr_fmt(fmt) "mc-hwpm: " fmt
@@ -11,235 +11,114 @@
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <soc/tegra/fuse.h>
#include "soc/tegra/mc.h"
#include <uapi/linux/tegra-soc-hwpm-uapi.h>
#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;
/* Broadcast Channel + 16 MC Channels */
#define MAX_MC_CHANNELS 17
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;
static struct tegra_soc_hwpm_ip_ops hwpm_ip_ops;
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);
}
struct tegra_mc_hwpm {
struct device *dev;
void __iomem **ch_regs;
u32 no_ch;
u64 base_addr;
};
/**
* 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.
* ch_no == 0 = Broadcast Channel
* ch_no == 1 = MC0
* ch_no == 2 = MC1
* .
* .
* ch_no == 16 = MC15
*/
static void memctrl_writel(u32 chnl_no, u32 val, u32 reg)
static u32 mc_readl(struct tegra_mc_hwpm *mc, u32 ch_no, 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);
return readl(mc->ch_regs[ch_no] + reg);
}
static u32 get_platform_dram_num_channels(void)
static void mc_writel(struct tegra_mc_hwpm *mc, u32 ch_no, u32 val, u32 reg)
{
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;
writel(val, mc->ch_regs[ch_no] + reg);
}
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;
struct device *dev = (struct device *)ip_dev;
struct tegra_mc_hwpm *mc;
mc = dev_get_drvdata(dev);
if (!mc) {
pr_err("tegra-mc-hwpm: Invalid device\n");
return -ENODEV;
}
if (inst_element_index > platform_ch_nums) {
pr_err("Incorrect channel number: %u\n", inst_element_index);
return -EPERM;
if (inst_element_index >= mc->no_ch) {
dev_err(mc->dev, "Incorrect channel number: %u\n", inst_element_index);
return -EINVAL;
}
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);
if (reg_op == TEGRA_SOC_HWPM_IP_REG_OP_READ) {
*reg_data = mc_readl(mc, inst_element_index, (u32)reg_offset);
} else if (reg_op == TEGRA_SOC_HWPM_IP_REG_OP_WRITE) {
mc_writel(mc, inst_element_index, *reg_data, (u32)reg_offset);
} else {
dev_err(mc->dev, "Invalid operation\n");
return -EINVAL;
}
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" },
{ }
};
MODULE_DEVICE_TABLE(of, mc_hwpm_of_ids);
/*
* 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;
struct tegra_mc_hwpm *mc;
struct resource *res;
u32 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])
mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL);
if (!mc)
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;
platform_set_drvdata(pdev, mc);
mc->dev = &pdev->dev;
mc->no_ch = MAX_MC_CHANNELS;
mc->ch_regs = devm_kcalloc(mc->dev, mc->no_ch, sizeof(*mc->ch_regs),
GFP_KERNEL);
if (!mc->ch_regs)
return -ENOMEM;
for (i = 0; i < mc->no_ch; i++) {
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
if (!res) {
dev_err(mc->dev, "Missing MC channels in device tree\n");
return -ENODEV;
}
mc->ch_regs[i] = devm_ioremap(mc->dev, res->start, resource_size(res));
if (IS_ERR(mc->ch_regs[i])) {
dev_err(mc->dev, "Failed to ioremap MC channels aperture\n");
return PTR_ERR(mc->ch_regs[i]);
}
if (i == 0)
mc->base_addr = res->start;
}
hwpm_ip_ops.ip_dev = (void *)pdev;
hwpm_ip_ops.ip_dev = (void *)mc->dev;
hwpm_ip_ops.resource_enum = TEGRA_SOC_HWPM_RESOURCE_MSS_CHANNEL;
hwpm_ip_ops.ip_base_address = pdev->resource[0].start;
hwpm_ip_ops.ip_base_address = mc->base_addr;
hwpm_ip_ops.hwpm_ip_reg_op = &tegra_mc_hwpm_reg_op;
tegra_soc_hwpm_ip_register(&hwpm_ip_ops);
@@ -248,9 +127,18 @@ static int tegra_mc_hwpm_hwpm_probe(struct platform_device *pdev)
static int tegra_mc_hwpm_remove(struct platform_device *pdev)
{
hwpm_ip_ops.ip_dev = (void *)pdev;
struct device *dev = &pdev->dev;
struct tegra_mc_hwpm *mc;
mc = dev_get_drvdata(dev);
if (!mc) {
pr_err("tegra-mc-hwpm: Invalid device\n");
return -ENODEV;
}
hwpm_ip_ops.ip_dev = (void *)mc->dev;
hwpm_ip_ops.resource_enum = TEGRA_SOC_HWPM_RESOURCE_MSS_CHANNEL;
hwpm_ip_ops.ip_base_address = pdev->resource[0].start;
hwpm_ip_ops.ip_base_address = mc->base_addr;
hwpm_ip_ops.hwpm_ip_reg_op = NULL;
tegra_soc_hwpm_ip_unregister(&hwpm_ip_ops);
@@ -270,21 +158,17 @@ static struct platform_driver mc_hwpm_driver = {
static int __init tegra_mc_hwpm_init(void)
{
int ret;
ret = platform_driver_register(&mc_hwpm_driver);
if (ret)
return ret;
return 0;
return platform_driver_register(&mc_hwpm_driver);
}
module_init(tegra_mc_hwpm_init);
static void __exit tegra_mc_hwpm_fini(void)
static void __exit tegra_mc_hwpm_exit(void)
{
platform_driver_unregister(&mc_hwpm_driver);
}
module_exit(tegra_mc_hwpm_fini);
module_exit(tegra_mc_hwpm_exit);
MODULE_DESCRIPTION("MC Hardware Performace Monitor driver");
MODULE_AUTHOR("Puneet Saxena <puneets@nvidia.com>");
MODULE_AUTHOR("Ashish Mhetre <amhetre@nvidia.com>");
MODULE_DESCRIPTION("MC Hardware Performace Monitor Interface driver");
MODULE_LICENSE("GPL v2");