1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> |
4 | * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. |
5 | */ |
6 | #include <linux/export.h> |
7 | #include <linux/kernel.h> |
8 | #include <linux/types.h> |
9 | #include <linux/errno.h> |
10 | #include <linux/io.h> |
11 | #include <linux/err.h> |
12 | |
13 | #include <drm/drm_color_mgmt.h> |
14 | #include <video/imx-ipu-v3.h> |
15 | #include "ipu-prv.h" |
16 | |
17 | #define DP_SYNC 0 |
18 | #define DP_ASYNC0 0x60 |
19 | #define DP_ASYNC1 0xBC |
20 | |
21 | #define DP_COM_CONF 0x0 |
22 | #define DP_GRAPH_WIND_CTRL 0x0004 |
23 | #define DP_FG_POS 0x0008 |
24 | #define DP_CSC_A_0 0x0044 |
25 | #define DP_CSC_A_1 0x0048 |
26 | #define DP_CSC_A_2 0x004C |
27 | #define DP_CSC_A_3 0x0050 |
28 | #define DP_CSC_0 0x0054 |
29 | #define DP_CSC_1 0x0058 |
30 | |
31 | #define DP_COM_CONF_FG_EN (1 << 0) |
32 | #define DP_COM_CONF_GWSEL (1 << 1) |
33 | #define DP_COM_CONF_GWAM (1 << 2) |
34 | #define DP_COM_CONF_GWCKE (1 << 3) |
35 | #define DP_COM_CONF_CSC_DEF_MASK (3 << 8) |
36 | #define DP_COM_CONF_CSC_DEF_OFFSET 8 |
37 | #define DP_COM_CONF_CSC_DEF_FG (3 << 8) |
38 | #define DP_COM_CONF_CSC_DEF_BG (2 << 8) |
39 | #define DP_COM_CONF_CSC_DEF_BOTH (1 << 8) |
40 | |
41 | #define IPUV3_NUM_FLOWS 3 |
42 | |
43 | struct ipu_dp_priv; |
44 | |
45 | struct ipu_dp { |
46 | u32 flow; |
47 | bool in_use; |
48 | bool foreground; |
49 | enum ipu_color_space in_cs; |
50 | }; |
51 | |
52 | struct ipu_flow { |
53 | struct ipu_dp foreground; |
54 | struct ipu_dp background; |
55 | enum ipu_color_space out_cs; |
56 | void __iomem *base; |
57 | struct ipu_dp_priv *priv; |
58 | }; |
59 | |
60 | struct ipu_dp_priv { |
61 | struct ipu_soc *ipu; |
62 | struct device *dev; |
63 | void __iomem *base; |
64 | struct ipu_flow flow[IPUV3_NUM_FLOWS]; |
65 | struct mutex mutex; |
66 | int use_count; |
67 | }; |
68 | |
69 | static u32 ipu_dp_flow_base[] = {DP_SYNC, DP_ASYNC0, DP_ASYNC1}; |
70 | |
71 | static inline struct ipu_flow *to_flow(struct ipu_dp *dp) |
72 | { |
73 | if (dp->foreground) |
74 | return container_of(dp, struct ipu_flow, foreground); |
75 | else |
76 | return container_of(dp, struct ipu_flow, background); |
77 | } |
78 | |
79 | int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, |
80 | u8 alpha, bool bg_chan) |
81 | { |
82 | struct ipu_flow *flow = to_flow(dp); |
83 | struct ipu_dp_priv *priv = flow->priv; |
84 | u32 reg; |
85 | |
86 | mutex_lock(&priv->mutex); |
87 | |
88 | reg = readl(addr: flow->base + DP_COM_CONF); |
89 | if (bg_chan) |
90 | reg &= ~DP_COM_CONF_GWSEL; |
91 | else |
92 | reg |= DP_COM_CONF_GWSEL; |
93 | writel(val: reg, addr: flow->base + DP_COM_CONF); |
94 | |
95 | if (enable) { |
96 | reg = readl(addr: flow->base + DP_GRAPH_WIND_CTRL) & 0x00FFFFFFL; |
97 | writel(val: reg | ((u32) alpha << 24), |
98 | addr: flow->base + DP_GRAPH_WIND_CTRL); |
99 | |
100 | reg = readl(addr: flow->base + DP_COM_CONF); |
101 | writel(val: reg | DP_COM_CONF_GWAM, addr: flow->base + DP_COM_CONF); |
102 | } else { |
103 | reg = readl(addr: flow->base + DP_COM_CONF); |
104 | writel(val: reg & ~DP_COM_CONF_GWAM, addr: flow->base + DP_COM_CONF); |
105 | } |
106 | |
107 | ipu_srm_dp_update(ipu: priv->ipu, sync: true); |
108 | |
109 | mutex_unlock(lock: &priv->mutex); |
110 | |
111 | return 0; |
112 | } |
113 | EXPORT_SYMBOL_GPL(ipu_dp_set_global_alpha); |
114 | |
115 | int ipu_dp_set_window_pos(struct ipu_dp *dp, u16 x_pos, u16 y_pos) |
116 | { |
117 | struct ipu_flow *flow = to_flow(dp); |
118 | struct ipu_dp_priv *priv = flow->priv; |
119 | |
120 | writel(val: (x_pos << 16) | y_pos, addr: flow->base + DP_FG_POS); |
121 | |
122 | ipu_srm_dp_update(ipu: priv->ipu, sync: true); |
123 | |
124 | return 0; |
125 | } |
126 | EXPORT_SYMBOL_GPL(ipu_dp_set_window_pos); |
127 | |
128 | static void ipu_dp_csc_init(struct ipu_flow *flow, |
129 | enum drm_color_encoding ycbcr_enc, |
130 | enum drm_color_range range, |
131 | enum ipu_color_space in, |
132 | enum ipu_color_space out, |
133 | u32 place) |
134 | { |
135 | u32 reg; |
136 | |
137 | reg = readl(addr: flow->base + DP_COM_CONF); |
138 | reg &= ~DP_COM_CONF_CSC_DEF_MASK; |
139 | |
140 | if (in == out) { |
141 | writel(val: reg, addr: flow->base + DP_COM_CONF); |
142 | return; |
143 | } |
144 | |
145 | if (in == IPUV3_COLORSPACE_RGB && out == IPUV3_COLORSPACE_YUV) { |
146 | writel(val: 0x099 | (0x12d << 16), addr: flow->base + DP_CSC_A_0); |
147 | writel(val: 0x03a | (0x3a9 << 16), addr: flow->base + DP_CSC_A_1); |
148 | writel(val: 0x356 | (0x100 << 16), addr: flow->base + DP_CSC_A_2); |
149 | writel(val: 0x100 | (0x329 << 16), addr: flow->base + DP_CSC_A_3); |
150 | writel(val: 0x3d6 | (0x0000 << 16) | (2 << 30), |
151 | addr: flow->base + DP_CSC_0); |
152 | writel(val: 0x200 | (2 << 14) | (0x200 << 16) | (2 << 30), |
153 | addr: flow->base + DP_CSC_1); |
154 | } else if (ycbcr_enc == DRM_COLOR_YCBCR_BT709) { |
155 | /* Rec.709 limited range */ |
156 | writel(val: 0x095 | (0x000 << 16), addr: flow->base + DP_CSC_A_0); |
157 | writel(val: 0x0e5 | (0x095 << 16), addr: flow->base + DP_CSC_A_1); |
158 | writel(val: 0x3e5 | (0x3bc << 16), addr: flow->base + DP_CSC_A_2); |
159 | writel(val: 0x095 | (0x10e << 16), addr: flow->base + DP_CSC_A_3); |
160 | writel(val: 0x000 | (0x3e10 << 16) | (1 << 30), |
161 | addr: flow->base + DP_CSC_0); |
162 | writel(val: 0x09a | (1 << 14) | (0x3dbe << 16) | (1 << 30), |
163 | addr: flow->base + DP_CSC_1); |
164 | } else { |
165 | /* BT.601 limited range */ |
166 | writel(val: 0x095 | (0x000 << 16), addr: flow->base + DP_CSC_A_0); |
167 | writel(val: 0x0cc | (0x095 << 16), addr: flow->base + DP_CSC_A_1); |
168 | writel(val: 0x3ce | (0x398 << 16), addr: flow->base + DP_CSC_A_2); |
169 | writel(val: 0x095 | (0x0ff << 16), addr: flow->base + DP_CSC_A_3); |
170 | writel(val: 0x000 | (0x3e42 << 16) | (1 << 30), |
171 | addr: flow->base + DP_CSC_0); |
172 | writel(val: 0x10a | (1 << 14) | (0x3dd6 << 16) | (1 << 30), |
173 | addr: flow->base + DP_CSC_1); |
174 | } |
175 | |
176 | reg |= place; |
177 | |
178 | writel(val: reg, addr: flow->base + DP_COM_CONF); |
179 | } |
180 | |
181 | int ipu_dp_setup_channel(struct ipu_dp *dp, |
182 | enum drm_color_encoding ycbcr_enc, |
183 | enum drm_color_range range, |
184 | enum ipu_color_space in, |
185 | enum ipu_color_space out) |
186 | { |
187 | struct ipu_flow *flow = to_flow(dp); |
188 | struct ipu_dp_priv *priv = flow->priv; |
189 | |
190 | mutex_lock(&priv->mutex); |
191 | |
192 | dp->in_cs = in; |
193 | |
194 | if (!dp->foreground) |
195 | flow->out_cs = out; |
196 | |
197 | if (flow->foreground.in_cs == flow->background.in_cs) { |
198 | /* |
199 | * foreground and background are of same colorspace, put |
200 | * colorspace converter after combining unit. |
201 | */ |
202 | ipu_dp_csc_init(flow, ycbcr_enc, range, |
203 | in: flow->foreground.in_cs, out: flow->out_cs, |
204 | DP_COM_CONF_CSC_DEF_BOTH); |
205 | } else { |
206 | if (flow->foreground.in_cs == IPUV3_COLORSPACE_UNKNOWN || |
207 | flow->foreground.in_cs == flow->out_cs) |
208 | /* |
209 | * foreground identical to output, apply color |
210 | * conversion on background |
211 | */ |
212 | ipu_dp_csc_init(flow, ycbcr_enc, range, |
213 | in: flow->background.in_cs, |
214 | out: flow->out_cs, DP_COM_CONF_CSC_DEF_BG); |
215 | else |
216 | ipu_dp_csc_init(flow, ycbcr_enc, range, |
217 | in: flow->foreground.in_cs, |
218 | out: flow->out_cs, DP_COM_CONF_CSC_DEF_FG); |
219 | } |
220 | |
221 | ipu_srm_dp_update(ipu: priv->ipu, sync: true); |
222 | |
223 | mutex_unlock(lock: &priv->mutex); |
224 | |
225 | return 0; |
226 | } |
227 | EXPORT_SYMBOL_GPL(ipu_dp_setup_channel); |
228 | |
229 | int ipu_dp_enable(struct ipu_soc *ipu) |
230 | { |
231 | struct ipu_dp_priv *priv = ipu->dp_priv; |
232 | |
233 | mutex_lock(&priv->mutex); |
234 | |
235 | if (!priv->use_count) |
236 | ipu_module_enable(ipu: priv->ipu, mask: IPU_CONF_DP_EN); |
237 | |
238 | priv->use_count++; |
239 | |
240 | mutex_unlock(lock: &priv->mutex); |
241 | |
242 | return 0; |
243 | } |
244 | EXPORT_SYMBOL_GPL(ipu_dp_enable); |
245 | |
246 | int ipu_dp_enable_channel(struct ipu_dp *dp) |
247 | { |
248 | struct ipu_flow *flow = to_flow(dp); |
249 | struct ipu_dp_priv *priv = flow->priv; |
250 | u32 reg; |
251 | |
252 | if (!dp->foreground) |
253 | return 0; |
254 | |
255 | mutex_lock(&priv->mutex); |
256 | |
257 | reg = readl(addr: flow->base + DP_COM_CONF); |
258 | reg |= DP_COM_CONF_FG_EN; |
259 | writel(val: reg, addr: flow->base + DP_COM_CONF); |
260 | |
261 | ipu_srm_dp_update(ipu: priv->ipu, sync: true); |
262 | |
263 | mutex_unlock(lock: &priv->mutex); |
264 | |
265 | return 0; |
266 | } |
267 | EXPORT_SYMBOL_GPL(ipu_dp_enable_channel); |
268 | |
269 | void ipu_dp_disable_channel(struct ipu_dp *dp, bool sync) |
270 | { |
271 | struct ipu_flow *flow = to_flow(dp); |
272 | struct ipu_dp_priv *priv = flow->priv; |
273 | u32 reg, csc; |
274 | |
275 | dp->in_cs = IPUV3_COLORSPACE_UNKNOWN; |
276 | |
277 | if (!dp->foreground) |
278 | return; |
279 | |
280 | mutex_lock(&priv->mutex); |
281 | |
282 | reg = readl(addr: flow->base + DP_COM_CONF); |
283 | csc = reg & DP_COM_CONF_CSC_DEF_MASK; |
284 | reg &= ~DP_COM_CONF_CSC_DEF_MASK; |
285 | if (csc == DP_COM_CONF_CSC_DEF_BOTH || csc == DP_COM_CONF_CSC_DEF_BG) |
286 | reg |= DP_COM_CONF_CSC_DEF_BG; |
287 | |
288 | reg &= ~DP_COM_CONF_FG_EN; |
289 | writel(val: reg, addr: flow->base + DP_COM_CONF); |
290 | |
291 | writel(val: 0, addr: flow->base + DP_FG_POS); |
292 | ipu_srm_dp_update(ipu: priv->ipu, sync); |
293 | |
294 | mutex_unlock(lock: &priv->mutex); |
295 | } |
296 | EXPORT_SYMBOL_GPL(ipu_dp_disable_channel); |
297 | |
298 | void ipu_dp_disable(struct ipu_soc *ipu) |
299 | { |
300 | struct ipu_dp_priv *priv = ipu->dp_priv; |
301 | |
302 | mutex_lock(&priv->mutex); |
303 | |
304 | priv->use_count--; |
305 | |
306 | if (!priv->use_count) |
307 | ipu_module_disable(ipu: priv->ipu, mask: IPU_CONF_DP_EN); |
308 | |
309 | if (priv->use_count < 0) |
310 | priv->use_count = 0; |
311 | |
312 | mutex_unlock(lock: &priv->mutex); |
313 | } |
314 | EXPORT_SYMBOL_GPL(ipu_dp_disable); |
315 | |
316 | struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow) |
317 | { |
318 | struct ipu_dp_priv *priv = ipu->dp_priv; |
319 | struct ipu_dp *dp; |
320 | |
321 | if ((flow >> 1) >= IPUV3_NUM_FLOWS) |
322 | return ERR_PTR(error: -EINVAL); |
323 | |
324 | if (flow & 1) |
325 | dp = &priv->flow[flow >> 1].foreground; |
326 | else |
327 | dp = &priv->flow[flow >> 1].background; |
328 | |
329 | if (dp->in_use) |
330 | return ERR_PTR(error: -EBUSY); |
331 | |
332 | dp->in_use = true; |
333 | |
334 | return dp; |
335 | } |
336 | EXPORT_SYMBOL_GPL(ipu_dp_get); |
337 | |
338 | void ipu_dp_put(struct ipu_dp *dp) |
339 | { |
340 | dp->in_use = false; |
341 | } |
342 | EXPORT_SYMBOL_GPL(ipu_dp_put); |
343 | |
344 | int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base) |
345 | { |
346 | struct ipu_dp_priv *priv; |
347 | int i; |
348 | |
349 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
350 | if (!priv) |
351 | return -ENOMEM; |
352 | priv->dev = dev; |
353 | priv->ipu = ipu; |
354 | |
355 | ipu->dp_priv = priv; |
356 | |
357 | priv->base = devm_ioremap(dev, offset: base, PAGE_SIZE); |
358 | if (!priv->base) |
359 | return -ENOMEM; |
360 | |
361 | mutex_init(&priv->mutex); |
362 | |
363 | for (i = 0; i < IPUV3_NUM_FLOWS; i++) { |
364 | priv->flow[i].background.in_cs = IPUV3_COLORSPACE_UNKNOWN; |
365 | priv->flow[i].foreground.in_cs = IPUV3_COLORSPACE_UNKNOWN; |
366 | priv->flow[i].foreground.foreground = true; |
367 | priv->flow[i].base = priv->base + ipu_dp_flow_base[i]; |
368 | priv->flow[i].priv = priv; |
369 | } |
370 | |
371 | return 0; |
372 | } |
373 | |
374 | void ipu_dp_exit(struct ipu_soc *ipu) |
375 | { |
376 | } |
377 | |