650 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright 2019-2020 NXP4 */5 6#include <linux/delay.h>7#include <linux/device.h>8#include <linux/io.h>9#include <linux/types.h>10 11#include "imx8-isi-core.h"12#include "imx8-isi-regs.h"13 14#define ISI_DOWNSCALE_THRESHOLD 0x400015 16static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg)17{18 return readl(pipe->regs + reg);19}20 21static inline void mxc_isi_write(struct mxc_isi_pipe *pipe, u32 reg, u32 val)22{23 writel(val, pipe->regs + reg);24}25 26/* -----------------------------------------------------------------------------27 * Buffers & M2M operation28 */29 30void mxc_isi_channel_set_inbuf(struct mxc_isi_pipe *pipe, dma_addr_t dma_addr)31{32 mxc_isi_write(pipe, CHNL_IN_BUF_ADDR, lower_32_bits(dma_addr));33 if (pipe->isi->pdata->has_36bit_dma)34 mxc_isi_write(pipe, CHNL_IN_BUF_XTND_ADDR,35 upper_32_bits(dma_addr));36}37 38void mxc_isi_channel_set_outbuf(struct mxc_isi_pipe *pipe,39 const dma_addr_t dma_addrs[3],40 enum mxc_isi_buf_id buf_id)41{42 int val;43 44 val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL);45 46 if (buf_id == MXC_ISI_BUF1) {47 mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_Y,48 lower_32_bits(dma_addrs[0]));49 mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_U,50 lower_32_bits(dma_addrs[1]));51 mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_V,52 lower_32_bits(dma_addrs[2]));53 if (pipe->isi->pdata->has_36bit_dma) {54 mxc_isi_write(pipe, CHNL_Y_BUF1_XTND_ADDR,55 upper_32_bits(dma_addrs[0]));56 mxc_isi_write(pipe, CHNL_U_BUF1_XTND_ADDR,57 upper_32_bits(dma_addrs[1]));58 mxc_isi_write(pipe, CHNL_V_BUF1_XTND_ADDR,59 upper_32_bits(dma_addrs[2]));60 }61 val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR;62 } else {63 mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_Y,64 lower_32_bits(dma_addrs[0]));65 mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_U,66 lower_32_bits(dma_addrs[1]));67 mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_V,68 lower_32_bits(dma_addrs[2]));69 if (pipe->isi->pdata->has_36bit_dma) {70 mxc_isi_write(pipe, CHNL_Y_BUF2_XTND_ADDR,71 upper_32_bits(dma_addrs[0]));72 mxc_isi_write(pipe, CHNL_U_BUF2_XTND_ADDR,73 upper_32_bits(dma_addrs[1]));74 mxc_isi_write(pipe, CHNL_V_BUF2_XTND_ADDR,75 upper_32_bits(dma_addrs[2]));76 }77 val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR;78 }79 80 mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val);81}82 83void mxc_isi_channel_m2m_start(struct mxc_isi_pipe *pipe)84{85 u32 val;86 87 val = mxc_isi_read(pipe, CHNL_MEM_RD_CTRL);88 val &= ~CHNL_MEM_RD_CTRL_READ_MEM;89 mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val);90 91 fsleep(300);92 93 val |= CHNL_MEM_RD_CTRL_READ_MEM;94 mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val);95}96 97/* -----------------------------------------------------------------------------98 * Pipeline configuration99 */100 101static u32 mxc_isi_channel_scaling_ratio(unsigned int from, unsigned int to,102 u32 *dec)103{104 unsigned int ratio = from / to;105 106 if (ratio < 2)107 *dec = 1;108 else if (ratio < 4)109 *dec = 2;110 else if (ratio < 8)111 *dec = 4;112 else113 *dec = 8;114 115 return min_t(u32, from * 0x1000 / (to * *dec), ISI_DOWNSCALE_THRESHOLD);116}117 118static void mxc_isi_channel_set_scaling(struct mxc_isi_pipe *pipe,119 enum mxc_isi_encoding encoding,120 const struct v4l2_area *in_size,121 const struct v4l2_area *out_size,122 bool *bypass)123{124 u32 xscale, yscale;125 u32 decx, decy;126 u32 val;127 128 dev_dbg(pipe->isi->dev, "input %ux%u, output %ux%u\n",129 in_size->width, in_size->height,130 out_size->width, out_size->height);131 132 xscale = mxc_isi_channel_scaling_ratio(in_size->width, out_size->width,133 &decx);134 yscale = mxc_isi_channel_scaling_ratio(in_size->height, out_size->height,135 &decy);136 137 val = mxc_isi_read(pipe, CHNL_IMG_CTRL);138 val &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK |139 CHNL_IMG_CTRL_YCBCR_MODE);140 141 val |= CHNL_IMG_CTRL_DEC_X(ilog2(decx))142 | CHNL_IMG_CTRL_DEC_Y(ilog2(decy));143 144 /*145 * Contrary to what the documentation states, YCBCR_MODE does not146 * control conversion between YCbCr and RGB, but whether the scaler147 * operates in YUV mode or in RGB mode. It must be set when the scaler148 * input is YUV.149 */150 if (encoding == MXC_ISI_ENC_YUV)151 val |= CHNL_IMG_CTRL_YCBCR_MODE;152 153 mxc_isi_write(pipe, CHNL_IMG_CTRL, val);154 155 mxc_isi_write(pipe, CHNL_SCALE_FACTOR,156 CHNL_SCALE_FACTOR_Y_SCALE(yscale) |157 CHNL_SCALE_FACTOR_X_SCALE(xscale));158 159 mxc_isi_write(pipe, CHNL_SCALE_OFFSET, 0);160 161 mxc_isi_write(pipe, CHNL_SCL_IMG_CFG,162 CHNL_SCL_IMG_CFG_HEIGHT(out_size->height) |163 CHNL_SCL_IMG_CFG_WIDTH(out_size->width));164 165 *bypass = in_size->height == out_size->height &&166 in_size->width == out_size->width;167}168 169static void mxc_isi_channel_set_crop(struct mxc_isi_pipe *pipe,170 const struct v4l2_area *src,171 const struct v4l2_rect *dst)172{173 u32 val, val0, val1;174 175 val = mxc_isi_read(pipe, CHNL_IMG_CTRL);176 val &= ~CHNL_IMG_CTRL_CROP_EN;177 178 if (src->height == dst->height && src->width == dst->width) {179 mxc_isi_write(pipe, CHNL_IMG_CTRL, val);180 return;181 }182 183 val |= CHNL_IMG_CTRL_CROP_EN;184 val0 = CHNL_CROP_ULC_X(dst->left) | CHNL_CROP_ULC_Y(dst->top);185 val1 = CHNL_CROP_LRC_X(dst->width) | CHNL_CROP_LRC_Y(dst->height);186 187 mxc_isi_write(pipe, CHNL_CROP_ULC, val0);188 mxc_isi_write(pipe, CHNL_CROP_LRC, val1 + val0);189 mxc_isi_write(pipe, CHNL_IMG_CTRL, val);190}191 192/*193 * A2,A1, B1, A3, B3, B2,194 * C2, C1, D1, C3, D3, D2195 */196static const u32 mxc_isi_yuv2rgb_coeffs[6] = {197 /* YUV -> RGB */198 0x0000012a, 0x012a0198, 0x0730079c,199 0x0204012a, 0x01f00000, 0x01800180200};201 202static const u32 mxc_isi_rgb2yuv_coeffs[6] = {203 /* RGB->YUV */204 0x00810041, 0x07db0019, 0x007007b6,205 0x07a20070, 0x001007ee, 0x00800080206};207 208static void mxc_isi_channel_set_csc(struct mxc_isi_pipe *pipe,209 enum mxc_isi_encoding in_encoding,210 enum mxc_isi_encoding out_encoding,211 bool *bypass)212{213 static const char * const encodings[] = {214 [MXC_ISI_ENC_RAW] = "RAW",215 [MXC_ISI_ENC_RGB] = "RGB",216 [MXC_ISI_ENC_YUV] = "YUV",217 };218 const u32 *coeffs = NULL;219 u32 val;220 221 val = mxc_isi_read(pipe, CHNL_IMG_CTRL);222 val &= ~(CHNL_IMG_CTRL_CSC_BYPASS | CHNL_IMG_CTRL_CSC_MODE_MASK);223 224 if (in_encoding == MXC_ISI_ENC_YUV &&225 out_encoding == MXC_ISI_ENC_RGB) {226 /* YUV2RGB */227 coeffs = mxc_isi_yuv2rgb_coeffs;228 /* YCbCr enable??? */229 val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB);230 } else if (in_encoding == MXC_ISI_ENC_RGB &&231 out_encoding == MXC_ISI_ENC_YUV) {232 /* RGB2YUV */233 coeffs = mxc_isi_rgb2yuv_coeffs;234 val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR);235 } else {236 /* Bypass CSC */237 val |= CHNL_IMG_CTRL_CSC_BYPASS;238 }239 240 dev_dbg(pipe->isi->dev, "CSC: %s -> %s\n",241 encodings[in_encoding], encodings[out_encoding]);242 243 if (coeffs) {244 mxc_isi_write(pipe, CHNL_CSC_COEFF0, coeffs[0]);245 mxc_isi_write(pipe, CHNL_CSC_COEFF1, coeffs[1]);246 mxc_isi_write(pipe, CHNL_CSC_COEFF2, coeffs[2]);247 mxc_isi_write(pipe, CHNL_CSC_COEFF3, coeffs[3]);248 mxc_isi_write(pipe, CHNL_CSC_COEFF4, coeffs[4]);249 mxc_isi_write(pipe, CHNL_CSC_COEFF5, coeffs[5]);250 }251 252 mxc_isi_write(pipe, CHNL_IMG_CTRL, val);253 254 *bypass = !coeffs;255}256 257void mxc_isi_channel_set_alpha(struct mxc_isi_pipe *pipe, u8 alpha)258{259 u32 val;260 261 val = mxc_isi_read(pipe, CHNL_IMG_CTRL);262 val &= ~CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK;263 val |= CHNL_IMG_CTRL_GBL_ALPHA_VAL(alpha) |264 CHNL_IMG_CTRL_GBL_ALPHA_EN;265 mxc_isi_write(pipe, CHNL_IMG_CTRL, val);266}267 268void mxc_isi_channel_set_flip(struct mxc_isi_pipe *pipe, bool hflip, bool vflip)269{270 u32 val;271 272 val = mxc_isi_read(pipe, CHNL_IMG_CTRL);273 val &= ~(CHNL_IMG_CTRL_VFLIP_EN | CHNL_IMG_CTRL_HFLIP_EN);274 275 if (vflip)276 val |= CHNL_IMG_CTRL_VFLIP_EN;277 if (hflip)278 val |= CHNL_IMG_CTRL_HFLIP_EN;279 280 mxc_isi_write(pipe, CHNL_IMG_CTRL, val);281}282 283static void mxc_isi_channel_set_panic_threshold(struct mxc_isi_pipe *pipe)284{285 const struct mxc_isi_set_thd *set_thd = pipe->isi->pdata->set_thd;286 u32 val;287 288 val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL);289 290 val &= ~(set_thd->panic_set_thd_y.mask);291 val |= set_thd->panic_set_thd_y.threshold << set_thd->panic_set_thd_y.offset;292 293 val &= ~(set_thd->panic_set_thd_u.mask);294 val |= set_thd->panic_set_thd_u.threshold << set_thd->panic_set_thd_u.offset;295 296 val &= ~(set_thd->panic_set_thd_v.mask);297 val |= set_thd->panic_set_thd_v.threshold << set_thd->panic_set_thd_v.offset;298 299 mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val);300}301 302static void mxc_isi_channel_set_control(struct mxc_isi_pipe *pipe,303 enum mxc_isi_input_id input,304 bool bypass)305{306 u32 val;307 308 mutex_lock(&pipe->lock);309 310 val = mxc_isi_read(pipe, CHNL_CTRL);311 val &= ~(CHNL_CTRL_CHNL_BYPASS | CHNL_CTRL_CHAIN_BUF_MASK |312 CHNL_CTRL_BLANK_PXL_MASK | CHNL_CTRL_SRC_TYPE_MASK |313 CHNL_CTRL_MIPI_VC_ID_MASK | CHNL_CTRL_SRC_INPUT_MASK);314 315 /*316 * If no scaling or color space conversion is needed, bypass the317 * channel.318 */319 if (bypass)320 val |= CHNL_CTRL_CHNL_BYPASS;321 322 /* Chain line buffers if needed. */323 if (pipe->chained)324 val |= CHNL_CTRL_CHAIN_BUF(CHNL_CTRL_CHAIN_BUF_2_CHAIN);325 326 val |= CHNL_CTRL_BLANK_PXL(0xff);327 328 /* Input source (including VC configuration for CSI-2) */329 if (input == MXC_ISI_INPUT_MEM) {330 /*331 * The memory input is connected to the last port of the332 * crossbar switch, after all pixel link inputs. The SRC_INPUT333 * field controls the input selection and must be set334 * accordingly, despite being documented as ignored when using335 * the memory input in the i.MX8MP reference manual, and336 * reserved in the i.MX8MN reference manual.337 */338 val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_MEMORY);339 val |= CHNL_CTRL_SRC_INPUT(pipe->isi->pdata->num_ports);340 } else {341 val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_DEVICE);342 val |= CHNL_CTRL_SRC_INPUT(input);343 val |= CHNL_CTRL_MIPI_VC_ID(0); /* FIXME: For CSI-2 only */344 }345 346 mxc_isi_write(pipe, CHNL_CTRL, val);347 348 mutex_unlock(&pipe->lock);349}350 351void mxc_isi_channel_config(struct mxc_isi_pipe *pipe,352 enum mxc_isi_input_id input,353 const struct v4l2_area *in_size,354 const struct v4l2_area *scale,355 const struct v4l2_rect *crop,356 enum mxc_isi_encoding in_encoding,357 enum mxc_isi_encoding out_encoding)358{359 bool csc_bypass;360 bool scaler_bypass;361 362 /* Input frame size */363 mxc_isi_write(pipe, CHNL_IMG_CFG,364 CHNL_IMG_CFG_HEIGHT(in_size->height) |365 CHNL_IMG_CFG_WIDTH(in_size->width));366 367 /* Scaling */368 mxc_isi_channel_set_scaling(pipe, in_encoding, in_size, scale,369 &scaler_bypass);370 mxc_isi_channel_set_crop(pipe, scale, crop);371 372 /* CSC */373 mxc_isi_channel_set_csc(pipe, in_encoding, out_encoding, &csc_bypass);374 375 /* Output buffer management */376 mxc_isi_channel_set_panic_threshold(pipe);377 378 /* Channel control */379 mxc_isi_channel_set_control(pipe, input, csc_bypass && scaler_bypass);380}381 382void mxc_isi_channel_set_input_format(struct mxc_isi_pipe *pipe,383 const struct mxc_isi_format_info *info,384 const struct v4l2_pix_format_mplane *format)385{386 unsigned int bpl = format->plane_fmt[0].bytesperline;387 388 mxc_isi_write(pipe, CHNL_MEM_RD_CTRL,389 CHNL_MEM_RD_CTRL_IMG_TYPE(info->isi_in_format));390 mxc_isi_write(pipe, CHNL_IN_BUF_PITCH,391 CHNL_IN_BUF_PITCH_LINE_PITCH(bpl));392}393 394void mxc_isi_channel_set_output_format(struct mxc_isi_pipe *pipe,395 const struct mxc_isi_format_info *info,396 struct v4l2_pix_format_mplane *format)397{398 u32 val;399 400 /* set outbuf format */401 dev_dbg(pipe->isi->dev, "output format %p4cc", &format->pixelformat);402 403 val = mxc_isi_read(pipe, CHNL_IMG_CTRL);404 val &= ~CHNL_IMG_CTRL_FORMAT_MASK;405 val |= CHNL_IMG_CTRL_FORMAT(info->isi_out_format);406 mxc_isi_write(pipe, CHNL_IMG_CTRL, val);407 408 /* line pitch */409 mxc_isi_write(pipe, CHNL_OUT_BUF_PITCH,410 format->plane_fmt[0].bytesperline);411}412 413/* -----------------------------------------------------------------------------414 * IRQ415 */416 417u32 mxc_isi_channel_irq_status(struct mxc_isi_pipe *pipe, bool clear)418{419 u32 status;420 421 status = mxc_isi_read(pipe, CHNL_STS);422 if (clear)423 mxc_isi_write(pipe, CHNL_STS, status);424 425 return status;426}427 428void mxc_isi_channel_irq_clear(struct mxc_isi_pipe *pipe)429{430 mxc_isi_write(pipe, CHNL_STS, 0xffffffff);431}432 433static void mxc_isi_channel_irq_enable(struct mxc_isi_pipe *pipe)434{435 const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg;436 u32 val;437 438 val = CHNL_IER_FRM_RCVD_EN |439 CHNL_IER_AXI_WR_ERR_U_EN |440 CHNL_IER_AXI_WR_ERR_V_EN |441 CHNL_IER_AXI_WR_ERR_Y_EN;442 443 /* Y/U/V overflow enable */444 val |= ier_reg->oflw_y_buf_en.mask |445 ier_reg->oflw_u_buf_en.mask |446 ier_reg->oflw_v_buf_en.mask;447 448 /* Y/U/V excess overflow enable */449 val |= ier_reg->excs_oflw_y_buf_en.mask |450 ier_reg->excs_oflw_u_buf_en.mask |451 ier_reg->excs_oflw_v_buf_en.mask;452 453 /* Y/U/V panic enable */454 val |= ier_reg->panic_y_buf_en.mask |455 ier_reg->panic_u_buf_en.mask |456 ier_reg->panic_v_buf_en.mask;457 458 mxc_isi_channel_irq_clear(pipe);459 mxc_isi_write(pipe, CHNL_IER, val);460}461 462static void mxc_isi_channel_irq_disable(struct mxc_isi_pipe *pipe)463{464 mxc_isi_write(pipe, CHNL_IER, 0);465}466 467/* -----------------------------------------------------------------------------468 * Init, deinit, enable, disable469 */470 471static void mxc_isi_channel_sw_reset(struct mxc_isi_pipe *pipe, bool enable_clk)472{473 mxc_isi_write(pipe, CHNL_CTRL, CHNL_CTRL_SW_RST);474 mdelay(5);475 mxc_isi_write(pipe, CHNL_CTRL, enable_clk ? CHNL_CTRL_CLK_EN : 0);476}477 478static void __mxc_isi_channel_get(struct mxc_isi_pipe *pipe)479{480 if (!pipe->use_count++)481 mxc_isi_channel_sw_reset(pipe, true);482}483 484void mxc_isi_channel_get(struct mxc_isi_pipe *pipe)485{486 mutex_lock(&pipe->lock);487 __mxc_isi_channel_get(pipe);488 mutex_unlock(&pipe->lock);489}490 491static void __mxc_isi_channel_put(struct mxc_isi_pipe *pipe)492{493 if (!--pipe->use_count)494 mxc_isi_channel_sw_reset(pipe, false);495}496 497void mxc_isi_channel_put(struct mxc_isi_pipe *pipe)498{499 mutex_lock(&pipe->lock);500 __mxc_isi_channel_put(pipe);501 mutex_unlock(&pipe->lock);502}503 504void mxc_isi_channel_enable(struct mxc_isi_pipe *pipe)505{506 u32 val;507 508 mxc_isi_channel_irq_enable(pipe);509 510 mutex_lock(&pipe->lock);511 512 val = mxc_isi_read(pipe, CHNL_CTRL);513 val |= CHNL_CTRL_CHNL_EN;514 mxc_isi_write(pipe, CHNL_CTRL, val);515 516 mutex_unlock(&pipe->lock);517}518 519void mxc_isi_channel_disable(struct mxc_isi_pipe *pipe)520{521 u32 val;522 523 mxc_isi_channel_irq_disable(pipe);524 525 mutex_lock(&pipe->lock);526 527 val = mxc_isi_read(pipe, CHNL_CTRL);528 val &= ~CHNL_CTRL_CHNL_EN;529 mxc_isi_write(pipe, CHNL_CTRL, val);530 531 mutex_unlock(&pipe->lock);532}533 534/* -----------------------------------------------------------------------------535 * Resource management & chaining536 */537int mxc_isi_channel_acquire(struct mxc_isi_pipe *pipe,538 mxc_isi_pipe_irq_t irq_handler, bool bypass)539{540 u8 resources;541 int ret = 0;542 543 mutex_lock(&pipe->lock);544 545 if (pipe->irq_handler) {546 ret = -EBUSY;547 goto unlock;548 }549 550 /*551 * Make sure the resources we need are available. The output buffer is552 * always needed to operate the channel, the line buffer is needed only553 * when the channel isn't in bypass mode.554 */555 resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF556 | (!bypass ? MXC_ISI_CHANNEL_RES_LINE_BUF : 0);557 if ((pipe->available_res & resources) != resources) {558 ret = -EBUSY;559 goto unlock;560 }561 562 /* Acquire the channel resources. */563 pipe->acquired_res = resources;564 pipe->available_res &= ~resources;565 pipe->irq_handler = irq_handler;566 567unlock:568 mutex_unlock(&pipe->lock);569 570 return ret;571}572 573void mxc_isi_channel_release(struct mxc_isi_pipe *pipe)574{575 mutex_lock(&pipe->lock);576 577 pipe->irq_handler = NULL;578 pipe->available_res |= pipe->acquired_res;579 pipe->acquired_res = 0;580 581 mutex_unlock(&pipe->lock);582}583 584/*585 * We currently support line buffer chaining only, for handling images with a586 * width larger than 2048 pixels.587 *588 * TODO: Support secondary line buffer for downscaling YUV420 images.589 */590int mxc_isi_channel_chain(struct mxc_isi_pipe *pipe, bool bypass)591{592 /* Channel chaining requires both line and output buffer. */593 const u8 resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF594 | MXC_ISI_CHANNEL_RES_LINE_BUF;595 struct mxc_isi_pipe *chained_pipe = pipe + 1;596 int ret = 0;597 598 /*599 * If buffer chaining is required, make sure this channel is not the600 * last one, otherwise there's no 'next' channel to chain with. This601 * should be prevented by checks in the set format handlers, but let's602 * be defensive.603 */604 if (WARN_ON(pipe->id == pipe->isi->pdata->num_channels - 1))605 return -EINVAL;606 607 mutex_lock(&chained_pipe->lock);608 609 /* Safety checks. */610 if (WARN_ON(pipe->chained || chained_pipe->chained_res)) {611 ret = -EINVAL;612 goto unlock;613 }614 615 if ((chained_pipe->available_res & resources) != resources) {616 ret = -EBUSY;617 goto unlock;618 }619 620 pipe->chained = true;621 chained_pipe->chained_res |= resources;622 chained_pipe->available_res &= ~resources;623 624 __mxc_isi_channel_get(chained_pipe);625 626unlock:627 mutex_unlock(&chained_pipe->lock);628 629 return ret;630}631 632void mxc_isi_channel_unchain(struct mxc_isi_pipe *pipe)633{634 struct mxc_isi_pipe *chained_pipe = pipe + 1;635 636 if (!pipe->chained)637 return;638 639 pipe->chained = false;640 641 mutex_lock(&chained_pipe->lock);642 643 chained_pipe->available_res |= chained_pipe->chained_res;644 chained_pipe->chained_res = 0;645 646 __mxc_isi_channel_put(chained_pipe);647 648 mutex_unlock(&chained_pipe->lock);649}650