| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright 2019-2020 NXP |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/io.h> |
| #include <linux/types.h> |
| |
| #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, lower_32_bits(dma_addr)); |
| if (pipe->isi->pdata->has_36bit_dma) |
| mxc_isi_write(pipe, CHNL_IN_BUF_XTND_ADDR, |
| upper_32_bits(dma_addr)); |
| } |
| |
| 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, |
| lower_32_bits(dma_addrs[0])); |
| mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_U, |
| lower_32_bits(dma_addrs[1])); |
| mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_V, |
| lower_32_bits(dma_addrs[2])); |
| if (pipe->isi->pdata->has_36bit_dma) { |
| mxc_isi_write(pipe, CHNL_Y_BUF1_XTND_ADDR, |
| upper_32_bits(dma_addrs[0])); |
| mxc_isi_write(pipe, CHNL_U_BUF1_XTND_ADDR, |
| upper_32_bits(dma_addrs[1])); |
| mxc_isi_write(pipe, CHNL_V_BUF1_XTND_ADDR, |
| upper_32_bits(dma_addrs[2])); |
| } |
| val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR; |
| } else { |
| mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_Y, |
| lower_32_bits(dma_addrs[0])); |
| mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_U, |
| lower_32_bits(dma_addrs[1])); |
| mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_V, |
| lower_32_bits(dma_addrs[2])); |
| if (pipe->isi->pdata->has_36bit_dma) { |
| mxc_isi_write(pipe, CHNL_Y_BUF2_XTND_ADDR, |
| upper_32_bits(dma_addrs[0])); |
| mxc_isi_write(pipe, CHNL_U_BUF2_XTND_ADDR, |
| upper_32_bits(dma_addrs[1])); |
| mxc_isi_write(pipe, CHNL_V_BUF2_XTND_ADDR, |
| upper_32_bits(dma_addrs[2])); |
| } |
| 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); |
| } |