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 | |
16 | static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg) |
17 | { |
18 | return readl(addr: pipe->regs + reg); |
19 | } |
20 | |
21 | static 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 | |
30 | void 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 | |
38 | void 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 | |
83 | void 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 | |
101 | static 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 | |
118 | static 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 | |
169 | static 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 | */ |
196 | static const u32 mxc_isi_yuv2rgb_coeffs[6] = { |
197 | /* YUV -> RGB */ |
198 | 0x0000012a, 0x012a0198, 0x0730079c, |
199 | 0x0204012a, 0x01f00000, 0x01800180 |
200 | }; |
201 | |
202 | static const u32 mxc_isi_rgb2yuv_coeffs[6] = { |
203 | /* RGB->YUV */ |
204 | 0x00810041, 0x07db0019, 0x007007b6, |
205 | 0x07a20070, 0x001007ee, 0x00800080 |
206 | }; |
207 | |
208 | static 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 | |
257 | void 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 | |
268 | void 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 | |
283 | static 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 | |
302 | static 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 | |
351 | void 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 | |
382 | void 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 | |
394 | void 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 | |
417 | u32 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 | |
428 | void mxc_isi_channel_irq_clear(struct mxc_isi_pipe *pipe) |
429 | { |
430 | mxc_isi_write(pipe, CHNL_STS, val: 0xffffffff); |
431 | } |
432 | |
433 | static 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 | |
462 | static 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 | |
471 | static 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 | |
478 | static 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 | |
484 | void 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 | |
491 | static 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 | |
497 | void 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 | |
504 | void 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 | |
519 | void 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 | */ |
537 | int 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 | |
567 | unlock: |
568 | mutex_unlock(lock: &pipe->lock); |
569 | |
570 | return ret; |
571 | } |
572 | |
573 | void 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 | */ |
590 | int 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 | |
626 | unlock: |
627 | mutex_unlock(lock: &chained_pipe->lock); |
628 | |
629 | return ret; |
630 | } |
631 | |
632 | void 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 | |