952 lines
22 KiB
C
952 lines
22 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-only
|
||
|
/*
|
||
|
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||
|
* http://www.samsung.com
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/version.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/compat.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/sort.h>
|
||
|
#include <linux/iommu.h>
|
||
|
#include <linux/dma-iommu.h>
|
||
|
#include <linux/ion.h>
|
||
|
#include <linux/dma-buf.h>
|
||
|
|
||
|
#include "scaler.h"
|
||
|
#include "scaler-ext.h"
|
||
|
#include "scaler-regs.h"
|
||
|
|
||
|
extern int sc_show_stat;
|
||
|
|
||
|
static int sc_ext_cmd_offset[MSCL_NR_CMDS] = {
|
||
|
[MSCL_SRC_CFG] = SCALER_SRC_CFG,
|
||
|
[MSCL_SRC_WH] = SCALER_SRC_WH,
|
||
|
[MSCL_SRC_SPAN] = SCALER_SRC_SPAN,
|
||
|
[MSCL_SRC_YPOS] = SCALER_SRC_Y_POS,
|
||
|
[MSCL_SRC_CPOS] = SCALER_SRC_C_POS,
|
||
|
[MSCL_DST_CFG] = SCALER_DST_CFG,
|
||
|
[MSCL_DST_WH] = SCALER_DST_WH,
|
||
|
[MSCL_DST_SPAN] = SCALER_DST_SPAN,
|
||
|
[MSCL_DST_POS] = SCALER_DST_POS,
|
||
|
[MSCL_V_RATIO] = SCALER_V_RATIO,
|
||
|
[MSCL_H_RATIO] = SCALER_H_RATIO,
|
||
|
[MSCL_ROT_CFG] = SCALER_ROT_CFG,
|
||
|
[MSCL_SRC_YH_IPHASE] = SCALER_SRC_YH_INIT_PHASE,
|
||
|
[MSCL_SRC_YV_IPHASE] = SCALER_SRC_YV_INIT_PHASE,
|
||
|
[MSCL_SRC_CH_IPHASE] = SCALER_SRC_CH_INIT_PHASE,
|
||
|
[MSCL_SRC_CV_IPHASE] = SCALER_SRC_CV_INIT_PHASE,
|
||
|
};
|
||
|
|
||
|
static int sc_ext_buf_offset[MSCL_NR_DIRS][MSCL_MAX_PLANES] = {
|
||
|
{ SCALER_SRC_Y_BASE, SCALER_SRC_CB_BASE, SCALER_SRC_CR_BASE, },
|
||
|
{ SCALER_DST_Y_BASE, SCALER_DST_CB_BASE, SCALER_DST_CR_BASE, },
|
||
|
};
|
||
|
|
||
|
#define SC_EXT_FORMAT_NV12 0
|
||
|
#define SC_EXT_FORMAT_NV21 16
|
||
|
#define SC_EXT_FORMAT_YUYV 10
|
||
|
|
||
|
static const struct sc_ext_format sc_ext_supported_formats[] = {
|
||
|
{ SC_EXT_FORMAT_NV12, 2, 1, 1, {0, 2, 0}, "NV12" },
|
||
|
{ SC_EXT_FORMAT_NV21, 2, 1, 1, {0, 2, 0}, "NV21" },
|
||
|
{ SC_EXT_FORMAT_YUYV, 1, 1, 0, {2, 0, 0}, "YUYV" },
|
||
|
};
|
||
|
|
||
|
static inline const char *sc_ext_get_format_name(uint32_t fmt)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(sc_ext_supported_formats); i++) {
|
||
|
if (sc_ext_supported_formats[i].format == fmt)
|
||
|
return sc_ext_supported_formats[i].name;
|
||
|
}
|
||
|
|
||
|
return "Unknown";
|
||
|
}
|
||
|
|
||
|
/* debugging information begin */
|
||
|
char *cmd_str[] = {
|
||
|
"MSCL_SRC_CFG",
|
||
|
"MSCL_SRC_WH",
|
||
|
"MSCL_SRC_SPAN",
|
||
|
"MSCL_SRC_YPOS",
|
||
|
"MSCL_SRC_CPOS",
|
||
|
"MSCL_DST_CFG",
|
||
|
"MSCL_DST_WH",
|
||
|
"MSCL_DST_SPAN",
|
||
|
"MSCL_DST_POS",
|
||
|
"MSCL_V_RATIO",
|
||
|
"MSCL_H_RATIO",
|
||
|
"MSCL_ROT_CFG",
|
||
|
"MSCL_SRC_YH_IPHASE",
|
||
|
"MSCL_SRC_YV_IPHASE",
|
||
|
"MSCL_SRC_CH_IPHASE",
|
||
|
"MSCL_SRC_CV_IPHASE",
|
||
|
};
|
||
|
|
||
|
#define SC_CMD_GET_POS_X(val) (((val) >> 16) & 0xffff)
|
||
|
#define SC_CMD_GET_POS_Y(val) ((val) & 0xffff)
|
||
|
#define SC_CMD_GET_WIDTH(val) (((val) >> 16) & 0xffff)
|
||
|
#define SC_CMD_GET_HEIGHT(val) ((val) & 0xffff)
|
||
|
#define SC_CMD_GET_SPAN_Y(val) ((val) & 0xffff)
|
||
|
#define SC_CMD_GET_SPAN_C(val) (((val) >> 16) & 0xffff)
|
||
|
|
||
|
static inline void show_cmd_line(int index, uint32_t value)
|
||
|
{
|
||
|
char comment[50] = { 0, };
|
||
|
|
||
|
switch (index) {
|
||
|
case MSCL_SRC_CFG:
|
||
|
case MSCL_DST_CFG:
|
||
|
snprintf(comment, sizeof(comment),
|
||
|
"[format:%s]", sc_ext_get_format_name(value));
|
||
|
break;
|
||
|
case MSCL_SRC_WH:
|
||
|
case MSCL_DST_WH:
|
||
|
snprintf(comment, sizeof(comment),
|
||
|
"[width:%d, height:%d]",
|
||
|
SC_CMD_GET_WIDTH(value),
|
||
|
SC_CMD_GET_HEIGHT(value));
|
||
|
break;
|
||
|
case MSCL_SRC_YPOS:
|
||
|
case MSCL_SRC_CPOS:
|
||
|
case MSCL_DST_POS:
|
||
|
snprintf(comment, sizeof(comment),
|
||
|
"[x pos:%d, y pos:%d]",
|
||
|
SC_CMD_GET_POS_X(value),
|
||
|
SC_CMD_GET_POS_Y(value));
|
||
|
break;
|
||
|
case MSCL_SRC_SPAN:
|
||
|
case MSCL_DST_SPAN:
|
||
|
snprintf(comment, sizeof(comment),
|
||
|
"[y span:%d, c span:%d]",
|
||
|
SC_CMD_GET_SPAN_Y(value),
|
||
|
SC_CMD_GET_SPAN_C(value));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pr_info(" [%20s:%#06x] : %#010x %s\n",
|
||
|
cmd_str[index], sc_ext_cmd_offset[index], value, comment);
|
||
|
}
|
||
|
|
||
|
static void show_task_cmd(uint32_t *cmd)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
pr_info(" - command list (command name:offset)-\n");
|
||
|
for (i = 0; i < MSCL_NR_CMDS; i++)
|
||
|
show_cmd_line(i, cmd[i]);
|
||
|
}
|
||
|
|
||
|
static void sc_ext_show_task_dma(struct sc_ext_buf *buf, char *str)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
pr_info(" - [%s] buffer\n", str);
|
||
|
for (i = 0; i < buf->count; i++)
|
||
|
pr_info(" Plane[%d] addr = (base %pad + offset %#lx)\n",
|
||
|
i, &buf->dma[i].dma_addr, buf->dma[i].offset);
|
||
|
}
|
||
|
|
||
|
static void sc_ext_show_task_buf(struct sc_ext_task_data *data, int index)
|
||
|
{
|
||
|
pr_info(" = task %#d =\n", index);
|
||
|
sc_ext_show_task_dma(&data->buf[MSCL_SRC], "SRC");
|
||
|
sc_ext_show_task_dma(&data->buf[MSCL_DST], "DST");
|
||
|
}
|
||
|
|
||
|
static void sc_ext_show_all_task_buf(struct sc_ext_task *task)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
pr_info("++ extracted buffer information\n");
|
||
|
for (i = 0; i < task->task_count; i++)
|
||
|
sc_ext_show_task_buf(&task->data[i], i);
|
||
|
}
|
||
|
|
||
|
static void sc_ext_show_task_data(struct sc_ext_task_data *data, int index)
|
||
|
{
|
||
|
pr_info(" = task %#d =\n", index);
|
||
|
|
||
|
show_task_cmd(data->cmd);
|
||
|
sc_ext_show_task_dma(&data->buf[MSCL_SRC], "SRC");
|
||
|
sc_ext_show_task_dma(&data->buf[MSCL_DST], "DST");
|
||
|
}
|
||
|
|
||
|
static void show_mscl_buf(struct mscl_buffer *buf, char *str)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
pr_info(" - [%s] buffer (count:%d, rsvd:%d) -\n",
|
||
|
str, buf->count, buf->reserved);
|
||
|
for (i = 0; i < buf->count; i++)
|
||
|
pr_info(" Plane[%d] dmabuf: %#x, offset: %#x\n",
|
||
|
i, buf->dmabuf[i], buf->offset[i]);
|
||
|
}
|
||
|
|
||
|
static void show_mscl_task(struct mscl_task *task, int index)
|
||
|
{
|
||
|
pr_info(" = task #%d =\n", index);
|
||
|
show_task_cmd(task->cmd);
|
||
|
show_mscl_buf(&task->buf[MSCL_SRC], "SRC");
|
||
|
show_mscl_buf(&task->buf[MSCL_DST], "DST");
|
||
|
}
|
||
|
|
||
|
static void sc_ext_show_mscl_task(struct mscl_task *task, int total)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
pr_info("++ user task information, total task count: %d\n", total);
|
||
|
for (i = 0; i < total; i++)
|
||
|
show_mscl_task(&task[i], i);
|
||
|
}
|
||
|
/* debugging information end */
|
||
|
|
||
|
static inline struct sc_ext_ctx *sc_ctx_to_xctx(struct sc_ctx *sc_ctx)
|
||
|
{
|
||
|
return container_of(sc_ctx, struct sc_ext_ctx, sc_ctx);
|
||
|
}
|
||
|
|
||
|
static void sc_ext_set_buffer(struct sc_dev *sc_dev,
|
||
|
struct sc_ext_buf *buf, int dir)
|
||
|
{
|
||
|
struct sc_ext_dma *dma = buf->dma;
|
||
|
uint32_t value;
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < buf->count; i++) {
|
||
|
value = dma[i].dma_addr + dma[i].offset;
|
||
|
writel_relaxed(value, sc_dev->regs + sc_ext_buf_offset[dir][i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int sc_ext_run_job(struct sc_ctx *ctx)
|
||
|
{
|
||
|
int i;
|
||
|
struct sc_dev *sc_dev = ctx->sc_dev;
|
||
|
struct sc_ext_task *task = sc_dev->xdev->current_task;
|
||
|
struct sc_ext_task_data *data = &task->data[task->curr_count];
|
||
|
uint32_t h_ratio, v_ratio;
|
||
|
|
||
|
/* Set the SFR from user */
|
||
|
for (i = 0; i < MSCL_NR_CMDS; i++) {
|
||
|
if (sc_dev->version >= SCALER_VERSION(6, 0, 1)) {
|
||
|
if (i == MSCL_SRC_SPAN) {
|
||
|
writel_relaxed(SC_CMD_GET_SPAN_Y(data->cmd[i]),
|
||
|
sc_dev->regs + SCALER_SRC_YSPAN);
|
||
|
writel_relaxed(SC_CMD_GET_SPAN_C(data->cmd[i]),
|
||
|
sc_dev->regs + SCALER_SRC_CSPAN);
|
||
|
continue;
|
||
|
} else if (i == MSCL_DST_SPAN) {
|
||
|
writel_relaxed(SC_CMD_GET_SPAN_Y(data->cmd[i]),
|
||
|
sc_dev->regs + SCALER_DST_YSPAN);
|
||
|
writel_relaxed(SC_CMD_GET_SPAN_C(data->cmd[i]),
|
||
|
sc_dev->regs + SCALER_DST_CSPAN);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
writel_relaxed(data->cmd[i], sc_dev->regs + sc_ext_cmd_offset[i]);
|
||
|
}
|
||
|
|
||
|
/* Set the SFR for buffer */
|
||
|
sc_ext_set_buffer(sc_dev, &data->buf[MSCL_SRC], MSCL_SRC);
|
||
|
sc_ext_set_buffer(sc_dev, &data->buf[MSCL_DST], MSCL_DST);
|
||
|
|
||
|
h_ratio = data->cmd[MSCL_H_RATIO];
|
||
|
v_ratio = data->cmd[MSCL_V_RATIO];
|
||
|
|
||
|
/* Set the SFR for internal */
|
||
|
sc_hwset_csc_coef(sc_dev, NO_CSC, NULL);
|
||
|
sc_hwset_polyphase_hcoef(sc_dev, h_ratio, h_ratio, 0);
|
||
|
sc_hwset_polyphase_vcoef(sc_dev, v_ratio, v_ratio, 0);
|
||
|
sc_hwset_int_en(sc_dev);
|
||
|
|
||
|
if (sc_show_stat & 0x1)
|
||
|
sc_hwregs_dump(sc_dev);
|
||
|
|
||
|
sc_hwset_start(sc_dev);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void sc_ext_try_task(struct sc_ext_dev *xdev, struct sc_ext_task *task)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave(&xdev->lock_task, flags);
|
||
|
xdev->current_task = task;
|
||
|
spin_unlock_irqrestore(&xdev->lock_task, flags);
|
||
|
|
||
|
task->state = SC_EXT_BUFSTATE_PROCESSING;
|
||
|
|
||
|
if (sc_ext_device_run(&task->xctx->sc_ctx)) {
|
||
|
task->state = SC_EXT_BUFSTATE_ERROR;
|
||
|
|
||
|
spin_lock_irqsave(&xdev->lock_task, flags);
|
||
|
xdev->current_task = NULL;
|
||
|
spin_unlock_irqrestore(&xdev->lock_task, flags);
|
||
|
|
||
|
complete(&task->complete);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void sc_ext_current_task_finish(struct sc_ext_dev *xdev, bool success)
|
||
|
{
|
||
|
struct sc_ext_task *task = xdev->current_task;
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave(&xdev->lock_task, flags);
|
||
|
BUG_ON(!xdev->current_task);
|
||
|
xdev->current_task = NULL;
|
||
|
spin_unlock_irqrestore(&xdev->lock_task, flags);
|
||
|
|
||
|
task->state = success ?
|
||
|
SC_EXT_BUFSTATE_DONE : SC_EXT_BUFSTATE_ERROR;
|
||
|
|
||
|
complete(&task->complete);
|
||
|
}
|
||
|
|
||
|
/* Returns true if final task is finished. */
|
||
|
bool sc_ext_job_finished(struct sc_ctx *ctx)
|
||
|
{
|
||
|
struct sc_ext_ctx *xctx = sc_ctx_to_xctx(ctx);
|
||
|
struct sc_ext_task *task = xctx->xdev->current_task;
|
||
|
|
||
|
task->curr_count++;
|
||
|
|
||
|
return (task->curr_count == task->task_count) ? true : false;
|
||
|
}
|
||
|
|
||
|
static const struct sc_ext_format *sc_ext_get_format(uint32_t fmt)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(sc_ext_supported_formats); i++)
|
||
|
if (sc_ext_supported_formats[i].format == fmt)
|
||
|
return &sc_ext_supported_formats[i];
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static int sc_ext_prepare_format(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_task *task)
|
||
|
{
|
||
|
uint32_t src, dst;
|
||
|
int i;
|
||
|
struct sc_ext_task_data *data = task->data;
|
||
|
const struct sc_ext_format *fmt;
|
||
|
|
||
|
for (i = 0; i < task->task_count; i++) {
|
||
|
src = data[i].cmd[MSCL_SRC_CFG];
|
||
|
dst = data[i].cmd[MSCL_DST_CFG];
|
||
|
fmt = sc_ext_get_format(src);
|
||
|
|
||
|
/*
|
||
|
* CSC is not supported.
|
||
|
* Source format and destination format must be same.
|
||
|
*/
|
||
|
if (src != dst || !fmt) {
|
||
|
dev_err(xdev->dev,
|
||
|
"Invalid src/dst format[%#x/%#x] for task %d\n",
|
||
|
src, dst, i);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
data[i].buf[MSCL_SRC].fmt = fmt;
|
||
|
data[i].buf[MSCL_DST].fmt = fmt;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void sc_ext_put_dmabuf(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_buf *buf, int dir)
|
||
|
{
|
||
|
struct sc_ext_dma *dma = buf->dma;
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < buf->count; i++) {
|
||
|
if (!IS_ERR_OR_NULL(dma[i].sgt)) {
|
||
|
dma_buf_unmap_attachment(dma[i].attachment,
|
||
|
dma[i].sgt, DMA_BIDIRECTIONAL);
|
||
|
}
|
||
|
if (!IS_ERR_OR_NULL(dma[i].attachment))
|
||
|
dma_buf_detach(dma[i].dmabuf, dma[i].attachment);
|
||
|
if (!IS_ERR_OR_NULL(dma[i].dmabuf))
|
||
|
dma_buf_put(dma[i].dmabuf);
|
||
|
|
||
|
memset(&dma[i], 0x0, sizeof(dma[i]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int sc_ext_get_dmabuf(struct sc_ext_dev *xdev,
|
||
|
struct mscl_buffer *taskbuf, struct sc_ext_buf *buf, int dir)
|
||
|
{
|
||
|
enum dma_data_direction dma_dir =
|
||
|
(dir == MSCL_SRC) ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
|
||
|
struct sc_ext_dma *dma = buf->dma;
|
||
|
int i, ret;
|
||
|
|
||
|
if (taskbuf->count > MSCL_MAX_PLANES) {
|
||
|
dev_err(xdev->dev,
|
||
|
"taskbuf->count(%d) is larger than MAX(%d)\n",
|
||
|
taskbuf->count, MSCL_MAX_PLANES);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
buf->count = taskbuf->count;
|
||
|
|
||
|
for (i = 0; i < buf->count; i++) {
|
||
|
dma[i].dmabuf = dma_buf_get(taskbuf->dmabuf[i]);
|
||
|
if (IS_ERR(dma[i].dmabuf)) {
|
||
|
ret = PTR_ERR(dma[i].dmabuf);
|
||
|
dev_err(xdev->dev,
|
||
|
"Failed to get dmabuf, ret:%d\n", ret);
|
||
|
goto err_dma_buf_get;
|
||
|
}
|
||
|
|
||
|
dma[i].attachment = dma_buf_attach(dma[i].dmabuf, xdev->dev);
|
||
|
if (IS_ERR(dma[i].attachment)) {
|
||
|
ret = PTR_ERR(dma[i].attachment);
|
||
|
dev_err(xdev->dev,
|
||
|
"Failed to attach dmabuf, ret:%d\n", ret);
|
||
|
goto err_dma_buf_get;
|
||
|
}
|
||
|
|
||
|
dma[i].sgt = dma_buf_map_attachment(dma[i].attachment, dma_dir);
|
||
|
if (IS_ERR(dma[i].sgt)) {
|
||
|
ret = PTR_ERR(dma[i].sgt);
|
||
|
dev_err(xdev->dev,
|
||
|
"Failed to map dmabuf, ret:%d\n", ret);
|
||
|
goto err_dma_buf_get;
|
||
|
}
|
||
|
|
||
|
dma[i].dma_addr = sg_dma_address(dma[i].sgt->sgl);
|
||
|
if (IS_ERR_VALUE(dma[i].dma_addr)) {
|
||
|
ret = dma[i].dma_addr;
|
||
|
dev_err(xdev->dev,
|
||
|
"Failed to map dma address, ret:%d\n", ret);
|
||
|
goto err_dma_buf_get;
|
||
|
}
|
||
|
|
||
|
dma[i].offset = taskbuf->offset[i];
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_dma_buf_get:
|
||
|
sc_ext_put_dmabuf(xdev, buf, dir);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int sc_ext_prepare_dmabuf(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_task_data *data, struct mscl_task *mscl_task)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = sc_ext_get_dmabuf(xdev, &mscl_task->buf[MSCL_SRC],
|
||
|
&data->buf[MSCL_SRC], MSCL_SRC);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = sc_ext_get_dmabuf(xdev, &mscl_task->buf[MSCL_DST],
|
||
|
&data->buf[MSCL_DST], MSCL_DST);
|
||
|
if (ret) {
|
||
|
sc_ext_put_dmabuf(xdev, &data->buf[MSCL_SRC], MSCL_SRC);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void sc_ext_finish_dmabuf(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_task_data *data)
|
||
|
{
|
||
|
sc_ext_put_dmabuf(xdev, &data->buf[MSCL_SRC], MSCL_SRC);
|
||
|
sc_ext_put_dmabuf(xdev, &data->buf[MSCL_DST], MSCL_DST);
|
||
|
}
|
||
|
|
||
|
static bool sc_ext_buf_access_safe(struct sc_ext_buf *buf,
|
||
|
int width, int height, int span, int plane_idx)
|
||
|
{
|
||
|
int payload = 0;
|
||
|
|
||
|
/* Luma is on plane 0 */
|
||
|
if (plane_idx == 0) {
|
||
|
payload = span * (height - 1) + width;
|
||
|
if (width < span)
|
||
|
payload += 128;
|
||
|
}
|
||
|
|
||
|
if (buf->fmt->nr_chroma[plane_idx] > 0) {
|
||
|
int chroma_payload;
|
||
|
|
||
|
span >>= buf->fmt->hshift;
|
||
|
width >>= buf->fmt->hshift;
|
||
|
height >>= buf->fmt->vshift;
|
||
|
|
||
|
chroma_payload = span * (height - 1) + width;
|
||
|
chroma_payload *= buf->fmt->nr_chroma[plane_idx];
|
||
|
if (width < span)
|
||
|
chroma_payload += 128;
|
||
|
|
||
|
payload += chroma_payload;
|
||
|
}
|
||
|
|
||
|
payload += buf->dma[plane_idx].offset;
|
||
|
|
||
|
if (payload > buf->dma[plane_idx].dmabuf->size)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static int sc_ext_check_buf_range(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_buf *buf, int width, int height, int span)
|
||
|
{
|
||
|
int i = 0;
|
||
|
|
||
|
if (width > span)
|
||
|
goto err_buf_range;
|
||
|
|
||
|
for (i = 0; i < buf->count; i++) {
|
||
|
if (!sc_ext_buf_access_safe(buf, width, height, span, i))
|
||
|
goto err_buf_range;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_buf_range:
|
||
|
dev_err(xdev->dev, "Error for buf %d, size %d, %d x %d (span:%d)\n",
|
||
|
i, buf->dma[i].dmabuf->size, width, height, span);
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
static int sc_ext_check_task(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_task_data *data)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
/* Buffer range check */
|
||
|
ret = sc_ext_check_buf_range(xdev, &data->buf[MSCL_SRC],
|
||
|
SC_CMD_GET_WIDTH(data->cmd[MSCL_SRC_WH]),
|
||
|
SC_CMD_GET_HEIGHT(data->cmd[MSCL_SRC_WH]),
|
||
|
SC_CMD_GET_SPAN_Y(data->cmd[MSCL_SRC_SPAN]));
|
||
|
if (ret) {
|
||
|
dev_err(xdev->dev, "Out of range for source buffer\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = sc_ext_check_buf_range(xdev, &data->buf[MSCL_DST],
|
||
|
SC_CMD_GET_WIDTH(data->cmd[MSCL_DST_WH]),
|
||
|
SC_CMD_GET_HEIGHT(data->cmd[MSCL_DST_WH]),
|
||
|
SC_CMD_GET_SPAN_Y(data->cmd[MSCL_DST_SPAN]));
|
||
|
if (ret) {
|
||
|
dev_err(xdev->dev, "Out of range for destination buffer\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sc_ext_check_sanity(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_task *task)
|
||
|
{
|
||
|
int i, ret;
|
||
|
|
||
|
for (i = 0; i < task->task_count; i++) {
|
||
|
ret = sc_ext_check_task(xdev, &task->data[i]);
|
||
|
if (ret) {
|
||
|
dev_err(xdev->dev, "Error detected in task %d\n", i);
|
||
|
sc_ext_show_task_data(&task->data[i], i);
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sc_ext_prepare_buffer(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_task *task, struct mscl_task *mscl_task)
|
||
|
{
|
||
|
int i, ret;
|
||
|
|
||
|
for (i = 0; i < task->task_count; i++) {
|
||
|
ret = sc_ext_prepare_dmabuf(xdev,
|
||
|
&task->data[i], &mscl_task[i]);
|
||
|
if (ret) {
|
||
|
dev_err(xdev->dev, "Failed to prepare dmabuf\n");
|
||
|
goto err_prepare;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_prepare:
|
||
|
for (i--; i >= 0; i--)
|
||
|
sc_ext_finish_dmabuf(xdev, &task->data[i]);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void sc_ext_finish_buffer(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_task *task)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < task->task_count; i++)
|
||
|
sc_ext_finish_dmabuf(xdev, &task->data[i]);
|
||
|
}
|
||
|
|
||
|
static int sc_ext_prepare_task(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_task *task, struct mscl_task *mscl_task)
|
||
|
{
|
||
|
int i, ret;
|
||
|
|
||
|
task->data = kcalloc(task->task_count,
|
||
|
sizeof(*task->data), GFP_KERNEL);
|
||
|
if (!task->data)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
for (i = 0; i < task->task_count; i++)
|
||
|
memcpy(&task->data[i].cmd, &mscl_task[i].cmd,
|
||
|
sizeof(task->data[i].cmd));
|
||
|
|
||
|
ret = sc_ext_prepare_format(xdev, task);
|
||
|
if (ret) {
|
||
|
dev_err(xdev->dev, "%s: Failed to prepare format, ret:%d\n",
|
||
|
__func__, ret);
|
||
|
goto err_prepare_task;
|
||
|
}
|
||
|
|
||
|
ret = sc_ext_prepare_buffer(xdev, task, mscl_task);
|
||
|
if (ret) {
|
||
|
dev_err(xdev->dev, "%s: Failed to prepare buffer, ret:%d\n",
|
||
|
__func__, ret);
|
||
|
goto err_prepare_task;
|
||
|
}
|
||
|
|
||
|
ret = sc_ext_check_sanity(xdev, task);
|
||
|
if (ret) {
|
||
|
dev_err(xdev->dev, "%s: Failed to check sanity, ret:%d\n",
|
||
|
__func__, ret);
|
||
|
goto err_check_sanity;
|
||
|
}
|
||
|
|
||
|
if (sc_show_stat & 0x2)
|
||
|
sc_ext_show_all_task_buf(task);
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_check_sanity:
|
||
|
sc_ext_finish_buffer(xdev, task);
|
||
|
|
||
|
err_prepare_task:
|
||
|
kfree(task->data);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void sc_ext_finish_task(struct sc_ext_dev *xdev,
|
||
|
struct sc_ext_task *task)
|
||
|
{
|
||
|
sc_ext_finish_buffer(xdev, task);
|
||
|
kfree(task->data);
|
||
|
}
|
||
|
|
||
|
static int sc_ext_process(struct sc_ext_task *task,
|
||
|
struct mscl_task *mscl_task)
|
||
|
{
|
||
|
struct sc_ext_ctx *xctx = task->xctx;
|
||
|
struct sc_ext_dev *xdev = xctx->xdev;
|
||
|
int ret;
|
||
|
|
||
|
init_completion(&task->complete);
|
||
|
|
||
|
ret = sc_ext_prepare_task(xdev, task, mscl_task);
|
||
|
if (ret)
|
||
|
goto err;
|
||
|
|
||
|
task->state = SC_EXT_BUFSTATE_READY;
|
||
|
|
||
|
sc_ext_try_task(xdev, task);
|
||
|
|
||
|
/* No timeout */
|
||
|
wait_for_completion(&task->complete);
|
||
|
|
||
|
BUG_ON(task->state == SC_EXT_BUFSTATE_READY);
|
||
|
|
||
|
sc_ext_finish_task(xdev, task);
|
||
|
err:
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
return (task->state == SC_EXT_BUFSTATE_DONE) ? 0 : -EINVAL;
|
||
|
}
|
||
|
|
||
|
static int sc_ext_open(struct inode *inode, struct file *filp)
|
||
|
{
|
||
|
struct sc_ext_dev *xdev = container_of(filp->private_data,
|
||
|
struct sc_ext_dev, misc);
|
||
|
struct sc_dev *sc_dev = dev_get_drvdata(xdev->dev);
|
||
|
struct sc_ext_ctx *xctx;
|
||
|
struct sc_ctx *sc_ctx;
|
||
|
int ret = 0;
|
||
|
|
||
|
xctx = kzalloc(sizeof(*xctx), GFP_KERNEL);
|
||
|
if (!xctx)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
INIT_LIST_HEAD(&xctx->node);
|
||
|
|
||
|
xctx->xdev = xdev;
|
||
|
|
||
|
spin_lock(&xdev->lock_ctx);
|
||
|
list_add_tail(&xctx->node, &xdev->contexts);
|
||
|
spin_unlock(&xdev->lock_ctx);
|
||
|
|
||
|
filp->private_data = xctx;
|
||
|
|
||
|
sc_ctx = &xctx->sc_ctx;
|
||
|
|
||
|
atomic_inc(&sc_dev->m2m.in_use);
|
||
|
|
||
|
if (!IS_ERR(sc_dev->pclk)) {
|
||
|
ret = clk_prepare(sc_dev->pclk);
|
||
|
if (ret) {
|
||
|
dev_err(sc_dev->dev,
|
||
|
"%s: failed to prepare PCLK(err %d)\n",
|
||
|
__func__, ret);
|
||
|
goto err_pclk;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!IS_ERR(sc_dev->aclk)) {
|
||
|
ret = clk_prepare(sc_dev->aclk);
|
||
|
if (ret) {
|
||
|
dev_err(sc_dev->dev,
|
||
|
"%s: failed to prepare ACLK(err %d)\n",
|
||
|
__func__, ret);
|
||
|
goto err_aclk;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sc_ctx->context_type = SC_CTX_EXT_TYPE;
|
||
|
INIT_LIST_HEAD(&sc_ctx->node);
|
||
|
sc_ctx->sc_dev = sc_dev;
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
err_aclk:
|
||
|
if (!IS_ERR(sc_dev->pclk))
|
||
|
clk_unprepare(sc_dev->pclk);
|
||
|
err_pclk:
|
||
|
kfree(xctx);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int sc_ext_release(struct inode *inode, struct file *filp)
|
||
|
{
|
||
|
struct sc_ext_ctx *xctx = filp->private_data;
|
||
|
struct sc_ext_dev *xdev = xctx->xdev;
|
||
|
struct sc_dev *sc_dev = dev_get_drvdata(xdev->dev);
|
||
|
unsigned long flags;
|
||
|
bool need_wait = false;
|
||
|
|
||
|
spin_lock_irqsave(&xdev->lock_task, flags);
|
||
|
if (xdev->current_task &&
|
||
|
xdev->current_task == xctx->task)
|
||
|
need_wait = true;
|
||
|
spin_unlock_irqrestore(&xdev->lock_task, flags);
|
||
|
|
||
|
if (need_wait)
|
||
|
wait_for_completion(&xctx->task->complete);
|
||
|
|
||
|
atomic_dec(&sc_dev->m2m.in_use);
|
||
|
|
||
|
if (!IS_ERR(sc_dev->aclk))
|
||
|
clk_unprepare(sc_dev->aclk);
|
||
|
if (!IS_ERR(sc_dev->pclk))
|
||
|
clk_unprepare(sc_dev->pclk);
|
||
|
|
||
|
spin_lock(&xctx->xdev->lock_ctx);
|
||
|
list_del(&xctx->node);
|
||
|
spin_unlock(&xctx->xdev->lock_ctx);
|
||
|
|
||
|
kfree(xctx);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#define MAX_MSCL_TASKS 10
|
||
|
static long sc_ext_ioctl(struct file *filp,
|
||
|
unsigned int cmd, unsigned long arg)
|
||
|
{
|
||
|
struct sc_ext_ctx *xctx = filp->private_data;
|
||
|
struct sc_ext_dev *xdev = xctx->xdev;
|
||
|
|
||
|
switch (cmd) {
|
||
|
case MSCL_IOC_JOB:
|
||
|
{
|
||
|
struct sc_ext_task task;
|
||
|
struct mscl_job job;
|
||
|
struct mscl_task *mscl_task;
|
||
|
int ret;
|
||
|
|
||
|
memset(&task, 0, sizeof(task));
|
||
|
|
||
|
if (copy_from_user(&job, (void __user *)arg, sizeof(job))) {
|
||
|
dev_err(xdev->dev,
|
||
|
"%s: Failed to read userdata\n", __func__);
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
|
||
|
/* TODO: check for supported version */
|
||
|
|
||
|
if (job.taskcount >= MAX_MSCL_TASKS || !job.taskcount) {
|
||
|
dev_err(xdev->dev,
|
||
|
"%s: (%s) count of tasks %d, max tasks: %d\n",
|
||
|
__func__,
|
||
|
(!job.taskcount) ? "zero-count" : "Too many",
|
||
|
job.taskcount, MAX_MSCL_TASKS);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
mscl_task = kcalloc(job.taskcount, sizeof(*mscl_task), GFP_KERNEL);
|
||
|
if (!mscl_task)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
if (copy_from_user(mscl_task, (void __user *)job.tasks,
|
||
|
job.taskcount * sizeof(*mscl_task))) {
|
||
|
dev_err(xdev->dev,
|
||
|
"%s: Failed to read tasks\n", __func__);
|
||
|
kfree(mscl_task);
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
|
||
|
|
||
|
task.task_count = job.taskcount;
|
||
|
task.xctx = xctx;
|
||
|
|
||
|
if (sc_show_stat & 0x2)
|
||
|
sc_ext_show_mscl_task(mscl_task, job.taskcount);
|
||
|
|
||
|
mutex_lock(&xdev->lock_ioctl);
|
||
|
|
||
|
/* No pending job is allowed for simple operation */
|
||
|
ret = sc_ext_process(&task, mscl_task);
|
||
|
|
||
|
mutex_unlock(&xdev->lock_ioctl);
|
||
|
|
||
|
kfree(mscl_task);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
default:
|
||
|
dev_err(xdev->dev, "%s: Unknown ioctl cmd %x\n",
|
||
|
__func__, cmd);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_COMPAT
|
||
|
struct compat_mscl_job {
|
||
|
uint32_t version;
|
||
|
uint32_t taskcount;
|
||
|
compat_uptr_t tasks;
|
||
|
};
|
||
|
|
||
|
#define COMPAT_MSCL_IOC_JOB _IOW('M', 1, struct compat_mscl_job)
|
||
|
|
||
|
static long sc_ext_compat_ioctl32(struct file *filp,
|
||
|
unsigned int cmd, unsigned long arg)
|
||
|
{
|
||
|
struct sc_ext_ctx *xctx = filp->private_data;
|
||
|
struct sc_ext_dev *xdev = xctx->xdev;
|
||
|
struct compat_mscl_job __user *udata = compat_ptr(arg);
|
||
|
struct mscl_job __user *data;
|
||
|
compat_uptr_t uptr;
|
||
|
size_t alloc_size;
|
||
|
__u32 w;
|
||
|
int ret;
|
||
|
|
||
|
if (cmd != COMPAT_MSCL_IOC_JOB) {
|
||
|
dev_err(xdev->dev, "Unknown ioctl %#x\n", cmd);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
alloc_size = sizeof(*data);
|
||
|
data = compat_alloc_user_space(alloc_size);
|
||
|
|
||
|
ret = get_user(w, &udata->version);
|
||
|
ret |= put_user(w, &data->version);
|
||
|
ret |= get_user(w, &udata->taskcount);
|
||
|
ret |= put_user(w, &data->taskcount);
|
||
|
ret |= get_user(uptr, &udata->tasks);
|
||
|
ret |= put_user(compat_ptr(uptr), &data->tasks);
|
||
|
if (ret) {
|
||
|
dev_err(xdev->dev, "failed to read task data\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = sc_ext_ioctl(filp,
|
||
|
(unsigned int)MSCL_IOC_JOB, (unsigned long)data);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static const struct file_operations sc_ext_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = sc_ext_open,
|
||
|
.release = sc_ext_release,
|
||
|
.unlocked_ioctl = sc_ext_ioctl,
|
||
|
#ifdef CONFIG_COMPAT
|
||
|
.compat_ioctl = sc_ext_compat_ioctl32,
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
struct sc_ext_dev *create_scaler_ext_device(struct device *dev)
|
||
|
{
|
||
|
struct sc_ext_dev *xdev;
|
||
|
int ret;
|
||
|
|
||
|
xdev = devm_kzalloc(dev, sizeof(*xdev), GFP_KERNEL);
|
||
|
if (!xdev)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
xdev->misc.minor = MISC_DYNAMIC_MINOR;
|
||
|
xdev->misc.name = SC_EXT_DEV_NAME;
|
||
|
xdev->misc.fops = &sc_ext_fops;
|
||
|
ret = misc_register(&xdev->misc);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to register %s\n", SC_EXT_DEV_NAME);
|
||
|
return ERR_PTR(ret);
|
||
|
}
|
||
|
|
||
|
INIT_LIST_HEAD(&xdev->contexts);
|
||
|
|
||
|
spin_lock_init(&xdev->lock_task);
|
||
|
spin_lock_init(&xdev->lock_ctx);
|
||
|
mutex_init(&xdev->lock_ioctl);
|
||
|
xdev->dev = dev;
|
||
|
|
||
|
return xdev;
|
||
|
}
|
||
|
|
||
|
void destroy_scaler_ext_device(struct sc_ext_dev *xdev)
|
||
|
{
|
||
|
misc_deregister(&xdev->misc);
|
||
|
}
|