blob: 67e69a98cf1e4b8ec9321c527eeef3259093689b [file] [log] [blame]
/*
* Samsung Exynos SoC series NPU driver
*
* Copyright (c) 2017 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/kfifo.h>
#include <linux/delay.h>
#include <linux/exynos_iovmm.h>
#include <linux/pm_runtime.h>
#include "npu-config.h"
#include "vs4l.h"
#include "npu-device.h"
#include "vision-dev.h"
#include "vision-ioctl.h"
#include "npu-log.h"
#include "npu-debug.h"
#include "npu-protodrv.h"
#include "npu-profile.h"
#include "npu-util-memdump.h"
#ifdef CONFIG_EXYNOS_NPU_PUBLISH_NPU_BUILD_VER
#include "npu-ver-info.h"
#endif
#ifdef CONFIG_NPU_LOOPBACK
#include "npu-mailbox-mgr-mock.h"
#endif
extern struct system_pwr sysPwr;
extern int npu_system_save_result(struct npu_session *session, struct nw_result nw_result);
static int __npu_device_start(struct npu_device *device)
{
int ret = 0;
if (test_bit(NPU_DEVICE_STATE_START, &device->state)) {
npu_err("already started\n");
ret = -EINVAL;
goto ErrorExit;
}
ret = npu_system_start(&device->system);
if (ret) {
npu_err("fail(%d) in npu_system_start\n", ret);
goto ErrorExit;
}
ret = npu_debug_start(device);
if (ret) {
npu_err("fail(%d) in npu_debug_start\n", ret);
goto ErrorExit;
}
ret = npu_log_start(device);
if (ret) {
npu_err("fail(%d) in npu_log_start\n", ret);
goto ErrorExit;
}
ret = proto_drv_start(device);
if (ret) {
npu_err("fail(%d) in proto_drv_start\n", ret);
goto ErrorExit;
}
#ifdef CONFIG_NPU_LOOPBACK
ret = mailbox_mgr_mock_start(device);
if (ret) {
npu_err("fail(%d) in mailbox_mgr_mock_start\n", ret);
goto ErrorExit;
}
#endif
set_bit(NPU_DEVICE_STATE_START, &device->state);
ErrorExit:
return ret;
}
static int __npu_device_stop(struct npu_device *device)
{
int ret = 0;
if (!test_bit(NPU_DEVICE_STATE_START, &device->state))
goto ErrorExit;
#ifdef CONFIG_NPU_LOOPBACK
ret = mailbox_mgr_mock_stop(device);
if (ret)
npu_err("fail(%d) in mailbox_mgr_mock_stop\n", ret);
#endif
ret = proto_drv_stop(device);
if (ret)
npu_err("fail(%d) in proto_drv_stop\n", ret);
ret = npu_log_stop(device);
if (ret)
npu_err("fail(%d) in npu_log_stop\n", ret);
ret = npu_debug_stop(device);
if (ret)
npu_err("fail(%d) in npu_debug_stop\n", ret);
ret = npu_system_stop(&device->system);
if (ret)
npu_err("fail(%d) in npu_system_stop\n", ret);
clear_bit(NPU_DEVICE_STATE_START, &device->state);
ErrorExit:
return ret;
}
static int __npu_device_power_on(struct npu_device *device)
{
int ret = 0;
ret = pm_runtime_get_sync(device->dev);
if (ret)
npu_err("fail(%d) in runtime resume\n", ret);
if (!test_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state)) {
if (ret != -ETIMEDOUT)
BUG_ON(ret < 0);
else
npu_err("EMERGENCY_RECOVERY during %s.\n", __func__);
}
return ret;
}
static int __npu_device_power_off(struct npu_device *device)
{
int ret = 0;
if (test_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state)) {
npu_info("EMERGENCY_RECOVERY - %ums delay insearted before power down.\n",
POWER_DOWN_DELAY_ON_EMERGENCY);
msleep(POWER_DOWN_DELAY_ON_EMERGENCY);
}
ret = pm_runtime_put_sync(device->dev);
if (ret)
npu_err("fail(%d) in runtime suspend\n", ret);
BUG_ON(ret < 0);
return ret;
}
/*
* Called after all the open procedure is completed.
* NPU H/W is fully working at this stage
*/
static int __npu_device_late_open(struct npu_device *device)
{
int ret;
npu_trace("starting\n");
ret = npu_profile_open(&device->system);
if (ret) {
npu_err("fail(%d) in npu_profile_open\n", ret);
goto err_exit;
}
ret = 0;
err_exit:
if (ret)
npu_err("error occurred: ret = %d\n", ret);
else
npu_trace("completed\n");
return ret;
}
/*
* Called before the close procedure is started.
* NPU H/W is fully working at this stage
*/
static int __npu_device_early_close(struct npu_device *device)
{
int ret;
npu_trace("Starting.\n");
ret = npu_profile_close(&device->system);
if (ret) {
npu_err("fail(%d) in npu_profile_close\n", ret);
goto err_exit;
}
ret = 0;
err_exit:
if (ret)
npu_err("error occurred : ret = %d\n", ret);
else
npu_trace("completed\n");
return ret;
}
static int npu_iommu_fault_handler(struct iommu_domain *domain,
struct device *dev, unsigned long fault_addr,
int fault_flags, void *token)
{
struct npu_device *device = token;
dev_err(dev, "%s() is called with fault addr(0x%lx), mode(%pK), state(%pK))\n",
__func__, fault_addr, &device->mode, &device->state);
npu_util_dump_handle_error_k(device);
return 0;
}
static int npu_device_probe(struct platform_device *pdev)
{
int ret = 0;
struct device *dev;
struct npu_device *device;
BUG_ON(!pdev);
dev = &pdev->dev;
#ifdef CONFIG_EXYNOS_NPU_PUBLISH_NPU_BUILD_VER
probe_info("NPU Device driver build info :\n%s\n%s\n",
npu_build_info, npu_git_log_str);
#endif
device = devm_kzalloc(dev, sizeof(*device), GFP_KERNEL);
if (!device) {
probe_err("fail in devm_kzalloc\n");
ret = -ENOMEM;
goto err_exit;
}
device->dev = dev;
ret = npu_system_probe(&device->system, pdev);
if (ret) {
probe_err("fail(%d) in npu_system_probe\n", ret);
ret = -EINVAL;
goto err_exit;
}
/* Probe sub modules */
ret = npu_debug_probe(device);
if (ret) {
probe_err("fail(%d) in npu_debug_probe\n", ret);
goto err_exit;
}
ret = npu_log_probe(device);
if (ret) {
probe_err("fail(%d) in npu_log_probe\n", ret);
goto err_exit;
}
ret = npu_vertex_probe(&device->vertex, dev);
if (ret) {
probe_err("fail(%d) in npu_vertex_probe\n", ret);
goto err_exit;
}
ret = proto_drv_probe(device);
if (ret) {
probe_err("fail(%d) in proto_drv_probe\n", ret);
goto err_exit;
}
ret = npu_sessionmgr_probe(&device->sessionmgr);
#ifdef CONFIG_NPU_GOLDEN_MATCH
ret = register_golden_matcher(dev);
if (ret) {
probe_err("fail(%d) in register_golden_matcher\n", ret);
goto err_exit;
}
#endif
#ifdef CONFIG_NPU_LOOPBACK
ret = mailbox_mgr_mock_probe(device);
if (ret) {
probe_err("fail(%d) in mailbox_mgr_mock_probe\n", ret);
goto err_exit;
}
#endif
ret = npu_profile_probe(&device->system);
if (ret) {
probe_err("fail(%d) in npu_profile_probe\n", ret);
goto err_exit;
}
ret = iovmm_activate(dev);
if (ret < 0)
return ret;
iovmm_set_fault_handler(dev, npu_iommu_fault_handler, device);
dev_set_drvdata(dev, device);
ret = 0;
probe_info("complete in %s\n", __func__);
goto ok_exit;
err_exit:
probe_err("error on %s ret(%d)\n", __func__, ret);
ok_exit:
return ret;
}
int npu_device_open(struct npu_device *device)
{
int ret = 0;
BUG_ON(!device);
ret = npu_debug_open(device);
if (ret) {
npu_err("fail(%d) in npu_debug_open\n", ret);
goto ErrorExit;
}
ret = npu_log_open(device);
if (ret) {
npu_err("fail(%d) in npu_log_open\n", ret);
goto ErrorExit;
}
ret = npu_system_open(&device->system);
if (ret) {
npu_err("fail(%d) in npu_system_open\n", ret);
goto ErrorExit;
}
ret = __npu_device_power_on(device);
if (ret) {
npu_err("fail(%d) in __npu_device_power_on\n", ret);
goto ErrorExit;
}
if (test_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state)) {
ret = -ELIBACC;
goto ErrorExit;
}
ret = proto_drv_open(device);
if (ret) {
npu_err("fail(%d) in proto_drv_open\n", ret);
goto ErrorExit;
}
ret = __npu_device_late_open(device);
if (ret) {
npu_err("fail(%d) in __npu_device_late_open\n", ret);
goto ErrorExit;
}
/* clear npu_devcice_err_state to no_error for emergency recover */
clear_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state);
set_bit(NPU_DEVICE_STATE_OPEN, &device->state);
ErrorExit:
npu_info("%s():%d\n", __func__, ret);
return ret;
}
int npu_system_put_req_nw(nw_cmd_e nw_cmd)
{
int ret = 0;
struct npu_nw req = {};
//req.uid = session->uid,
req.npu_req_id = 0,
req.result_code = 0,
//req.session = session,
req.cmd = nw_cmd,
//req.ncp_addr = session->ncp_info.ncp_addr,
//req.magic_tail = NPU_NW_MAGIC_TAIL,
req.notify_func = npu_system_save_result;
ret = npu_ncp_mgmt_put(&req);
if (!ret) {
npu_err("fail in npu_ncp_mgmt_put");
return ret;
}
return ret;
}
int npu_system_NW_CMD_POWER_DOWN(void)
{
int ret = 0;
nw_cmd_e nw_cmd;
npu_info("start power_down\n");
nw_cmd = NPU_NW_CMD_POWER_DOWN;
sysPwr.system_result.result_code = NPU_SYSTEM_JUST_STARTED;
npu_system_put_req_nw(nw_cmd);
wait_event(sysPwr.wq, sysPwr.system_result.result_code != NPU_SYSTEM_JUST_STARTED);
if (sysPwr.system_result.result_code != NPU_ERR_NO_ERROR) {
ret = -EINVAL;
goto ErrorExit;
}
npu_info("complete in powerdown(%d)\n", ret);
ErrorExit:
return ret;
}
int npu_device_recovery_close(struct npu_device *device)
{
int ret = 0;
ret += npu_system_close(&device->system);
ret += __npu_device_power_off(device);
ret += npu_log_close(device);
ret += npu_debug_close(device);
if (ret)
goto err_coma;
clear_bit(NPU_DEVICE_STATE_OPEN, &device->state);
clear_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state);
npu_info("%s():%d\n", __func__, ret);
return ret;
err_coma:
BUG_ON(1);
}
int npu_device_close(struct npu_device *device)
{
int ret = 0;
BUG_ON(!device);
if (!test_bit(NPU_DEVICE_STATE_OPEN, &device->state)) {
npu_err("already closed of device\n");
ret = -EINVAL;
goto ErrorExit;
}
ret = __npu_device_early_close(device);
if (ret)
npu_err("fail(%d) in __npu_device_early_close\n", ret);
if (!npu_device_is_emergency_err(device)) {
ret = npu_system_NW_CMD_POWER_DOWN();
if (ret) {
npu_err("fail(%d) in __npu_powerdown\n", ret);
}
} else {
npu_warn("NPU DEVICE IS EMERGENCY ERROR!\n");
}
#if 0 // 0521_CLEAN_CODE
ret = __npu_device_stop(device);
if (ret)
npu_err("fail(%d) in __npu_device_stop\n", ret);
#endif
#ifdef CONFIG_NPU_LOOPBACK
ret = mailbox_mgr_mock_close(device);
if (ret)
npu_err("fail(%d) in mailbox_mgr_mock_close\n", ret);
#endif
ret = proto_drv_close(device);
if (ret)
npu_err("fail(%d) in proto_drv_close\n", ret);
ret = npu_system_close(&device->system);
if (ret)
npu_err("fail(%d) in npu_system_close\n", ret);
ret = __npu_device_power_off(device);
if (ret)
npu_err("fail(%d) in __npu_device_power_off\n", ret);
ret = npu_log_close(device);
if (ret)
npu_err("fail(%d) in npu_log_close)\n", ret);
ret = npu_debug_close(device);
if (ret)
npu_err("fail(%d) in npu_debug_close\n", ret);
clear_bit(NPU_DEVICE_STATE_OPEN, &device->state);
if (test_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state)) {
npu_dbg("%s() : NPU_DEVICE_ERR_STATE_EMERGENCY bit was set before\n", __func__);
clear_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state);
npu_dbg("%s() : NPU_DEVICE_ERR_STATE_EMERGENCY bit is clear now", __func__);
npu_dbg(" (%d) \n", test_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state));
}
ErrorExit:
npu_info("%s():%d\n", __func__, ret);
return ret;
}
int npu_device_start(struct npu_device *device)
{
int ret = 0;
BUG_ON(!device);
ret = __npu_device_start(device);
if (ret)
npu_err("fail(%d) in __npu_device_start\n", ret);
npu_info("%s():%d\n", __func__, ret);
return ret;
}
int npu_device_stop(struct npu_device *device)
{
int ret = 0;
BUG_ON(!device);
ret = __npu_device_stop(device);
if (ret)
npu_err("fail(%d) in __npu_device_stop\n", ret);
npu_info("%s():%d\n", __func__, ret);
return ret;
}
#ifdef CONFIG_PM_SLEEP
static int npu_device_suspend(struct device *dev)
{
npu_info("%s()\n", __func__);
return 0;
}
static int npu_device_resume(struct device *dev)
{
npu_info("%s()\n", __func__);
return 0;
}
#endif
#ifdef CONFIG_PM
static int npu_device_runtime_suspend(struct device *dev)
{
int ret = 0;
struct npu_device *device;
device = dev_get_drvdata(dev);
ret = npu_system_suspend(&device->system);
if (ret)
npu_err("fail(%d) in npu_system_suspend\n", ret);
npu_info("%s():%d\n", __func__, ret);
return ret;
}
static int npu_device_runtime_resume(struct device *dev)
{
int ret = 0;
struct npu_device *device;
device = dev_get_drvdata(dev);
ret = npu_system_resume(&device->system, device->mode);
if (ret) {
npu_err("fail(%d) in npu_system_resume\n", ret);
goto ErrorExit;
}
ErrorExit:
npu_info("%s():%d\n", __func__, ret);
return ret;
}
#endif
static int npu_device_remove(struct platform_device *pdev)
{
int ret = 0;
struct device *dev;
struct npu_device *device;
BUG_ON(!pdev);
dev = &pdev->dev;
device = dev_get_drvdata(dev);
/* Error will not be propagated to upper layer, but just logging */
ret = npu_profile_release();
if (ret)
npu_err("fail(%d) in npu_profile_release\n", ret);
#ifdef CONFIG_NPU_LOOPBACK
ret = mailbox_mgr_mock_release();
if (ret) {
npu_err("fail(%d) in mailbox_mgr_mock_release\n", ret);
}
#endif
ret = proto_drv_release();
if (ret) {
npu_err("fail(%d) in proto_drv_release\n", ret);
}
ret = npu_debug_release();
if (ret) {
npu_err("fail(%d) in npu_debug_release\n", ret);
}
ret = npu_log_release(device);
if (ret) {
npu_err("fail(%d) in npu_log_release\n", ret);
}
ret = npu_system_release(&device->system, pdev);
if (ret) {
npu_err("fail(%d) in npu_system_release\n", ret);
}
npu_info("completed in %s\n", __func__);
return 0;
}
/*
int npu_device_emergency_recover(struct npu_device *device){
npu_dbg("npu_device_emergency_recover!!\n");
return 0;
}
*/
void npu_device_set_emergency_err(struct npu_device *device)
{
npu_warn("Emergency flag set.\n");
set_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state);
}
int npu_device_is_emergency_err(struct npu_device *device)
{
return test_bit(NPU_DEVICE_ERR_STATE_EMERGENCY, &device->err_state);
}
static const struct dev_pm_ops npu_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(npu_device_suspend, npu_device_resume)
SET_RUNTIME_PM_OPS(npu_device_runtime_suspend, npu_device_runtime_resume, NULL)
};
#ifdef CONFIG_OF
static const struct of_device_id exynos_npu_match[] = {
{
.compatible = "samsung,exynos-npu"
},
{}
};
MODULE_DEVICE_TABLE(of, exynos_npu_match);
#endif
static struct platform_driver npu_driver = {
.probe = npu_device_probe,
.remove = npu_device_remove,
.driver = {
.name = "exynos-npu",
.owner = THIS_MODULE,
.pm = &npu_pm_ops,
.of_match_table = of_match_ptr(exynos_npu_match),
},
};
static int __init npu_device_init(void)
{
int ret = platform_driver_register(&npu_driver);
if (ret) {
probe_err("error(%d) in platform_driver_register\n", ret);
goto err_exit;
}
probe_info("success in %s\n", __func__);
ret = 0;
goto ok_exit;
err_exit:
// necessary clean-up
ok_exit:
return ret;
}
static void __exit npu_device_exit(void)
{
platform_driver_unregister(&npu_driver);
npu_info("success in %s\n", __func__);
}
late_initcall(npu_device_init);
module_exit(npu_device_exit);
MODULE_AUTHOR("Eungjoo Kim<ej7.kim@samsung.com>");
MODULE_DESCRIPTION("Exynos NPU driver");
MODULE_LICENSE("GPL");