1850 lines
53 KiB
C
1850 lines
53 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (C) 2010-2016 Freescale Semiconductor, Inc.
|
|
* Copyright 2017-2019 NXP
|
|
*/
|
|
/*
|
|
* Based on STMP378X PxP driver
|
|
* Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/dmaengine.h>
|
|
#include <linux/freezer.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pxp_dma.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#include "regs-pxp_v2.h"
|
|
|
|
#define PXP_DOWNSCALE_THRESHOLD 0x4000
|
|
|
|
static LIST_HEAD(head);
|
|
static int timeout_in_ms = 600;
|
|
static unsigned int block_size;
|
|
static struct kmem_cache *tx_desc_cache;
|
|
|
|
struct pxp_dma {
|
|
struct dma_device dma;
|
|
};
|
|
|
|
struct pxps {
|
|
struct platform_device *pdev;
|
|
struct clk *clk;
|
|
struct clk *clk_disp_axi; /* may exist on some SoC for gating */
|
|
void __iomem *base;
|
|
int irq; /* PXP IRQ to the CPU */
|
|
|
|
spinlock_t lock;
|
|
struct mutex clk_mutex;
|
|
int clk_stat;
|
|
#define CLK_STAT_OFF 0
|
|
#define CLK_STAT_ON 1
|
|
int pxp_ongoing;
|
|
int lut_state;
|
|
|
|
struct device *dev;
|
|
struct pxp_dma pxp_dma;
|
|
struct pxp_channel channel[NR_PXP_VIRT_CHANNEL];
|
|
struct work_struct work;
|
|
|
|
/* describes most recent processing configuration */
|
|
struct pxp_config_data pxp_conf_state;
|
|
|
|
/* to turn clock off when pxp is inactive */
|
|
struct timer_list clk_timer;
|
|
|
|
/* for pxp config dispatch asynchronously*/
|
|
struct task_struct *dispatch;
|
|
wait_queue_head_t thread_waitq;
|
|
struct completion complete;
|
|
};
|
|
|
|
#define to_pxp_dma(d) container_of(d, struct pxp_dma, dma)
|
|
#define to_tx_desc(tx) container_of(tx, struct pxp_tx_desc, txd)
|
|
#define to_pxp_channel(d) container_of(d, struct pxp_channel, dma_chan)
|
|
#define to_pxp(id) container_of(id, struct pxps, pxp_dma)
|
|
|
|
#define PXP_DEF_BUFS 2
|
|
#define PXP_MIN_PIX 8
|
|
|
|
/*
|
|
* PXP common functions
|
|
*/
|
|
static void dump_pxp_reg(struct pxps *pxp)
|
|
{
|
|
dev_dbg(pxp->dev, "PXP_CTRL 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CTRL));
|
|
dev_dbg(pxp->dev, "PXP_STAT 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_STAT));
|
|
dev_dbg(pxp->dev, "PXP_OUT_CTRL 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_OUT_CTRL));
|
|
dev_dbg(pxp->dev, "PXP_OUT_BUF 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_OUT_BUF));
|
|
dev_dbg(pxp->dev, "PXP_OUT_BUF2 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_OUT_BUF2));
|
|
dev_dbg(pxp->dev, "PXP_OUT_PITCH 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_OUT_PITCH));
|
|
dev_dbg(pxp->dev, "PXP_OUT_LRC 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_OUT_LRC));
|
|
dev_dbg(pxp->dev, "PXP_OUT_PS_ULC 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_OUT_PS_ULC));
|
|
dev_dbg(pxp->dev, "PXP_OUT_PS_LRC 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_OUT_PS_LRC));
|
|
dev_dbg(pxp->dev, "PXP_OUT_AS_ULC 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_OUT_AS_ULC));
|
|
dev_dbg(pxp->dev, "PXP_OUT_AS_LRC 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_OUT_AS_LRC));
|
|
dev_dbg(pxp->dev, "PXP_PS_CTRL 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_CTRL));
|
|
dev_dbg(pxp->dev, "PXP_PS_BUF 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_BUF));
|
|
dev_dbg(pxp->dev, "PXP_PS_UBUF 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_UBUF));
|
|
dev_dbg(pxp->dev, "PXP_PS_VBUF 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_VBUF));
|
|
dev_dbg(pxp->dev, "PXP_PS_PITCH 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_PITCH));
|
|
dev_dbg(pxp->dev, "PXP_PS_BACKGROUND 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_BACKGROUND));
|
|
dev_dbg(pxp->dev, "PXP_PS_SCALE 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_SCALE));
|
|
dev_dbg(pxp->dev, "PXP_PS_OFFSET 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_OFFSET));
|
|
dev_dbg(pxp->dev, "PXP_PS_CLRKEYLOW 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_CLRKEYLOW));
|
|
dev_dbg(pxp->dev, "PXP_PS_CLRKEYHIGH 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_PS_CLRKEYHIGH));
|
|
dev_dbg(pxp->dev, "PXP_AS_CTRL 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_AS_CTRL));
|
|
dev_dbg(pxp->dev, "PXP_AS_BUF 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_AS_BUF));
|
|
dev_dbg(pxp->dev, "PXP_AS_PITCH 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_AS_PITCH));
|
|
dev_dbg(pxp->dev, "PXP_AS_CLRKEYLOW 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_AS_CLRKEYLOW));
|
|
dev_dbg(pxp->dev, "PXP_AS_CLRKEYHIGH 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_AS_CLRKEYHIGH));
|
|
dev_dbg(pxp->dev, "PXP_CSC1_COEF0 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC1_COEF0));
|
|
dev_dbg(pxp->dev, "PXP_CSC1_COEF1 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC1_COEF1));
|
|
dev_dbg(pxp->dev, "PXP_CSC1_COEF2 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC1_COEF2));
|
|
dev_dbg(pxp->dev, "PXP_CSC2_CTRL 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC2_CTRL));
|
|
dev_dbg(pxp->dev, "PXP_CSC2_COEF0 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC2_COEF0));
|
|
dev_dbg(pxp->dev, "PXP_CSC2_COEF1 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC2_COEF1));
|
|
dev_dbg(pxp->dev, "PXP_CSC2_COEF2 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC2_COEF2));
|
|
dev_dbg(pxp->dev, "PXP_CSC2_COEF3 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC2_COEF3));
|
|
dev_dbg(pxp->dev, "PXP_CSC2_COEF4 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC2_COEF4));
|
|
dev_dbg(pxp->dev, "PXP_CSC2_COEF5 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CSC2_COEF5));
|
|
dev_dbg(pxp->dev, "PXP_LUT_CTRL 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_LUT_CTRL));
|
|
dev_dbg(pxp->dev, "PXP_LUT_ADDR 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_LUT_ADDR));
|
|
dev_dbg(pxp->dev, "PXP_LUT_DATA 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_LUT_DATA));
|
|
dev_dbg(pxp->dev, "PXP_LUT_EXTMEM 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_LUT_EXTMEM));
|
|
dev_dbg(pxp->dev, "PXP_CFA 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_CFA));
|
|
dev_dbg(pxp->dev, "PXP_HIST_CTRL 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_HIST_CTRL));
|
|
dev_dbg(pxp->dev, "PXP_HIST2_PARAM 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_HIST2_PARAM));
|
|
dev_dbg(pxp->dev, "PXP_HIST4_PARAM 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_HIST4_PARAM));
|
|
dev_dbg(pxp->dev, "PXP_HIST8_PARAM0 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_HIST8_PARAM0));
|
|
dev_dbg(pxp->dev, "PXP_HIST8_PARAM1 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_HIST8_PARAM1));
|
|
dev_dbg(pxp->dev, "PXP_HIST16_PARAM0 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_HIST16_PARAM0));
|
|
dev_dbg(pxp->dev, "PXP_HIST16_PARAM1 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_HIST16_PARAM1));
|
|
dev_dbg(pxp->dev, "PXP_HIST16_PARAM2 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_HIST16_PARAM2));
|
|
dev_dbg(pxp->dev, "PXP_HIST16_PARAM3 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_HIST16_PARAM3));
|
|
dev_dbg(pxp->dev, "PXP_POWER 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_POWER));
|
|
dev_dbg(pxp->dev, "PXP_NEXT 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_NEXT));
|
|
dev_dbg(pxp->dev, "PXP_DEBUGCTRL 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_DEBUGCTRL));
|
|
dev_dbg(pxp->dev, "PXP_DEBUG 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_DEBUG));
|
|
dev_dbg(pxp->dev, "PXP_VERSION 0x%x",
|
|
__raw_readl(pxp->base + HW_PXP_VERSION));
|
|
}
|
|
|
|
static bool is_yuv(u32 pix_fmt)
|
|
{
|
|
switch (pix_fmt) {
|
|
case PXP_PIX_FMT_YUYV:
|
|
case PXP_PIX_FMT_UYVY:
|
|
case PXP_PIX_FMT_YVYU:
|
|
case PXP_PIX_FMT_VYUY:
|
|
case PXP_PIX_FMT_Y41P:
|
|
case PXP_PIX_FMT_VUY444:
|
|
case PXP_PIX_FMT_NV12:
|
|
case PXP_PIX_FMT_NV21:
|
|
case PXP_PIX_FMT_NV16:
|
|
case PXP_PIX_FMT_NV61:
|
|
case PXP_PIX_FMT_GREY:
|
|
case PXP_PIX_FMT_GY04:
|
|
case PXP_PIX_FMT_YVU410P:
|
|
case PXP_PIX_FMT_YUV410P:
|
|
case PXP_PIX_FMT_YVU420P:
|
|
case PXP_PIX_FMT_YUV420P:
|
|
case PXP_PIX_FMT_YUV420P2:
|
|
case PXP_PIX_FMT_YVU422P:
|
|
case PXP_PIX_FMT_YUV422P:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void pxp_soft_reset(struct pxps *pxp)
|
|
{
|
|
__raw_writel(BM_PXP_CTRL_SFTRST, pxp->base + HW_PXP_CTRL_CLR);
|
|
__raw_writel(BM_PXP_CTRL_CLKGATE, pxp->base + HW_PXP_CTRL_CLR);
|
|
|
|
__raw_writel(BM_PXP_CTRL_SFTRST, pxp->base + HW_PXP_CTRL_SET);
|
|
while (!(__raw_readl(pxp->base + HW_PXP_CTRL) & BM_PXP_CTRL_CLKGATE))
|
|
dev_dbg(pxp->dev, "%s: wait for clock gate off", __func__);
|
|
|
|
__raw_writel(BM_PXP_CTRL_SFTRST, pxp->base + HW_PXP_CTRL_CLR);
|
|
__raw_writel(BM_PXP_CTRL_CLKGATE, pxp->base + HW_PXP_CTRL_CLR);
|
|
}
|
|
|
|
static void pxp_set_ctrl(struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_proc_data *proc_data = &pxp_conf->proc_data;
|
|
u32 ctrl;
|
|
u32 fmt_ctrl;
|
|
int need_swap = 0; /* to support YUYV and YVYU formats */
|
|
|
|
/* Configure S0 input format */
|
|
switch (pxp_conf->s0_param.pixel_fmt) {
|
|
case PXP_PIX_FMT_XRGB32:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__RGB888;
|
|
break;
|
|
case PXP_PIX_FMT_RGB565:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__RGB565;
|
|
break;
|
|
case PXP_PIX_FMT_RGB555:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__RGB555;
|
|
break;
|
|
case PXP_PIX_FMT_YUV420P:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__YUV420;
|
|
break;
|
|
case PXP_PIX_FMT_YVU420P:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__YUV420;
|
|
break;
|
|
case PXP_PIX_FMT_GREY:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__Y8;
|
|
break;
|
|
case PXP_PIX_FMT_GY04:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__Y4;
|
|
break;
|
|
case PXP_PIX_FMT_VUY444:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__YUV1P444;
|
|
break;
|
|
case PXP_PIX_FMT_YUV422P:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__YUV422;
|
|
break;
|
|
case PXP_PIX_FMT_UYVY:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__UYVY1P422;
|
|
break;
|
|
case PXP_PIX_FMT_YUYV:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__UYVY1P422;
|
|
need_swap = 1;
|
|
break;
|
|
case PXP_PIX_FMT_VYUY:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__VYUY1P422;
|
|
break;
|
|
case PXP_PIX_FMT_YVYU:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__VYUY1P422;
|
|
need_swap = 1;
|
|
break;
|
|
case PXP_PIX_FMT_NV12:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__YUV2P420;
|
|
break;
|
|
case PXP_PIX_FMT_NV21:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__YVU2P420;
|
|
break;
|
|
case PXP_PIX_FMT_NV16:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__YUV2P422;
|
|
break;
|
|
case PXP_PIX_FMT_NV61:
|
|
fmt_ctrl = BV_PXP_PS_CTRL_FORMAT__YVU2P422;
|
|
break;
|
|
default:
|
|
fmt_ctrl = 0;
|
|
}
|
|
|
|
ctrl = BF_PXP_PS_CTRL_FORMAT(fmt_ctrl) | BF_PXP_PS_CTRL_SWAP(need_swap);
|
|
__raw_writel(ctrl, pxp->base + HW_PXP_PS_CTRL_SET);
|
|
|
|
/* Configure output format based on out_channel format */
|
|
switch (pxp_conf->out_param.pixel_fmt) {
|
|
case PXP_PIX_FMT_XRGB32:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__RGB888;
|
|
break;
|
|
case PXP_PIX_FMT_BGRA32:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__ARGB8888;
|
|
break;
|
|
case PXP_PIX_FMT_RGB24:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__RGB888P;
|
|
break;
|
|
case PXP_PIX_FMT_RGB565:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__RGB565;
|
|
break;
|
|
case PXP_PIX_FMT_RGB555:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__RGB555;
|
|
break;
|
|
case PXP_PIX_FMT_GREY:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__Y8;
|
|
break;
|
|
case PXP_PIX_FMT_GY04:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__Y4;
|
|
break;
|
|
case PXP_PIX_FMT_UYVY:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__UYVY1P422;
|
|
break;
|
|
case PXP_PIX_FMT_VYUY:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__VYUY1P422;
|
|
break;
|
|
case PXP_PIX_FMT_NV12:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__YUV2P420;
|
|
break;
|
|
case PXP_PIX_FMT_NV21:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__YVU2P420;
|
|
break;
|
|
case PXP_PIX_FMT_NV16:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__YUV2P422;
|
|
break;
|
|
case PXP_PIX_FMT_NV61:
|
|
fmt_ctrl = BV_PXP_OUT_CTRL_FORMAT__YVU2P422;
|
|
break;
|
|
default:
|
|
fmt_ctrl = 0;
|
|
}
|
|
|
|
ctrl = BF_PXP_OUT_CTRL_FORMAT(fmt_ctrl);
|
|
__raw_writel(ctrl, pxp->base + HW_PXP_OUT_CTRL);
|
|
|
|
ctrl = 0;
|
|
if (proc_data->scaling)
|
|
;
|
|
if (proc_data->vflip)
|
|
ctrl |= BM_PXP_CTRL_VFLIP;
|
|
if (proc_data->hflip)
|
|
ctrl |= BM_PXP_CTRL_HFLIP;
|
|
if (proc_data->rotate)
|
|
ctrl |= BF_PXP_CTRL_ROTATE(proc_data->rotate / 90);
|
|
|
|
/* In default, the block size is set to 8x8
|
|
* But block size can be set to 16x16 due to
|
|
* blocksize variable modification
|
|
*/
|
|
ctrl |= block_size << 23;
|
|
|
|
__raw_writel(ctrl, pxp->base + HW_PXP_CTRL);
|
|
}
|
|
|
|
static int pxp_start(struct pxps *pxp)
|
|
{
|
|
__raw_writel(BM_PXP_CTRL_IRQ_ENABLE, pxp->base + HW_PXP_CTRL_SET);
|
|
__raw_writel(BM_PXP_CTRL_ENABLE, pxp->base + HW_PXP_CTRL_SET);
|
|
dump_pxp_reg(pxp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pxp_set_outbuf(struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_layer_param *out_params = &pxp_conf->out_param;
|
|
struct pxp_proc_data *proc_data = &pxp_conf->proc_data;
|
|
|
|
__raw_writel(out_params->paddr, pxp->base + HW_PXP_OUT_BUF);
|
|
|
|
if ((out_params->pixel_fmt == PXP_PIX_FMT_NV12) ||
|
|
(out_params->pixel_fmt == PXP_PIX_FMT_NV21) ||
|
|
(out_params->pixel_fmt == PXP_PIX_FMT_NV16) ||
|
|
(out_params->pixel_fmt == PXP_PIX_FMT_NV61)) {
|
|
dma_addr_t Y, U;
|
|
|
|
Y = out_params->paddr;
|
|
U = Y + (out_params->width * out_params->height);
|
|
|
|
__raw_writel(U, pxp->base + HW_PXP_OUT_BUF2);
|
|
}
|
|
|
|
if (proc_data->rotate == 90 || proc_data->rotate == 270)
|
|
__raw_writel(BF_PXP_OUT_LRC_X(out_params->height - 1) |
|
|
BF_PXP_OUT_LRC_Y(out_params->width - 1),
|
|
pxp->base + HW_PXP_OUT_LRC);
|
|
else
|
|
__raw_writel(BF_PXP_OUT_LRC_X(out_params->width - 1) |
|
|
BF_PXP_OUT_LRC_Y(out_params->height - 1),
|
|
pxp->base + HW_PXP_OUT_LRC);
|
|
|
|
if (out_params->pixel_fmt == PXP_PIX_FMT_RGB24) {
|
|
__raw_writel(out_params->stride * 3,
|
|
pxp->base + HW_PXP_OUT_PITCH);
|
|
} else if (out_params->pixel_fmt == PXP_PIX_FMT_BGRA32 ||
|
|
out_params->pixel_fmt == PXP_PIX_FMT_XRGB32) {
|
|
__raw_writel(out_params->stride << 2,
|
|
pxp->base + HW_PXP_OUT_PITCH);
|
|
} else if ((out_params->pixel_fmt == PXP_PIX_FMT_RGB565) ||
|
|
(out_params->pixel_fmt == PXP_PIX_FMT_RGB555)) {
|
|
__raw_writel(out_params->stride << 1,
|
|
pxp->base + HW_PXP_OUT_PITCH);
|
|
} else if (out_params->pixel_fmt == PXP_PIX_FMT_UYVY ||
|
|
(out_params->pixel_fmt == PXP_PIX_FMT_VYUY)) {
|
|
__raw_writel(out_params->stride << 1,
|
|
pxp->base + HW_PXP_OUT_PITCH);
|
|
} else if (out_params->pixel_fmt == PXP_PIX_FMT_GREY ||
|
|
out_params->pixel_fmt == PXP_PIX_FMT_NV12 ||
|
|
out_params->pixel_fmt == PXP_PIX_FMT_NV21 ||
|
|
out_params->pixel_fmt == PXP_PIX_FMT_NV16 ||
|
|
out_params->pixel_fmt == PXP_PIX_FMT_NV61) {
|
|
__raw_writel(out_params->stride,
|
|
pxp->base + HW_PXP_OUT_PITCH);
|
|
} else if (out_params->pixel_fmt == PXP_PIX_FMT_GY04) {
|
|
__raw_writel(out_params->stride >> 1,
|
|
pxp->base + HW_PXP_OUT_PITCH);
|
|
} else {
|
|
__raw_writel(0, pxp->base + HW_PXP_OUT_PITCH);
|
|
}
|
|
|
|
/* set global alpha if necessary */
|
|
if (out_params->global_alpha_enable) {
|
|
__raw_writel(out_params->global_alpha << 24,
|
|
pxp->base + HW_PXP_OUT_CTRL_SET);
|
|
__raw_writel(BM_PXP_OUT_CTRL_ALPHA_OUTPUT,
|
|
pxp->base + HW_PXP_OUT_CTRL_SET);
|
|
}
|
|
}
|
|
|
|
static void pxp_set_s0colorkey(struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_layer_param *s0_params = &pxp_conf->s0_param;
|
|
|
|
/* Low and high are set equal. V4L does not allow a chromakey range */
|
|
if (s0_params->color_key_enable == 0 || s0_params->color_key == -1) {
|
|
/* disable color key */
|
|
__raw_writel(0xFFFFFF, pxp->base + HW_PXP_PS_CLRKEYLOW);
|
|
__raw_writel(0, pxp->base + HW_PXP_PS_CLRKEYHIGH);
|
|
} else {
|
|
__raw_writel(s0_params->color_key,
|
|
pxp->base + HW_PXP_PS_CLRKEYLOW);
|
|
__raw_writel(s0_params->color_key,
|
|
pxp->base + HW_PXP_PS_CLRKEYHIGH);
|
|
}
|
|
}
|
|
|
|
static void pxp_set_olcolorkey(int layer_no, struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_layer_param *ol_params = &pxp_conf->ol_param[layer_no];
|
|
|
|
/* Low and high are set equal. V4L does not allow a chromakey range */
|
|
if (ol_params->color_key_enable != 0 && ol_params->color_key != -1) {
|
|
__raw_writel(ol_params->color_key,
|
|
pxp->base + HW_PXP_AS_CLRKEYLOW);
|
|
__raw_writel(ol_params->color_key,
|
|
pxp->base + HW_PXP_AS_CLRKEYHIGH);
|
|
} else {
|
|
/* disable color key */
|
|
__raw_writel(0xFFFFFF, pxp->base + HW_PXP_AS_CLRKEYLOW);
|
|
__raw_writel(0, pxp->base + HW_PXP_AS_CLRKEYHIGH);
|
|
}
|
|
}
|
|
|
|
static void pxp_set_oln(int layer_no, struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_layer_param *olparams_data = &pxp_conf->ol_param[layer_no];
|
|
dma_addr_t phys_addr = olparams_data->paddr;
|
|
u32 pitch = olparams_data->stride ? olparams_data->stride :
|
|
olparams_data->width;
|
|
|
|
__raw_writel(phys_addr, pxp->base + HW_PXP_AS_BUF);
|
|
|
|
/* Fixme */
|
|
if (olparams_data->width == 0 && olparams_data->height == 0) {
|
|
__raw_writel(0xffffffff, pxp->base + HW_PXP_OUT_AS_ULC);
|
|
__raw_writel(0x0, pxp->base + HW_PXP_OUT_AS_LRC);
|
|
} else {
|
|
__raw_writel(0x0, pxp->base + HW_PXP_OUT_AS_ULC);
|
|
__raw_writel(BF_PXP_OUT_AS_LRC_X(olparams_data->width - 1) |
|
|
BF_PXP_OUT_AS_LRC_Y(olparams_data->height - 1),
|
|
pxp->base + HW_PXP_OUT_AS_LRC);
|
|
}
|
|
|
|
if ((olparams_data->pixel_fmt == PXP_PIX_FMT_BGRA32) ||
|
|
(olparams_data->pixel_fmt == PXP_PIX_FMT_XRGB32)) {
|
|
__raw_writel(pitch << 2,
|
|
pxp->base + HW_PXP_AS_PITCH);
|
|
} else if ((olparams_data->pixel_fmt == PXP_PIX_FMT_RGB565) ||
|
|
(olparams_data->pixel_fmt == PXP_PIX_FMT_RGB555)) {
|
|
__raw_writel(pitch << 1,
|
|
pxp->base + HW_PXP_AS_PITCH);
|
|
} else {
|
|
__raw_writel(0, pxp->base + HW_PXP_AS_PITCH);
|
|
}
|
|
}
|
|
|
|
static void pxp_set_olparam(int layer_no, struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_layer_param *olparams_data = &pxp_conf->ol_param[layer_no];
|
|
u32 olparam;
|
|
|
|
olparam = BF_PXP_AS_CTRL_ALPHA(olparams_data->global_alpha);
|
|
if (olparams_data->pixel_fmt == PXP_PIX_FMT_XRGB32) {
|
|
olparam |=
|
|
BF_PXP_AS_CTRL_FORMAT(BV_PXP_AS_CTRL_FORMAT__RGB888);
|
|
} else if (olparams_data->pixel_fmt == PXP_PIX_FMT_BGRA32) {
|
|
olparam |=
|
|
BF_PXP_AS_CTRL_FORMAT(BV_PXP_AS_CTRL_FORMAT__ARGB8888);
|
|
if (!olparams_data->combine_enable) {
|
|
olparam |=
|
|
BF_PXP_AS_CTRL_ALPHA_CTRL
|
|
(BV_PXP_AS_CTRL_ALPHA_CTRL__ROPs);
|
|
olparam |= 0x3 << 16;
|
|
}
|
|
} else if (olparams_data->pixel_fmt == PXP_PIX_FMT_RGB565) {
|
|
olparam |=
|
|
BF_PXP_AS_CTRL_FORMAT(BV_PXP_AS_CTRL_FORMAT__RGB565);
|
|
} else if (olparams_data->pixel_fmt == PXP_PIX_FMT_RGB555) {
|
|
olparam |=
|
|
BF_PXP_AS_CTRL_FORMAT(BV_PXP_AS_CTRL_FORMAT__RGB555);
|
|
}
|
|
|
|
if (olparams_data->global_alpha_enable) {
|
|
if (olparams_data->global_override) {
|
|
olparam |=
|
|
BF_PXP_AS_CTRL_ALPHA_CTRL
|
|
(BV_PXP_AS_CTRL_ALPHA_CTRL__Override);
|
|
} else {
|
|
olparam |=
|
|
BF_PXP_AS_CTRL_ALPHA_CTRL
|
|
(BV_PXP_AS_CTRL_ALPHA_CTRL__Multiply);
|
|
}
|
|
if (olparams_data->alpha_invert)
|
|
olparam |= BM_PXP_AS_CTRL_ALPHA_INVERT;
|
|
}
|
|
if (olparams_data->color_key_enable)
|
|
olparam |= BM_PXP_AS_CTRL_ENABLE_COLORKEY;
|
|
|
|
__raw_writel(olparam, pxp->base + HW_PXP_AS_CTRL);
|
|
}
|
|
|
|
static void pxp_set_s0param(struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_proc_data *proc_data = &pxp_conf->proc_data;
|
|
struct pxp_layer_param *out_params = &pxp_conf->out_param;
|
|
u32 s0param_ulc, s0param_lrc;
|
|
|
|
/* contains the coordinate for the PS in the OUTPUT buffer. */
|
|
if ((pxp_conf->s0_param).width == 0 &&
|
|
(pxp_conf->s0_param).height == 0) {
|
|
__raw_writel(0xffffffff, pxp->base + HW_PXP_OUT_PS_ULC);
|
|
__raw_writel(0x0, pxp->base + HW_PXP_OUT_PS_LRC);
|
|
} else {
|
|
switch (proc_data->rotate) {
|
|
case 0:
|
|
s0param_ulc = BF_PXP_OUT_PS_ULC_X(proc_data->drect.left);
|
|
s0param_ulc |= BF_PXP_OUT_PS_ULC_Y(proc_data->drect.top);
|
|
s0param_lrc = BF_PXP_OUT_PS_LRC_X(((s0param_ulc & BM_PXP_OUT_PS_ULC_X) >> 16) + proc_data->drect.width - 1);
|
|
s0param_lrc |= BF_PXP_OUT_PS_LRC_Y((s0param_ulc & BM_PXP_OUT_PS_ULC_Y) + proc_data->drect.height - 1);
|
|
break;
|
|
case 90:
|
|
s0param_ulc = BF_PXP_OUT_PS_ULC_Y(out_params->width - (proc_data->drect.left + proc_data->drect.width));
|
|
s0param_ulc |= BF_PXP_OUT_PS_ULC_X(proc_data->drect.top);
|
|
s0param_lrc = BF_PXP_OUT_PS_LRC_X(((s0param_ulc & BM_PXP_OUT_PS_ULC_X) >> 16) + proc_data->drect.height - 1);
|
|
s0param_lrc |= BF_PXP_OUT_PS_LRC_Y((s0param_ulc & BM_PXP_OUT_PS_ULC_Y) + proc_data->drect.width - 1);
|
|
break;
|
|
case 180:
|
|
s0param_ulc = BF_PXP_OUT_PS_ULC_X(out_params->width - (proc_data->drect.left + proc_data->drect.width));
|
|
s0param_ulc |= BF_PXP_OUT_PS_ULC_Y(out_params->height - (proc_data->drect.top + proc_data->drect.height));
|
|
s0param_lrc = BF_PXP_OUT_PS_LRC_X(((s0param_ulc & BM_PXP_OUT_PS_ULC_X) >> 16) + proc_data->drect.width - 1);
|
|
s0param_lrc |= BF_PXP_OUT_PS_LRC_Y((s0param_ulc & BM_PXP_OUT_PS_ULC_Y) + proc_data->drect.height - 1);
|
|
break;
|
|
case 270:
|
|
s0param_ulc = BF_PXP_OUT_PS_ULC_X(out_params->height - (proc_data->drect.top + proc_data->drect.height));
|
|
s0param_ulc |= BF_PXP_OUT_PS_ULC_Y(proc_data->drect.left);
|
|
s0param_lrc = BF_PXP_OUT_PS_LRC_X(((s0param_ulc & BM_PXP_OUT_PS_ULC_X) >> 16) + proc_data->drect.height - 1);
|
|
s0param_lrc |= BF_PXP_OUT_PS_LRC_Y((s0param_ulc & BM_PXP_OUT_PS_ULC_Y) + proc_data->drect.width - 1);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
__raw_writel(s0param_ulc, pxp->base + HW_PXP_OUT_PS_ULC);
|
|
__raw_writel(s0param_lrc, pxp->base + HW_PXP_OUT_PS_LRC);
|
|
}
|
|
|
|
/* Since user apps always pass the rotated drect
|
|
* to this driver, we need to first swap the width
|
|
* and height which is used to calculate the scale
|
|
* factors later.
|
|
*/
|
|
if (proc_data->rotate == 90 || proc_data->rotate == 270) {
|
|
int temp;
|
|
temp = proc_data->drect.width;
|
|
proc_data->drect.width = proc_data->drect.height;
|
|
proc_data->drect.height = temp;
|
|
}
|
|
}
|
|
|
|
/* crop behavior is re-designed in h/w. */
|
|
static void pxp_set_s0crop(struct pxps *pxp)
|
|
{
|
|
/*
|
|
* place-holder, it's implemented in other functions in this driver.
|
|
* Refer to "Clipping source images" section in RM for detail.
|
|
*/
|
|
}
|
|
|
|
static int pxp_set_scaling(struct pxps *pxp)
|
|
{
|
|
int ret = 0;
|
|
u32 xscale, yscale, s0scale;
|
|
u32 decx, decy, xdec = 0, ydec = 0;
|
|
struct pxp_proc_data *proc_data = &pxp->pxp_conf_state.proc_data;
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_layer_param *s0_params = &pxp_conf->s0_param;
|
|
struct pxp_layer_param *out_params = &pxp_conf->out_param;
|
|
|
|
proc_data->scaling = 1;
|
|
|
|
if (!proc_data->drect.width || !proc_data->drect.height) {
|
|
pr_err("Invalid drect width and height passed in\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
decx = proc_data->srect.width / proc_data->drect.width;
|
|
decy = proc_data->srect.height / proc_data->drect.height;
|
|
if (decx > 1) {
|
|
if (decx >= 2 && decx < 4) {
|
|
decx = 2;
|
|
xdec = 1;
|
|
} else if (decx >= 4 && decx < 8) {
|
|
decx = 4;
|
|
xdec = 2;
|
|
} else if (decx >= 8) {
|
|
decx = 8;
|
|
xdec = 3;
|
|
}
|
|
xscale = proc_data->srect.width * 0x1000 /
|
|
(proc_data->drect.width * decx);
|
|
} else {
|
|
if (!is_yuv(s0_params->pixel_fmt) ||
|
|
(is_yuv(s0_params->pixel_fmt) ==
|
|
is_yuv(out_params->pixel_fmt)) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_GREY) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_GY04) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_VUY444)) {
|
|
if ((proc_data->srect.width > 1) &&
|
|
(proc_data->drect.width > 1))
|
|
xscale = (proc_data->srect.width - 1) * 0x1000 /
|
|
(proc_data->drect.width - 1);
|
|
else
|
|
xscale = proc_data->srect.width * 0x1000 /
|
|
proc_data->drect.width;
|
|
} else {
|
|
if ((proc_data->srect.width > 2) &&
|
|
(proc_data->drect.width > 1))
|
|
xscale = (proc_data->srect.width - 2) * 0x1000 /
|
|
(proc_data->drect.width - 1);
|
|
else
|
|
xscale = proc_data->srect.width * 0x1000 /
|
|
proc_data->drect.width;
|
|
}
|
|
}
|
|
if (decy > 1) {
|
|
if (decy >= 2 && decy < 4) {
|
|
decy = 2;
|
|
ydec = 1;
|
|
} else if (decy >= 4 && decy < 8) {
|
|
decy = 4;
|
|
ydec = 2;
|
|
} else if (decy >= 8) {
|
|
decy = 8;
|
|
ydec = 3;
|
|
}
|
|
yscale = proc_data->srect.height * 0x1000 /
|
|
(proc_data->drect.height * decy);
|
|
} else {
|
|
if ((proc_data->srect.height > 1) &&
|
|
(proc_data->drect.height > 1))
|
|
yscale = (proc_data->srect.height - 1) * 0x1000 /
|
|
(proc_data->drect.height - 1);
|
|
else
|
|
yscale = proc_data->srect.height * 0x1000 /
|
|
proc_data->drect.height;
|
|
}
|
|
|
|
__raw_writel((xdec << 10) | (ydec << 8), pxp->base + HW_PXP_PS_CTRL);
|
|
|
|
if (xscale > PXP_DOWNSCALE_THRESHOLD)
|
|
xscale = PXP_DOWNSCALE_THRESHOLD;
|
|
if (yscale > PXP_DOWNSCALE_THRESHOLD)
|
|
yscale = PXP_DOWNSCALE_THRESHOLD;
|
|
s0scale = BF_PXP_PS_SCALE_YSCALE(yscale) |
|
|
BF_PXP_PS_SCALE_XSCALE(xscale);
|
|
__raw_writel(s0scale, pxp->base + HW_PXP_PS_SCALE);
|
|
|
|
pxp_set_ctrl(pxp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void pxp_set_bg(struct pxps *pxp)
|
|
{
|
|
__raw_writel(pxp->pxp_conf_state.proc_data.bgcolor,
|
|
pxp->base + HW_PXP_PS_BACKGROUND);
|
|
}
|
|
|
|
static void pxp_set_lut(struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
int lut_op = pxp_conf->proc_data.lut_transform;
|
|
u32 reg_val;
|
|
int i;
|
|
bool use_cmap = (lut_op & PXP_LUT_USE_CMAP) ? true : false;
|
|
u8 *cmap = pxp_conf->proc_data.lut_map;
|
|
u32 entry_src;
|
|
u32 pix_val;
|
|
u8 entry[4];
|
|
|
|
/*
|
|
* If LUT already configured as needed, return...
|
|
* Unless CMAP is needed and it has been updated.
|
|
*/
|
|
if ((pxp->lut_state == lut_op) &&
|
|
!(use_cmap && pxp_conf->proc_data.lut_map_updated))
|
|
return;
|
|
|
|
if (lut_op == PXP_LUT_NONE) {
|
|
__raw_writel(BM_PXP_LUT_CTRL_BYPASS,
|
|
pxp->base + HW_PXP_LUT_CTRL);
|
|
} else if (((lut_op & PXP_LUT_INVERT) != 0)
|
|
&& ((lut_op & PXP_LUT_BLACK_WHITE) != 0)) {
|
|
/* Fill out LUT table with inverted monochromized values */
|
|
|
|
/* clear bypass bit, set lookup mode & out mode */
|
|
__raw_writel(BF_PXP_LUT_CTRL_LOOKUP_MODE
|
|
(BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_Y8) |
|
|
BF_PXP_LUT_CTRL_OUT_MODE
|
|
(BV_PXP_LUT_CTRL_OUT_MODE__Y8),
|
|
pxp->base + HW_PXP_LUT_CTRL);
|
|
|
|
/* Initialize LUT address to 0 and set NUM_BYTES to 0 */
|
|
__raw_writel(0, pxp->base + HW_PXP_LUT_ADDR);
|
|
|
|
/* LUT address pointer auto-increments after each data write */
|
|
for (pix_val = 0; pix_val < 256; pix_val += 4) {
|
|
for (i = 0; i < 4; i++) {
|
|
entry_src = use_cmap ?
|
|
cmap[pix_val + i] : pix_val + i;
|
|
entry[i] = (entry_src < 0x80) ? 0xFF : 0x00;
|
|
}
|
|
reg_val = (entry[3] << 24) | (entry[2] << 16) |
|
|
(entry[1] << 8) | entry[0];
|
|
__raw_writel(reg_val, pxp->base + HW_PXP_LUT_DATA);
|
|
}
|
|
} else if ((lut_op & PXP_LUT_INVERT) != 0) {
|
|
/* Fill out LUT table with 8-bit inverted values */
|
|
|
|
/* clear bypass bit, set lookup mode & out mode */
|
|
__raw_writel(BF_PXP_LUT_CTRL_LOOKUP_MODE
|
|
(BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_Y8) |
|
|
BF_PXP_LUT_CTRL_OUT_MODE
|
|
(BV_PXP_LUT_CTRL_OUT_MODE__Y8),
|
|
pxp->base + HW_PXP_LUT_CTRL);
|
|
|
|
/* Initialize LUT address to 0 and set NUM_BYTES to 0 */
|
|
__raw_writel(0, pxp->base + HW_PXP_LUT_ADDR);
|
|
|
|
/* LUT address pointer auto-increments after each data write */
|
|
for (pix_val = 0; pix_val < 256; pix_val += 4) {
|
|
for (i = 0; i < 4; i++) {
|
|
entry_src = use_cmap ?
|
|
cmap[pix_val + i] : pix_val + i;
|
|
entry[i] = ~entry_src & 0xFF;
|
|
}
|
|
reg_val = (entry[3] << 24) | (entry[2] << 16) |
|
|
(entry[1] << 8) | entry[0];
|
|
__raw_writel(reg_val, pxp->base + HW_PXP_LUT_DATA);
|
|
}
|
|
} else if ((lut_op & PXP_LUT_BLACK_WHITE) != 0) {
|
|
/* Fill out LUT table with 8-bit monochromized values */
|
|
|
|
/* clear bypass bit, set lookup mode & out mode */
|
|
__raw_writel(BF_PXP_LUT_CTRL_LOOKUP_MODE
|
|
(BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_Y8) |
|
|
BF_PXP_LUT_CTRL_OUT_MODE
|
|
(BV_PXP_LUT_CTRL_OUT_MODE__Y8),
|
|
pxp->base + HW_PXP_LUT_CTRL);
|
|
|
|
/* Initialize LUT address to 0 and set NUM_BYTES to 0 */
|
|
__raw_writel(0, pxp->base + HW_PXP_LUT_ADDR);
|
|
|
|
/* LUT address pointer auto-increments after each data write */
|
|
for (pix_val = 0; pix_val < 256; pix_val += 4) {
|
|
for (i = 0; i < 4; i++) {
|
|
entry_src = use_cmap ?
|
|
cmap[pix_val + i] : pix_val + i;
|
|
entry[i] = (entry_src < 0x80) ? 0x00 : 0xFF;
|
|
}
|
|
reg_val = (entry[3] << 24) | (entry[2] << 16) |
|
|
(entry[1] << 8) | entry[0];
|
|
__raw_writel(reg_val, pxp->base + HW_PXP_LUT_DATA);
|
|
}
|
|
} else if (use_cmap) {
|
|
/* Fill out LUT table using colormap values */
|
|
|
|
/* clear bypass bit, set lookup mode & out mode */
|
|
__raw_writel(BF_PXP_LUT_CTRL_LOOKUP_MODE
|
|
(BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_Y8) |
|
|
BF_PXP_LUT_CTRL_OUT_MODE
|
|
(BV_PXP_LUT_CTRL_OUT_MODE__Y8),
|
|
pxp->base + HW_PXP_LUT_CTRL);
|
|
|
|
/* Initialize LUT address to 0 and set NUM_BYTES to 0 */
|
|
__raw_writel(0, pxp->base + HW_PXP_LUT_ADDR);
|
|
|
|
/* LUT address pointer auto-increments after each data write */
|
|
for (pix_val = 0; pix_val < 256; pix_val += 4) {
|
|
for (i = 0; i < 4; i++)
|
|
entry[i] = cmap[pix_val + i];
|
|
reg_val = (entry[3] << 24) | (entry[2] << 16) |
|
|
(entry[1] << 8) | entry[0];
|
|
__raw_writel(reg_val, pxp->base + HW_PXP_LUT_DATA);
|
|
}
|
|
}
|
|
|
|
pxp->lut_state = lut_op;
|
|
}
|
|
|
|
static void pxp_set_csc(struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_layer_param *s0_params = &pxp_conf->s0_param;
|
|
struct pxp_layer_param *ol_params = &pxp_conf->ol_param[0];
|
|
struct pxp_layer_param *out_params = &pxp_conf->out_param;
|
|
|
|
bool input_is_YUV = is_yuv(s0_params->pixel_fmt);
|
|
bool output_is_YUV = is_yuv(out_params->pixel_fmt);
|
|
|
|
if (input_is_YUV && output_is_YUV) {
|
|
/*
|
|
* Input = YUV, Output = YUV
|
|
* No CSC unless we need to do combining
|
|
*/
|
|
if (ol_params->combine_enable) {
|
|
/* Must convert to RGB for combining with RGB overlay */
|
|
|
|
/* CSC1 - YUV->RGB */
|
|
__raw_writel(0x04030000, pxp->base + HW_PXP_CSC1_COEF0);
|
|
__raw_writel(0x01230208, pxp->base + HW_PXP_CSC1_COEF1);
|
|
__raw_writel(0x076b079c, pxp->base + HW_PXP_CSC1_COEF2);
|
|
|
|
/* CSC2 - RGB->YUV */
|
|
__raw_writel(0x4, pxp->base + HW_PXP_CSC2_CTRL);
|
|
__raw_writel(0x0096004D, pxp->base + HW_PXP_CSC2_COEF0);
|
|
__raw_writel(0x05DA001D, pxp->base + HW_PXP_CSC2_COEF1);
|
|
__raw_writel(0x007005B6, pxp->base + HW_PXP_CSC2_COEF2);
|
|
__raw_writel(0x057C009E, pxp->base + HW_PXP_CSC2_COEF3);
|
|
__raw_writel(0x000005E6, pxp->base + HW_PXP_CSC2_COEF4);
|
|
__raw_writel(0x00000000, pxp->base + HW_PXP_CSC2_COEF5);
|
|
} else {
|
|
/* Input & Output both YUV, so bypass both CSCs */
|
|
|
|
/* CSC1 - Bypass */
|
|
__raw_writel(0x40000000, pxp->base + HW_PXP_CSC1_COEF0);
|
|
|
|
/* CSC2 - Bypass */
|
|
__raw_writel(0x1, pxp->base + HW_PXP_CSC2_CTRL);
|
|
}
|
|
} else if (input_is_YUV && !output_is_YUV) {
|
|
/*
|
|
* Input = YUV, Output = RGB
|
|
* Use CSC1 to convert to RGB
|
|
*/
|
|
|
|
/* CSC1 - YUV->RGB */
|
|
__raw_writel(0x84ab01f0, pxp->base + HW_PXP_CSC1_COEF0);
|
|
__raw_writel(0x01980204, pxp->base + HW_PXP_CSC1_COEF1);
|
|
__raw_writel(0x0730079c, pxp->base + HW_PXP_CSC1_COEF2);
|
|
|
|
/* CSC2 - Bypass */
|
|
__raw_writel(0x1, pxp->base + HW_PXP_CSC2_CTRL);
|
|
} else if (!input_is_YUV && output_is_YUV) {
|
|
/*
|
|
* Input = RGB, Output = YUV
|
|
* Use CSC2 to convert to YUV
|
|
*/
|
|
|
|
/* CSC1 - Bypass */
|
|
__raw_writel(0x40000000, pxp->base + HW_PXP_CSC1_COEF0);
|
|
|
|
/* CSC2 - RGB->YUV */
|
|
__raw_writel(0x4, pxp->base + HW_PXP_CSC2_CTRL);
|
|
__raw_writel(0x0096004D, pxp->base + HW_PXP_CSC2_COEF0);
|
|
__raw_writel(0x05DA001D, pxp->base + HW_PXP_CSC2_COEF1);
|
|
__raw_writel(0x007005B6, pxp->base + HW_PXP_CSC2_COEF2);
|
|
__raw_writel(0x057C009E, pxp->base + HW_PXP_CSC2_COEF3);
|
|
__raw_writel(0x000005E6, pxp->base + HW_PXP_CSC2_COEF4);
|
|
__raw_writel(0x00000000, pxp->base + HW_PXP_CSC2_COEF5);
|
|
} else {
|
|
/*
|
|
* Input = RGB, Output = RGB
|
|
* Input & Output both RGB, so bypass both CSCs
|
|
*/
|
|
|
|
/* CSC1 - Bypass */
|
|
__raw_writel(0x40000000, pxp->base + HW_PXP_CSC1_COEF0);
|
|
|
|
/* CSC2 - Bypass */
|
|
__raw_writel(0x1, pxp->base + HW_PXP_CSC2_CTRL);
|
|
}
|
|
|
|
/* YCrCb colorspace */
|
|
/* Not sure when we use this...no YCrCb formats are defined for PxP */
|
|
/*
|
|
__raw_writel(0x84ab01f0, HW_PXP_CSCCOEFF0_ADDR);
|
|
__raw_writel(0x01230204, HW_PXP_CSCCOEFF1_ADDR);
|
|
__raw_writel(0x0730079c, HW_PXP_CSCCOEFF2_ADDR);
|
|
*/
|
|
|
|
}
|
|
|
|
static void pxp_set_s0buf(struct pxps *pxp)
|
|
{
|
|
struct pxp_config_data *pxp_conf = &pxp->pxp_conf_state;
|
|
struct pxp_layer_param *s0_params = &pxp_conf->s0_param;
|
|
struct pxp_proc_data *proc_data = &pxp_conf->proc_data;
|
|
dma_addr_t Y, U, V;
|
|
dma_addr_t Y1, U1, V1;
|
|
u32 offset, bpp = 1;
|
|
u32 pitch = s0_params->stride ? s0_params->stride :
|
|
s0_params->width;
|
|
|
|
Y = s0_params->paddr;
|
|
|
|
if ((s0_params->pixel_fmt == PXP_PIX_FMT_RGB565) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_RGB555))
|
|
bpp = 2;
|
|
else if (s0_params->pixel_fmt == PXP_PIX_FMT_XRGB32)
|
|
bpp = 4;
|
|
offset = (proc_data->srect.top * s0_params->width +
|
|
proc_data->srect.left) * bpp;
|
|
/* clipping or cropping */
|
|
Y1 = Y + offset;
|
|
__raw_writel(Y1, pxp->base + HW_PXP_PS_BUF);
|
|
if ((s0_params->pixel_fmt == PXP_PIX_FMT_YUV420P) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_YVU420P) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_GREY) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_YUV422P)) {
|
|
/* Set to 1 if YUV format is 4:2:2 rather than 4:2:0 */
|
|
int s = 2;
|
|
if (s0_params->pixel_fmt == PXP_PIX_FMT_YUV422P)
|
|
s = 1;
|
|
|
|
offset = proc_data->srect.top * s0_params->width / 4 +
|
|
proc_data->srect.left / 2;
|
|
U = Y + (s0_params->width * s0_params->height);
|
|
U1 = U + offset;
|
|
V = U + ((s0_params->width * s0_params->height) >> s);
|
|
V1 = V + offset;
|
|
if (s0_params->pixel_fmt == PXP_PIX_FMT_YVU420P) {
|
|
__raw_writel(V1, pxp->base + HW_PXP_PS_UBUF);
|
|
__raw_writel(U1, pxp->base + HW_PXP_PS_VBUF);
|
|
} else {
|
|
__raw_writel(U1, pxp->base + HW_PXP_PS_UBUF);
|
|
__raw_writel(V1, pxp->base + HW_PXP_PS_VBUF);
|
|
}
|
|
} else if ((s0_params->pixel_fmt == PXP_PIX_FMT_NV12) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_NV21) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_NV16) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_NV61)) {
|
|
int s = 2;
|
|
if ((s0_params->pixel_fmt == PXP_PIX_FMT_NV16) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_NV61))
|
|
s = 1;
|
|
|
|
offset = (proc_data->srect.top * s0_params->width +
|
|
proc_data->srect.left) / s;
|
|
U = Y + (s0_params->width * s0_params->height);
|
|
U1 = U + offset;
|
|
|
|
__raw_writel(U1, pxp->base + HW_PXP_PS_UBUF);
|
|
}
|
|
|
|
/* TODO: only support RGB565, Y8, Y4, YUV420 */
|
|
if (s0_params->pixel_fmt == PXP_PIX_FMT_GREY ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_YUV420P ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_YVU420P ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_NV12 ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_NV21 ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_NV16 ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_NV61 ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_YUV422P) {
|
|
__raw_writel(pitch, pxp->base + HW_PXP_PS_PITCH);
|
|
}
|
|
else if (s0_params->pixel_fmt == PXP_PIX_FMT_GY04)
|
|
__raw_writel(pitch >> 1,
|
|
pxp->base + HW_PXP_PS_PITCH);
|
|
else if (s0_params->pixel_fmt == PXP_PIX_FMT_XRGB32 ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_VUY444)
|
|
__raw_writel(pitch << 2,
|
|
pxp->base + HW_PXP_PS_PITCH);
|
|
else if (s0_params->pixel_fmt == PXP_PIX_FMT_UYVY ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_YUYV ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_VYUY ||
|
|
s0_params->pixel_fmt == PXP_PIX_FMT_YVYU)
|
|
__raw_writel(pitch << 1,
|
|
pxp->base + HW_PXP_PS_PITCH);
|
|
else if ((s0_params->pixel_fmt == PXP_PIX_FMT_RGB565) ||
|
|
(s0_params->pixel_fmt == PXP_PIX_FMT_RGB555))
|
|
__raw_writel(pitch << 1,
|
|
pxp->base + HW_PXP_PS_PITCH);
|
|
else
|
|
__raw_writel(0, pxp->base + HW_PXP_PS_PITCH);
|
|
}
|
|
|
|
/**
|
|
* pxp_config() - configure PxP for a processing task
|
|
* @pxps: PXP context.
|
|
* @pxp_chan: PXP channel.
|
|
* @return: 0 on success or negative error code on failure.
|
|
*/
|
|
static int pxp_config(struct pxps *pxp, struct pxp_channel *pxp_chan)
|
|
{
|
|
/* Configure PxP regs */
|
|
pxp_set_ctrl(pxp);
|
|
pxp_set_s0param(pxp);
|
|
pxp_set_s0crop(pxp);
|
|
pxp_set_scaling(pxp);
|
|
pxp_set_s0colorkey(pxp);
|
|
|
|
pxp_set_oln(0, pxp);
|
|
pxp_set_olparam(0, pxp);
|
|
pxp_set_olcolorkey(0, pxp);
|
|
|
|
pxp_set_csc(pxp);
|
|
pxp_set_bg(pxp);
|
|
pxp_set_lut(pxp);
|
|
|
|
pxp_set_s0buf(pxp);
|
|
pxp_set_outbuf(pxp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pxp_clk_enable(struct pxps *pxp)
|
|
{
|
|
mutex_lock(&pxp->clk_mutex);
|
|
|
|
if (pxp->clk_stat == CLK_STAT_ON) {
|
|
mutex_unlock(&pxp->clk_mutex);
|
|
return;
|
|
}
|
|
|
|
pm_runtime_get_sync(pxp->dev);
|
|
|
|
if (pxp->clk_disp_axi)
|
|
clk_prepare_enable(pxp->clk_disp_axi);
|
|
clk_prepare_enable(pxp->clk);
|
|
pxp->clk_stat = CLK_STAT_ON;
|
|
|
|
mutex_unlock(&pxp->clk_mutex);
|
|
}
|
|
|
|
static void pxp_clk_disable(struct pxps *pxp)
|
|
{
|
|
unsigned long flags;
|
|
|
|
mutex_lock(&pxp->clk_mutex);
|
|
|
|
if (pxp->clk_stat == CLK_STAT_OFF) {
|
|
mutex_unlock(&pxp->clk_mutex);
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&pxp->lock, flags);
|
|
if ((pxp->pxp_ongoing == 0) && list_empty(&head)) {
|
|
spin_unlock_irqrestore(&pxp->lock, flags);
|
|
clk_disable_unprepare(pxp->clk);
|
|
if (pxp->clk_disp_axi)
|
|
clk_disable_unprepare(pxp->clk_disp_axi);
|
|
pxp->clk_stat = CLK_STAT_OFF;
|
|
pm_runtime_put_sync_suspend(pxp->dev);
|
|
} else
|
|
spin_unlock_irqrestore(&pxp->lock, flags);
|
|
|
|
mutex_unlock(&pxp->clk_mutex);
|
|
}
|
|
|
|
static inline void clkoff_callback(struct work_struct *w)
|
|
{
|
|
struct pxps *pxp = container_of(w, struct pxps, work);
|
|
|
|
pxp_clk_disable(pxp);
|
|
}
|
|
|
|
static void pxp_clkoff_timer(struct timer_list *t)
|
|
{
|
|
struct pxps *pxp = from_timer(pxp, t, clk_timer);
|
|
|
|
if ((pxp->pxp_ongoing == 0) && list_empty(&head))
|
|
schedule_work(&pxp->work);
|
|
else
|
|
mod_timer(&pxp->clk_timer,
|
|
jiffies + msecs_to_jiffies(timeout_in_ms));
|
|
}
|
|
|
|
static struct pxp_tx_desc *pxpdma_first_queued(struct pxp_channel *pxp_chan)
|
|
{
|
|
return list_entry(pxp_chan->queue.next, struct pxp_tx_desc, list);
|
|
}
|
|
|
|
/* called with pxp_chan->lock held */
|
|
static void __pxpdma_dostart(struct pxp_channel *pxp_chan)
|
|
{
|
|
struct pxp_dma *pxp_dma = to_pxp_dma(pxp_chan->dma_chan.device);
|
|
struct pxps *pxp = to_pxp(pxp_dma);
|
|
struct pxp_tx_desc *desc;
|
|
struct pxp_tx_desc *child;
|
|
int i = 0;
|
|
|
|
memset(&pxp->pxp_conf_state.s0_param, 0, sizeof(struct pxp_layer_param));
|
|
memset(&pxp->pxp_conf_state.out_param, 0, sizeof(struct pxp_layer_param));
|
|
memset(pxp->pxp_conf_state.ol_param, 0, sizeof(struct pxp_layer_param));
|
|
memset(&pxp->pxp_conf_state.proc_data, 0, sizeof(struct pxp_proc_data));
|
|
/* S0 */
|
|
desc = list_first_entry(&head, struct pxp_tx_desc, list);
|
|
memcpy(&pxp->pxp_conf_state.s0_param,
|
|
&desc->layer_param.s0_param, sizeof(struct pxp_layer_param));
|
|
memcpy(&pxp->pxp_conf_state.proc_data,
|
|
&desc->proc_data, sizeof(struct pxp_proc_data));
|
|
|
|
/* Save PxP configuration */
|
|
list_for_each_entry(child, &desc->tx_list, list) {
|
|
if (i == 0) { /* Output */
|
|
memcpy(&pxp->pxp_conf_state.out_param,
|
|
&child->layer_param.out_param,
|
|
sizeof(struct pxp_layer_param));
|
|
} else { /* Overlay */
|
|
memcpy(&pxp->pxp_conf_state.ol_param[i - 1],
|
|
&child->layer_param.ol_param,
|
|
sizeof(struct pxp_layer_param));
|
|
}
|
|
|
|
i++;
|
|
}
|
|
pr_debug("%s:%d S0 w/h %d/%d paddr %08x\n", __func__, __LINE__,
|
|
pxp->pxp_conf_state.s0_param.width,
|
|
pxp->pxp_conf_state.s0_param.height,
|
|
pxp->pxp_conf_state.s0_param.paddr);
|
|
pr_debug("%s:%d OUT w/h %d/%d paddr %08x\n", __func__, __LINE__,
|
|
pxp->pxp_conf_state.out_param.width,
|
|
pxp->pxp_conf_state.out_param.height,
|
|
pxp->pxp_conf_state.out_param.paddr);
|
|
}
|
|
|
|
static void pxpdma_dostart_work(struct pxps *pxp)
|
|
{
|
|
struct pxp_channel *pxp_chan = NULL;
|
|
unsigned long flags;
|
|
struct pxp_tx_desc *desc = NULL;
|
|
|
|
spin_lock_irqsave(&pxp->lock, flags);
|
|
|
|
desc = list_entry(head.next, struct pxp_tx_desc, list);
|
|
pxp_chan = to_pxp_channel(desc->txd.chan);
|
|
|
|
__pxpdma_dostart(pxp_chan);
|
|
|
|
/* Configure PxP */
|
|
pxp_config(pxp, pxp_chan);
|
|
|
|
pxp_start(pxp);
|
|
|
|
spin_unlock_irqrestore(&pxp->lock, flags);
|
|
}
|
|
|
|
static void pxpdma_dequeue(struct pxp_channel *pxp_chan, struct pxps *pxp)
|
|
{
|
|
unsigned long flags;
|
|
struct pxp_tx_desc *desc = NULL;
|
|
|
|
do {
|
|
desc = pxpdma_first_queued(pxp_chan);
|
|
spin_lock_irqsave(&pxp->lock, flags);
|
|
list_move_tail(&desc->list, &head);
|
|
spin_unlock_irqrestore(&pxp->lock, flags);
|
|
} while (!list_empty(&pxp_chan->queue));
|
|
}
|
|
|
|
static dma_cookie_t pxp_tx_submit(struct dma_async_tx_descriptor *tx)
|
|
{
|
|
struct pxp_tx_desc *desc = to_tx_desc(tx);
|
|
struct pxp_channel *pxp_chan = to_pxp_channel(tx->chan);
|
|
dma_cookie_t cookie;
|
|
|
|
dev_dbg(&pxp_chan->dma_chan.dev->device, "received TX\n");
|
|
|
|
/* pxp_chan->lock can be taken under ichan->lock, but not v.v. */
|
|
spin_lock(&pxp_chan->lock);
|
|
|
|
cookie = pxp_chan->dma_chan.cookie;
|
|
|
|
if (++cookie < 0)
|
|
cookie = 1;
|
|
|
|
/* from dmaengine.h: "last cookie value returned to client" */
|
|
pxp_chan->dma_chan.cookie = cookie;
|
|
tx->cookie = cookie;
|
|
|
|
/* Here we add the tx descriptor to our PxP task queue. */
|
|
list_add_tail(&desc->list, &pxp_chan->queue);
|
|
|
|
spin_unlock(&pxp_chan->lock);
|
|
|
|
dev_dbg(&pxp_chan->dma_chan.dev->device, "done TX\n");
|
|
|
|
return cookie;
|
|
}
|
|
|
|
/**
|
|
* pxp_init_channel() - initialize a PXP channel.
|
|
* @pxp_dma: PXP DMA context.
|
|
* @pchan: pointer to the channel object.
|
|
* @return 0 on success or negative error code on failure.
|
|
*/
|
|
static int pxp_init_channel(struct pxp_dma *pxp_dma,
|
|
struct pxp_channel *pxp_chan)
|
|
{
|
|
int ret = 0;
|
|
|
|
/*
|
|
* We are using _virtual_ channel here.
|
|
* Each channel contains all parameters of corresponding layers
|
|
* for one transaction; each layer is represented as one descriptor
|
|
* (i.e., pxp_tx_desc) here.
|
|
*/
|
|
|
|
INIT_LIST_HEAD(&pxp_chan->queue);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t pxp_irq(int irq, void *dev_id)
|
|
{
|
|
struct pxps *pxp = dev_id;
|
|
struct pxp_channel *pxp_chan;
|
|
struct pxp_tx_desc *desc;
|
|
struct pxp_tx_desc *child, *_child;
|
|
dma_async_tx_callback callback;
|
|
void *callback_param;
|
|
unsigned long flags;
|
|
u32 hist_status;
|
|
|
|
dump_pxp_reg(pxp);
|
|
|
|
hist_status =
|
|
__raw_readl(pxp->base + HW_PXP_HIST_CTRL) & BM_PXP_HIST_CTRL_STATUS;
|
|
|
|
__raw_writel(BM_PXP_STAT_IRQ, pxp->base + HW_PXP_STAT_CLR);
|
|
|
|
/* set the SFTRST bit to be 1 to reset
|
|
* the PXP block to its default state.
|
|
*/
|
|
pxp_soft_reset(pxp);
|
|
|
|
spin_lock_irqsave(&pxp->lock, flags);
|
|
|
|
if (list_empty(&head)) {
|
|
pxp->pxp_ongoing = 0;
|
|
spin_unlock_irqrestore(&pxp->lock, flags);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
/* Get descriptor and call callback */
|
|
desc = list_entry(head.next, struct pxp_tx_desc, list);
|
|
pxp_chan = to_pxp_channel(desc->txd.chan);
|
|
|
|
pxp_chan->completed = desc->txd.cookie;
|
|
|
|
callback = desc->txd.callback;
|
|
callback_param = desc->txd.callback_param;
|
|
|
|
/* Send histogram status back to caller */
|
|
desc->hist_status = hist_status;
|
|
|
|
if ((desc->txd.flags & DMA_PREP_INTERRUPT) && callback)
|
|
callback(callback_param);
|
|
|
|
pxp_chan->status = PXP_CHANNEL_INITIALIZED;
|
|
|
|
list_for_each_entry_safe(child, _child, &desc->tx_list, list) {
|
|
list_del_init(&child->list);
|
|
kmem_cache_free(tx_desc_cache, (void *)child);
|
|
}
|
|
list_del_init(&desc->list);
|
|
kmem_cache_free(tx_desc_cache, (void *)desc);
|
|
|
|
complete(&pxp->complete);
|
|
pxp->pxp_ongoing = 0;
|
|
mod_timer(&pxp->clk_timer, jiffies + msecs_to_jiffies(timeout_in_ms));
|
|
|
|
spin_unlock_irqrestore(&pxp->lock, flags);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* allocate/free dma tx descriptor dynamically*/
|
|
static struct pxp_tx_desc *pxpdma_desc_alloc(struct pxp_channel *pxp_chan)
|
|
{
|
|
struct pxp_tx_desc *desc = NULL;
|
|
struct dma_async_tx_descriptor *txd = NULL;
|
|
|
|
desc = kmem_cache_alloc(tx_desc_cache, GFP_KERNEL | __GFP_ZERO);
|
|
if (desc == NULL)
|
|
return NULL;
|
|
|
|
INIT_LIST_HEAD(&desc->list);
|
|
INIT_LIST_HEAD(&desc->tx_list);
|
|
txd = &desc->txd;
|
|
dma_async_tx_descriptor_init(txd, &pxp_chan->dma_chan);
|
|
txd->tx_submit = pxp_tx_submit;
|
|
|
|
return desc;
|
|
}
|
|
|
|
/* Allocate and initialise a transfer descriptor. */
|
|
static struct dma_async_tx_descriptor *pxp_prep_slave_sg(struct dma_chan *chan,
|
|
struct scatterlist
|
|
*sgl,
|
|
unsigned int sg_len,
|
|
enum
|
|
dma_transfer_direction
|
|
direction,
|
|
unsigned long tx_flags,
|
|
void *context)
|
|
{
|
|
struct pxp_channel *pxp_chan = to_pxp_channel(chan);
|
|
struct pxp_dma *pxp_dma = to_pxp_dma(chan->device);
|
|
struct pxps *pxp = to_pxp(pxp_dma);
|
|
struct pxp_tx_desc *pos = NULL, *next = NULL;
|
|
struct pxp_tx_desc *desc = NULL;
|
|
struct pxp_tx_desc *first = NULL, *prev = NULL;
|
|
struct scatterlist *sg;
|
|
dma_addr_t phys_addr;
|
|
int i;
|
|
|
|
if (direction != DMA_DEV_TO_MEM && direction != DMA_MEM_TO_DEV) {
|
|
dev_err(chan->device->dev, "Invalid DMA direction %d!\n",
|
|
direction);
|
|
return NULL;
|
|
}
|
|
|
|
if (unlikely(sg_len < 2))
|
|
return NULL;
|
|
|
|
for_each_sg(sgl, sg, sg_len, i) {
|
|
desc = pxpdma_desc_alloc(pxp_chan);
|
|
if (!desc) {
|
|
dev_err(chan->device->dev, "no enough memory to allocate tx descriptor\n");
|
|
|
|
if (first) {
|
|
list_for_each_entry_safe(pos, next, &first->tx_list, list) {
|
|
list_del_init(&pos->list);
|
|
kmem_cache_free(tx_desc_cache, (void*)pos);
|
|
}
|
|
list_del_init(&first->list);
|
|
kmem_cache_free(tx_desc_cache, (void*)first);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
phys_addr = sg_dma_address(sg);
|
|
|
|
if (!first) {
|
|
first = desc;
|
|
|
|
desc->layer_param.s0_param.paddr = phys_addr;
|
|
} else {
|
|
list_add_tail(&desc->list, &first->tx_list);
|
|
prev->next = desc;
|
|
desc->next = NULL;
|
|
|
|
if (i == 1)
|
|
desc->layer_param.out_param.paddr = phys_addr;
|
|
else
|
|
desc->layer_param.ol_param.paddr = phys_addr;
|
|
}
|
|
|
|
prev = desc;
|
|
}
|
|
|
|
pxp->pxp_conf_state.layer_nr = sg_len;
|
|
first->txd.flags = tx_flags;
|
|
first->len = sg_len;
|
|
pr_debug("%s:%d first %p, first->len %d, flags %08x\n",
|
|
__func__, __LINE__, first, first->len, first->txd.flags);
|
|
|
|
return &first->txd;
|
|
}
|
|
|
|
static void pxp_issue_pending(struct dma_chan *chan)
|
|
{
|
|
struct pxp_channel *pxp_chan = to_pxp_channel(chan);
|
|
struct pxp_dma *pxp_dma = to_pxp_dma(chan->device);
|
|
struct pxps *pxp = to_pxp(pxp_dma);
|
|
|
|
spin_lock(&pxp_chan->lock);
|
|
|
|
if (list_empty(&pxp_chan->queue)) {
|
|
spin_unlock(&pxp_chan->lock);
|
|
return;
|
|
}
|
|
|
|
pxpdma_dequeue(pxp_chan, pxp);
|
|
pxp_chan->status = PXP_CHANNEL_READY;
|
|
|
|
spin_unlock(&pxp_chan->lock);
|
|
|
|
pxp_clk_enable(pxp);
|
|
wake_up_interruptible(&pxp->thread_waitq);
|
|
}
|
|
|
|
static void __pxp_terminate_all(struct dma_chan *chan)
|
|
{
|
|
struct pxp_channel *pxp_chan = to_pxp_channel(chan);
|
|
|
|
pxp_chan->status = PXP_CHANNEL_INITIALIZED;
|
|
}
|
|
|
|
static int pxp_device_terminate_all(struct dma_chan *chan)
|
|
{
|
|
struct pxp_channel *pxp_chan = to_pxp_channel(chan);
|
|
|
|
spin_lock(&pxp_chan->lock);
|
|
__pxp_terminate_all(chan);
|
|
spin_unlock(&pxp_chan->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pxp_alloc_chan_resources(struct dma_chan *chan)
|
|
{
|
|
struct pxp_channel *pxp_chan = to_pxp_channel(chan);
|
|
struct pxp_dma *pxp_dma = to_pxp_dma(chan->device);
|
|
int ret;
|
|
|
|
/* dmaengine.c now guarantees to only offer free channels */
|
|
BUG_ON(chan->client_count > 1);
|
|
WARN_ON(pxp_chan->status != PXP_CHANNEL_FREE);
|
|
|
|
chan->cookie = 1;
|
|
pxp_chan->completed = -ENXIO;
|
|
|
|
pr_debug("%s dma_chan.chan_id %d\n", __func__, chan->chan_id);
|
|
ret = pxp_init_channel(pxp_dma, pxp_chan);
|
|
if (ret < 0)
|
|
goto err_chan;
|
|
|
|
pxp_chan->status = PXP_CHANNEL_INITIALIZED;
|
|
|
|
dev_dbg(&chan->dev->device, "Found channel 0x%x, irq %d\n",
|
|
chan->chan_id, pxp_chan->eof_irq);
|
|
|
|
return ret;
|
|
|
|
err_chan:
|
|
return ret;
|
|
}
|
|
|
|
static void pxp_free_chan_resources(struct dma_chan *chan)
|
|
{
|
|
struct pxp_channel *pxp_chan = to_pxp_channel(chan);
|
|
|
|
spin_lock(&pxp_chan->lock);
|
|
|
|
__pxp_terminate_all(chan);
|
|
|
|
pxp_chan->status = PXP_CHANNEL_FREE;
|
|
|
|
spin_unlock(&pxp_chan->lock);
|
|
}
|
|
|
|
static enum dma_status pxp_tx_status(struct dma_chan *chan,
|
|
dma_cookie_t cookie,
|
|
struct dma_tx_state *txstate)
|
|
{
|
|
struct pxp_channel *pxp_chan = to_pxp_channel(chan);
|
|
|
|
if (cookie != chan->cookie)
|
|
return DMA_ERROR;
|
|
|
|
if (txstate) {
|
|
txstate->last = pxp_chan->completed;
|
|
txstate->used = chan->cookie;
|
|
txstate->residue = 0;
|
|
}
|
|
return DMA_COMPLETE;
|
|
}
|
|
|
|
static int pxp_dma_init(struct pxps *pxp)
|
|
{
|
|
struct pxp_dma *pxp_dma = &pxp->pxp_dma;
|
|
struct dma_device *dma = &pxp_dma->dma;
|
|
int i;
|
|
|
|
dma_cap_set(DMA_SLAVE, dma->cap_mask);
|
|
dma_cap_set(DMA_PRIVATE, dma->cap_mask);
|
|
|
|
/* Compulsory common fields */
|
|
dma->dev = pxp->dev;
|
|
dma->device_alloc_chan_resources = pxp_alloc_chan_resources;
|
|
dma->device_free_chan_resources = pxp_free_chan_resources;
|
|
dma->device_tx_status = pxp_tx_status;
|
|
dma->device_issue_pending = pxp_issue_pending;
|
|
|
|
/* Compulsory for DMA_SLAVE fields */
|
|
dma->device_prep_slave_sg = pxp_prep_slave_sg;
|
|
dma->device_terminate_all = pxp_device_terminate_all;
|
|
|
|
/* Initialize PxP Channels */
|
|
INIT_LIST_HEAD(&dma->channels);
|
|
for (i = 0; i < NR_PXP_VIRT_CHANNEL; i++) {
|
|
struct pxp_channel *pxp_chan = pxp->channel + i;
|
|
struct dma_chan *dma_chan = &pxp_chan->dma_chan;
|
|
|
|
spin_lock_init(&pxp_chan->lock);
|
|
|
|
/* Only one EOF IRQ for PxP, shared by all channels */
|
|
pxp_chan->eof_irq = pxp->irq;
|
|
pxp_chan->status = PXP_CHANNEL_FREE;
|
|
pxp_chan->completed = -ENXIO;
|
|
snprintf(pxp_chan->eof_name, sizeof(pxp_chan->eof_name),
|
|
"PXP EOF %d", i);
|
|
|
|
dma_chan->device = &pxp_dma->dma;
|
|
dma_chan->cookie = 1;
|
|
dma_chan->chan_id = i;
|
|
list_add_tail(&dma_chan->device_node, &dma->channels);
|
|
}
|
|
|
|
return dma_async_device_register(&pxp_dma->dma);
|
|
}
|
|
|
|
static ssize_t clk_off_timeout_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "%d\n", timeout_in_ms);
|
|
}
|
|
|
|
static ssize_t clk_off_timeout_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int val;
|
|
if (sscanf(buf, "%d", &val) > 0) {
|
|
timeout_in_ms = val;
|
|
return count;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static DEVICE_ATTR(clk_off_timeout, 0644, clk_off_timeout_show,
|
|
clk_off_timeout_store);
|
|
|
|
static ssize_t block_size_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return sprintf(buf, "%d\n", block_size);
|
|
}
|
|
|
|
static ssize_t block_size_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
char **last = NULL;
|
|
|
|
block_size = simple_strtoul(buf, last, 0);
|
|
if (block_size > 1)
|
|
block_size = 1;
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR(block_size, S_IWUSR | S_IRUGO,
|
|
block_size_show, block_size_store);
|
|
|
|
static const struct of_device_id imx_pxpdma_dt_ids[] = {
|
|
{ .compatible = "fsl,imx6dl-pxp-dma", },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, imx_pxpdma_dt_ids);
|
|
|
|
static int has_pending_task(struct pxps *pxp, struct pxp_channel *task)
|
|
{
|
|
int found;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&pxp->lock, flags);
|
|
found = !list_empty(&head);
|
|
spin_unlock_irqrestore(&pxp->lock, flags);
|
|
|
|
return found;
|
|
}
|
|
|
|
static int pxp_dispatch_thread(void *argv)
|
|
{
|
|
struct pxps *pxp = (struct pxps *)argv;
|
|
struct pxp_channel *pending = NULL;
|
|
unsigned long flags;
|
|
|
|
set_freezable();
|
|
|
|
while (!kthread_should_stop()) {
|
|
int ret;
|
|
ret = wait_event_freezable(pxp->thread_waitq,
|
|
has_pending_task(pxp, pending) ||
|
|
kthread_should_stop());
|
|
if (ret < 0)
|
|
continue;
|
|
|
|
if (kthread_should_stop())
|
|
break;
|
|
|
|
spin_lock_irqsave(&pxp->lock, flags);
|
|
pxp->pxp_ongoing = 1;
|
|
spin_unlock_irqrestore(&pxp->lock, flags);
|
|
init_completion(&pxp->complete);
|
|
pxpdma_dostart_work(pxp);
|
|
ret = wait_for_completion_timeout(&pxp->complete, 2 * HZ);
|
|
if (ret == 0) {
|
|
printk(KERN_EMERG "%s: task is timeout\n\n", __func__);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pxp_probe(struct platform_device *pdev)
|
|
{
|
|
struct pxps *pxp;
|
|
struct resource *res;
|
|
int irq;
|
|
int err = 0;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (!res || irq < 0) {
|
|
err = -ENODEV;
|
|
goto exit;
|
|
}
|
|
|
|
pxp = devm_kzalloc(&pdev->dev, sizeof(*pxp), GFP_KERNEL);
|
|
if (!pxp) {
|
|
dev_err(&pdev->dev, "failed to allocate control object\n");
|
|
err = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
pxp->dev = &pdev->dev;
|
|
|
|
platform_set_drvdata(pdev, pxp);
|
|
pxp->irq = irq;
|
|
|
|
spin_lock_init(&pxp->lock);
|
|
mutex_init(&pxp->clk_mutex);
|
|
|
|
pxp->base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (pxp->base == NULL) {
|
|
dev_err(&pdev->dev, "Couldn't ioremap regs\n");
|
|
err = -ENODEV;
|
|
goto exit;
|
|
}
|
|
|
|
pxp->pdev = pdev;
|
|
|
|
pxp->clk_disp_axi = devm_clk_get(&pdev->dev, "disp-axi");
|
|
if (IS_ERR(pxp->clk_disp_axi))
|
|
pxp->clk_disp_axi = NULL;
|
|
pxp->clk = devm_clk_get(&pdev->dev, "pxp-axi");
|
|
|
|
err = devm_request_irq(&pdev->dev, pxp->irq, pxp_irq, 0,
|
|
"pxp-dmaengine", pxp);
|
|
if (err)
|
|
goto exit;
|
|
/* Initialize DMA engine */
|
|
err = pxp_dma_init(pxp);
|
|
if (err < 0)
|
|
goto exit;
|
|
|
|
if (device_create_file(&pdev->dev, &dev_attr_clk_off_timeout)) {
|
|
dev_err(&pdev->dev,
|
|
"Unable to create file from clk_off_timeout\n");
|
|
goto exit;
|
|
}
|
|
|
|
device_create_file(&pdev->dev, &dev_attr_block_size);
|
|
pxp_clk_enable(pxp);
|
|
dump_pxp_reg(pxp);
|
|
pxp_clk_disable(pxp);
|
|
|
|
INIT_WORK(&pxp->work, clkoff_callback);
|
|
timer_setup(&pxp->clk_timer, pxp_clkoff_timer, 0);
|
|
|
|
init_waitqueue_head(&pxp->thread_waitq);
|
|
/* allocate a kernel thread to dispatch pxp conf */
|
|
pxp->dispatch = kthread_run(pxp_dispatch_thread, pxp, "pxp_dispatch");
|
|
if (IS_ERR(pxp->dispatch)) {
|
|
err = PTR_ERR(pxp->dispatch);
|
|
goto exit;
|
|
}
|
|
tx_desc_cache = kmem_cache_create("tx_desc", sizeof(struct pxp_tx_desc),
|
|
0, SLAB_HWCACHE_ALIGN, NULL);
|
|
if (!tx_desc_cache) {
|
|
err = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
register_pxp_device();
|
|
|
|
pm_runtime_enable(pxp->dev);
|
|
|
|
exit:
|
|
if (err)
|
|
dev_err(&pdev->dev, "Exiting (unsuccessfully) pxp_probe()\n");
|
|
return err;
|
|
}
|
|
|
|
static int pxp_remove(struct platform_device *pdev)
|
|
{
|
|
struct pxps *pxp = platform_get_drvdata(pdev);
|
|
|
|
unregister_pxp_device();
|
|
kmem_cache_destroy(tx_desc_cache);
|
|
kthread_stop(pxp->dispatch);
|
|
cancel_work_sync(&pxp->work);
|
|
del_timer_sync(&pxp->clk_timer);
|
|
clk_disable_unprepare(pxp->clk);
|
|
if (pxp->clk_disp_axi)
|
|
clk_disable_unprepare(pxp->clk_disp_axi);
|
|
device_remove_file(&pdev->dev, &dev_attr_clk_off_timeout);
|
|
device_remove_file(&pdev->dev, &dev_attr_block_size);
|
|
dma_async_device_unregister(&(pxp->pxp_dma.dma));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int pxp_suspend(struct device *dev)
|
|
{
|
|
struct pxps *pxp = dev_get_drvdata(dev);
|
|
|
|
pxp_clk_enable(pxp);
|
|
while (__raw_readl(pxp->base + HW_PXP_CTRL) & BM_PXP_CTRL_ENABLE)
|
|
;
|
|
|
|
__raw_writel(BM_PXP_CTRL_SFTRST, pxp->base + HW_PXP_CTRL);
|
|
pxp_clk_disable(pxp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pxp_resume(struct device *dev)
|
|
{
|
|
struct pxps *pxp = dev_get_drvdata(dev);
|
|
|
|
pxp_clk_enable(pxp);
|
|
/* Pull PxP out of reset */
|
|
__raw_writel(0, pxp->base + HW_PXP_CTRL);
|
|
pxp_clk_disable(pxp);
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
#define pxp_suspend NULL
|
|
#define pxp_resume NULL
|
|
#endif
|
|
|
|
#ifdef CONFIG_PM
|
|
static int pxp_runtime_suspend(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "pxp busfreq high release.\n");
|
|
return 0;
|
|
}
|
|
|
|
static int pxp_runtime_resume(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "pxp busfreq high request.\n");
|
|
return 0;
|
|
}
|
|
#else
|
|
#define pxp_runtime_suspend NULL
|
|
#define pxp_runtime_resume NULL
|
|
#endif
|
|
|
|
static const struct dev_pm_ops pxp_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(pxp_runtime_suspend, pxp_runtime_resume, NULL)
|
|
SET_SYSTEM_SLEEP_PM_OPS(pxp_suspend, pxp_resume)
|
|
};
|
|
|
|
static struct platform_driver pxp_driver = {
|
|
.driver = {
|
|
.name = "imx-pxp",
|
|
.of_match_table = of_match_ptr(imx_pxpdma_dt_ids),
|
|
.pm = &pxp_pm_ops,
|
|
},
|
|
.probe = pxp_probe,
|
|
.remove = pxp_remove,
|
|
};
|
|
|
|
module_platform_driver(pxp_driver);
|
|
|
|
|
|
MODULE_DESCRIPTION("i.MX PxP driver");
|
|
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
|
MODULE_LICENSE("GPL");
|