blob: 76861396ba864d211098bd6e2c7223bc5cb050ff [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Support for Clovertrail PNW Camera Imaging ISP subsystem.
*
* Copyright (c) 2012 Intel Corporation. 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 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.
*
*
*/
/*
* This file implements loadable acceleration firmware API,
* including ioctls to map and unmap acceleration parameters and buffers.
*/
#include <linux/init.h>
#include <media/v4l2-event.h>
#include "hmm.h"
#include "atomisp_acc.h"
#include "atomisp_internal.h"
#include "atomisp_compat.h"
#include "atomisp_cmd.h"
#include "ia_css.h"
static const struct {
unsigned int flag;
enum ia_css_pipe_id pipe_id;
} acc_flag_to_pipe[] = {
{ ATOMISP_ACC_FW_LOAD_FL_PREVIEW, IA_CSS_PIPE_ID_PREVIEW },
{ ATOMISP_ACC_FW_LOAD_FL_COPY, IA_CSS_PIPE_ID_COPY },
{ ATOMISP_ACC_FW_LOAD_FL_VIDEO, IA_CSS_PIPE_ID_VIDEO },
{ ATOMISP_ACC_FW_LOAD_FL_CAPTURE, IA_CSS_PIPE_ID_CAPTURE },
{ ATOMISP_ACC_FW_LOAD_FL_ACC, IA_CSS_PIPE_ID_ACC }
};
/*
* Allocate struct atomisp_acc_fw along with space for firmware.
* The returned struct atomisp_acc_fw is cleared (firmware region is not).
*/
static struct atomisp_acc_fw *acc_alloc_fw(unsigned int fw_size)
{
struct atomisp_acc_fw *acc_fw;
acc_fw = kzalloc(sizeof(*acc_fw), GFP_KERNEL);
if (!acc_fw)
return NULL;
acc_fw->fw = vmalloc(fw_size);
if (!acc_fw->fw) {
kfree(acc_fw);
return NULL;
}
return acc_fw;
}
static void acc_free_fw(struct atomisp_acc_fw *acc_fw)
{
vfree(acc_fw->fw);
kfree(acc_fw);
}
static struct atomisp_acc_fw *
acc_get_fw(struct atomisp_sub_device *asd, unsigned int handle)
{
struct atomisp_acc_fw *acc_fw;
list_for_each_entry(acc_fw, &asd->acc.fw, list)
if (acc_fw->handle == handle)
return acc_fw;
return NULL;
}
static struct atomisp_map *acc_get_map(struct atomisp_sub_device *asd,
unsigned long css_ptr, size_t length)
{
struct atomisp_map *atomisp_map;
list_for_each_entry(atomisp_map, &asd->acc.memory_maps, list) {
if (atomisp_map->ptr == css_ptr &&
atomisp_map->length == length)
return atomisp_map;
}
return NULL;
}
static int acc_stop_acceleration(struct atomisp_sub_device *asd)
{
int ret;
ret = atomisp_css_stop_acc_pipe(asd);
atomisp_css_destroy_acc_pipe(asd);
return ret;
}
void atomisp_acc_cleanup(struct atomisp_device *isp)
{
int i;
for (i = 0; i < isp->num_of_streams; i++)
ida_destroy(&isp->asd[i].acc.ida);
}
void atomisp_acc_release(struct atomisp_sub_device *asd)
{
struct atomisp_acc_fw *acc_fw, *ta;
struct atomisp_map *atomisp_map, *tm;
/* Stop acceleration if already running */
if (asd->acc.pipeline)
acc_stop_acceleration(asd);
/* Unload all loaded acceleration binaries */
list_for_each_entry_safe(acc_fw, ta, &asd->acc.fw, list) {
list_del(&acc_fw->list);
ida_free(&asd->acc.ida, acc_fw->handle);
acc_free_fw(acc_fw);
}
/* Free all mapped memory blocks */
list_for_each_entry_safe(atomisp_map, tm, &asd->acc.memory_maps, list) {
list_del(&atomisp_map->list);
hmm_free(atomisp_map->ptr);
kfree(atomisp_map);
}
}
int atomisp_acc_load_to_pipe(struct atomisp_sub_device *asd,
struct atomisp_acc_fw_load_to_pipe *user_fw)
{
static const unsigned int pipeline_flags =
ATOMISP_ACC_FW_LOAD_FL_PREVIEW | ATOMISP_ACC_FW_LOAD_FL_COPY |
ATOMISP_ACC_FW_LOAD_FL_VIDEO |
ATOMISP_ACC_FW_LOAD_FL_CAPTURE | ATOMISP_ACC_FW_LOAD_FL_ACC;
struct atomisp_acc_fw *acc_fw;
int handle;
if (!user_fw->data || user_fw->size < sizeof(*acc_fw->fw))
return -EINVAL;
/* Binary has to be enabled at least for one pipeline */
if (!(user_fw->flags & pipeline_flags))
return -EINVAL;
/* We do not support other flags yet */
if (user_fw->flags & ~pipeline_flags)
return -EINVAL;
if (user_fw->type < ATOMISP_ACC_FW_LOAD_TYPE_OUTPUT ||
user_fw->type > ATOMISP_ACC_FW_LOAD_TYPE_STANDALONE)
return -EINVAL;
if (asd->acc.pipeline || asd->acc.extension_mode)
return -EBUSY;
acc_fw = acc_alloc_fw(user_fw->size);
if (!acc_fw)
return -ENOMEM;
if (copy_from_user(acc_fw->fw, user_fw->data, user_fw->size)) {
acc_free_fw(acc_fw);
return -EFAULT;
}
handle = ida_alloc(&asd->acc.ida, GFP_KERNEL);
if (handle < 0) {
acc_free_fw(acc_fw);
return -ENOSPC;
}
user_fw->fw_handle = handle;
acc_fw->handle = handle;
acc_fw->flags = user_fw->flags;
acc_fw->type = user_fw->type;
acc_fw->fw->handle = handle;
/*
* correct isp firmware type in order ISP firmware can be appended
* to correct pipe properly
*/
if (acc_fw->fw->type == ia_css_isp_firmware) {
static const int type_to_css[] = {
[ATOMISP_ACC_FW_LOAD_TYPE_OUTPUT] =
IA_CSS_ACC_OUTPUT,
[ATOMISP_ACC_FW_LOAD_TYPE_VIEWFINDER] =
IA_CSS_ACC_VIEWFINDER,
[ATOMISP_ACC_FW_LOAD_TYPE_STANDALONE] =
IA_CSS_ACC_STANDALONE,
};
acc_fw->fw->info.isp.type = type_to_css[acc_fw->type];
}
list_add_tail(&acc_fw->list, &asd->acc.fw);
return 0;
}
int atomisp_acc_load(struct atomisp_sub_device *asd,
struct atomisp_acc_fw_load *user_fw)
{
struct atomisp_acc_fw_load_to_pipe ltp = {0};
int r;
ltp.flags = ATOMISP_ACC_FW_LOAD_FL_ACC;
ltp.type = ATOMISP_ACC_FW_LOAD_TYPE_STANDALONE;
ltp.size = user_fw->size;
ltp.data = user_fw->data;
r = atomisp_acc_load_to_pipe(asd, &ltp);
user_fw->fw_handle = ltp.fw_handle;
return r;
}
int atomisp_acc_unload(struct atomisp_sub_device *asd, unsigned int *handle)
{
struct atomisp_acc_fw *acc_fw;
if (asd->acc.pipeline || asd->acc.extension_mode)
return -EBUSY;
acc_fw = acc_get_fw(asd, *handle);
if (!acc_fw)
return -EINVAL;
list_del(&acc_fw->list);
ida_free(&asd->acc.ida, acc_fw->handle);
acc_free_fw(acc_fw);
return 0;
}
int atomisp_acc_start(struct atomisp_sub_device *asd, unsigned int *handle)
{
struct atomisp_device *isp = asd->isp;
struct atomisp_acc_fw *acc_fw;
int ret;
unsigned int nbin;
if (asd->acc.pipeline || asd->acc.extension_mode)
return -EBUSY;
/* Invalidate caches. FIXME: should flush only necessary buffers */
wbinvd();
ret = atomisp_css_create_acc_pipe(asd);
if (ret)
return ret;
nbin = 0;
list_for_each_entry(acc_fw, &asd->acc.fw, list) {
if (*handle != 0 && *handle != acc_fw->handle)
continue;
if (acc_fw->type != ATOMISP_ACC_FW_LOAD_TYPE_STANDALONE)
continue;
/* Add the binary into the pipeline */
ret = atomisp_css_load_acc_binary(asd, acc_fw->fw, nbin);
if (ret < 0) {
dev_err(isp->dev, "acc_load_binary failed\n");
goto err_stage;
}
ret = atomisp_css_set_acc_parameters(acc_fw);
if (ret < 0) {
dev_err(isp->dev, "acc_set_parameters failed\n");
goto err_stage;
}
nbin++;
}
if (nbin < 1) {
/* Refuse creating pipelines with no binaries */
dev_err(isp->dev, "%s: no acc binary available\n", __func__);
ret = -EINVAL;
goto err_stage;
}
ret = atomisp_css_start_acc_pipe(asd);
if (ret) {
dev_err(isp->dev, "%s: atomisp_acc_start_acc_pipe failed\n",
__func__);
goto err_stage;
}
return 0;
err_stage:
atomisp_css_destroy_acc_pipe(asd);
return ret;
}
int atomisp_acc_wait(struct atomisp_sub_device *asd, unsigned int *handle)
{
struct atomisp_device *isp = asd->isp;
int ret;
if (!asd->acc.pipeline)
return -ENOENT;
if (*handle && !acc_get_fw(asd, *handle))
return -EINVAL;
ret = atomisp_css_wait_acc_finish(asd);
if (acc_stop_acceleration(asd) == -EIO) {
atomisp_reset(isp);
return -EINVAL;
}
return ret;
}
void atomisp_acc_done(struct atomisp_sub_device *asd, unsigned int handle)
{
struct v4l2_event event = { 0 };
event.type = V4L2_EVENT_ATOMISP_ACC_COMPLETE;
event.u.frame_sync.frame_sequence = atomic_read(&asd->sequence);
event.id = handle;
v4l2_event_queue(asd->subdev.devnode, &event);
}
int atomisp_acc_map(struct atomisp_sub_device *asd, struct atomisp_acc_map *map)
{
struct atomisp_map *atomisp_map;
ia_css_ptr cssptr;
int pgnr;
if (map->css_ptr)
return -EINVAL;
if (asd->acc.pipeline)
return -EBUSY;
if (map->user_ptr) {
/* Buffer to map must be page-aligned */
if ((unsigned long)map->user_ptr & ~PAGE_MASK) {
dev_err(asd->isp->dev,
"%s: mapped buffer address %p is not page aligned\n",
__func__, map->user_ptr);
return -EINVAL;
}
pgnr = DIV_ROUND_UP(map->length, PAGE_SIZE);
if (pgnr < ((PAGE_ALIGN(map->length)) >> PAGE_SHIFT)) {
dev_err(atomisp_dev,
"user space memory size is less than the expected size..\n");
return -ENOMEM;
} else if (pgnr > ((PAGE_ALIGN(map->length)) >> PAGE_SHIFT)) {
dev_err(atomisp_dev,
"user space memory size is large than the expected size..\n");
return -ENOMEM;
}
cssptr = hmm_alloc(map->length, HMM_BO_USER, 0, map->user_ptr,
map->flags & ATOMISP_MAP_FLAG_CACHED);
} else {
/* Allocate private buffer. */
cssptr = hmm_alloc(map->length, HMM_BO_PRIVATE, 0, NULL,
map->flags & ATOMISP_MAP_FLAG_CACHED);
}
if (!cssptr)
return -ENOMEM;
atomisp_map = kmalloc(sizeof(*atomisp_map), GFP_KERNEL);
if (!atomisp_map) {
hmm_free(cssptr);
return -ENOMEM;
}
atomisp_map->ptr = cssptr;
atomisp_map->length = map->length;
list_add(&atomisp_map->list, &asd->acc.memory_maps);
dev_dbg(asd->isp->dev, "%s: userptr %p, css_address 0x%x, size %d\n",
__func__, map->user_ptr, cssptr, map->length);
map->css_ptr = cssptr;
return 0;
}
int atomisp_acc_unmap(struct atomisp_sub_device *asd,
struct atomisp_acc_map *map)
{
struct atomisp_map *atomisp_map;
if (asd->acc.pipeline)
return -EBUSY;
atomisp_map = acc_get_map(asd, map->css_ptr, map->length);
if (!atomisp_map)
return -EINVAL;
list_del(&atomisp_map->list);
hmm_free(atomisp_map->ptr);
kfree(atomisp_map);
return 0;
}
int atomisp_acc_s_mapped_arg(struct atomisp_sub_device *asd,
struct atomisp_acc_s_mapped_arg *arg)
{
struct atomisp_acc_fw *acc_fw;
if (arg->memory >= ATOMISP_ACC_NR_MEMORY)
return -EINVAL;
if (asd->acc.pipeline)
return -EBUSY;
acc_fw = acc_get_fw(asd, arg->fw_handle);
if (!acc_fw)
return -EINVAL;
if (arg->css_ptr != 0 || arg->length != 0) {
/* Unless the parameter is cleared, check that it exists */
if (!acc_get_map(asd, arg->css_ptr, arg->length))
return -EINVAL;
}
acc_fw->args[arg->memory].length = arg->length;
acc_fw->args[arg->memory].css_ptr = arg->css_ptr;
dev_dbg(asd->isp->dev, "%s: mem %d, address %p, size %ld\n",
__func__, arg->memory, (void *)arg->css_ptr,
(unsigned long)arg->length);
return 0;
}
/*
* Appends the loaded acceleration binary extensions to the
* current ISP mode. Must be called just before sh_css_start().
*/
int atomisp_acc_load_extensions(struct atomisp_sub_device *asd)
{
struct atomisp_acc_fw *acc_fw;
bool ext_loaded = false;
bool continuous = asd->continuous_mode->val &&
asd->run_mode->val == ATOMISP_RUN_MODE_PREVIEW;
int ret = 0, i = -1;
struct atomisp_device *isp = asd->isp;
if (asd->acc.pipeline || asd->acc.extension_mode)
return -EBUSY;
/* Invalidate caches. FIXME: should flush only necessary buffers */
wbinvd();
list_for_each_entry(acc_fw, &asd->acc.fw, list) {
if (acc_fw->type != ATOMISP_ACC_FW_LOAD_TYPE_OUTPUT &&
acc_fw->type != ATOMISP_ACC_FW_LOAD_TYPE_VIEWFINDER)
continue;
for (i = 0; i < ARRAY_SIZE(acc_flag_to_pipe); i++) {
/* QoS (ACC pipe) acceleration stages are currently
* allowed only in continuous mode. Skip them for
* all other modes. */
if (!continuous &&
acc_flag_to_pipe[i].flag ==
ATOMISP_ACC_FW_LOAD_FL_ACC)
continue;
if (acc_fw->flags & acc_flag_to_pipe[i].flag) {
ret = atomisp_css_load_acc_extension(asd,
acc_fw->fw,
acc_flag_to_pipe[i].pipe_id,
acc_fw->type);
if (ret)
goto error;
ext_loaded = true;
}
}
ret = atomisp_css_set_acc_parameters(acc_fw);
if (ret < 0)
goto error;
}
if (!ext_loaded)
return ret;
ret = atomisp_css_update_stream(asd);
if (ret) {
dev_err(isp->dev, "%s: update stream failed.\n", __func__);
goto error;
}
asd->acc.extension_mode = true;
return 0;
error:
while (--i >= 0) {
if (acc_fw->flags & acc_flag_to_pipe[i].flag) {
atomisp_css_unload_acc_extension(asd, acc_fw->fw,
acc_flag_to_pipe[i].pipe_id);
}
}
list_for_each_entry_continue_reverse(acc_fw, &asd->acc.fw, list) {
if (acc_fw->type != ATOMISP_ACC_FW_LOAD_TYPE_OUTPUT &&
acc_fw->type != ATOMISP_ACC_FW_LOAD_TYPE_VIEWFINDER)
continue;
for (i = ARRAY_SIZE(acc_flag_to_pipe) - 1; i >= 0; i--) {
if (!continuous &&
acc_flag_to_pipe[i].flag ==
ATOMISP_ACC_FW_LOAD_FL_ACC)
continue;
if (acc_fw->flags & acc_flag_to_pipe[i].flag) {
atomisp_css_unload_acc_extension(asd,
acc_fw->fw,
acc_flag_to_pipe[i].pipe_id);
}
}
}
return ret;
}
void atomisp_acc_unload_extensions(struct atomisp_sub_device *asd)
{
struct atomisp_acc_fw *acc_fw;
int i;
if (!asd->acc.extension_mode)
return;
list_for_each_entry_reverse(acc_fw, &asd->acc.fw, list) {
if (acc_fw->type != ATOMISP_ACC_FW_LOAD_TYPE_OUTPUT &&
acc_fw->type != ATOMISP_ACC_FW_LOAD_TYPE_VIEWFINDER)
continue;
for (i = ARRAY_SIZE(acc_flag_to_pipe) - 1; i >= 0; i--) {
if (acc_fw->flags & acc_flag_to_pipe[i].flag) {
atomisp_css_unload_acc_extension(asd,
acc_fw->fw,
acc_flag_to_pipe[i].pipe_id);
}
}
}
asd->acc.extension_mode = false;
}
int atomisp_acc_set_state(struct atomisp_sub_device *asd,
struct atomisp_acc_state *arg)
{
struct atomisp_acc_fw *acc_fw;
bool enable = (arg->flags & ATOMISP_STATE_FLAG_ENABLE) != 0;
struct ia_css_pipe *pipe;
int r;
int i;
if (!asd->acc.extension_mode)
return -EBUSY;
if (arg->flags & ~ATOMISP_STATE_FLAG_ENABLE)
return -EINVAL;
acc_fw = acc_get_fw(asd, arg->fw_handle);
if (!acc_fw)
return -EINVAL;
if (enable)
wbinvd();
for (i = 0; i < ARRAY_SIZE(acc_flag_to_pipe); i++) {
if (acc_fw->flags & acc_flag_to_pipe[i].flag) {
pipe = asd->stream_env[ATOMISP_INPUT_STREAM_GENERAL].
pipes[acc_flag_to_pipe[i].pipe_id];
r = ia_css_pipe_set_qos_ext_state(pipe, acc_fw->handle,
enable);
if (r)
return -EBADRQC;
}
}
if (enable)
acc_fw->flags |= ATOMISP_ACC_FW_LOAD_FL_ENABLE;
else
acc_fw->flags &= ~ATOMISP_ACC_FW_LOAD_FL_ENABLE;
return 0;
}
int atomisp_acc_get_state(struct atomisp_sub_device *asd,
struct atomisp_acc_state *arg)
{
struct atomisp_acc_fw *acc_fw;
if (!asd->acc.extension_mode)
return -EBUSY;
acc_fw = acc_get_fw(asd, arg->fw_handle);
if (!acc_fw)
return -EINVAL;
arg->flags = acc_fw->flags;
return 0;
}