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 <puneets@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2781118
Reviewed-by: Ketan Patil <ketanp@nvidia.com>
Reviewed-by: Bitan Biswas <bbiswas@nvidia.com>
Reviewed-by: Sachin Nikam <snikam@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
This commit is contained in:
Puneet Saxena
2022-09-23 10:59:03 +00:00
committed by mobile promotions
parent 8442d5d299
commit aa0b664723
2 changed files with 285 additions and 0 deletions

View File

@@ -20,3 +20,4 @@ obj-m += cvnas/
obj-m += hwpm/ obj-m += hwpm/
obj-m += mce/ obj-m += mce/
obj-m += uncore_pmu/ obj-m += uncore_pmu/
obj-m += mc-hwpm.o

View File

@@ -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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/export.h>
#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;
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 <puneets@nvidia.com>");
MODULE_LICENSE("GPL v2");