// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2019-2020 NXP */ #include #include #include #include #include "imx8-isi-core.h" #include "imx8-isi-regs.h" #define ISI_DOWNSCALE_THRESHOLD 0x4000 static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg) { return readl(pipe->regs + reg); } static inline void mxc_isi_write(struct mxc_isi_pipe *pipe, u32 reg, u32 val) { writel(val, pipe->regs + reg); } /* ----------------------------------------------------------------------------- * Buffers & M2M operation */ void mxc_isi_channel_set_inbuf(struct mxc_isi_pipe *pipe, dma_addr_t dma_addr) { mxc_isi_write(pipe, CHNL_IN_BUF_ADDR, dma_addr); #if CONFIG_ARCH_DMA_ADDR_T_64BIT if (pipe->isi->pdata->has_36bit_dma) mxc_isi_write(pipe, CHNL_IN_BUF_XTND_ADDR, dma_addr >> 32); #endif } void mxc_isi_channel_set_outbuf(struct mxc_isi_pipe *pipe, const dma_addr_t dma_addrs[3], enum mxc_isi_buf_id buf_id) { int val; val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL); if (buf_id == MXC_ISI_BUF1) { mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_Y, dma_addrs[0]); mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_U, dma_addrs[1]); mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_V, dma_addrs[2]); #if CONFIG_ARCH_DMA_ADDR_T_64BIT if (pipe->isi->pdata->has_36bit_dma) { mxc_isi_write(pipe, CHNL_Y_BUF1_XTND_ADDR, dma_addrs[0] >> 32); mxc_isi_write(pipe, CHNL_U_BUF1_XTND_ADDR, dma_addrs[1] >> 32); mxc_isi_write(pipe, CHNL_V_BUF1_XTND_ADDR, dma_addrs[2] >> 32); } #endif val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR; } else { mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_Y, dma_addrs[0]); mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_U, dma_addrs[1]); mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_V, dma_addrs[2]); #if CONFIG_ARCH_DMA_ADDR_T_64BIT if (pipe->isi->pdata->has_36bit_dma) { mxc_isi_write(pipe, CHNL_Y_BUF2_XTND_ADDR, dma_addrs[0] >> 32); mxc_isi_write(pipe, CHNL_U_BUF2_XTND_ADDR, dma_addrs[1] >> 32); mxc_isi_write(pipe, CHNL_V_BUF2_XTND_ADDR, dma_addrs[2] >> 32); } #endif val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR; } mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val); } void mxc_isi_channel_m2m_start(struct mxc_isi_pipe *pipe) { u32 val; val = mxc_isi_read(pipe, CHNL_MEM_RD_CTRL); val &= ~CHNL_MEM_RD_CTRL_READ_MEM; mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val); fsleep(300); val |= CHNL_MEM_RD_CTRL_READ_MEM; mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val); } /* ----------------------------------------------------------------------------- * Pipeline configuration */ static u32 mxc_isi_channel_scaling_ratio(unsigned int from, unsigned int to, u32 *dec) { unsigned int ratio = from / to; if (ratio < 2) *dec = 1; else if (ratio < 4) *dec = 2; else if (ratio < 8) *dec = 4; else *dec = 8; return min_t(u32, from * 0x1000 / (to * *dec), ISI_DOWNSCALE_THRESHOLD); } static void mxc_isi_channel_set_scaling(struct mxc_isi_pipe *pipe, enum mxc_isi_encoding encoding, const struct v4l2_area *in_size, const struct v4l2_area *out_size, bool *bypass) { u32 xscale, yscale; u32 decx, decy; u32 val; dev_dbg(pipe->isi->dev, "input %ux%u, output %ux%u\n", in_size->width, in_size->height, out_size->width, out_size->height); xscale = mxc_isi_channel_scaling_ratio(in_size->width, out_size->width, &decx); yscale = mxc_isi_channel_scaling_ratio(in_size->height, out_size->height, &decy); val = mxc_isi_read(pipe, CHNL_IMG_CTRL); val &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK | CHNL_IMG_CTRL_YCBCR_MODE); val |= CHNL_IMG_CTRL_DEC_X(ilog2(decx)) | CHNL_IMG_CTRL_DEC_Y(ilog2(decy)); /* * Contrary to what the documentation states, YCBCR_MODE does not * control conversion between YCbCr and RGB, but whether the scaler * operates in YUV mode or in RGB mode. It must be set when the scaler * input is YUV. */ if (encoding == MXC_ISI_ENC_YUV) val |= CHNL_IMG_CTRL_YCBCR_MODE; mxc_isi_write(pipe, CHNL_IMG_CTRL, val); mxc_isi_write(pipe, CHNL_SCALE_FACTOR, CHNL_SCALE_FACTOR_Y_SCALE(yscale) | CHNL_SCALE_FACTOR_X_SCALE(xscale)); mxc_isi_write(pipe, CHNL_SCALE_OFFSET, 0); mxc_isi_write(pipe, CHNL_SCL_IMG_CFG, CHNL_SCL_IMG_CFG_HEIGHT(out_size->height) | CHNL_SCL_IMG_CFG_WIDTH(out_size->width)); *bypass = in_size->height == out_size->height && in_size->width == out_size->width; } static void mxc_isi_channel_set_crop(struct mxc_isi_pipe *pipe, const struct v4l2_area *src, const struct v4l2_rect *dst) { u32 val, val0, val1; val = mxc_isi_read(pipe, CHNL_IMG_CTRL); val &= ~CHNL_IMG_CTRL_CROP_EN; if (src->height == dst->height && src->width == dst->width) { mxc_isi_write(pipe, CHNL_IMG_CTRL, val); return; } val |= CHNL_IMG_CTRL_CROP_EN; val0 = CHNL_CROP_ULC_X(dst->left) | CHNL_CROP_ULC_Y(dst->top); val1 = CHNL_CROP_LRC_X(dst->width) | CHNL_CROP_LRC_Y(dst->height); mxc_isi_write(pipe, CHNL_CROP_ULC, val0); mxc_isi_write(pipe, CHNL_CROP_LRC, val1 + val0); mxc_isi_write(pipe, CHNL_IMG_CTRL, val); } /* * A2,A1, B1, A3, B3, B2, * C2, C1, D1, C3, D3, D2 */ static const u32 mxc_isi_yuv2rgb_coeffs[6] = { /* YUV -> RGB */ 0x0000012a, 0x012a0198, 0x0730079c, 0x0204012a, 0x01f00000, 0x01800180 }; static const u32 mxc_isi_rgb2yuv_coeffs[6] = { /* RGB->YUV */ 0x00810041, 0x07db0019, 0x007007b6, 0x07a20070, 0x001007ee, 0x00800080 }; static void mxc_isi_channel_set_csc(struct mxc_isi_pipe *pipe, enum mxc_isi_encoding in_encoding, enum mxc_isi_encoding out_encoding, bool *bypass) { static const char * const encodings[] = { [MXC_ISI_ENC_RAW] = "RAW", [MXC_ISI_ENC_RGB] = "RGB", [MXC_ISI_ENC_YUV] = "YUV", }; const u32 *coeffs; bool cscen = true; u32 val; val = mxc_isi_read(pipe, CHNL_IMG_CTRL); val &= ~(CHNL_IMG_CTRL_CSC_BYPASS | CHNL_IMG_CTRL_CSC_MODE_MASK); if (in_encoding == MXC_ISI_ENC_YUV && out_encoding == MXC_ISI_ENC_RGB) { /* YUV2RGB */ coeffs = mxc_isi_yuv2rgb_coeffs; /* YCbCr enable??? */ val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB); } else if (in_encoding == MXC_ISI_ENC_RGB && out_encoding == MXC_ISI_ENC_YUV) { /* RGB2YUV */ coeffs = mxc_isi_rgb2yuv_coeffs; val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR); } else { /* Bypass CSC */ cscen = false; val |= CHNL_IMG_CTRL_CSC_BYPASS; } dev_dbg(pipe->isi->dev, "CSC: %s -> %s\n", encodings[in_encoding], encodings[out_encoding]); if (cscen) { mxc_isi_write(pipe, CHNL_CSC_COEFF0, coeffs[0]); mxc_isi_write(pipe, CHNL_CSC_COEFF1, coeffs[1]); mxc_isi_write(pipe, CHNL_CSC_COEFF2, coeffs[2]); mxc_isi_write(pipe, CHNL_CSC_COEFF3, coeffs[3]); mxc_isi_write(pipe, CHNL_CSC_COEFF4, coeffs[4]); mxc_isi_write(pipe, CHNL_CSC_COEFF5, coeffs[5]); } mxc_isi_write(pipe, CHNL_IMG_CTRL, val); *bypass = !cscen; } void mxc_isi_channel_set_alpha(struct mxc_isi_pipe *pipe, u8 alpha) { u32 val; val = mxc_isi_read(pipe, CHNL_IMG_CTRL); val &= ~CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK; val |= CHNL_IMG_CTRL_GBL_ALPHA_VAL(alpha) | CHNL_IMG_CTRL_GBL_ALPHA_EN; mxc_isi_write(pipe, CHNL_IMG_CTRL, val); } void mxc_isi_channel_set_flip(struct mxc_isi_pipe *pipe, bool hflip, bool vflip) { u32 val; val = mxc_isi_read(pipe, CHNL_IMG_CTRL); val &= ~(CHNL_IMG_CTRL_VFLIP_EN | CHNL_IMG_CTRL_HFLIP_EN); if (vflip) val |= CHNL_IMG_CTRL_VFLIP_EN; if (hflip) val |= CHNL_IMG_CTRL_HFLIP_EN; mxc_isi_write(pipe, CHNL_IMG_CTRL, val); } static void mxc_isi_channel_set_panic_threshold(struct mxc_isi_pipe *pipe) { const struct mxc_isi_set_thd *set_thd = pipe->isi->pdata->set_thd; u32 val; val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL); val &= ~(set_thd->panic_set_thd_y.mask); val |= set_thd->panic_set_thd_y.threshold << set_thd->panic_set_thd_y.offset; val &= ~(set_thd->panic_set_thd_u.mask); val |= set_thd->panic_set_thd_u.threshold << set_thd->panic_set_thd_u.offset; val &= ~(set_thd->panic_set_thd_v.mask); val |= set_thd->panic_set_thd_v.threshold << set_thd->panic_set_thd_v.offset; mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val); } static void mxc_isi_channel_set_control(struct mxc_isi_pipe *pipe, enum mxc_isi_input_id input, bool bypass) { u32 val; mutex_lock(&pipe->lock); val = mxc_isi_read(pipe, CHNL_CTRL); val &= ~(CHNL_CTRL_CHNL_BYPASS | CHNL_CTRL_CHAIN_BUF_MASK | CHNL_CTRL_BLANK_PXL_MASK | CHNL_CTRL_SRC_TYPE_MASK | CHNL_CTRL_MIPI_VC_ID_MASK | CHNL_CTRL_SRC_INPUT_MASK); /* * If no scaling or color space conversion is needed, bypass the * channel. */ if (bypass) val |= CHNL_CTRL_CHNL_BYPASS; /* Chain line buffers if needed. */ if (pipe->chained) val |= CHNL_CTRL_CHAIN_BUF(CHNL_CTRL_CHAIN_BUF_2_CHAIN); val |= CHNL_CTRL_BLANK_PXL(0xff); /* Input source (including VC configuration for CSI-2) */ if (input == MXC_ISI_INPUT_MEM) { /* * The memory input is connected to the last port of the * crossbar switch, after all pixel link inputs. The SRC_INPUT * field controls the input selection and must be set * accordingly, despite being documented as ignored when using * the memory input in the i.MX8MP reference manual, and * reserved in the i.MX8MN reference manual. */ val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_MEMORY); val |= CHNL_CTRL_SRC_INPUT(pipe->isi->pdata->num_ports); } else { val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_DEVICE); val |= CHNL_CTRL_SRC_INPUT(input); val |= CHNL_CTRL_MIPI_VC_ID(0); /* FIXME: For CSI-2 only */ } mxc_isi_write(pipe, CHNL_CTRL, val); mutex_unlock(&pipe->lock); } void mxc_isi_channel_config(struct mxc_isi_pipe *pipe, enum mxc_isi_input_id input, const struct v4l2_area *in_size, const struct v4l2_area *scale, const struct v4l2_rect *crop, enum mxc_isi_encoding in_encoding, enum mxc_isi_encoding out_encoding) { bool csc_bypass; bool scaler_bypass; /* Input frame size */ mxc_isi_write(pipe, CHNL_IMG_CFG, CHNL_IMG_CFG_HEIGHT(in_size->height) | CHNL_IMG_CFG_WIDTH(in_size->width)); /* Scaling */ mxc_isi_channel_set_scaling(pipe, in_encoding, in_size, scale, &scaler_bypass); mxc_isi_channel_set_crop(pipe, scale, crop); /* CSC */ mxc_isi_channel_set_csc(pipe, in_encoding, out_encoding, &csc_bypass); /* Output buffer management */ mxc_isi_channel_set_panic_threshold(pipe); /* Channel control */ mxc_isi_channel_set_control(pipe, input, csc_bypass && scaler_bypass); } void mxc_isi_channel_set_input_format(struct mxc_isi_pipe *pipe, const struct mxc_isi_format_info *info, const struct v4l2_pix_format_mplane *format) { unsigned int bpl = format->plane_fmt[0].bytesperline; mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, CHNL_MEM_RD_CTRL_IMG_TYPE(info->isi_in_format)); mxc_isi_write(pipe, CHNL_IN_BUF_PITCH, CHNL_IN_BUF_PITCH_LINE_PITCH(bpl)); } void mxc_isi_channel_set_output_format(struct mxc_isi_pipe *pipe, const struct mxc_isi_format_info *info, struct v4l2_pix_format_mplane *format) { u32 val; /* set outbuf format */ dev_dbg(pipe->isi->dev, "output format %p4cc", &format->pixelformat); val = mxc_isi_read(pipe, CHNL_IMG_CTRL); val &= ~CHNL_IMG_CTRL_FORMAT_MASK; val |= CHNL_IMG_CTRL_FORMAT(info->isi_out_format); mxc_isi_write(pipe, CHNL_IMG_CTRL, val); /* line pitch */ mxc_isi_write(pipe, CHNL_OUT_BUF_PITCH, format->plane_fmt[0].bytesperline); } /* ----------------------------------------------------------------------------- * IRQ */ u32 mxc_isi_channel_irq_status(struct mxc_isi_pipe *pipe, bool clear) { u32 status; status = mxc_isi_read(pipe, CHNL_STS); if (clear) mxc_isi_write(pipe, CHNL_STS, status); return status; } void mxc_isi_channel_irq_clear(struct mxc_isi_pipe *pipe) { mxc_isi_write(pipe, CHNL_STS, 0xffffffff); } static void mxc_isi_channel_irq_enable(struct mxc_isi_pipe *pipe) { const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg; u32 val; val = CHNL_IER_FRM_RCVD_EN | CHNL_IER_AXI_WR_ERR_U_EN | CHNL_IER_AXI_WR_ERR_V_EN | CHNL_IER_AXI_WR_ERR_Y_EN; /* Y/U/V overflow enable */ val |= ier_reg->oflw_y_buf_en.mask | ier_reg->oflw_u_buf_en.mask | ier_reg->oflw_v_buf_en.mask; /* Y/U/V excess overflow enable */ val |= ier_reg->excs_oflw_y_buf_en.mask | ier_reg->excs_oflw_u_buf_en.mask | ier_reg->excs_oflw_v_buf_en.mask; /* Y/U/V panic enable */ val |= ier_reg->panic_y_buf_en.mask | ier_reg->panic_u_buf_en.mask | ier_reg->panic_v_buf_en.mask; mxc_isi_channel_irq_clear(pipe); mxc_isi_write(pipe, CHNL_IER, val); } static void mxc_isi_channel_irq_disable(struct mxc_isi_pipe *pipe) { mxc_isi_write(pipe, CHNL_IER, 0); } /* ----------------------------------------------------------------------------- * Init, deinit, enable, disable */ static void mxc_isi_channel_sw_reset(struct mxc_isi_pipe *pipe, bool enable_clk) { mxc_isi_write(pipe, CHNL_CTRL, CHNL_CTRL_SW_RST); mdelay(5); mxc_isi_write(pipe, CHNL_CTRL, enable_clk ? CHNL_CTRL_CLK_EN : 0); } static void __mxc_isi_channel_get(struct mxc_isi_pipe *pipe) { if (!pipe->use_count++) mxc_isi_channel_sw_reset(pipe, true); } void mxc_isi_channel_get(struct mxc_isi_pipe *pipe) { mutex_lock(&pipe->lock); __mxc_isi_channel_get(pipe); mutex_unlock(&pipe->lock); } static void __mxc_isi_channel_put(struct mxc_isi_pipe *pipe) { if (!--pipe->use_count) mxc_isi_channel_sw_reset(pipe, false); } void mxc_isi_channel_put(struct mxc_isi_pipe *pipe) { mutex_lock(&pipe->lock); __mxc_isi_channel_put(pipe); mutex_unlock(&pipe->lock); } void mxc_isi_channel_enable(struct mxc_isi_pipe *pipe) { u32 val; mxc_isi_channel_irq_enable(pipe); mutex_lock(&pipe->lock); val = mxc_isi_read(pipe, CHNL_CTRL); val |= CHNL_CTRL_CHNL_EN; mxc_isi_write(pipe, CHNL_CTRL, val); mutex_unlock(&pipe->lock); } void mxc_isi_channel_disable(struct mxc_isi_pipe *pipe) { u32 val; mxc_isi_channel_irq_disable(pipe); mutex_lock(&pipe->lock); val = mxc_isi_read(pipe, CHNL_CTRL); val &= ~CHNL_CTRL_CHNL_EN; mxc_isi_write(pipe, CHNL_CTRL, val); mutex_unlock(&pipe->lock); } /* ----------------------------------------------------------------------------- * Resource management & chaining */ int mxc_isi_channel_acquire(struct mxc_isi_pipe *pipe, mxc_isi_pipe_irq_t irq_handler, bool bypass) { u8 resources; int ret = 0; mutex_lock(&pipe->lock); if (pipe->irq_handler) { ret = -EBUSY; goto unlock; } /* * Make sure the resources we need are available. The output buffer is * always needed to operate the channel, the line buffer is needed only * when the channel isn't in bypass mode. */ resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF | (!bypass ? MXC_ISI_CHANNEL_RES_LINE_BUF : 0); if ((pipe->available_res & resources) != resources) { ret = -EBUSY; goto unlock; } /* Acquire the channel resources. */ pipe->acquired_res = resources; pipe->available_res &= ~resources; pipe->irq_handler = irq_handler; unlock: mutex_unlock(&pipe->lock); return ret; } void mxc_isi_channel_release(struct mxc_isi_pipe *pipe) { mutex_lock(&pipe->lock); pipe->irq_handler = NULL; pipe->available_res |= pipe->acquired_res; pipe->acquired_res = 0; mutex_unlock(&pipe->lock); } /* * We currently support line buffer chaining only, for handling images with a * width larger than 2048 pixels. * * TODO: Support secondary line buffer for downscaling YUV420 images. */ int mxc_isi_channel_chain(struct mxc_isi_pipe *pipe, bool bypass) { /* Channel chaining requires both line and output buffer. */ const u8 resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF | MXC_ISI_CHANNEL_RES_LINE_BUF; struct mxc_isi_pipe *chained_pipe = pipe + 1; int ret = 0; /* * If buffer chaining is required, make sure this channel is not the * last one, otherwise there's no 'next' channel to chain with. This * should be prevented by checks in the set format handlers, but let's * be defensive. */ if (WARN_ON(pipe->id == pipe->isi->pdata->num_channels - 1)) return -EINVAL; mutex_lock(&chained_pipe->lock); /* Safety checks. */ if (WARN_ON(pipe->chained || chained_pipe->chained_res)) { ret = -EINVAL; goto unlock; } if ((chained_pipe->available_res & resources) != resources) { ret = -EBUSY; goto unlock; } pipe->chained = true; chained_pipe->chained_res |= resources; chained_pipe->available_res &= ~resources; __mxc_isi_channel_get(chained_pipe); unlock: mutex_unlock(&chained_pipe->lock); return ret; } void mxc_isi_channel_unchain(struct mxc_isi_pipe *pipe) { struct mxc_isi_pipe *chained_pipe = pipe + 1; if (!pipe->chained) return; pipe->chained = false; mutex_lock(&chained_pipe->lock); chained_pipe->available_res |= chained_pipe->chained_res; chained_pipe->chained_res = 0; __mxc_isi_channel_put(chained_pipe); mutex_unlock(&chained_pipe->lock); }