1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright 2019 NXP.
4 */
5
6#include <linux/clk.h>
7#include <linux/delay.h>
8#include <linux/interrupt.h>
9#include <linux/of.h>
10#include <linux/platform_device.h>
11#include <linux/slab.h>
12
13#include "dcss-dev.h"
14
15#define DCSS_DTG_TC_CONTROL_STATUS 0x00
16#define CH3_EN BIT(0)
17#define CH2_EN BIT(1)
18#define CH1_EN BIT(2)
19#define OVL_DATA_MODE BIT(3)
20#define BLENDER_VIDEO_ALPHA_SEL BIT(7)
21#define DTG_START BIT(8)
22#define DBY_MODE_EN BIT(9)
23#define CH1_ALPHA_SEL BIT(10)
24#define CSS_PIX_COMP_SWAP_POS 12
25#define CSS_PIX_COMP_SWAP_MASK GENMASK(14, 12)
26#define DEFAULT_FG_ALPHA_POS 24
27#define DEFAULT_FG_ALPHA_MASK GENMASK(31, 24)
28#define DCSS_DTG_TC_DTG 0x04
29#define DCSS_DTG_TC_DISP_TOP 0x08
30#define DCSS_DTG_TC_DISP_BOT 0x0C
31#define DCSS_DTG_TC_CH1_TOP 0x10
32#define DCSS_DTG_TC_CH1_BOT 0x14
33#define DCSS_DTG_TC_CH2_TOP 0x18
34#define DCSS_DTG_TC_CH2_BOT 0x1C
35#define DCSS_DTG_TC_CH3_TOP 0x20
36#define DCSS_DTG_TC_CH3_BOT 0x24
37#define TC_X_POS 0
38#define TC_X_MASK GENMASK(12, 0)
39#define TC_Y_POS 16
40#define TC_Y_MASK GENMASK(28, 16)
41#define DCSS_DTG_TC_CTXLD 0x28
42#define TC_CTXLD_DB_Y_POS 0
43#define TC_CTXLD_DB_Y_MASK GENMASK(12, 0)
44#define TC_CTXLD_SB_Y_POS 16
45#define TC_CTXLD_SB_Y_MASK GENMASK(28, 16)
46#define DCSS_DTG_TC_CH1_BKRND 0x2C
47#define DCSS_DTG_TC_CH2_BKRND 0x30
48#define BKRND_R_Y_COMP_POS 20
49#define BKRND_R_Y_COMP_MASK GENMASK(29, 20)
50#define BKRND_G_U_COMP_POS 10
51#define BKRND_G_U_COMP_MASK GENMASK(19, 10)
52#define BKRND_B_V_COMP_POS 0
53#define BKRND_B_V_COMP_MASK GENMASK(9, 0)
54#define DCSS_DTG_BLENDER_DBY_RANGEINV 0x38
55#define DCSS_DTG_BLENDER_DBY_RANGEMIN 0x3C
56#define DCSS_DTG_BLENDER_DBY_BDP 0x40
57#define DCSS_DTG_BLENDER_BKRND_I 0x44
58#define DCSS_DTG_BLENDER_BKRND_P 0x48
59#define DCSS_DTG_BLENDER_BKRND_T 0x4C
60#define DCSS_DTG_LINE0_INT 0x50
61#define DCSS_DTG_LINE1_INT 0x54
62#define DCSS_DTG_BG_ALPHA_DEFAULT 0x58
63#define DCSS_DTG_INT_STATUS 0x5C
64#define DCSS_DTG_INT_CONTROL 0x60
65#define DCSS_DTG_TC_CH3_BKRND 0x64
66#define DCSS_DTG_INT_MASK 0x68
67#define LINE0_IRQ BIT(0)
68#define LINE1_IRQ BIT(1)
69#define LINE2_IRQ BIT(2)
70#define LINE3_IRQ BIT(3)
71#define DCSS_DTG_LINE2_INT 0x6C
72#define DCSS_DTG_LINE3_INT 0x70
73#define DCSS_DTG_DBY_OL 0x74
74#define DCSS_DTG_DBY_BL 0x78
75#define DCSS_DTG_DBY_EL 0x7C
76
77struct dcss_dtg {
78 struct device *dev;
79 struct dcss_ctxld *ctxld;
80 void __iomem *base_reg;
81 u32 base_ofs;
82
83 u32 ctx_id;
84
85 bool in_use;
86
87 u32 dis_ulc_x;
88 u32 dis_ulc_y;
89
90 u32 control_status;
91 u32 alpha;
92 u32 alpha_cfg;
93
94 int ctxld_kick_irq;
95 bool ctxld_kick_irq_en;
96};
97
98static void dcss_dtg_write(struct dcss_dtg *dtg, u32 val, u32 ofs)
99{
100 if (!dtg->in_use)
101 dcss_writel(val, dtg->base_reg + ofs);
102
103 dcss_ctxld_write(ctxld: dtg->ctxld, ctx_id: dtg->ctx_id,
104 val, reg_idx: dtg->base_ofs + ofs);
105}
106
107static irqreturn_t dcss_dtg_irq_handler(int irq, void *data)
108{
109 struct dcss_dtg *dtg = data;
110 u32 status;
111
112 status = dcss_readl(dtg->base_reg + DCSS_DTG_INT_STATUS);
113
114 if (!(status & LINE0_IRQ))
115 return IRQ_NONE;
116
117 dcss_ctxld_kick(ctxld: dtg->ctxld);
118
119 dcss_writel(status & LINE0_IRQ, dtg->base_reg + DCSS_DTG_INT_CONTROL);
120
121 return IRQ_HANDLED;
122}
123
124static int dcss_dtg_irq_config(struct dcss_dtg *dtg,
125 struct platform_device *pdev)
126{
127 int ret;
128
129 dtg->ctxld_kick_irq = platform_get_irq_byname(pdev, "ctxld_kick");
130 if (dtg->ctxld_kick_irq < 0)
131 return dtg->ctxld_kick_irq;
132
133 dcss_update(v: 0, LINE0_IRQ | LINE1_IRQ,
134 c: dtg->base_reg + DCSS_DTG_INT_MASK);
135
136 ret = request_irq(irq: dtg->ctxld_kick_irq, handler: dcss_dtg_irq_handler,
137 flags: 0, name: "dcss_ctxld_kick", dev: dtg);
138 if (ret) {
139 dev_err(dtg->dev, "dtg: irq request failed.\n");
140 return ret;
141 }
142
143 disable_irq(irq: dtg->ctxld_kick_irq);
144
145 dtg->ctxld_kick_irq_en = false;
146
147 return 0;
148}
149
150int dcss_dtg_init(struct dcss_dev *dcss, unsigned long dtg_base)
151{
152 int ret = 0;
153 struct dcss_dtg *dtg;
154
155 dtg = devm_kzalloc(dev: dcss->dev, size: sizeof(*dtg), GFP_KERNEL);
156 if (!dtg)
157 return -ENOMEM;
158
159 dcss->dtg = dtg;
160 dtg->dev = dcss->dev;
161 dtg->ctxld = dcss->ctxld;
162
163 dtg->base_reg = devm_ioremap(dev: dtg->dev, offset: dtg_base, SZ_4K);
164 if (!dtg->base_reg) {
165 dev_err(dtg->dev, "dtg: unable to remap dtg base\n");
166 return -ENOMEM;
167 }
168
169 dtg->base_ofs = dtg_base;
170 dtg->ctx_id = CTX_DB;
171
172 dtg->alpha = 255;
173
174 dtg->control_status |= OVL_DATA_MODE | BLENDER_VIDEO_ALPHA_SEL |
175 ((dtg->alpha << DEFAULT_FG_ALPHA_POS) & DEFAULT_FG_ALPHA_MASK);
176
177 ret = dcss_dtg_irq_config(dtg, to_platform_device(dtg->dev));
178
179 return ret;
180}
181
182void dcss_dtg_exit(struct dcss_dtg *dtg)
183{
184 free_irq(dtg->ctxld_kick_irq, dtg);
185}
186
187void dcss_dtg_sync_set(struct dcss_dtg *dtg, struct videomode *vm)
188{
189 struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev: dtg->dev);
190 u16 dtg_lrc_x, dtg_lrc_y;
191 u16 dis_ulc_x, dis_ulc_y;
192 u16 dis_lrc_x, dis_lrc_y;
193 u32 sb_ctxld_trig, db_ctxld_trig;
194 u32 pixclock = vm->pixelclock;
195 u32 actual_clk;
196
197 dtg_lrc_x = vm->hfront_porch + vm->hback_porch + vm->hsync_len +
198 vm->hactive - 1;
199 dtg_lrc_y = vm->vfront_porch + vm->vback_porch + vm->vsync_len +
200 vm->vactive - 1;
201 dis_ulc_x = vm->hsync_len + vm->hback_porch - 1;
202 dis_ulc_y = vm->vsync_len + vm->vfront_porch + vm->vback_porch - 1;
203 dis_lrc_x = vm->hsync_len + vm->hback_porch + vm->hactive - 1;
204 dis_lrc_y = vm->vsync_len + vm->vfront_porch + vm->vback_porch +
205 vm->vactive - 1;
206
207 clk_disable_unprepare(clk: dcss->pix_clk);
208 clk_set_rate(clk: dcss->pix_clk, rate: vm->pixelclock);
209 clk_prepare_enable(clk: dcss->pix_clk);
210
211 actual_clk = clk_get_rate(clk: dcss->pix_clk);
212 if (pixclock != actual_clk) {
213 dev_info(dtg->dev,
214 "Pixel clock set to %u kHz instead of %u kHz.\n",
215 (actual_clk / 1000), (pixclock / 1000));
216 }
217
218 dcss_dtg_write(dtg, val: ((dtg_lrc_y << TC_Y_POS) | dtg_lrc_x),
219 DCSS_DTG_TC_DTG);
220 dcss_dtg_write(dtg, val: ((dis_ulc_y << TC_Y_POS) | dis_ulc_x),
221 DCSS_DTG_TC_DISP_TOP);
222 dcss_dtg_write(dtg, val: ((dis_lrc_y << TC_Y_POS) | dis_lrc_x),
223 DCSS_DTG_TC_DISP_BOT);
224
225 dtg->dis_ulc_x = dis_ulc_x;
226 dtg->dis_ulc_y = dis_ulc_y;
227
228 sb_ctxld_trig = ((0 * dis_lrc_y / 100) << TC_CTXLD_SB_Y_POS) &
229 TC_CTXLD_SB_Y_MASK;
230 db_ctxld_trig = ((99 * dis_lrc_y / 100) << TC_CTXLD_DB_Y_POS) &
231 TC_CTXLD_DB_Y_MASK;
232
233 dcss_dtg_write(dtg, val: sb_ctxld_trig | db_ctxld_trig, DCSS_DTG_TC_CTXLD);
234
235 /* vblank trigger */
236 dcss_dtg_write(dtg, val: 0, DCSS_DTG_LINE1_INT);
237
238 /* CTXLD trigger */
239 dcss_dtg_write(dtg, val: ((90 * dis_lrc_y) / 100) << 16, DCSS_DTG_LINE0_INT);
240}
241
242void dcss_dtg_plane_pos_set(struct dcss_dtg *dtg, int ch_num,
243 int px, int py, int pw, int ph)
244{
245 u16 p_ulc_x, p_ulc_y;
246 u16 p_lrc_x, p_lrc_y;
247
248 p_ulc_x = dtg->dis_ulc_x + px;
249 p_ulc_y = dtg->dis_ulc_y + py;
250 p_lrc_x = p_ulc_x + pw;
251 p_lrc_y = p_ulc_y + ph;
252
253 if (!px && !py && !pw && !ph) {
254 dcss_dtg_write(dtg, val: 0, DCSS_DTG_TC_CH1_TOP + 0x8 * ch_num);
255 dcss_dtg_write(dtg, val: 0, DCSS_DTG_TC_CH1_BOT + 0x8 * ch_num);
256 } else {
257 dcss_dtg_write(dtg, val: ((p_ulc_y << TC_Y_POS) | p_ulc_x),
258 DCSS_DTG_TC_CH1_TOP + 0x8 * ch_num);
259 dcss_dtg_write(dtg, val: ((p_lrc_y << TC_Y_POS) | p_lrc_x),
260 DCSS_DTG_TC_CH1_BOT + 0x8 * ch_num);
261 }
262}
263
264bool dcss_dtg_global_alpha_changed(struct dcss_dtg *dtg, int ch_num, int alpha)
265{
266 if (ch_num)
267 return false;
268
269 return alpha != dtg->alpha;
270}
271
272void dcss_dtg_plane_alpha_set(struct dcss_dtg *dtg, int ch_num,
273 const struct drm_format_info *format, int alpha)
274{
275 /* we care about alpha only when channel 0 is concerned */
276 if (ch_num)
277 return;
278
279 /*
280 * Use global alpha if pixel format does not have alpha channel or the
281 * user explicitly chose to use global alpha (i.e. alpha is not OPAQUE).
282 */
283 if (!format->has_alpha || alpha != 255)
284 dtg->alpha_cfg = (alpha << DEFAULT_FG_ALPHA_POS) & DEFAULT_FG_ALPHA_MASK;
285 else /* use per-pixel alpha otherwise */
286 dtg->alpha_cfg = CH1_ALPHA_SEL;
287
288 dtg->alpha = alpha;
289}
290
291void dcss_dtg_css_set(struct dcss_dtg *dtg)
292{
293 dtg->control_status |=
294 (0x5 << CSS_PIX_COMP_SWAP_POS) & CSS_PIX_COMP_SWAP_MASK;
295}
296
297void dcss_dtg_enable(struct dcss_dtg *dtg)
298{
299 dtg->control_status |= DTG_START;
300
301 dtg->control_status &= ~(CH1_ALPHA_SEL | DEFAULT_FG_ALPHA_MASK);
302 dtg->control_status |= dtg->alpha_cfg;
303
304 dcss_dtg_write(dtg, val: dtg->control_status, DCSS_DTG_TC_CONTROL_STATUS);
305
306 dtg->in_use = true;
307}
308
309void dcss_dtg_shutoff(struct dcss_dtg *dtg)
310{
311 dtg->control_status &= ~DTG_START;
312
313 dcss_writel(dtg->control_status,
314 dtg->base_reg + DCSS_DTG_TC_CONTROL_STATUS);
315
316 dtg->in_use = false;
317}
318
319bool dcss_dtg_is_enabled(struct dcss_dtg *dtg)
320{
321 return dtg->in_use;
322}
323
324void dcss_dtg_ch_enable(struct dcss_dtg *dtg, int ch_num, bool en)
325{
326 u32 ch_en_map[] = {CH1_EN, CH2_EN, CH3_EN};
327 u32 control_status;
328
329 control_status = dtg->control_status & ~ch_en_map[ch_num];
330 control_status |= en ? ch_en_map[ch_num] : 0;
331
332 control_status &= ~(CH1_ALPHA_SEL | DEFAULT_FG_ALPHA_MASK);
333 control_status |= dtg->alpha_cfg;
334
335 if (dtg->control_status != control_status)
336 dcss_dtg_write(dtg, val: control_status, DCSS_DTG_TC_CONTROL_STATUS);
337
338 dtg->control_status = control_status;
339}
340
341void dcss_dtg_vblank_irq_enable(struct dcss_dtg *dtg, bool en)
342{
343 u32 status;
344 u32 mask = en ? LINE1_IRQ : 0;
345
346 if (en) {
347 status = dcss_readl(dtg->base_reg + DCSS_DTG_INT_STATUS);
348 dcss_writel(status & LINE1_IRQ,
349 dtg->base_reg + DCSS_DTG_INT_CONTROL);
350 }
351
352 dcss_update(v: mask, LINE1_IRQ, c: dtg->base_reg + DCSS_DTG_INT_MASK);
353}
354
355void dcss_dtg_ctxld_kick_irq_enable(struct dcss_dtg *dtg, bool en)
356{
357 u32 status;
358 u32 mask = en ? LINE0_IRQ : 0;
359
360 if (en) {
361 status = dcss_readl(dtg->base_reg + DCSS_DTG_INT_STATUS);
362
363 if (!dtg->ctxld_kick_irq_en) {
364 dcss_writel(status & LINE0_IRQ,
365 dtg->base_reg + DCSS_DTG_INT_CONTROL);
366 enable_irq(irq: dtg->ctxld_kick_irq);
367 dtg->ctxld_kick_irq_en = true;
368 dcss_update(v: mask, LINE0_IRQ,
369 c: dtg->base_reg + DCSS_DTG_INT_MASK);
370 }
371
372 return;
373 }
374
375 if (!dtg->ctxld_kick_irq_en)
376 return;
377
378 disable_irq_nosync(irq: dtg->ctxld_kick_irq);
379 dtg->ctxld_kick_irq_en = false;
380
381 dcss_update(v: mask, LINE0_IRQ, c: dtg->base_reg + DCSS_DTG_INT_MASK);
382}
383
384void dcss_dtg_vblank_irq_clear(struct dcss_dtg *dtg)
385{
386 dcss_update(LINE1_IRQ, LINE1_IRQ, c: dtg->base_reg + DCSS_DTG_INT_CONTROL);
387}
388
389bool dcss_dtg_vblank_irq_valid(struct dcss_dtg *dtg)
390{
391 return !!(dcss_readl(dtg->base_reg + DCSS_DTG_INT_STATUS) & LINE1_IRQ);
392}
393
394

source code of linux/drivers/gpu/drm/imx/dcss/dcss-dtg.c