blob: e7d5605b1020ff3f77413c548c2fe39500c3c3e4 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Intel Corporation
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <asm/cacheflush.h>
#include "ipu6-ppg.h"
static bool enable_suspend_resume;
module_param(enable_suspend_resume, bool, 0664);
MODULE_PARM_DESC(enable_suspend_resume, "enable fw ppg suspend/resume api");
static struct ipu_psys_kcmd *
ipu_psys_ppg_get_kcmd(struct ipu_psys_ppg *kppg, enum ipu_psys_cmd_state state)
{
struct ipu_psys_kcmd *kcmd;
if (list_empty(&kppg->kcmds_new_list))
return NULL;
list_for_each_entry(kcmd, &kppg->kcmds_new_list, list) {
if (kcmd->state == state)
return kcmd;
}
return NULL;
}
struct ipu_psys_kcmd *ipu_psys_ppg_get_stop_kcmd(struct ipu_psys_ppg *kppg)
{
struct ipu_psys_kcmd *kcmd;
WARN(!mutex_is_locked(&kppg->mutex), "ppg locking error");
if (list_empty(&kppg->kcmds_processing_list))
return NULL;
list_for_each_entry(kcmd, &kppg->kcmds_processing_list, list) {
if (kcmd->state == KCMD_STATE_PPG_STOP)
return kcmd;
}
return NULL;
}
static struct ipu_psys_buffer_set *
__get_buf_set(struct ipu_psys_fh *fh, size_t buf_set_size)
{
struct ipu_psys_buffer_set *kbuf_set;
struct ipu_psys_scheduler *sched = &fh->sched;
mutex_lock(&sched->bs_mutex);
list_for_each_entry(kbuf_set, &sched->buf_sets, list) {
if (!kbuf_set->buf_set_size &&
kbuf_set->size >= buf_set_size) {
kbuf_set->buf_set_size = buf_set_size;
mutex_unlock(&sched->bs_mutex);
return kbuf_set;
}
}
mutex_unlock(&sched->bs_mutex);
/* no suitable buffer available, allocate new one */
kbuf_set = kzalloc(sizeof(*kbuf_set), GFP_KERNEL);
if (!kbuf_set)
return NULL;
kbuf_set->kaddr = dma_alloc_attrs(&fh->psys->adev->dev,
buf_set_size, &kbuf_set->dma_addr,
GFP_KERNEL, 0);
if (!kbuf_set->kaddr) {
kfree(kbuf_set);
return NULL;
}
kbuf_set->buf_set_size = buf_set_size;
kbuf_set->size = buf_set_size;
mutex_lock(&sched->bs_mutex);
list_add(&kbuf_set->list, &sched->buf_sets);
mutex_unlock(&sched->bs_mutex);
return kbuf_set;
}
static struct ipu_psys_buffer_set *
ipu_psys_create_buffer_set(struct ipu_psys_kcmd *kcmd,
struct ipu_psys_ppg *kppg)
{
struct ipu_psys_fh *fh = kcmd->fh;
struct ipu_psys *psys = fh->psys;
struct ipu_psys_buffer_set *kbuf_set;
size_t buf_set_size;
u32 *keb;
buf_set_size = ipu_fw_psys_ppg_get_buffer_set_size(kcmd);
kbuf_set = __get_buf_set(fh, buf_set_size);
if (!kbuf_set) {
dev_err(&psys->adev->dev, "failed to create buffer set\n");
return NULL;
}
kbuf_set->buf_set = ipu_fw_psys_ppg_create_buffer_set(kcmd,
kbuf_set->kaddr,
0);
ipu_fw_psys_ppg_buffer_set_vaddress(kbuf_set->buf_set,
kbuf_set->dma_addr);
keb = kcmd->kernel_enable_bitmap;
ipu_fw_psys_ppg_buffer_set_set_kernel_enable_bitmap(kbuf_set->buf_set,
keb);
return kbuf_set;
}
int ipu_psys_ppg_get_bufset(struct ipu_psys_kcmd *kcmd,
struct ipu_psys_ppg *kppg)
{
struct ipu_psys *psys = kppg->fh->psys;
struct ipu_psys_buffer_set *kbuf_set;
unsigned int i;
int ret;
kbuf_set = ipu_psys_create_buffer_set(kcmd, kppg);
if (!kbuf_set) {
ret = -EINVAL;
goto error;
}
kcmd->kbuf_set = kbuf_set;
kbuf_set->kcmd = kcmd;
for (i = 0; i < kcmd->nbuffers; i++) {
struct ipu_fw_psys_terminal *terminal;
u32 buffer;
terminal = ipu_fw_psys_pg_get_terminal(kcmd, i);
if (!terminal)
continue;
buffer = (u32)kcmd->kbufs[i]->dma_addr +
kcmd->buffers[i].data_offset;
ret = ipu_fw_psys_ppg_set_buffer_set(kcmd, terminal, i, buffer);
if (ret) {
dev_err(&psys->adev->dev, "Unable to set bufset\n");
goto error;
}
}
return 0;
error:
dev_err(&psys->adev->dev, "failed to get buffer set\n");
return ret;
}
void ipu_psys_ppg_complete(struct ipu_psys *psys, struct ipu_psys_ppg *kppg)
{
u8 queue_id;
int old_ppg_state;
if (!psys || !kppg)
return;
mutex_lock(&kppg->mutex);
old_ppg_state = kppg->state;
if (kppg->state == PPG_STATE_STOPPING) {
struct ipu_psys_kcmd tmp_kcmd = {
.kpg = kppg->kpg,
};
kppg->state = PPG_STATE_STOPPED;
ipu_psys_free_resources(&kppg->kpg->resource_alloc,
&psys->resource_pool_running);
queue_id = ipu_fw_psys_ppg_get_base_queue_id(&tmp_kcmd);
ipu_psys_free_cmd_queue_resource(&psys->resource_pool_running,
queue_id);
pm_runtime_put(&psys->adev->dev);
} else {
if (kppg->state == PPG_STATE_SUSPENDING) {
kppg->state = PPG_STATE_SUSPENDED;
ipu_psys_free_resources(&kppg->kpg->resource_alloc,
&psys->resource_pool_running);
} else if (kppg->state == PPG_STATE_STARTED ||
kppg->state == PPG_STATE_RESUMED) {
kppg->state = PPG_STATE_RUNNING;
}
/* Kick l-scheduler thread for FW callback,
* also for checking if need to enter power gating
*/
atomic_set(&psys->wakeup_count, 1);
wake_up_interruptible(&psys->sched_cmd_wq);
}
if (old_ppg_state != kppg->state)
dev_dbg(&psys->adev->dev, "s_change:%s: %p %d -> %d\n",
__func__, kppg, old_ppg_state, kppg->state);
mutex_unlock(&kppg->mutex);
}
int ipu_psys_ppg_start(struct ipu_psys_ppg *kppg)
{
struct ipu_psys *psys = kppg->fh->psys;
struct ipu_psys_kcmd *kcmd = ipu_psys_ppg_get_kcmd(kppg,
KCMD_STATE_PPG_START);
unsigned int i;
int ret;
if (!kcmd) {
dev_err(&psys->adev->dev, "failed to find start kcmd!\n");
return -EINVAL;
}
dev_dbg(&psys->adev->dev, "start ppg id %d, addr 0x%p\n",
ipu_fw_psys_pg_get_id(kcmd), kppg);
kppg->state = PPG_STATE_STARTING;
for (i = 0; i < kcmd->nbuffers; i++) {
struct ipu_fw_psys_terminal *terminal;
terminal = ipu_fw_psys_pg_get_terminal(kcmd, i);
if (!terminal)
continue;
ret = ipu_fw_psys_terminal_set(terminal, i, kcmd, 0,
kcmd->buffers[i].len);
if (ret) {
dev_err(&psys->adev->dev, "Unable to set terminal\n");
return ret;
}
}
ret = ipu_fw_psys_pg_submit(kcmd);
if (ret) {
dev_err(&psys->adev->dev, "failed to submit kcmd!\n");
return ret;
}
ret = ipu_psys_allocate_resources(&psys->adev->dev,
kcmd->kpg->pg,
kcmd->pg_manifest,
&kcmd->kpg->resource_alloc,
&psys->resource_pool_running);
if (ret) {
dev_err(&psys->adev->dev, "alloc resources failed!\n");
return ret;
}
ret = pm_runtime_get_sync(&psys->adev->dev);
if (ret < 0) {
dev_err(&psys->adev->dev, "failed to power on psys\n");
goto error;
}
ret = ipu_psys_kcmd_start(psys, kcmd);
if (ret) {
ipu_psys_kcmd_complete(kppg, kcmd, -EIO);
goto error;
}
dev_dbg(&psys->adev->dev, "s_change:%s: %p %d -> %d\n",
__func__, kppg, kppg->state, PPG_STATE_STARTED);
kppg->state = PPG_STATE_STARTED;
ipu_psys_kcmd_complete(kppg, kcmd, 0);
return 0;
error:
pm_runtime_put_noidle(&psys->adev->dev);
ipu_psys_reset_process_cell(&psys->adev->dev,
kcmd->kpg->pg,
kcmd->pg_manifest,
kcmd->kpg->pg->process_count);
ipu_psys_free_resources(&kppg->kpg->resource_alloc,
&psys->resource_pool_running);
dev_err(&psys->adev->dev, "failed to start ppg\n");
return ret;
}
int ipu_psys_ppg_resume(struct ipu_psys_ppg *kppg)
{
struct ipu_psys *psys = kppg->fh->psys;
struct ipu_psys_kcmd tmp_kcmd = {
.kpg = kppg->kpg,
.fh = kppg->fh,
};
int ret;
dev_dbg(&psys->adev->dev, "resume ppg id %d, addr 0x%p\n",
ipu_fw_psys_pg_get_id(&tmp_kcmd), kppg);
kppg->state = PPG_STATE_RESUMING;
if (enable_suspend_resume) {
ret = ipu_psys_allocate_resources(&psys->adev->dev,
kppg->kpg->pg,
kppg->manifest,
&kppg->kpg->resource_alloc,
&psys->resource_pool_running);
if (ret) {
dev_err(&psys->adev->dev, "failed to allocate res\n");
return -EIO;
}
ret = ipu_fw_psys_ppg_resume(&tmp_kcmd);
if (ret) {
dev_err(&psys->adev->dev, "failed to resume ppg\n");
goto error;
}
} else {
kppg->kpg->pg->state = IPU_FW_PSYS_PROCESS_GROUP_READY;
ret = ipu_fw_psys_pg_submit(&tmp_kcmd);
if (ret) {
dev_err(&psys->adev->dev, "failed to submit kcmd!\n");
return ret;
}
ret = ipu_psys_allocate_resources(&psys->adev->dev,
kppg->kpg->pg,
kppg->manifest,
&kppg->kpg->resource_alloc,
&psys->resource_pool_running);
if (ret) {
dev_err(&psys->adev->dev, "failed to allocate res\n");
return ret;
}
ret = ipu_psys_kcmd_start(psys, &tmp_kcmd);
if (ret) {
dev_err(&psys->adev->dev, "failed to start kcmd!\n");
goto error;
}
}
dev_dbg(&psys->adev->dev, "s_change:%s: %p %d -> %d\n",
__func__, kppg, kppg->state, PPG_STATE_RESUMED);
kppg->state = PPG_STATE_RESUMED;
return 0;
error:
ipu_psys_reset_process_cell(&psys->adev->dev,
kppg->kpg->pg,
kppg->manifest,
kppg->kpg->pg->process_count);
ipu_psys_free_resources(&kppg->kpg->resource_alloc,
&psys->resource_pool_running);
return ret;
}
int ipu_psys_ppg_stop(struct ipu_psys_ppg *kppg)
{
struct ipu_psys_kcmd *kcmd = ipu_psys_ppg_get_kcmd(kppg,
KCMD_STATE_PPG_STOP);
struct ipu_psys *psys = kppg->fh->psys;
struct ipu_psys_kcmd kcmd_temp;
int ppg_id, ret = 0;
if (kcmd) {
list_move_tail(&kcmd->list, &kppg->kcmds_processing_list);
} else {
dev_dbg(&psys->adev->dev, "Exceptional stop happened!\n");
kcmd_temp.kpg = kppg->kpg;
kcmd_temp.fh = kppg->fh;
kcmd = &kcmd_temp;
/* delete kppg in stop list to avoid this ppg resuming */
ipu_psys_scheduler_remove_kppg(kppg, SCHED_STOP_LIST);
}
ppg_id = ipu_fw_psys_pg_get_id(kcmd);
dev_dbg(&psys->adev->dev, "stop ppg(%d, addr 0x%p)\n", ppg_id, kppg);
if (kppg->state & PPG_STATE_SUSPENDED) {
if (enable_suspend_resume) {
dev_dbg(&psys->adev->dev, "need resume before stop!\n");
kcmd_temp.kpg = kppg->kpg;
kcmd_temp.fh = kppg->fh;
ret = ipu_fw_psys_ppg_resume(&kcmd_temp);
if (ret)
dev_err(&psys->adev->dev,
"ppg(%d) failed to resume\n", ppg_id);
} else if (kcmd != &kcmd_temp) {
ipu_psys_free_cmd_queue_resource(
&psys->resource_pool_running,
ipu_fw_psys_ppg_get_base_queue_id(kcmd));
ipu_psys_kcmd_complete(kppg, kcmd, 0);
dev_dbg(&psys->adev->dev,
"s_change:%s %p %d -> %d\n", __func__,
kppg, kppg->state, PPG_STATE_STOPPED);
pm_runtime_put(&psys->adev->dev);
kppg->state = PPG_STATE_STOPPED;
return 0;
} else {
return 0;
}
}
dev_dbg(&psys->adev->dev, "s_change:%s %p %d -> %d\n",
__func__, kppg, kppg->state, PPG_STATE_STOPPING);
kppg->state = PPG_STATE_STOPPING;
ret = ipu_fw_psys_pg_abort(kcmd);
if (ret)
dev_err(&psys->adev->dev, "ppg(%d) failed to abort\n", ppg_id);
return ret;
}
int ipu_psys_ppg_suspend(struct ipu_psys_ppg *kppg)
{
struct ipu_psys *psys = kppg->fh->psys;
struct ipu_psys_kcmd tmp_kcmd = {
.kpg = kppg->kpg,
.fh = kppg->fh,
};
int ppg_id = ipu_fw_psys_pg_get_id(&tmp_kcmd);
int ret = 0;
dev_dbg(&psys->adev->dev, "suspend ppg(%d, addr 0x%p)\n", ppg_id, kppg);
dev_dbg(&psys->adev->dev, "s_change:%s %p %d -> %d\n",
__func__, kppg, kppg->state, PPG_STATE_SUSPENDING);
kppg->state = PPG_STATE_SUSPENDING;
if (enable_suspend_resume)
ret = ipu_fw_psys_ppg_suspend(&tmp_kcmd);
else
ret = ipu_fw_psys_pg_abort(&tmp_kcmd);
if (ret)
dev_err(&psys->adev->dev, "failed to %s ppg(%d)\n",
enable_suspend_resume ? "suspend" : "stop", ret);
return ret;
}
static bool ipu_psys_ppg_is_bufset_existing(struct ipu_psys_ppg *kppg)
{
return !list_empty(&kppg->kcmds_new_list);
}
/*
* ipu_psys_ppg_enqueue_bufsets - enqueue buffer sets to firmware
* Sometimes, if the ppg is at suspended state, this function will return true
* to reschedule and let the resume command scheduled before the buffer sets
* enqueuing.
*/
bool ipu_psys_ppg_enqueue_bufsets(struct ipu_psys_ppg *kppg)
{
struct ipu_psys_kcmd *kcmd, *kcmd0;
struct ipu_psys *psys = kppg->fh->psys;
bool need_resume = false;
mutex_lock(&kppg->mutex);
if (kppg->state & (PPG_STATE_STARTED | PPG_STATE_RESUMED |
PPG_STATE_RUNNING)) {
if (ipu_psys_ppg_is_bufset_existing(kppg)) {
list_for_each_entry_safe(kcmd, kcmd0,
&kppg->kcmds_new_list, list) {
int ret;
if (kcmd->state != KCMD_STATE_PPG_ENQUEUE) {
need_resume = true;
break;
}
ret = ipu_fw_psys_ppg_enqueue_bufs(kcmd);
if (ret) {
dev_err(&psys->adev->dev,
"kppg 0x%p fail to qbufset %d",
kppg, ret);
break;
}
list_move_tail(&kcmd->list,
&kppg->kcmds_processing_list);
dev_dbg(&psys->adev->dev,
"kppg %d %p queue kcmd 0x%p fh 0x%p\n",
ipu_fw_psys_pg_get_id(kcmd),
kppg, kcmd, kcmd->fh);
}
}
}
mutex_unlock(&kppg->mutex);
return need_resume;
}
void ipu_psys_enter_power_gating(struct ipu_psys *psys)
{
struct ipu_psys_scheduler *sched;
struct ipu_psys_ppg *kppg, *tmp;
struct ipu_psys_fh *fh;
int ret = 0;
list_for_each_entry(fh, &psys->fhs, list) {
mutex_lock(&fh->mutex);
sched = &fh->sched;
if (list_empty(&sched->ppgs)) {
mutex_unlock(&fh->mutex);
continue;
}
list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) {
mutex_lock(&kppg->mutex);
/*
* Only for SUSPENDED kppgs, STOPPED kppgs has already
* power down and new kppgs might come now.
*/
if (kppg->state != PPG_STATE_SUSPENDED) {
mutex_unlock(&kppg->mutex);
continue;
}
ret = pm_runtime_put(&psys->adev->dev);
if (ret < 0) {
dev_err(&psys->adev->dev,
"failed to power gating off\n");
pm_runtime_get_sync(&psys->adev->dev);
}
mutex_unlock(&kppg->mutex);
}
mutex_unlock(&fh->mutex);
}
}
void ipu_psys_exit_power_gating(struct ipu_psys *psys)
{
struct ipu_psys_scheduler *sched;
struct ipu_psys_ppg *kppg, *tmp;
struct ipu_psys_fh *fh;
int ret = 0;
list_for_each_entry(fh, &psys->fhs, list) {
mutex_lock(&fh->mutex);
sched = &fh->sched;
if (list_empty(&sched->ppgs)) {
mutex_unlock(&fh->mutex);
continue;
}
list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) {
mutex_lock(&kppg->mutex);
/* Only for SUSPENDED kppgs */
if (kppg->state != PPG_STATE_SUSPENDED) {
mutex_unlock(&kppg->mutex);
continue;
}
ret = pm_runtime_get_sync(&psys->adev->dev);
if (ret < 0) {
dev_err(&psys->adev->dev,
"failed to power gating\n");
pm_runtime_put_noidle(&psys->adev->dev);
}
mutex_unlock(&kppg->mutex);
}
mutex_unlock(&fh->mutex);
}
}