Contributors: 3
Author Tokens Token Proportion Commits Commit Proportion
Laurent Pinchart 2659 98.34% 2 50.00%
Arnd Bergmann 42 1.55% 1 25.00%
Ricardo Ribalda Delgado 3 0.11% 1 25.00%
Total 2704 4


// 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 = NULL;
	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 */
		val |= CHNL_IMG_CTRL_CSC_BYPASS;
	}

	dev_dbg(pipe->isi->dev, "CSC: %s -> %s\n",
		encodings[in_encoding], encodings[out_encoding]);

	if (coeffs) {
		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 = !coeffs;
}

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);
}