blob: 259c04e7c1a9fe47c3f6a6170b744d779f9fb989 [file] [log] [blame]
/* Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* -------------------------------------------------------------------------
* Includes
* -------------------------------------------------------------------------
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/of_platform.h>
#include <linux/poll.h>
#include <linux/regulator/consumer.h>
#include <linux/sizes.h>
#include <linux/thermal.h>
#include <linux/soc/qcom/llcc-qcom.h>
#include <linux/soc/qcom/cdsprm_cxlimit.h>
#include <soc/qcom/devfreq_devbw.h>
#include "npu_common.h"
#include "npu_hw.h"
/* -------------------------------------------------------------------------
* Defines
* -------------------------------------------------------------------------
*/
#define CLASS_NAME "npu"
#define DRIVER_NAME "msm_npu"
#define DDR_MAPPED_START_ADDR 0x00000000
#define DDR_MAPPED_SIZE (SZ_1G * 4ULL)
#define MBOX_OP_TIMEOUTMS 1000
/* -------------------------------------------------------------------------
* File Scope Prototypes
* -------------------------------------------------------------------------
*/
static int npu_enable_regulators(struct npu_device *npu_dev);
static void npu_disable_regulators(struct npu_device *npu_dev);
static int npu_enable_clocks(struct npu_device *npu_dev, bool post_pil);
static void npu_disable_clocks(struct npu_device *npu_dev, bool post_pil);
static int npu_enable_core_clocks(struct npu_device *npu_dev);
static void npu_disable_core_clocks(struct npu_device *npu_dev);
static uint32_t npu_calc_power_level(struct npu_device *npu_dev);
static ssize_t npu_show_capabilities(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t npu_show_pwr_state(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t npu_store_pwr_state(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
static ssize_t npu_show_perf_mode_override(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t npu_store_perf_mode_override(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
static ssize_t npu_show_dcvs_mode(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t npu_store_dcvs_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
static ssize_t npu_show_fw_unload_delay_ms(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t npu_store_fw_unload_delay_ms(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
static ssize_t npu_show_fw_state(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t npu_store_fw_state(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
static void npu_suspend_devbw(struct npu_device *npu_dev);
static void npu_resume_devbw(struct npu_device *npu_dev);
static bool npu_is_post_clock(const char *clk_name);
static bool npu_is_exclude_rate_clock(const char *clk_name);
static int npu_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state);
static int npu_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state);
static int npu_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state);
static int npu_open(struct inode *inode, struct file *file);
static int npu_close(struct inode *inode, struct file *file);
static int npu_get_info(struct npu_client *client, unsigned long arg);
static int npu_map_buf(struct npu_client *client, unsigned long arg);
static int npu_unmap_buf(struct npu_client *client,
unsigned long arg);
static int npu_load_network(struct npu_client *client,
unsigned long arg);
static int npu_load_network_v2(struct npu_client *client,
unsigned long arg);
static int npu_unload_network(struct npu_client *client,
unsigned long arg);
static int npu_exec_network(struct npu_client *client,
unsigned long arg);
static int npu_exec_network_v2(struct npu_client *client,
unsigned long arg);
static int npu_receive_event(struct npu_client *client,
unsigned long arg);
static int npu_set_fw_state(struct npu_client *client, uint32_t enable);
static int npu_set_property(struct npu_client *client,
unsigned long arg);
static int npu_get_property(struct npu_client *client,
unsigned long arg);
static long npu_ioctl(struct file *file, unsigned int cmd,
unsigned long arg);
static unsigned int npu_poll(struct file *filp, struct poll_table_struct *p);
static int npu_parse_dt_clock(struct npu_device *npu_dev);
static int npu_parse_dt_regulator(struct npu_device *npu_dev);
static int npu_of_parse_pwrlevels(struct npu_device *npu_dev,
struct device_node *node);
static int npu_pwrctrl_init(struct npu_device *npu_dev);
static int npu_probe(struct platform_device *pdev);
static int npu_remove(struct platform_device *pdev);
static int npu_suspend(struct platform_device *dev, pm_message_t state);
static int npu_resume(struct platform_device *dev);
static int __init npu_init(void);
static void __exit npu_exit(void);
static int npu_set_power_level(struct npu_device *npu_dev, bool notify_cxlimit);
static uint32_t npu_notify_cdsprm_cxlimit_corner(struct npu_device *npu_dev,
uint32_t pwr_lvl);
/* -------------------------------------------------------------------------
* File Scope Variables
* -------------------------------------------------------------------------
*/
static const char * const npu_post_clocks[] = {
"npu_cpc_clk",
"npu_cpc_timer_clk"
};
static const char * const npu_exclude_rate_clocks[] = {
"qdss_clk",
"at_clk",
"trig_clk",
"sleep_clk",
"xo_clk",
"conf_noc_ahb_clk",
"comp_noc_axi_clk",
"npu_core_cti_clk",
"npu_core_apb_clk",
"npu_core_atb_clk",
"npu_cpc_timer_clk",
"qtimer_core_clk",
"bwmon_clk",
"bto_core_clk"
};
static struct npu_reg npu_saved_bw_registers[] = {
{ BWMON2_SAMPLING_WINDOW, 0, false },
{ BWMON2_BYTE_COUNT_THRESHOLD_HIGH, 0, false },
{ BWMON2_BYTE_COUNT_THRESHOLD_MEDIUM, 0, false },
{ BWMON2_BYTE_COUNT_THRESHOLD_LOW, 0, false },
{ BWMON2_ZONE_ACTIONS, 0, false },
{ BWMON2_ZONE_COUNT_THRESHOLD, 0, false },
};
static const struct npu_irq npu_irq_info[NPU_MAX_IRQ] = {
{"ipc_irq", 0, IRQF_TRIGGER_HIGH},
{"error_irq", 0, IRQF_TRIGGER_RISING | IRQF_ONESHOT},
{"wdg_bite_irq", 0, IRQF_TRIGGER_RISING | IRQF_ONESHOT},
};
static struct npu_device *g_npu_dev;
/* -------------------------------------------------------------------------
* Entry Points for Probe
* -------------------------------------------------------------------------
*/
/* Sys FS */
static DEVICE_ATTR(caps, 0444, npu_show_capabilities, NULL);
static DEVICE_ATTR(pwr, 0644, npu_show_pwr_state, npu_store_pwr_state);
static DEVICE_ATTR(perf_mode_override, 0644,
npu_show_perf_mode_override, npu_store_perf_mode_override);
static DEVICE_ATTR(dcvs_mode, 0644,
npu_show_dcvs_mode, npu_store_dcvs_mode);
static DEVICE_ATTR(fw_unload_delay_ms, 0644,
npu_show_fw_unload_delay_ms, npu_store_fw_unload_delay_ms);
static DEVICE_ATTR(fw_state, 0644, npu_show_fw_state, npu_store_fw_state);
static struct attribute *npu_fs_attrs[] = {
&dev_attr_caps.attr,
&dev_attr_pwr.attr,
&dev_attr_perf_mode_override.attr,
&dev_attr_dcvs_mode.attr,
&dev_attr_fw_state.attr,
&dev_attr_fw_unload_delay_ms.attr,
NULL
};
static struct attribute_group npu_fs_attr_group = {
.attrs = npu_fs_attrs
};
static const struct of_device_id npu_dt_match[] = {
{ .compatible = "qcom,msm-npu",},
{}
};
static struct platform_driver npu_driver = {
.probe = npu_probe,
.remove = npu_remove,
#if defined(CONFIG_PM)
.suspend = npu_suspend,
.resume = npu_resume,
#endif
.driver = {
.name = "msm_npu",
.owner = THIS_MODULE,
.of_match_table = npu_dt_match,
.pm = NULL,
},
};
static const struct file_operations npu_fops = {
.owner = THIS_MODULE,
.open = npu_open,
.release = npu_close,
.unlocked_ioctl = npu_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = npu_ioctl,
#endif
.poll = npu_poll,
};
static const struct thermal_cooling_device_ops npu_cooling_ops = {
.get_max_state = npu_get_max_state,
.get_cur_state = npu_get_cur_state,
.set_cur_state = npu_set_cur_state,
};
/* -------------------------------------------------------------------------
* SysFS - Capabilities
* -------------------------------------------------------------------------
*/
static ssize_t npu_show_capabilities(struct device *dev,
struct device_attribute *attr, char *buf)
{
size_t ret = 0;
struct npu_device *npu_dev = dev_get_drvdata(dev);
if (!npu_enable_core_power(npu_dev)) {
if (scnprintf(buf, PAGE_SIZE, "hw_version :0x%X",
REGR(npu_dev, NPU_HW_VERSION)) < 0)
ret = -EINVAL;
npu_disable_core_power(npu_dev);
} else
ret = -EPERM;
return ret;
}
/* -------------------------------------------------------------------------
* SysFS - Power State
* -------------------------------------------------------------------------
*/
static ssize_t npu_show_pwr_state(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct npu_device *npu_dev = dev_get_drvdata(dev);
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
return scnprintf(buf, PAGE_SIZE, "%s\n",
(pwr->pwr_vote_num > 0) ? "on" : "off");
}
static ssize_t npu_store_pwr_state(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct npu_device *npu_dev = dev_get_drvdata(dev);
bool pwr_on = false;
if (strtobool(buf, &pwr_on) < 0)
return -EINVAL;
if (pwr_on) {
if (npu_enable_core_power(npu_dev))
return -EPERM;
} else {
npu_disable_core_power(npu_dev);
}
return count;
}
/* -------------------------------------------------------------------------
* SysFS - perf_mode_override
* -------------------------------------------------------------------------
*/
static ssize_t npu_show_perf_mode_override(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct npu_device *npu_dev = dev_get_drvdata(dev);
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
return scnprintf(buf, PAGE_SIZE, "%d\n", pwr->perf_mode_override);
}
static ssize_t npu_store_perf_mode_override(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct npu_client client;
struct npu_device *npu_dev = dev_get_drvdata(dev);
uint32_t val;
int rc;
rc = kstrtou32(buf, 10, &val);
if (rc) {
pr_err("Invalid input for perf mode setting\n");
return -EINVAL;
}
val = min(val, npu_dev->pwrctrl.num_pwrlevels);
npu_dev->pwrctrl.perf_mode_override = val;
pr_info("setting uc_pwrlevel_override to %d\n", val);
client.npu_dev = npu_dev;
npu_host_set_perf_mode(&client, 0, val);
return count;
}
static ssize_t npu_show_dcvs_mode(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct npu_device *npu_dev = dev_get_drvdata(dev);
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
return scnprintf(buf, PAGE_SIZE, "%d\n", pwr->dcvs_mode);
}
static ssize_t npu_store_dcvs_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct npu_device *npu_dev = dev_get_drvdata(dev);
struct msm_npu_property prop;
uint32_t val;
int ret = 0;
ret = kstrtou32(buf, 10, &val);
if (ret) {
pr_err("Invalid input for dcvs mode setting\n");
return -EINVAL;
}
val = min(val, (uint32_t)(npu_dev->pwrctrl.num_pwrlevels - 1));
pr_debug("sysfs: setting dcvs_mode to %d\n", val);
prop.prop_id = MSM_NPU_PROP_ID_DCVS_MODE;
prop.num_of_params = 1;
prop.network_hdl = 0;
prop.prop_param[0] = val;
ret = npu_host_set_fw_property(npu_dev, &prop);
if (ret) {
pr_err("npu_host_set_fw_property failed %d\n", ret);
return ret;
}
npu_dev->pwrctrl.dcvs_mode = val;
return count;
}
/* -------------------------------------------------------------------------
* SysFS - Delayed FW unload
* -------------------------------------------------------------------------
*/
static ssize_t npu_show_fw_unload_delay_ms(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct npu_device *npu_dev = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n",
npu_dev->host_ctx.fw_unload_delay_ms);
}
static ssize_t npu_store_fw_unload_delay_ms(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct npu_device *npu_dev = dev_get_drvdata(dev);
uint32_t val;
int rc;
rc = kstrtou32(buf, 10, &val);
if (rc) {
pr_err("Invalid input for fw unload delay setting\n");
return -EINVAL;
}
npu_dev->host_ctx.fw_unload_delay_ms = val;
pr_debug("setting fw_unload_delay_ms to %d\n", val);
return count;
}
/* -------------------------------------------------------------------------
* SysFS - firmware state
* -------------------------------------------------------------------------
*/
static ssize_t npu_show_fw_state(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct npu_device *npu_dev = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%s\n",
(npu_dev->host_ctx.fw_state == FW_ENABLED) ?
"on" : "off");
}
static ssize_t npu_store_fw_state(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct npu_device *npu_dev = dev_get_drvdata(dev);
struct npu_client client;
bool enable = false;
int rc;
if (strtobool(buf, &enable) < 0)
return -EINVAL;
client.npu_dev = npu_dev;
rc = npu_set_fw_state(&client, enable ? 1 : 0);
if (rc) {
pr_err("%s fw failed\n", enable ? "enable" : "disable");
return rc;
}
return count;
}
/* -------------------------------------------------------------------------
* Power Related
* -------------------------------------------------------------------------
*/
static enum npu_power_level cdsprm_corner_to_npu_power_level(
enum cdsprm_npu_corner corner)
{
enum npu_power_level pwr_lvl = NPU_PWRLEVEL_TURBO_L1;
switch (corner) {
case CDSPRM_NPU_CLK_OFF:
pwr_lvl = NPU_PWRLEVEL_OFF;
break;
case CDSPRM_NPU_MIN_SVS:
pwr_lvl = NPU_PWRLEVEL_MINSVS;
break;
case CDSPRM_NPU_LOW_SVS:
pwr_lvl = NPU_PWRLEVEL_LOWSVS;
break;
case CDSPRM_NPU_SVS:
pwr_lvl = NPU_PWRLEVEL_SVS;
break;
case CDSPRM_NPU_SVS_L1:
pwr_lvl = NPU_PWRLEVEL_SVS_L1;
break;
case CDSPRM_NPU_NOM:
pwr_lvl = NPU_PWRLEVEL_NOM;
break;
case CDSPRM_NPU_NOM_L1:
pwr_lvl = NPU_PWRLEVEL_NOM_L1;
break;
case CDSPRM_NPU_TURBO:
pwr_lvl = NPU_PWRLEVEL_TURBO;
break;
case CDSPRM_NPU_TURBO_L1:
default:
pwr_lvl = NPU_PWRLEVEL_TURBO_L1;
break;
}
return pwr_lvl;
}
static enum cdsprm_npu_corner npu_power_level_to_cdsprm_corner(
enum npu_power_level pwr_lvl)
{
enum cdsprm_npu_corner corner = CDSPRM_NPU_MIN_SVS;
switch (pwr_lvl) {
case NPU_PWRLEVEL_OFF:
corner = CDSPRM_NPU_CLK_OFF;
break;
case NPU_PWRLEVEL_MINSVS:
corner = CDSPRM_NPU_MIN_SVS;
break;
case NPU_PWRLEVEL_LOWSVS:
corner = CDSPRM_NPU_LOW_SVS;
break;
case NPU_PWRLEVEL_SVS:
corner = CDSPRM_NPU_SVS;
break;
case NPU_PWRLEVEL_SVS_L1:
corner = CDSPRM_NPU_SVS_L1;
break;
case NPU_PWRLEVEL_NOM:
corner = CDSPRM_NPU_NOM;
break;
case NPU_PWRLEVEL_NOM_L1:
corner = CDSPRM_NPU_NOM_L1;
break;
case NPU_PWRLEVEL_TURBO:
corner = CDSPRM_NPU_TURBO;
break;
case NPU_PWRLEVEL_TURBO_L1:
default:
corner = CDSPRM_NPU_TURBO_L1;
break;
}
return corner;
}
static int npu_set_cdsprm_corner_limit(enum cdsprm_npu_corner corner)
{
struct npu_pwrctrl *pwr;
enum npu_power_level pwr_lvl;
if (!g_npu_dev)
return 0;
pwr = &g_npu_dev->pwrctrl;
pwr_lvl = cdsprm_corner_to_npu_power_level(corner);
pwr->cdsprm_pwrlevel = pwr_lvl;
pr_debug("power level from cdsp %d\n", pwr_lvl);
return npu_set_power_level(g_npu_dev, false);
}
const struct cdsprm_npu_limit_cbs cdsprm_npu_limit_cbs = {
.set_corner_limit = npu_set_cdsprm_corner_limit,
};
int npu_notify_cdsprm_cxlimit_activity(struct npu_device *npu_dev, bool enable)
{
if (!npu_dev->cxlimit_registered)
return 0;
pr_debug("notify cxlimit %s activity\n", enable ? "enable" : "disable");
return cdsprm_cxlimit_npu_activity_notify(enable ? 1 : 0);
}
static uint32_t npu_notify_cdsprm_cxlimit_corner(
struct npu_device *npu_dev, uint32_t pwr_lvl)
{
uint32_t corner, pwr_lvl_to_set;
if (!npu_dev->cxlimit_registered)
return pwr_lvl;
corner = npu_power_level_to_cdsprm_corner(pwr_lvl);
corner = cdsprm_cxlimit_npu_corner_notify(corner);
pwr_lvl_to_set = cdsprm_corner_to_npu_power_level(corner);
pr_debug("Notify cdsprm %d:%d\n", pwr_lvl,
pwr_lvl_to_set);
return pwr_lvl_to_set;
}
int npu_cdsprm_cxlimit_init(struct npu_device *npu_dev)
{
bool enabled;
int ret = 0;
enabled = of_property_read_bool(npu_dev->pdev->dev.of_node,
"qcom,npu-cxlimit-enable");
pr_debug("qcom,npu-xclimit-enable is %s\n", enabled ? "true" : "false");
npu_dev->cxlimit_registered = false;
if (enabled) {
ret = cdsprm_cxlimit_npu_limit_register(&cdsprm_npu_limit_cbs);
if (ret) {
pr_err("register cxlimit npu limit failed\n");
} else {
pr_debug("register cxlimit npu limit succeeds\n");
npu_dev->cxlimit_registered = true;
}
}
return ret;
}
int npu_cdsprm_cxlimit_deinit(struct npu_device *npu_dev)
{
int ret = 0;
if (npu_dev->cxlimit_registered) {
ret = cdsprm_cxlimit_npu_limit_deregister();
if (ret)
pr_err("deregister cxlimit npu limit failed\n");
npu_dev->cxlimit_registered = false;
}
return ret;
}
int npu_enable_core_power(struct npu_device *npu_dev)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
int ret = 0;
mutex_lock(&npu_dev->dev_lock);
if (!pwr->pwr_vote_num) {
ret = npu_enable_regulators(npu_dev);
if (ret)
goto fail;
ret = npu_enable_core_clocks(npu_dev);
if (ret) {
npu_disable_regulators(npu_dev);
pwr->pwr_vote_num = 0;
goto fail;
}
npu_resume_devbw(npu_dev);
}
pwr->pwr_vote_num++;
fail:
mutex_unlock(&npu_dev->dev_lock);
return ret;
}
void npu_disable_core_power(struct npu_device *npu_dev)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
mutex_lock(&npu_dev->dev_lock);
if (!pwr->pwr_vote_num) {
mutex_unlock(&npu_dev->dev_lock);
return;
}
pwr->pwr_vote_num--;
if (!pwr->pwr_vote_num) {
npu_suspend_devbw(npu_dev);
npu_disable_core_clocks(npu_dev);
npu_disable_regulators(npu_dev);
pwr->active_pwrlevel = pwr->default_pwrlevel;
pwr->uc_pwrlevel = pwr->max_pwrlevel;
pwr->cdsprm_pwrlevel = pwr->max_pwrlevel;
pwr->cur_dcvs_activity = pwr->num_pwrlevels;
pr_debug("setting back to power level=%d\n",
pwr->active_pwrlevel);
}
mutex_unlock(&npu_dev->dev_lock);
}
static int npu_enable_core_clocks(struct npu_device *npu_dev)
{
return npu_enable_clocks(npu_dev, false);
}
static void npu_disable_core_clocks(struct npu_device *npu_dev)
{
return npu_disable_clocks(npu_dev, false);
}
int npu_enable_post_pil_clocks(struct npu_device *npu_dev)
{
return npu_enable_clocks(npu_dev, true);
}
void npu_disable_post_pil_clocks(struct npu_device *npu_dev)
{
npu_disable_clocks(npu_dev, true);
}
static uint32_t npu_power_level_from_index(struct npu_device *npu_dev,
uint32_t index)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
if (index >= pwr->num_pwrlevels)
index = pwr->num_pwrlevels - 1;
return pwr->pwrlevels[index].pwr_level;
}
static uint32_t npu_power_level_to_index(struct npu_device *npu_dev,
uint32_t pwr_lvl)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
int i;
for (i = 0; i < pwr->num_pwrlevels; i++) {
if (pwr->pwrlevels[i].pwr_level > pwr_lvl)
break;
}
return i == 0 ? 0 : i - 1;
}
static uint32_t npu_calc_power_level(struct npu_device *npu_dev)
{
uint32_t ret_level;
uint32_t therm_pwr_level = npu_dev->thermalctrl.pwr_level;
uint32_t active_pwr_level = npu_dev->pwrctrl.active_pwrlevel;
uint32_t uc_pwr_level = npu_dev->pwrctrl.uc_pwrlevel;
/*
* pick the lowese power level between thermal power and usecase power
* settings
*/
ret_level = min(therm_pwr_level, uc_pwr_level);
pr_debug("%s therm=%d active=%d uc=%d set level=%d\n",
__func__, therm_pwr_level, active_pwr_level, uc_pwr_level,
ret_level);
return ret_level;
}
static int npu_set_power_level(struct npu_device *npu_dev, bool notify_cxlimit)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
struct npu_pwrlevel *pwrlevel;
int i, ret = 0;
uint32_t pwr_level_to_set, pwr_level_to_cdsprm, pwr_level_idx;
/* get power level to set */
pwr_level_to_set = npu_calc_power_level(npu_dev);
pwr_level_to_cdsprm = pwr_level_to_set;
if (!pwr->pwr_vote_num) {
pr_debug("power is not enabled during set request\n");
pwr->active_pwrlevel = min(pwr_level_to_set,
npu_dev->pwrctrl.cdsprm_pwrlevel);
return 0;
}
/* notify cxlimit to get allowed power level */
if ((pwr_level_to_set > pwr->active_pwrlevel) && notify_cxlimit)
pwr_level_to_set = npu_notify_cdsprm_cxlimit_corner(
npu_dev, pwr_level_to_cdsprm);
pwr_level_to_set = min(pwr_level_to_set,
npu_dev->pwrctrl.cdsprm_pwrlevel);
/* if the same as current, dont do anything */
if (pwr_level_to_set == pwr->active_pwrlevel) {
pr_debug("power level %d doesn't change\n", pwr_level_to_set);
return 0;
}
pr_debug("setting power level to [%d]\n", pwr_level_to_set);
pwr_level_idx = npu_power_level_to_index(npu_dev, pwr_level_to_set);
pwrlevel = &npu_dev->pwrctrl.pwrlevels[pwr_level_idx];
for (i = 0; i < npu_dev->core_clk_num; i++) {
if (npu_is_exclude_rate_clock(
npu_dev->core_clks[i].clk_name))
continue;
if (npu_dev->host_ctx.fw_state == FW_DISABLED) {
if (npu_is_post_clock(
npu_dev->core_clks[i].clk_name))
continue;
}
pr_debug("requested rate of clock [%s] to [%ld]\n",
npu_dev->core_clks[i].clk_name, pwrlevel->clk_freq[i]);
ret = clk_set_rate(npu_dev->core_clks[i].clk,
pwrlevel->clk_freq[i]);
if (ret) {
pr_debug("clk_set_rate %s to %ld failed with %d\n",
npu_dev->core_clks[i].clk_name,
pwrlevel->clk_freq[i], ret);
break;
}
}
if ((pwr_level_to_cdsprm < pwr->active_pwrlevel) && notify_cxlimit) {
npu_notify_cdsprm_cxlimit_corner(npu_dev,
pwr_level_to_cdsprm);
pr_debug("Notify cdsprm(post) %d\n", pwr_level_to_cdsprm);
}
pwr->active_pwrlevel = pwr_level_to_set;
return ret;
}
int npu_set_uc_power_level(struct npu_device *npu_dev,
uint32_t perf_mode)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
uint32_t uc_pwrlevel_to_set;
uc_pwrlevel_to_set = npu_power_level_from_index(npu_dev,
perf_mode - 1);
if (uc_pwrlevel_to_set > pwr->max_pwrlevel)
uc_pwrlevel_to_set = pwr->max_pwrlevel;
pwr->uc_pwrlevel = uc_pwrlevel_to_set;
return npu_set_power_level(npu_dev, true);
}
/* -------------------------------------------------------------------------
* Bandwidth Related
* -------------------------------------------------------------------------
*/
static void npu_save_bw_registers(struct npu_device *npu_dev)
{
int i;
if (!npu_dev->bwmon_io.base)
return;
for (i = 0; i < ARRAY_SIZE(npu_saved_bw_registers); i++) {
npu_saved_bw_registers[i].val = npu_bwmon_reg_read(npu_dev,
npu_saved_bw_registers[i].off);
npu_saved_bw_registers[i].valid = true;
}
}
static void npu_restore_bw_registers(struct npu_device *npu_dev)
{
int i;
if (!npu_dev->bwmon_io.base)
return;
for (i = 0; i < ARRAY_SIZE(npu_saved_bw_registers); i++) {
if (npu_saved_bw_registers[i].valid) {
npu_bwmon_reg_write(npu_dev,
npu_saved_bw_registers[i].off,
npu_saved_bw_registers[i].val);
npu_saved_bw_registers[i].valid = false;
}
}
}
static void npu_suspend_devbw(struct npu_device *npu_dev)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
int ret;
if (pwr->bwmon_enabled && pwr->devbw) {
pwr->bwmon_enabled = 0;
ret = devfreq_suspend_devbw(pwr->devbw);
if (ret)
pr_err("devfreq_suspend_devbw failed rc:%d\n",
ret);
npu_save_bw_registers(npu_dev);
}
}
static void npu_resume_devbw(struct npu_device *npu_dev)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
int ret;
if (!pwr->bwmon_enabled && pwr->devbw) {
pwr->bwmon_enabled = 1;
npu_restore_bw_registers(npu_dev);
ret = devfreq_resume_devbw(pwr->devbw);
if (ret)
pr_err("devfreq_resume_devbw failed rc:%d\n", ret);
}
}
/* -------------------------------------------------------------------------
* Clocks Related
* -------------------------------------------------------------------------
*/
static bool npu_is_post_clock(const char *clk_name)
{
int ret = false;
int i;
for (i = 0; i < ARRAY_SIZE(npu_post_clocks); i++) {
if (!strcmp(clk_name, npu_post_clocks[i])) {
ret = true;
break;
}
}
return ret;
}
static bool npu_is_exclude_rate_clock(const char *clk_name)
{
int ret = false;
int i;
for (i = 0; i < ARRAY_SIZE(npu_exclude_rate_clocks); i++) {
if (!strcmp(clk_name, npu_exclude_rate_clocks[i])) {
ret = true;
break;
}
}
return ret;
}
static int npu_enable_clocks(struct npu_device *npu_dev, bool post_pil)
{
int i, rc = 0;
struct npu_clk *core_clks = npu_dev->core_clks;
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
struct npu_pwrlevel *pwrlevel;
uint32_t pwrlevel_to_set, pwrlevel_idx;
pwrlevel_to_set = pwr->active_pwrlevel;
if (!post_pil) {
pwrlevel_to_set = npu_notify_cdsprm_cxlimit_corner(
npu_dev, pwrlevel_to_set);
pr_debug("Notify cdsprm %d\n", pwrlevel_to_set);
pwr->active_pwrlevel = pwrlevel_to_set;
}
pwrlevel_idx = npu_power_level_to_index(npu_dev, pwrlevel_to_set);
pwrlevel = &pwr->pwrlevels[pwrlevel_idx];
for (i = 0; i < npu_dev->core_clk_num; i++) {
if (post_pil) {
if (!npu_is_post_clock(core_clks[i].clk_name))
continue;
} else {
if (npu_is_post_clock(core_clks[i].clk_name))
continue;
}
pr_debug("enabling clock %s\n", core_clks[i].clk_name);
rc = clk_prepare_enable(core_clks[i].clk);
if (rc) {
pr_err("%s enable failed\n",
core_clks[i].clk_name);
break;
}
if (npu_is_exclude_rate_clock(core_clks[i].clk_name))
continue;
pr_debug("setting rate of clock %s to %ld\n",
core_clks[i].clk_name, pwrlevel->clk_freq[i]);
rc = clk_set_rate(core_clks[i].clk,
pwrlevel->clk_freq[i]);
/* not fatal error, keep using previous clk rate */
if (rc) {
pr_err("clk_set_rate %s to %ld failed\n",
core_clks[i].clk_name,
pwrlevel->clk_freq[i]);
rc = 0;
}
}
if (rc) {
for (i--; i >= 0; i--) {
if (post_pil) {
if (!npu_is_post_clock(core_clks[i].clk_name))
continue;
} else {
if (npu_is_post_clock(core_clks[i].clk_name))
continue;
}
pr_debug("disabling clock %s\n", core_clks[i].clk_name);
clk_disable_unprepare(core_clks[i].clk);
}
}
return rc;
}
static void npu_disable_clocks(struct npu_device *npu_dev, bool post_pil)
{
int i = 0;
struct npu_clk *core_clks = npu_dev->core_clks;
if (!post_pil) {
npu_notify_cdsprm_cxlimit_corner(npu_dev, NPU_PWRLEVEL_OFF);
pr_debug("Notify cdsprm clock off\n");
}
for (i = npu_dev->core_clk_num - 1; i >= 0 ; i--) {
if (post_pil) {
if (!npu_is_post_clock(core_clks[i].clk_name))
continue;
} else {
if (npu_is_post_clock(core_clks[i].clk_name))
continue;
}
pr_debug("disabling clock %s\n", core_clks[i].clk_name);
clk_disable_unprepare(core_clks[i].clk);
}
}
/* -------------------------------------------------------------------------
* Thermal Functions
* -------------------------------------------------------------------------
*/
static int npu_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct npu_device *npu_dev = cdev->devdata;
struct npu_thermalctrl *thermalctrl = &npu_dev->thermalctrl;
pr_debug("enter %s thermal max state=%lu\n", __func__,
thermalctrl->max_state);
*state = thermalctrl->max_state;
return 0;
}
static int npu_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct npu_device *npu_dev = cdev->devdata;
struct npu_thermalctrl *thermal = &npu_dev->thermalctrl;
pr_debug("enter %s thermal current state=%lu\n", __func__,
thermal->current_state);
*state = thermal->current_state;
return 0;
}
static int
npu_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
{
struct npu_device *npu_dev = cdev->devdata;
struct npu_thermalctrl *thermal = &npu_dev->thermalctrl;
pr_debug("enter %s request state=%lu\n", __func__, state);
if (state > thermal->max_state)
return -EINVAL;
thermal->current_state = state;
thermal->pwr_level = npu_power_level_from_index(npu_dev,
thermal->max_state - state);
return npu_set_power_level(npu_dev, true);
}
/* -------------------------------------------------------------------------
* Regulator Related
* -------------------------------------------------------------------------
*/
static int npu_enable_regulators(struct npu_device *npu_dev)
{
int i = 0;
int rc = 0;
struct npu_host_ctx *host_ctx = &npu_dev->host_ctx;
struct npu_regulator *regulators = npu_dev->regulators;
if (!host_ctx->power_vote_num) {
for (i = 0; i < npu_dev->regulator_num; i++) {
rc = regulator_enable(regulators[i].regulator);
if (rc < 0) {
pr_err("%s enable failed\n",
regulators[i].regulator_name);
break;
}
pr_debug("regulator %s enabled\n",
regulators[i].regulator_name);
}
}
host_ctx->power_vote_num++;
return rc;
}
static void npu_disable_regulators(struct npu_device *npu_dev)
{
int i = 0;
struct npu_host_ctx *host_ctx = &npu_dev->host_ctx;
struct npu_regulator *regulators = npu_dev->regulators;
if (host_ctx->power_vote_num > 0) {
for (i = 0; i < npu_dev->regulator_num; i++) {
regulator_disable(regulators[i].regulator);
pr_debug("regulator %s disabled\n",
regulators[i].regulator_name);
}
host_ctx->power_vote_num--;
}
}
/* -------------------------------------------------------------------------
* Interrupt Related
* -------------------------------------------------------------------------
*/
int npu_enable_irq(struct npu_device *npu_dev)
{
int i;
/* clear pending irq state */
REGW(npu_dev, NPU_MASTERn_IPC_IRQ_OUT(0), 0x0);
REGW(npu_dev, NPU_MASTERn_ERROR_IRQ_CLEAR(0), NPU_ERROR_IRQ_MASK);
REGW(npu_dev, NPU_MASTERn_ERROR_IRQ_ENABLE(0), NPU_ERROR_IRQ_MASK);
REGW(npu_dev, NPU_MASTERn_ERROR_IRQ_OWNER(0), NPU_ERROR_IRQ_MASK);
REGW(npu_dev, NPU_MASTERn_WDOG_IRQ_OWNER(0), NPU_WDOG_IRQ_MASK);
for (i = 0; i < NPU_MAX_IRQ; i++) {
if (npu_dev->irq[i].irq != 0) {
enable_irq(npu_dev->irq[i].irq);
pr_debug("enable irq %d\n", npu_dev->irq[i].irq);
}
}
return 0;
}
void npu_disable_irq(struct npu_device *npu_dev)
{
int i;
for (i = 0; i < NPU_MAX_IRQ; i++) {
if (npu_dev->irq[i].irq != 0) {
disable_irq(npu_dev->irq[i].irq);
pr_debug("disable irq %d\n", npu_dev->irq[i].irq);
}
}
REGW(npu_dev, NPU_MASTERn_ERROR_IRQ_ENABLE(0), 0);
/* clear pending irq state */
REGW(npu_dev, NPU_MASTERn_IPC_IRQ_OUT(0), 0x0);
REGW(npu_dev, NPU_MASTERn_ERROR_IRQ_CLEAR(0), NPU_ERROR_IRQ_MASK);
}
/* -------------------------------------------------------------------------
* System Cache
* -------------------------------------------------------------------------
*/
int npu_enable_sys_cache(struct npu_device *npu_dev)
{
int rc = 0;
uint32_t reg_val = 0;
if (!npu_dev->host_ctx.sys_cache_disable) {
npu_dev->sys_cache = llcc_slice_getd(&(npu_dev->pdev->dev),
"npu");
if (IS_ERR_OR_NULL(npu_dev->sys_cache)) {
pr_warn("unable to init sys cache\n");
npu_dev->sys_cache = NULL;
npu_dev->host_ctx.sys_cache_disable = true;
return 0;
}
/* set npu side regs - program SCID */
reg_val = NPU_CACHE_ATTR_IDn___POR | SYS_CACHE_SCID;
REGW(npu_dev, NPU_CACHE_ATTR_IDn(0), reg_val);
REGW(npu_dev, NPU_CACHE_ATTR_IDn(1), reg_val);
REGW(npu_dev, NPU_CACHE_ATTR_IDn(2), reg_val);
REGW(npu_dev, NPU_CACHE_ATTR_IDn(3), reg_val);
REGW(npu_dev, NPU_CACHE_ATTR_IDn(4), reg_val);
pr_debug("prior to activate sys cache\n");
rc = llcc_slice_activate(npu_dev->sys_cache);
if (rc) {
pr_warn("failed to activate sys cache\n");
llcc_slice_putd(npu_dev->sys_cache);
npu_dev->sys_cache = NULL;
return 0;
}
pr_debug("sys cache activated\n");
}
return rc;
}
void npu_disable_sys_cache(struct npu_device *npu_dev)
{
int rc = 0;
if (!npu_dev->host_ctx.sys_cache_disable) {
if (npu_dev->sys_cache) {
rc = llcc_slice_deactivate(npu_dev->sys_cache);
if (rc) {
pr_err("failed to deactivate sys cache\n");
return;
}
pr_debug("sys cache deactivated\n");
llcc_slice_putd(npu_dev->sys_cache);
npu_dev->sys_cache = NULL;
}
}
}
/* -------------------------------------------------------------------------
* Open/Close
* -------------------------------------------------------------------------
*/
static int npu_open(struct inode *inode, struct file *file)
{
struct npu_device *npu_dev = container_of(inode->i_cdev,
struct npu_device, cdev);
struct npu_client *client;
client = kmalloc(sizeof(*client), GFP_KERNEL);
if (!client)
return -ENOMEM;
client->npu_dev = npu_dev;
init_waitqueue_head(&client->wait);
mutex_init(&client->list_lock);
INIT_LIST_HEAD(&client->evt_list);
INIT_LIST_HEAD(&(client->mapped_buffer_list));
file->private_data = client;
return 0;
}
static int npu_close(struct inode *inode, struct file *file)
{
struct npu_client *client = file->private_data;
struct npu_kevent *kevent;
npu_host_cleanup_networks(client);
while (!list_empty(&client->evt_list)) {
kevent = list_first_entry(&client->evt_list,
struct npu_kevent, list);
list_del(&kevent->list);
kfree(kevent);
}
mutex_destroy(&client->list_lock);
kfree(client);
return 0;
}
/* -------------------------------------------------------------------------
* IOCTL Implementations
* -------------------------------------------------------------------------
*/
static int npu_get_info(struct npu_client *client, unsigned long arg)
{
struct npu_device *npu_dev = client->npu_dev;
struct msm_npu_get_info_ioctl req;
void __user *argp = (void __user *)arg;
int ret = 0;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
ret = npu_host_get_info(npu_dev, &req);
if (ret) {
pr_err("npu_host_get_info failed\n");
return ret;
}
ret = copy_to_user(argp, &req, sizeof(req));
if (ret) {
pr_err("fail to copy to user\n");
return -EFAULT;
}
return 0;
}
static int npu_map_buf(struct npu_client *client, unsigned long arg)
{
struct msm_npu_map_buf_ioctl req;
void __user *argp = (void __user *)arg;
int ret = 0;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
ret = npu_host_map_buf(client, &req);
if (ret) {
pr_err("npu_host_map_buf failed\n");
return ret;
}
ret = copy_to_user(argp, &req, sizeof(req));
if (ret) {
pr_err("fail to copy to user\n");
return -EFAULT;
}
return 0;
}
static int npu_unmap_buf(struct npu_client *client, unsigned long arg)
{
struct msm_npu_unmap_buf_ioctl req;
void __user *argp = (void __user *)arg;
int ret = 0;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
ret = npu_host_unmap_buf(client, &req);
if (ret) {
pr_err("npu_host_unmap_buf failed\n");
return ret;
}
ret = copy_to_user(argp, &req, sizeof(req));
if (ret) {
pr_err("fail to copy to user\n");
return -EFAULT;
}
return 0;
}
static int npu_load_network(struct npu_client *client,
unsigned long arg)
{
struct msm_npu_load_network_ioctl req;
struct msm_npu_unload_network_ioctl unload_req;
void __user *argp = (void __user *)arg;
int ret = 0;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
pr_debug("network load with perf request %d\n", req.perf_mode);
ret = npu_host_load_network(client, &req);
if (ret) {
pr_err("npu_host_load_network failed %d\n", ret);
return ret;
}
ret = copy_to_user(argp, &req, sizeof(req));
if (ret) {
pr_err("fail to copy to user\n");
ret = -EFAULT;
unload_req.network_hdl = req.network_hdl;
npu_host_unload_network(client, &unload_req);
}
return ret;
}
static int npu_load_network_v2(struct npu_client *client,
unsigned long arg)
{
struct msm_npu_load_network_ioctl_v2 req;
struct msm_npu_unload_network_ioctl unload_req;
void __user *argp = (void __user *)arg;
struct msm_npu_patch_info_v2 *patch_info = NULL;
int ret;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
if (req.patch_info_num > NPU_MAX_PATCH_NUM) {
pr_err("Invalid patch info num %d[max:%d]\n",
req.patch_info_num, NPU_MAX_PATCH_NUM);
return -EINVAL;
}
if (req.patch_info_num) {
patch_info = kmalloc_array(req.patch_info_num,
sizeof(*patch_info), GFP_KERNEL);
if (!patch_info)
return -ENOMEM;
ret = copy_from_user(patch_info,
(void __user *)req.patch_info,
req.patch_info_num * sizeof(*patch_info));
if (ret) {
pr_err("fail to copy patch info\n");
kfree(patch_info);
return -EFAULT;
}
}
pr_debug("network load with perf request %d\n", req.perf_mode);
ret = npu_host_load_network_v2(client, &req, patch_info);
kfree(patch_info);
if (ret) {
pr_err("npu_host_load_network_v2 failed %d\n", ret);
return ret;
}
ret = copy_to_user(argp, &req, sizeof(req));
if (ret) {
pr_err("fail to copy to user\n");
ret = -EFAULT;
unload_req.network_hdl = req.network_hdl;
npu_host_unload_network(client, &unload_req);
}
return ret;
}
static int npu_unload_network(struct npu_client *client,
unsigned long arg)
{
struct msm_npu_unload_network_ioctl req;
void __user *argp = (void __user *)arg;
int ret = 0;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
ret = npu_host_unload_network(client, &req);
if (ret) {
pr_err("npu_host_unload_network failed %d\n", ret);
return ret;
}
ret = copy_to_user(argp, &req, sizeof(req));
if (ret) {
pr_err("fail to copy to user\n");
return -EFAULT;
}
return 0;
}
static int npu_exec_network(struct npu_client *client,
unsigned long arg)
{
struct msm_npu_exec_network_ioctl req;
void __user *argp = (void __user *)arg;
int ret = 0;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
if ((req.input_layer_num > MSM_NPU_MAX_INPUT_LAYER_NUM) ||
(req.output_layer_num > MSM_NPU_MAX_OUTPUT_LAYER_NUM)) {
pr_err("Invalid input/out layer num %d[max:%d] %d[max:%d]\n",
req.input_layer_num, MSM_NPU_MAX_INPUT_LAYER_NUM,
req.output_layer_num, MSM_NPU_MAX_OUTPUT_LAYER_NUM);
return -EINVAL;
}
if (!req.patching_required) {
pr_err("Only support patched network");
return -EINVAL;
}
ret = npu_host_exec_network(client, &req);
if (ret) {
pr_err("npu_host_exec_network failed %d\n", ret);
return ret;
}
ret = copy_to_user(argp, &req, sizeof(req));
if (ret) {
pr_err("fail to copy to user\n");
return -EFAULT;
}
return 0;
}
static int npu_exec_network_v2(struct npu_client *client,
unsigned long arg)
{
struct msm_npu_exec_network_ioctl_v2 req;
void __user *argp = (void __user *)arg;
struct msm_npu_patch_buf_info *patch_buf_info = NULL;
int ret;
ret = copy_from_user(&req, argp, sizeof(req));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
if ((req.patch_buf_info_num > NPU_MAX_PATCH_NUM) ||
(req.patch_buf_info_num == 0)) {
pr_err("Invalid patch buf info num %d[max:%d]\n",
req.patch_buf_info_num, NPU_MAX_PATCH_NUM);
return -EINVAL;
}
if (req.stats_buf_size > NPU_MAX_STATS_BUF_SIZE) {
pr_err("Invalid stats buffer size %d max %d\n",
req.stats_buf_size, NPU_MAX_STATS_BUF_SIZE);
return -EINVAL;
}
if (req.patch_buf_info_num) {
patch_buf_info = kmalloc_array(req.patch_buf_info_num,
sizeof(*patch_buf_info), GFP_KERNEL);
if (!patch_buf_info)
return -ENOMEM;
ret = copy_from_user(patch_buf_info,
(void __user *)req.patch_buf_info,
req.patch_buf_info_num * sizeof(*patch_buf_info));
if (ret) {
pr_err("fail to copy patch buf info\n");
kfree(patch_buf_info);
return -EFAULT;
}
}
ret = npu_host_exec_network_v2(client, &req, patch_buf_info);
kfree(patch_buf_info);
if (ret) {
pr_err("npu_host_exec_network_v2 failed %d\n", ret);
return ret;
}
ret = copy_to_user(argp, &req, sizeof(req));
if (ret) {
pr_err("fail to copy to user\n");
ret = -EFAULT;
}
return ret;
}
static int npu_process_kevent(struct npu_kevent *kevt)
{
int ret = 0;
switch (kevt->evt.type) {
case MSM_NPU_EVENT_TYPE_EXEC_V2_DONE:
ret = copy_to_user((void __user *)kevt->reserved[1],
(void *)&kevt->reserved[0],
kevt->evt.u.exec_v2_done.stats_buf_size);
if (ret) {
pr_err("fail to copy to user\n");
kevt->evt.u.exec_v2_done.stats_buf_size = 0;
ret = -EFAULT;
}
break;
default:
break;
}
return ret;
}
static int npu_receive_event(struct npu_client *client,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
struct npu_kevent *kevt;
int ret = 0;
mutex_lock(&client->list_lock);
if (list_empty(&client->evt_list)) {
pr_err("event list is empty\n");
ret = -EINVAL;
} else {
kevt = list_first_entry(&client->evt_list,
struct npu_kevent, list);
list_del(&kevt->list);
npu_process_kevent(kevt);
ret = copy_to_user(argp, &kevt->evt,
sizeof(struct msm_npu_event));
if (ret) {
pr_err("fail to copy to user\n");
ret = -EFAULT;
}
kfree(kevt);
}
mutex_unlock(&client->list_lock);
return ret;
}
static int npu_set_fw_state(struct npu_client *client, uint32_t enable)
{
struct npu_device *npu_dev = client->npu_dev;
struct npu_host_ctx *host_ctx = &npu_dev->host_ctx;
int rc = 0;
if (host_ctx->network_num > 0) {
pr_err("Need to unload network first\n");
mutex_unlock(&npu_dev->dev_lock);
return -EINVAL;
}
if (enable) {
pr_debug("enable fw\n");
rc = fw_init(npu_dev);
if (rc) {
pr_err("enable fw failed\n");
} else {
host_ctx->npu_init_cnt++;
pr_debug("npu_init_cnt %d\n",
host_ctx->npu_init_cnt);
/* set npu to lowest power level */
if (npu_set_uc_power_level(npu_dev, 1))
pr_warn("Failed to set uc power level");
}
} else if (host_ctx->npu_init_cnt > 0) {
pr_debug("disable fw\n");
fw_deinit(npu_dev, false, true);
host_ctx->npu_init_cnt--;
pr_debug("npu_init_cnt %d\n", host_ctx->npu_init_cnt);
} else {
pr_err("can't disable fw %d\n", host_ctx->npu_init_cnt);
}
return rc;
}
static int npu_set_property(struct npu_client *client,
unsigned long arg)
{
struct msm_npu_property prop;
void __user *argp = (void __user *)arg;
int ret = -EINVAL;
ret = copy_from_user(&prop, argp, sizeof(prop));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
switch (prop.prop_id) {
case MSM_NPU_PROP_ID_FW_STATE:
ret = npu_set_fw_state(client,
(uint32_t)prop.prop_param[0]);
break;
case MSM_NPU_PROP_ID_PERF_MODE:
ret = npu_host_set_perf_mode(client,
(uint32_t)prop.network_hdl,
(uint32_t)prop.prop_param[0]);
break;
default:
ret = npu_host_set_fw_property(client->npu_dev, &prop);
if (ret)
pr_err("npu_host_set_fw_property failed\n");
break;
}
return ret;
}
static int npu_get_property(struct npu_client *client,
unsigned long arg)
{
struct msm_npu_property prop;
void __user *argp = (void __user *)arg;
int ret = -EINVAL;
struct npu_device *npu_dev = client->npu_dev;
struct npu_host_ctx *host_ctx = &npu_dev->host_ctx;
ret = copy_from_user(&prop, argp, sizeof(prop));
if (ret) {
pr_err("fail to copy from user\n");
return -EFAULT;
}
switch (prop.prop_id) {
case MSM_NPU_PROP_ID_FW_STATE:
prop.prop_param[0] = host_ctx->fw_state;
break;
case MSM_NPU_PROP_ID_PERF_MODE:
prop.prop_param[0] = npu_host_get_perf_mode(client,
(uint32_t)prop.network_hdl);
break;
case MSM_NPU_PROP_ID_PERF_MODE_MAX:
prop.prop_param[0] = npu_dev->pwrctrl.num_pwrlevels;
break;
case MSM_NPU_PROP_ID_DRV_VERSION:
prop.prop_param[0] = 0;
break;
case MSM_NPU_PROP_ID_HARDWARE_VERSION:
prop.prop_param[0] = npu_dev->hw_version;
break;
default:
ret = npu_host_get_fw_property(client->npu_dev, &prop);
if (ret) {
pr_err("npu_host_set_fw_property failed\n");
return ret;
}
break;
}
ret = copy_to_user(argp, &prop, sizeof(prop));
if (ret) {
pr_err("fail to copy to user\n");
return -EFAULT;
}
return ret;
}
static long npu_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
int ret = -ENOIOCTLCMD;
struct npu_client *client = file->private_data;
switch (cmd) {
case MSM_NPU_GET_INFO:
ret = npu_get_info(client, arg);
break;
case MSM_NPU_MAP_BUF:
ret = npu_map_buf(client, arg);
break;
case MSM_NPU_UNMAP_BUF:
ret = npu_unmap_buf(client, arg);
break;
case MSM_NPU_LOAD_NETWORK:
ret = npu_load_network(client, arg);
break;
case MSM_NPU_LOAD_NETWORK_V2:
ret = npu_load_network_v2(client, arg);
break;
case MSM_NPU_UNLOAD_NETWORK:
ret = npu_unload_network(client, arg);
break;
case MSM_NPU_EXEC_NETWORK:
ret = npu_exec_network(client, arg);
break;
case MSM_NPU_EXEC_NETWORK_V2:
ret = npu_exec_network_v2(client, arg);
break;
case MSM_NPU_RECEIVE_EVENT:
ret = npu_receive_event(client, arg);
break;
case MSM_NPU_SET_PROP:
ret = npu_set_property(client, arg);
break;
case MSM_NPU_GET_PROP:
ret = npu_get_property(client, arg);
break;
default:
pr_err("unexpected IOCTL %x\n", cmd);
}
return ret;
}
static unsigned int npu_poll(struct file *filp, struct poll_table_struct *p)
{
struct npu_client *client = filp->private_data;
int rc = 0;
poll_wait(filp, &client->wait, p);
mutex_lock(&client->list_lock);
if (!list_empty(&client->evt_list)) {
pr_debug("poll cmd done\n");
rc = POLLIN | POLLRDNORM;
}
mutex_unlock(&client->list_lock);
return rc;
}
/* -------------------------------------------------------------------------
* Device Tree Parsing
* -------------------------------------------------------------------------
*/
static int npu_parse_dt_clock(struct npu_device *npu_dev)
{
int rc = 0;
uint32_t i;
const char *clock_name;
int num_clk;
struct npu_clk *core_clks = npu_dev->core_clks;
struct platform_device *pdev = npu_dev->pdev;
num_clk = of_property_count_strings(pdev->dev.of_node,
"clock-names");
if (num_clk <= 0) {
pr_err("clocks are not defined\n");
rc = -EINVAL;
goto clk_err;
} else if (num_clk > NUM_MAX_CLK_NUM) {
pr_err("number of clocks %d exceeds limit\n", num_clk);
rc = -EINVAL;
goto clk_err;
}
npu_dev->core_clk_num = num_clk;
for (i = 0; i < num_clk; i++) {
of_property_read_string_index(pdev->dev.of_node, "clock-names",
i, &clock_name);
strlcpy(core_clks[i].clk_name, clock_name,
sizeof(core_clks[i].clk_name));
core_clks[i].clk = devm_clk_get(&pdev->dev, clock_name);
if (IS_ERR(core_clks[i].clk)) {
pr_err("unable to get clk: %s\n", clock_name);
rc = -EINVAL;
break;
}
}
clk_err:
return rc;
}
static int npu_parse_dt_regulator(struct npu_device *npu_dev)
{
int rc = 0;
uint32_t i;
const char *name;
int num;
struct npu_regulator *regulators = npu_dev->regulators;
struct platform_device *pdev = npu_dev->pdev;
num = of_property_count_strings(pdev->dev.of_node,
"qcom,proxy-reg-names");
if (num <= 0) {
rc = -EINVAL;
pr_err("regulator not defined\n");
goto regulator_err;
}
if (num > NPU_MAX_REGULATOR_NUM) {
rc = -EINVAL;
pr_err("regulator number %d is over the limit %d\n", num,
NPU_MAX_REGULATOR_NUM);
num = NPU_MAX_REGULATOR_NUM;
}
npu_dev->regulator_num = num;
for (i = 0; i < num; i++) {
of_property_read_string_index(pdev->dev.of_node,
"qcom,proxy-reg-names", i, &name);
strlcpy(regulators[i].regulator_name, name,
sizeof(regulators[i].regulator_name));
regulators[i].regulator = devm_regulator_get(&pdev->dev, name);
if (IS_ERR(regulators[i].regulator)) {
pr_err("unable to get regulator: %s\n", name);
rc = -EINVAL;
break;
}
}
regulator_err:
return rc;
}
static int npu_of_parse_pwrlevels(struct npu_device *npu_dev,
struct device_node *node)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
struct device_node *child;
uint32_t init_level_index = 0, init_power_level;
uint32_t fmax, fmax_pwrlvl;
pwr->num_pwrlevels = 0;
pwr->min_pwrlevel = NPU_PWRLEVEL_TURBO_L1;
pwr->max_pwrlevel = NPU_PWRLEVEL_MINSVS;
for_each_available_child_of_node(node, child) {
uint32_t i = 0;
uint32_t index;
uint32_t pwr_level;
uint32_t clk_array_values[NUM_MAX_CLK_NUM];
uint32_t clk_rate;
struct npu_pwrlevel *level;
if (of_property_read_u32(child, "reg", &index)) {
pr_err("Can't find reg property\n");
return -EINVAL;
}
if (of_property_read_u32(child, "vreg", &pwr_level)) {
pr_err("Can't find vreg property\n");
return -EINVAL;
}
if (index >= NPU_MAX_PWRLEVELS) {
pr_err("pwrlevel index %d is out of range\n",
index);
continue;
}
if (index >= pwr->num_pwrlevels)
pwr->num_pwrlevels = index + 1;
if (of_property_read_u32_array(child, "clk-freq",
clk_array_values, npu_dev->core_clk_num)) {
pr_err("pwrlevel index %d read clk-freq failed %d\n",
index, npu_dev->core_clk_num);
return -EINVAL;
}
level = &pwr->pwrlevels[index];
level->pwr_level = pwr_level;
if (pwr->min_pwrlevel > pwr_level)
pwr->min_pwrlevel = pwr_level;
if (pwr->max_pwrlevel < pwr_level)
pwr->max_pwrlevel = pwr_level;
for (i = 0; i < npu_dev->core_clk_num; i++) {
if (npu_is_exclude_rate_clock(
npu_dev->core_clks[i].clk_name))
continue;
clk_rate = clk_round_rate(npu_dev->core_clks[i].clk,
clk_array_values[i]);
pr_debug("clk %s rate [%u]:[%u]\n",
npu_dev->core_clks[i].clk_name,
clk_array_values[i], clk_rate);
level->clk_freq[i] = clk_rate;
}
}
/* Read FMAX info if available */
if (npu_dev->qfprom_io.base) {
fmax = (npu_qfprom_reg_read(npu_dev,
QFPROM_FMAX_REG_OFFSET) & QFPROM_FMAX_BITS_MASK) >>
QFPROM_FMAX_BITS_SHIFT;
pr_debug("fmax %x\n", fmax);
switch (fmax) {
case 1:
case 2:
fmax_pwrlvl = NPU_PWRLEVEL_NOM;
break;
case 3:
fmax_pwrlvl = NPU_PWRLEVEL_SVS_L1;
break;
default:
fmax_pwrlvl = pwr->max_pwrlevel;
break;
}
if (fmax_pwrlvl < pwr->max_pwrlevel)
pwr->max_pwrlevel = fmax_pwrlvl;
}
of_property_read_u32(node, "initial-pwrlevel", &init_level_index);
pr_debug("initial-pwrlevel %d\n", init_level_index);
if (init_level_index >= pwr->num_pwrlevels)
init_level_index = pwr->num_pwrlevels - 1;
init_power_level = npu_power_level_from_index(npu_dev,
init_level_index);
if (init_power_level > pwr->max_pwrlevel) {
init_power_level = pwr->max_pwrlevel;
pr_debug("Adjust init power level to %d\n", init_power_level);
}
pr_debug("init power level %d max %d min %d\n", init_power_level,
pwr->max_pwrlevel, pwr->min_pwrlevel);
pwr->active_pwrlevel = pwr->default_pwrlevel = init_power_level;
pwr->uc_pwrlevel = pwr->max_pwrlevel;
pwr->perf_mode_override = 0;
pwr->cdsprm_pwrlevel = pwr->max_pwrlevel;
pwr->cur_dcvs_activity = pwr->num_pwrlevels;
return 0;
}
static int npu_pwrctrl_init(struct npu_device *npu_dev)
{
struct platform_device *pdev = npu_dev->pdev;
struct device_node *node;
int ret = 0;
struct platform_device *p2dev;
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
/* Power levels */
node = of_find_node_by_name(pdev->dev.of_node, "qcom,npu-pwrlevels");
if (!node) {
pr_err("unable to find 'qcom,npu-pwrlevels'\n");
return -EINVAL;
}
ret = npu_of_parse_pwrlevels(npu_dev, node);
if (ret)
return ret;
/* Parse Bandwidth */
node = of_parse_phandle(pdev->dev.of_node,
"qcom,npubw-dev", 0);
if (node) {
/* Set to 1 initially - we assume bwmon is on */
pwr->bwmon_enabled = 1;
p2dev = of_find_device_by_node(node);
if (p2dev) {
pwr->devbw = &p2dev->dev;
} else {
pr_err("parser power level failed\n");
ret = -EINVAL;
return ret;
}
} else {
pr_warn("bwdev is not defined in dts\n");
pwr->devbw = NULL;
}
return ret;
}
static int npu_thermalctrl_init(struct npu_device *npu_dev)
{
struct npu_pwrctrl *pwr = &npu_dev->pwrctrl;
struct npu_thermalctrl *thermalctrl = &npu_dev->thermalctrl;
thermalctrl->max_state = pwr->num_pwrlevels - 1;
thermalctrl->current_state = 0;
return 0;
}
static int npu_irq_init(struct npu_device *npu_dev)
{
unsigned long irq_type;
int ret = 0, i;
memcpy(npu_dev->irq, npu_irq_info, sizeof(npu_irq_info));
for (i = 0; i < NPU_MAX_IRQ; i++) {
irq_type = npu_irq_info[i].irq_type;
npu_dev->irq[i].irq = platform_get_irq_byname(
npu_dev->pdev, npu_dev->irq[i].name);
if (npu_dev->irq[i].irq < 0) {
pr_err("get_irq for %s failed\n\n",
npu_dev->irq[i].name);
ret = -EINVAL;
break;
}
pr_debug("irq %s: %d\n", npu_dev->irq[i].name,
npu_dev->irq[i].irq);
irq_set_status_flags(npu_dev->irq[i].irq,
IRQ_NOAUTOEN);
ret = devm_request_irq(&npu_dev->pdev->dev,
npu_dev->irq[i].irq, npu_intr_hdler,
irq_type, npu_dev->irq[i].name,
npu_dev);
if (ret) {
pr_err("devm_request_irq(%s:%d) failed\n",
npu_dev->irq[i].name,
npu_dev->irq[i].irq);
break;
}
}
return ret;
}
static int npu_mbox_init(struct npu_device *npu_dev)
{
struct platform_device *pdev = npu_dev->pdev;
struct npu_mbox *mbox_aop = &npu_dev->mbox_aop;
if (of_find_property(pdev->dev.of_node, "mboxes", NULL)) {
mbox_aop->client.dev = &pdev->dev;
mbox_aop->client.tx_block = true;
mbox_aop->client.tx_tout = MBOX_OP_TIMEOUTMS;
mbox_aop->client.knows_txdone = false;
mbox_aop->chan = mbox_request_channel(&mbox_aop->client, 0);
if (IS_ERR(mbox_aop->chan)) {
pr_warn("aop mailbox is not available\n");
mbox_aop->chan = NULL;
}
}
return 0;
}
static void npu_mbox_deinit(struct npu_device *npu_dev)
{
if (npu_dev->mbox_aop.chan) {
mbox_free_channel(npu_dev->mbox_aop.chan);
npu_dev->mbox_aop.chan = NULL;
}
}
static int npu_hw_info_init(struct npu_device *npu_dev)
{
int rc = 0;
rc = npu_enable_core_power(npu_dev);
if (rc) {
pr_err("Failed to enable power\n");
return rc;
}
npu_dev->hw_version = REGR(npu_dev, NPU_HW_VERSION);
pr_debug("NPU_HW_VERSION 0x%x\n", npu_dev->hw_version);
npu_disable_core_power(npu_dev);
return rc;
}
/* -------------------------------------------------------------------------
* Probe/Remove
* -------------------------------------------------------------------------
*/
static int npu_probe(struct platform_device *pdev)
{
int rc = 0;
struct resource *res = 0;
struct npu_device *npu_dev = 0;
struct thermal_cooling_device *tcdev = 0;
npu_dev = devm_kzalloc(&pdev->dev,
sizeof(struct npu_device), GFP_KERNEL);
if (!npu_dev)
return -EFAULT;
npu_dev->pdev = pdev;
mutex_init(&npu_dev->dev_lock);
platform_set_drvdata(pdev, npu_dev);
res = platform_get_resource_byname(pdev,
IORESOURCE_MEM, "core");
if (!res) {
pr_err("unable to get core resource\n");
rc = -ENODEV;
goto error_get_dev_num;
}
npu_dev->core_io.size = resource_size(res);
npu_dev->core_io.base = devm_ioremap(&pdev->dev, res->start,
npu_dev->core_io.size);
if (unlikely(!npu_dev->core_io.base)) {
pr_err("unable to map core\n");
rc = -ENOMEM;
goto error_get_dev_num;
}
pr_debug("core phy address=0x%llx virt=%pK\n",
res->start, npu_dev->core_io.base);
res = platform_get_resource_byname(pdev,
IORESOURCE_MEM, "tcm");
if (!res) {
pr_err("unable to get tcm resource\n");
rc = -ENODEV;
goto error_get_dev_num;
}
npu_dev->tcm_io.size = resource_size(res);
npu_dev->tcm_io.base = devm_ioremap(&pdev->dev, res->start,
npu_dev->tcm_io.size);
if (unlikely(!npu_dev->tcm_io.base)) {
pr_err("unable to map tcm\n");
rc = -ENOMEM;
goto error_get_dev_num;
}
pr_debug("core phy address=0x%llx virt=%pK\n",
res->start, npu_dev->tcm_io.base);
res = platform_get_resource_byname(pdev,
IORESOURCE_MEM, "bwmon");
if (!res) {
pr_err("unable to get bwmon resource\n");
rc = -ENODEV;
goto error_get_dev_num;
}
npu_dev->bwmon_io.size = resource_size(res);
npu_dev->bwmon_io.base = devm_ioremap(&pdev->dev, res->start,
npu_dev->bwmon_io.size);
if (unlikely(!npu_dev->bwmon_io.base)) {
pr_err("unable to map bwmon\n");
rc = -ENOMEM;
goto error_get_dev_num;
}
pr_debug("bwmon phy address=0x%llx virt=%pK\n",
res->start, npu_dev->bwmon_io.base);
res = platform_get_resource_byname(pdev,
IORESOURCE_MEM, "qfprom_physical");
if (!res) {
pr_info("unable to get qfprom_physical resource\n");
} else {
npu_dev->qfprom_io.size = resource_size(res);
npu_dev->qfprom_io.base = devm_ioremap(&pdev->dev, res->start,
npu_dev->qfprom_io.size);
if (unlikely(!npu_dev->qfprom_io.base)) {
pr_err("unable to map qfprom_physical\n");
rc = -ENOMEM;
goto error_get_dev_num;
}
pr_debug("qfprom_physical phy address=0x%llx virt=%pK\n",
res->start, npu_dev->qfprom_io.base);
}
rc = npu_parse_dt_regulator(npu_dev);
if (rc)
goto error_get_dev_num;
rc = npu_parse_dt_clock(npu_dev);
if (rc)
goto error_get_dev_num;
rc = npu_hw_info_init(npu_dev);
if (rc)
goto error_get_dev_num;
rc = npu_pwrctrl_init(npu_dev);
if (rc)
goto error_get_dev_num;
rc = npu_thermalctrl_init(npu_dev);
if (rc)
goto error_get_dev_num;
rc = npu_irq_init(npu_dev);
if (rc)
goto error_get_dev_num;
rc = npu_mbox_init(npu_dev);
if (rc)
goto error_get_dev_num;
/* character device might be optional */
rc = alloc_chrdev_region(&npu_dev->dev_num, 0, 1, DRIVER_NAME);
if (rc < 0) {
pr_err("alloc_chrdev_region failed: %d\n", rc);
goto error_get_dev_num;
}
npu_dev->class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(npu_dev->class)) {
rc = PTR_ERR(npu_dev->class);
pr_err("class_create failed: %d\n", rc);
goto error_class_create;
}
npu_dev->device = device_create(npu_dev->class, NULL,
npu_dev->dev_num, NULL, DRIVER_NAME);
if (IS_ERR(npu_dev->device)) {
rc = PTR_ERR(npu_dev->device);
pr_err("device_create failed: %d\n", rc);
goto error_class_device_create;
}
cdev_init(&npu_dev->cdev, &npu_fops);
rc = cdev_add(&npu_dev->cdev,
MKDEV(MAJOR(npu_dev->dev_num), 0), 1);
if (rc < 0) {
pr_err("cdev_add failed %d\n", rc);
goto error_cdev_add;
}
dev_set_drvdata(npu_dev->device, npu_dev);
pr_debug("drvdata %pK %pK\n", dev_get_drvdata(&pdev->dev),
dev_get_drvdata(npu_dev->device));
rc = sysfs_create_group(&npu_dev->device->kobj, &npu_fs_attr_group);
if (rc) {
pr_err("unable to register npu sysfs nodes\n");
goto error_res_init;
}
if (IS_ENABLED(CONFIG_THERMAL)) {
tcdev = thermal_of_cooling_device_register(pdev->dev.of_node,
"npu", npu_dev,
&npu_cooling_ops);
if (IS_ERR(tcdev)) {
dev_err(&pdev->dev,
"npu: failed to register npu as cooling device\n");
rc = PTR_ERR(tcdev);
goto error_driver_init;
}
npu_dev->tcdev = tcdev;
thermal_cdev_update(tcdev);
}
rc = npu_cdsprm_cxlimit_init(npu_dev);
if (rc)
goto error_driver_init;
rc = npu_debugfs_init(npu_dev);
if (rc)
goto error_driver_init;
npu_dev->smmu_ctx.attach_cnt = 0;
npu_dev->smmu_ctx.mmu_mapping = arm_iommu_create_mapping(
pdev->dev.bus, DDR_MAPPED_START_ADDR, DDR_MAPPED_SIZE);
if (IS_ERR(npu_dev->smmu_ctx.mmu_mapping)) {
pr_err("iommu create mapping failed\n");
rc = -ENOMEM;
npu_dev->smmu_ctx.mmu_mapping = NULL;
goto error_driver_init;
}
rc = arm_iommu_attach_device(&(npu_dev->pdev->dev),
npu_dev->smmu_ctx.mmu_mapping);
if (rc) {
pr_err("arm_iommu_attach_device failed\n");
goto error_driver_init;
}
rc = npu_host_init(npu_dev);
if (rc) {
pr_err("unable to init host\n");
goto error_driver_init;
}
g_npu_dev = npu_dev;
return rc;
error_driver_init:
arm_iommu_detach_device(&(npu_dev->pdev->dev));
if (!npu_dev->smmu_ctx.mmu_mapping)
arm_iommu_release_mapping(npu_dev->smmu_ctx.mmu_mapping);
npu_cdsprm_cxlimit_deinit(npu_dev);
if (npu_dev->tcdev)
thermal_cooling_device_unregister(npu_dev->tcdev);
sysfs_remove_group(&npu_dev->device->kobj, &npu_fs_attr_group);
error_res_init:
cdev_del(&npu_dev->cdev);
error_cdev_add:
device_destroy(npu_dev->class, npu_dev->dev_num);
error_class_device_create:
class_destroy(npu_dev->class);
error_class_create:
unregister_chrdev_region(npu_dev->dev_num, 1);
npu_mbox_deinit(npu_dev);
error_get_dev_num:
return rc;
}
static int npu_remove(struct platform_device *pdev)
{
struct npu_device *npu_dev;
npu_dev = platform_get_drvdata(pdev);
npu_host_deinit(npu_dev);
arm_iommu_detach_device(&(npu_dev->pdev->dev));
arm_iommu_release_mapping(npu_dev->smmu_ctx.mmu_mapping);
npu_debugfs_deinit(npu_dev);
npu_cdsprm_cxlimit_deinit(npu_dev);
if (npu_dev->tcdev)
thermal_cooling_device_unregister(npu_dev->tcdev);
sysfs_remove_group(&npu_dev->device->kobj, &npu_fs_attr_group);
cdev_del(&npu_dev->cdev);
device_destroy(npu_dev->class, npu_dev->dev_num);
class_destroy(npu_dev->class);
unregister_chrdev_region(npu_dev->dev_num, 1);
platform_set_drvdata(pdev, NULL);
npu_mbox_deinit(npu_dev);
g_npu_dev = NULL;
return 0;
}
/* -------------------------------------------------------------------------
* Suspend/Resume
* -------------------------------------------------------------------------
*/
#if defined(CONFIG_PM)
static int npu_suspend(struct platform_device *dev, pm_message_t state)
{
return 0;
}
static int npu_resume(struct platform_device *dev)
{
return 0;
}
#endif
/* -------------------------------------------------------------------------
* Module Entry Points
* -------------------------------------------------------------------------
*/
static int __init npu_init(void)
{
int rc;
rc = platform_driver_register(&npu_driver);
if (rc)
pr_err("register failed %d\n", rc);
return rc;
}
static void __exit npu_exit(void)
{
platform_driver_unregister(&npu_driver);
}
module_init(npu_init);
module_exit(npu_exit);
MODULE_DEVICE_TABLE(of, npu_dt_match);
MODULE_DESCRIPTION("MSM NPU driver");
MODULE_LICENSE("GPL v2");
MODULE_INFO(intree, "Y");