1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright 2019-2020 NXP
4 */
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 0x4000
15
16static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg)
17{
18 return readl(addr: pipe->regs + reg);
19}
20
21static inline void mxc_isi_write(struct mxc_isi_pipe *pipe, u32 reg, u32 val)
22{
23 writel(val, addr: pipe->regs + reg);
24}
25
26/* -----------------------------------------------------------------------------
27 * Buffers & M2M operation
28 */
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(usecs: 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 configuration
99 */
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 else
113 *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(from: in_size->width, to: out_size->width,
133 dec: &decx);
134 yscale = mxc_isi_channel_scaling_ratio(from: in_size->height, to: out_size->height,
135 dec: &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 not
146 * control conversion between YCbCr and RGB, but whether the scaler
147 * operates in YUV mode or in RGB mode. It must be set when the scaler
148 * 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, val: 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, val: val0);
188 mxc_isi_write(pipe, CHNL_CROP_LRC, val: 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, D2
195 */
196static const u32 mxc_isi_yuv2rgb_coeffs[6] = {
197 /* YUV -> RGB */
198 0x0000012a, 0x012a0198, 0x0730079c,
199 0x0204012a, 0x01f00000, 0x01800180
200};
201
202static const u32 mxc_isi_rgb2yuv_coeffs[6] = {
203 /* RGB->YUV */
204 0x00810041, 0x07db0019, 0x007007b6,
205 0x07a20070, 0x001007ee, 0x00800080
206};
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, val: coeffs[0]);
245 mxc_isi_write(pipe, CHNL_CSC_COEFF1, val: coeffs[1]);
246 mxc_isi_write(pipe, CHNL_CSC_COEFF2, val: coeffs[2]);
247 mxc_isi_write(pipe, CHNL_CSC_COEFF3, val: coeffs[3]);
248 mxc_isi_write(pipe, CHNL_CSC_COEFF4, val: coeffs[4]);
249 mxc_isi_write(pipe, CHNL_CSC_COEFF5, val: 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 the
317 * 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 the
332 * crossbar switch, after all pixel link inputs. The SRC_INPUT
333 * field controls the input selection and must be set
334 * accordingly, despite being documented as ignored when using
335 * the memory input in the i.MX8MP reference manual, and
336 * 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(lock: &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, encoding: in_encoding, in_size, out_size: scale,
369 bypass: &scaler_bypass);
370 mxc_isi_channel_set_crop(pipe, src: scale, dst: crop);
371
372 /* CSC */
373 mxc_isi_channel_set_csc(pipe, in_encoding, out_encoding, bypass: &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, bypass: 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 val: format->plane_fmt[0].bytesperline);
411}
412
413/* -----------------------------------------------------------------------------
414 * IRQ
415 */
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, val: 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, val: 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, val: 0);
465}
466
467/* -----------------------------------------------------------------------------
468 * Init, deinit, enable, disable
469 */
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, val: 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, enable_clk: 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(lock: &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, enable_clk: 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(lock: &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(lock: &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(lock: &pipe->lock);
532}
533
534/* -----------------------------------------------------------------------------
535 * Resource management & chaining
536 */
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 is
552 * always needed to operate the channel, the line buffer is needed only
553 * when the channel isn't in bypass mode.
554 */
555 resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF
556 | (!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(lock: &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(lock: &pipe->lock);
582}
583
584/*
585 * We currently support line buffer chaining only, for handling images with a
586 * 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_BUF
594 | 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 the
600 * last one, otherwise there's no 'next' channel to chain with. This
601 * should be prevented by checks in the set format handlers, but let's
602 * 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(pipe: chained_pipe);
625
626unlock:
627 mutex_unlock(lock: &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(pipe: chained_pipe);
647
648 mutex_unlock(lock: &chained_pipe->lock);
649}
650

source code of linux/drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c