mirror of
git://nv-tegra.nvidia.com/linux-hwpm.git
synced 2025-12-22 17:30:40 +03:00
- Add macro to define maximum hwpm ioctl structure size. - Use switch-case logic for ioctl command to invoke corresponding functions. - Remove structure and array describing hwpm ioctl commands. Jira THWPM-58 Change-Id: I6c274123c82af928c3fdc82d7b1fb4b805704156 Signed-off-by: Vedashree Vidwans <vvidwans@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2729470 Reviewed-by: svc_kernel_abi <svc_kernel_abi@nvidia.com> Reviewed-by: Vasuki Shankar <vasukis@nvidia.com> Reviewed-by: Seema Khowala <seemaj@nvidia.com> GVS: Gerrit_Virtual_Submit
560 lines
13 KiB
C
560 lines
13 KiB
C
/*
|
|
* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. 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.
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/dma-buf.h>
|
|
#include <soc/tegra/fuse.h>
|
|
#include <uapi/linux/tegra-soc-hwpm-uapi.h>
|
|
|
|
#include <tegra_hwpm_log.h>
|
|
#include <tegra_hwpm_io.h>
|
|
#include <tegra_hwpm.h>
|
|
#include <tegra_hwpm_common.h>
|
|
|
|
#define LA_CLK_RATE 625000000UL
|
|
|
|
static int tegra_hwpm_get_device_info_ioctl(struct tegra_soc_hwpm *hwpm,
|
|
struct tegra_soc_hwpm_device_info *device_info)
|
|
{
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
device_info->chip = hwpm->device_info.chip;
|
|
device_info->chip_revision = hwpm->device_info.chip_revision;
|
|
device_info->revision = hwpm->device_info.revision;
|
|
device_info->platform = hwpm->device_info.platform;
|
|
|
|
tegra_hwpm_dbg(hwpm, hwpm_info | hwpm_dbg_device_info,
|
|
"chip id 0x%x", device_info->chip);
|
|
tegra_hwpm_dbg(hwpm, hwpm_info | hwpm_dbg_device_info,
|
|
"chip_revision 0x%x", device_info->chip_revision);
|
|
tegra_hwpm_dbg(hwpm, hwpm_info | hwpm_dbg_device_info,
|
|
"revision 0x%x", device_info->revision);
|
|
tegra_hwpm_dbg(hwpm, hwpm_info | hwpm_dbg_device_info,
|
|
"platform 0x%x", device_info->platform);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_hwpm_get_floorsweep_info_ioctl(struct tegra_soc_hwpm *hwpm,
|
|
struct tegra_soc_hwpm_ip_floorsweep_info *fs_info)
|
|
{
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
if (fs_info->num_queries > TEGRA_SOC_HWPM_IP_QUERIES_MAX) {
|
|
tegra_hwpm_err(hwpm, "Number of queries exceed max limit of %u",
|
|
TEGRA_SOC_HWPM_IP_QUERIES_MAX);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return tegra_hwpm_get_floorsweep_info(hwpm, fs_info);
|
|
}
|
|
|
|
static int tegra_hwpm_get_resource_info_ioctl(struct tegra_soc_hwpm *hwpm,
|
|
struct tegra_soc_hwpm_resource_info *rsrc_info)
|
|
{
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
if (rsrc_info->num_queries > TEGRA_SOC_HWPM_RESOURCE_QUERIES_MAX) {
|
|
tegra_hwpm_err(hwpm, "Number of queries exceed max limit of %u",
|
|
TEGRA_SOC_HWPM_RESOURCE_QUERIES_MAX);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return tegra_hwpm_get_resource_info(hwpm, rsrc_info);
|
|
|
|
}
|
|
|
|
static int tegra_hwpm_reserve_resource_ioctl(struct tegra_soc_hwpm *hwpm,
|
|
struct tegra_soc_hwpm_reserve_resource *reserve_resource)
|
|
{
|
|
u32 resource = reserve_resource->resource;
|
|
int ret = 0;
|
|
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
if (hwpm->bind_completed) {
|
|
tegra_hwpm_err(hwpm, "The RESERVE_RESOURCE IOCTL can only be"
|
|
" called before the BIND IOCTL.");
|
|
return -EPERM;
|
|
}
|
|
|
|
if (resource >= TERGA_SOC_HWPM_NUM_RESOURCES) {
|
|
tegra_hwpm_err(hwpm, "Requested resource %d is out of bounds.",
|
|
resource);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = tegra_hwpm_reserve_resource(hwpm, resource);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to reserve resource %d", resource);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_hwpm_alloc_pma_stream_ioctl(struct tegra_soc_hwpm *hwpm,
|
|
struct tegra_soc_hwpm_alloc_pma_stream *alloc_pma_stream)
|
|
{
|
|
int ret = 0;
|
|
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
if (hwpm->bind_completed) {
|
|
tegra_hwpm_err(hwpm, "The ALLOC_PMA_STREAM IOCTL can only be"
|
|
" called before the BIND IOCTL.");
|
|
return -EPERM;
|
|
}
|
|
|
|
if (alloc_pma_stream->stream_buf_size == 0) {
|
|
tegra_hwpm_err(hwpm, "stream_buf_size is 0");
|
|
return -EINVAL;
|
|
}
|
|
if (alloc_pma_stream->stream_buf_fd == 0) {
|
|
tegra_hwpm_err(hwpm, "Invalid stream_buf_fd");
|
|
return -EINVAL;
|
|
}
|
|
if (alloc_pma_stream->mem_bytes_buf_fd == 0) {
|
|
tegra_hwpm_err(hwpm, "Invalid mem_bytes_buf_fd");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = tegra_hwpm_map_stream_buffer(hwpm, alloc_pma_stream);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to map stream buffer");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_hwpm_bind_ioctl(struct tegra_soc_hwpm *hwpm)
|
|
{
|
|
int ret = 0;
|
|
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
ret = tegra_hwpm_bind_resources(hwpm);
|
|
if (ret != 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to bind resources");
|
|
} else {
|
|
hwpm->bind_completed = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_hwpm_query_allowlist_ioctl(struct tegra_soc_hwpm *hwpm,
|
|
struct tegra_soc_hwpm_query_allowlist *query_allowlist)
|
|
{
|
|
int ret = 0;
|
|
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
if (!hwpm->bind_completed) {
|
|
tegra_hwpm_err(hwpm,
|
|
"The QUERY_ALLOWLIST IOCTL can only be called"
|
|
" after the BIND IOCTL.");
|
|
return -EPERM;
|
|
}
|
|
|
|
if (query_allowlist->allowlist == NULL) {
|
|
/* Userspace is querying allowlist size only */
|
|
if (hwpm->full_alist_size == 0) {
|
|
/*Full alist size is not computed yet */
|
|
ret = tegra_hwpm_get_allowlist_size(hwpm);
|
|
if (ret != 0) {
|
|
tegra_hwpm_err(hwpm,
|
|
"failed to get alist_size");
|
|
return ret;
|
|
}
|
|
}
|
|
query_allowlist->allowlist_size = hwpm->full_alist_size;
|
|
} else {
|
|
/* Concatenate allowlists and return */
|
|
ret = tegra_hwpm_update_allowlist(hwpm, query_allowlist);
|
|
if (ret != 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to update full alist");
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_hwpm_exec_reg_ops_ioctl(struct tegra_soc_hwpm *hwpm,
|
|
struct tegra_soc_hwpm_exec_reg_ops *exec_reg_ops)
|
|
{
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
if (!hwpm->bind_completed) {
|
|
tegra_hwpm_err(hwpm, "The EXEC_REG_OPS IOCTL can only be called"
|
|
" after the BIND IOCTL.");
|
|
return -EPERM;
|
|
}
|
|
|
|
return tegra_hwpm_exec_regops(hwpm, exec_reg_ops);
|
|
}
|
|
|
|
static int tegra_hwpm_update_get_put_ioctl(struct tegra_soc_hwpm *hwpm,
|
|
struct tegra_soc_hwpm_update_get_put *update_get_put)
|
|
{
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
if (!hwpm->bind_completed) {
|
|
tegra_hwpm_err(hwpm,
|
|
"The UPDATE_GET_PUT IOCTL can only be called"
|
|
" after the BIND IOCTL.");
|
|
return -EPERM;
|
|
}
|
|
if (!hwpm->mem_bytes_kernel) {
|
|
tegra_hwpm_err(hwpm,
|
|
"mem_bytes buffer is not mapped in the driver");
|
|
return -ENXIO;
|
|
}
|
|
|
|
return tegra_hwpm_update_mem_bytes(hwpm, update_get_put);
|
|
}
|
|
|
|
static long tegra_hwpm_ioctl(struct file *file,
|
|
unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
struct tegra_soc_hwpm *hwpm = NULL;
|
|
u8 *buf;
|
|
|
|
if ((_IOC_TYPE(cmd) != TEGRA_SOC_HWPM_IOC_MAGIC) ||
|
|
(_IOC_NR(cmd) < 0) ||
|
|
(_IOC_NR(cmd) >= TERGA_SOC_HWPM_NUM_IOCTLS) ||
|
|
(_IOC_SIZE(cmd) > TEGRA_SOC_HWPM_MAX_ARG_SIZE)) {
|
|
tegra_hwpm_err(hwpm, "Invalid IOCTL call");
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
if (!file) {
|
|
tegra_hwpm_err(hwpm, "Invalid file");
|
|
ret = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
hwpm = file->private_data;
|
|
if (!hwpm) {
|
|
tegra_hwpm_err(hwpm, "Invalid hwpm struct");
|
|
ret = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
if (!hwpm->device_opened) {
|
|
tegra_hwpm_err(hwpm, "Device open failed, can't process IOCTL");
|
|
ret = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
if (!(_IOC_DIR(cmd) & _IOC_NONE)) {
|
|
buf = kzalloc(TEGRA_SOC_HWPM_MAX_ARG_SIZE, GFP_KERNEL);
|
|
if (!buf) {
|
|
tegra_hwpm_err(hwpm, "Kernel buf allocation failed");
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (_IOC_DIR(cmd) & _IOC_WRITE) {
|
|
if (copy_from_user(buf, (void __user *)arg, _IOC_SIZE(cmd))) {
|
|
tegra_hwpm_err(hwpm, "Copy data from userspace failed");
|
|
ret = -EFAULT;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
switch (cmd) {
|
|
case TEGRA_CTRL_CMD_SOC_HWPM_DEVICE_INFO:
|
|
ret = tegra_hwpm_get_device_info_ioctl(hwpm,
|
|
(struct tegra_soc_hwpm_device_info *)buf);
|
|
break;
|
|
case TEGRA_CTRL_CMD_SOC_HWPM_IP_FLOORSWEEP_INFO:
|
|
ret = tegra_hwpm_get_floorsweep_info_ioctl(hwpm,
|
|
(struct tegra_soc_hwpm_ip_floorsweep_info *)buf);
|
|
break;
|
|
case TEGRA_CTRL_CMD_SOC_HWPM_RESOURCE_INFO:
|
|
ret = tegra_hwpm_get_resource_info_ioctl(hwpm,
|
|
(struct tegra_soc_hwpm_resource_info *)buf);
|
|
break;
|
|
case TEGRA_CTRL_CMD_SOC_HWPM_RESERVE_RESOURCE:
|
|
ret = tegra_hwpm_reserve_resource_ioctl(hwpm,
|
|
(struct tegra_soc_hwpm_reserve_resource *)buf);
|
|
break;
|
|
case TEGRA_CTRL_CMD_SOC_HWPM_ALLOC_PMA_STREAM:
|
|
ret = tegra_hwpm_alloc_pma_stream_ioctl(hwpm,
|
|
(struct tegra_soc_hwpm_alloc_pma_stream *)buf);
|
|
break;
|
|
case TEGRA_CTRL_CMD_BIND:
|
|
ret = tegra_hwpm_bind_ioctl(hwpm);
|
|
break;
|
|
case TEGRA_CTRL_CMD_SOC_HWPM_QUERY_ALLOWLIST:
|
|
ret = tegra_hwpm_query_allowlist_ioctl(hwpm,
|
|
(struct tegra_soc_hwpm_query_allowlist *)buf);
|
|
break;
|
|
case TEGRA_CTRL_CMD_SOC_HWPM_EXEC_REG_OPS:
|
|
ret = tegra_hwpm_exec_reg_ops_ioctl(hwpm,
|
|
(struct tegra_soc_hwpm_exec_reg_ops *)buf);
|
|
break;
|
|
case TEGRA_CTRL_CMD_SOC_HWPM_UPDATE_GET_PUT:
|
|
ret = tegra_hwpm_update_get_put_ioctl(hwpm,
|
|
(struct tegra_soc_hwpm_update_get_put *)buf);
|
|
break;
|
|
default:
|
|
tegra_hwpm_err(hwpm, "Unknown IOCTL command");
|
|
ret = -ENOTTY;
|
|
goto fail;
|
|
}
|
|
|
|
if ((ret == 0) && (_IOC_DIR(cmd) & _IOC_READ)) {
|
|
if (copy_to_user((void __user *)arg, buf, _IOC_SIZE(cmd))) {
|
|
tegra_hwpm_err(hwpm, "Copy buffer to user failed");
|
|
ret = -EFAULT;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
fail:
|
|
if (buf) {
|
|
kfree(buf);
|
|
}
|
|
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "IOCTL cmd %d failed(%d)!", cmd, ret);
|
|
} else {
|
|
tegra_hwpm_dbg(hwpm, hwpm_info,
|
|
"IOCTL cmd %d completed successfully!", cmd);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_hwpm_open(struct inode *inode, struct file *filp)
|
|
{
|
|
int ret = 0;
|
|
unsigned int minor;
|
|
struct tegra_soc_hwpm *hwpm = NULL;
|
|
|
|
if (!inode) {
|
|
tegra_hwpm_err(hwpm, "Invalid inode");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!filp) {
|
|
tegra_hwpm_err(hwpm, "Invalid file");
|
|
return -EINVAL;
|
|
}
|
|
|
|
minor = iminor(inode);
|
|
if (minor > 0) {
|
|
tegra_hwpm_err(hwpm, "Incorrect minor number");
|
|
return -EBADFD;
|
|
}
|
|
|
|
hwpm = container_of(inode->i_cdev, struct tegra_soc_hwpm, cdev);
|
|
if (!hwpm) {
|
|
tegra_hwpm_err(hwpm, "Invalid hwpm struct");
|
|
return -EINVAL;
|
|
}
|
|
filp->private_data = hwpm;
|
|
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
/* Initialize driver on first open call only */
|
|
if (!atomic_add_unless(&hwpm->hwpm_in_use, 1U, 1U)) {
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (tegra_platform_is_silicon()) {
|
|
ret = reset_control_assert(hwpm->hwpm_rst);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "hwpm reset assert failed");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_assert(hwpm->la_rst);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "la reset assert failed");
|
|
goto fail;
|
|
}
|
|
/* Set required parent for la_clk */
|
|
if (hwpm->la_clk && hwpm->la_parent_clk) {
|
|
ret = clk_set_parent(hwpm->la_clk, hwpm->la_parent_clk);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm,
|
|
"la clk set parent failed");
|
|
goto fail;
|
|
}
|
|
}
|
|
/* set la_clk rate to 625 MHZ */
|
|
ret = clk_set_rate(hwpm->la_clk, LA_CLK_RATE);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "la clock set rate failed");
|
|
goto fail;
|
|
}
|
|
ret = clk_prepare_enable(hwpm->la_clk);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "la clock enable failed");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_deassert(hwpm->la_rst);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "la reset deassert failed");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_deassert(hwpm->hwpm_rst);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "hwpm reset deassert failed");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
ret = tegra_hwpm_setup_hw(hwpm);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to setup hw");
|
|
goto fail;
|
|
}
|
|
|
|
ret = tegra_hwpm_setup_sw(hwpm);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to setup sw");
|
|
goto fail;
|
|
}
|
|
|
|
hwpm->device_opened = true;
|
|
|
|
return 0;
|
|
fail:
|
|
ret = tegra_hwpm_release_hw(hwpm);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to release hw");
|
|
}
|
|
|
|
tegra_hwpm_err(hwpm, "%s failed", __func__);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t tegra_hwpm_read(struct file *file,
|
|
char __user *ubuf,
|
|
size_t count,
|
|
loff_t *offp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* FIXME: Fix double release bug */
|
|
static int tegra_hwpm_release(struct inode *inode, struct file *filp)
|
|
{
|
|
int ret = 0, err = 0;
|
|
struct tegra_soc_hwpm *hwpm = NULL;
|
|
|
|
if (!inode) {
|
|
tegra_hwpm_err(hwpm, "Invalid inode");
|
|
return -EINVAL;
|
|
}
|
|
if (!filp) {
|
|
tegra_hwpm_err(hwpm, "Invalid file");
|
|
return -EINVAL;
|
|
}
|
|
|
|
hwpm = container_of(inode->i_cdev, struct tegra_soc_hwpm, cdev);
|
|
if (!hwpm) {
|
|
tegra_hwpm_err(hwpm, "Invalid hwpm struct");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tegra_hwpm_fn(hwpm, " ");
|
|
|
|
if (hwpm->device_opened == false) {
|
|
/* Device was not opened, do nothing */
|
|
return 0;
|
|
}
|
|
|
|
ret = tegra_hwpm_disable_triggers(hwpm);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to disable PMA triggers");
|
|
err = ret;
|
|
}
|
|
|
|
/* Disable and release reserved IPs */
|
|
ret = tegra_hwpm_release_resources(hwpm);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to release IP apertures");
|
|
err = ret;
|
|
goto fail;
|
|
}
|
|
|
|
/* Clear MEM_BYTES pipeline */
|
|
ret = tegra_hwpm_clear_mem_pipeline(hwpm);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to clear MEM_BYTES pipeline");
|
|
err = ret;
|
|
goto fail;
|
|
}
|
|
|
|
ret = tegra_hwpm_release_hw(hwpm);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "Failed to release hw");
|
|
err = ret;
|
|
goto fail;
|
|
}
|
|
|
|
if (tegra_platform_is_silicon()) {
|
|
ret = reset_control_assert(hwpm->hwpm_rst);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "hwpm reset assert failed");
|
|
err = ret;
|
|
goto fail;
|
|
}
|
|
ret = reset_control_assert(hwpm->la_rst);
|
|
if (ret < 0) {
|
|
tegra_hwpm_err(hwpm, "la reset assert failed");
|
|
err = ret;
|
|
goto fail;
|
|
}
|
|
clk_disable_unprepare(hwpm->la_clk);
|
|
}
|
|
|
|
/* De-init driver on last close call only */
|
|
if (!atomic_dec_and_test(&hwpm->hwpm_in_use)) {
|
|
return 0;
|
|
}
|
|
|
|
hwpm->device_opened = false;
|
|
fail:
|
|
return err;
|
|
}
|
|
|
|
/* File ops for device node */
|
|
const struct file_operations tegra_soc_hwpm_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = tegra_hwpm_open,
|
|
.read = tegra_hwpm_read,
|
|
.release = tegra_hwpm_release,
|
|
.unlocked_ioctl = tegra_hwpm_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = tegra_hwpm_ioctl,
|
|
#endif
|
|
};
|