/* * Copyright (C) 2010-2016 Freescale Semiconductor, Inc. * Copyright 2017 NXP * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* * Based on STMP378X PxP driver * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(unsigned long arg) { struct pxps *pxp = (struct pxps *)arg; 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) * 8); 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); init_timer(&pxp->clk_timer); pxp->clk_timer.function = pxp_clkoff_timer; pxp->clk_timer.data = (unsigned long)pxp; 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");