.. | .. |
---|
7 | 7 | * Boris Brezillon <boris.brezillon@bootlin.com> |
---|
8 | 8 | */ |
---|
9 | 9 | |
---|
10 | | -#include <drm/drm_atomic_helper.h> |
---|
11 | | -#include <drm/drm_fb_cma_helper.h> |
---|
12 | | -#include <drm/drm_crtc_helper.h> |
---|
13 | | -#include <drm/drm_edid.h> |
---|
14 | | -#include <drm/drm_panel.h> |
---|
15 | | -#include <drm/drm_writeback.h> |
---|
16 | 10 | #include <linux/clk.h> |
---|
17 | 11 | #include <linux/component.h> |
---|
18 | 12 | #include <linux/of_graph.h> |
---|
19 | 13 | #include <linux/of_platform.h> |
---|
20 | 14 | #include <linux/pm_runtime.h> |
---|
| 15 | + |
---|
| 16 | +#include <drm/drm_atomic_helper.h> |
---|
| 17 | +#include <drm/drm_edid.h> |
---|
| 18 | +#include <drm/drm_fb_cma_helper.h> |
---|
| 19 | +#include <drm/drm_fourcc.h> |
---|
| 20 | +#include <drm/drm_panel.h> |
---|
| 21 | +#include <drm/drm_probe_helper.h> |
---|
| 22 | +#include <drm/drm_vblank.h> |
---|
| 23 | +#include <drm/drm_writeback.h> |
---|
21 | 24 | |
---|
22 | 25 | #include "vc4_drv.h" |
---|
23 | 26 | #include "vc4_regs.h" |
---|
.. | .. |
---|
143 | 146 | #define TXP_WRITE(offset, val) writel(val, txp->regs + (offset)) |
---|
144 | 147 | |
---|
145 | 148 | struct vc4_txp { |
---|
| 149 | + struct vc4_crtc base; |
---|
| 150 | + |
---|
146 | 151 | struct platform_device *pdev; |
---|
147 | 152 | |
---|
148 | 153 | struct drm_writeback_connector connector; |
---|
149 | 154 | |
---|
150 | 155 | void __iomem *regs; |
---|
| 156 | + struct debugfs_regset32 regset; |
---|
151 | 157 | }; |
---|
152 | 158 | |
---|
153 | 159 | static inline struct vc4_txp *encoder_to_vc4_txp(struct drm_encoder *encoder) |
---|
.. | .. |
---|
160 | 166 | return container_of(conn, struct vc4_txp, connector.base); |
---|
161 | 167 | } |
---|
162 | 168 | |
---|
163 | | -#define TXP_REG(reg) { reg, #reg } |
---|
164 | | -static const struct { |
---|
165 | | - u32 reg; |
---|
166 | | - const char *name; |
---|
167 | | -} txp_regs[] = { |
---|
168 | | - TXP_REG(TXP_DST_PTR), |
---|
169 | | - TXP_REG(TXP_DST_PITCH), |
---|
170 | | - TXP_REG(TXP_DIM), |
---|
171 | | - TXP_REG(TXP_DST_CTRL), |
---|
172 | | - TXP_REG(TXP_PROGRESS), |
---|
| 169 | +static const struct debugfs_reg32 txp_regs[] = { |
---|
| 170 | + VC4_REG32(TXP_DST_PTR), |
---|
| 171 | + VC4_REG32(TXP_DST_PITCH), |
---|
| 172 | + VC4_REG32(TXP_DIM), |
---|
| 173 | + VC4_REG32(TXP_DST_CTRL), |
---|
| 174 | + VC4_REG32(TXP_PROGRESS), |
---|
173 | 175 | }; |
---|
174 | | - |
---|
175 | | -#ifdef CONFIG_DEBUG_FS |
---|
176 | | -int vc4_txp_debugfs_regs(struct seq_file *m, void *unused) |
---|
177 | | -{ |
---|
178 | | - struct drm_info_node *node = (struct drm_info_node *)m->private; |
---|
179 | | - struct drm_device *dev = node->minor->dev; |
---|
180 | | - struct vc4_dev *vc4 = to_vc4_dev(dev); |
---|
181 | | - struct vc4_txp *txp = vc4->txp; |
---|
182 | | - int i; |
---|
183 | | - |
---|
184 | | - if (!txp) |
---|
185 | | - return 0; |
---|
186 | | - |
---|
187 | | - for (i = 0; i < ARRAY_SIZE(txp_regs); i++) { |
---|
188 | | - seq_printf(m, "%s (0x%04x): 0x%08x\n", |
---|
189 | | - txp_regs[i].name, txp_regs[i].reg, |
---|
190 | | - TXP_READ(txp_regs[i].reg)); |
---|
191 | | - } |
---|
192 | | - |
---|
193 | | - return 0; |
---|
194 | | -} |
---|
195 | | -#endif |
---|
196 | 176 | |
---|
197 | 177 | static int vc4_txp_connector_get_modes(struct drm_connector *connector) |
---|
198 | 178 | { |
---|
.. | .. |
---|
245 | 225 | TXP_FORMAT_BGRA8888, |
---|
246 | 226 | }; |
---|
247 | 227 | |
---|
| 228 | +static void vc4_txp_armed(struct drm_crtc_state *state) |
---|
| 229 | +{ |
---|
| 230 | + struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state); |
---|
| 231 | + |
---|
| 232 | + vc4_state->txp_armed = true; |
---|
| 233 | +} |
---|
| 234 | + |
---|
248 | 235 | static int vc4_txp_connector_atomic_check(struct drm_connector *conn, |
---|
249 | 236 | struct drm_atomic_state *state) |
---|
250 | 237 | { |
---|
251 | 238 | struct drm_connector_state *conn_state; |
---|
252 | 239 | struct drm_crtc_state *crtc_state; |
---|
253 | | - struct drm_gem_cma_object *gem; |
---|
254 | 240 | struct drm_framebuffer *fb; |
---|
255 | 241 | int i; |
---|
256 | 242 | |
---|
257 | 243 | conn_state = drm_atomic_get_new_connector_state(state, conn); |
---|
258 | | - if (!conn_state->writeback_job || !conn_state->writeback_job->fb) |
---|
| 244 | + if (!conn_state->writeback_job) |
---|
259 | 245 | return 0; |
---|
260 | 246 | |
---|
261 | 247 | crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); |
---|
.. | .. |
---|
276 | 262 | if (i == ARRAY_SIZE(drm_fmts)) |
---|
277 | 263 | return -EINVAL; |
---|
278 | 264 | |
---|
279 | | - gem = drm_fb_cma_get_gem_obj(fb, 0); |
---|
280 | | - |
---|
281 | 265 | /* Pitch must be aligned on 16 bytes. */ |
---|
282 | 266 | if (fb->pitches[0] & GENMASK(3, 0)) |
---|
283 | 267 | return -EINVAL; |
---|
284 | 268 | |
---|
285 | | - vc4_crtc_txp_armed(crtc_state); |
---|
| 269 | + vc4_txp_armed(crtc_state); |
---|
286 | 270 | |
---|
287 | 271 | return 0; |
---|
288 | 272 | } |
---|
.. | .. |
---|
297 | 281 | u32 ctrl; |
---|
298 | 282 | int i; |
---|
299 | 283 | |
---|
300 | | - if (WARN_ON(!conn_state->writeback_job || |
---|
301 | | - !conn_state->writeback_job->fb)) |
---|
| 284 | + if (WARN_ON(!conn_state->writeback_job)) |
---|
302 | 285 | return; |
---|
303 | 286 | |
---|
304 | 287 | mode = &conn_state->crtc->state->adjusted_mode; |
---|
.. | .. |
---|
312 | 295 | if (WARN_ON(i == ARRAY_SIZE(drm_fmts))) |
---|
313 | 296 | return; |
---|
314 | 297 | |
---|
315 | | - ctrl = TXP_GO | TXP_VSTART_AT_EOF | TXP_EI | |
---|
| 298 | + ctrl = TXP_GO | TXP_EI | |
---|
316 | 299 | VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE) | |
---|
317 | 300 | VC4_SET_FIELD(txp_fmts[i], TXP_FORMAT); |
---|
318 | 301 | |
---|
319 | 302 | if (fb->format->has_alpha) |
---|
320 | 303 | ctrl |= TXP_ALPHA_ENABLE; |
---|
| 304 | + else |
---|
| 305 | + /* |
---|
| 306 | + * If TXP_ALPHA_ENABLE isn't set and TXP_ALPHA_INVERT is, the |
---|
| 307 | + * hardware will force the output padding to be 0xff. |
---|
| 308 | + */ |
---|
| 309 | + ctrl |= TXP_ALPHA_INVERT; |
---|
321 | 310 | |
---|
322 | 311 | gem = drm_fb_cma_get_gem_obj(fb, 0); |
---|
323 | 312 | TXP_WRITE(TXP_DST_PTR, gem->paddr + fb->offsets[0]); |
---|
.. | .. |
---|
328 | 317 | |
---|
329 | 318 | TXP_WRITE(TXP_DST_CTRL, ctrl); |
---|
330 | 319 | |
---|
331 | | - drm_writeback_queue_job(&txp->connector, conn_state->writeback_job); |
---|
| 320 | + drm_writeback_queue_job(&txp->connector, conn_state); |
---|
332 | 321 | } |
---|
333 | 322 | |
---|
334 | 323 | static const struct drm_connector_helper_funcs vc4_txp_connector_helper_funcs = { |
---|
.. | .. |
---|
382 | 371 | .disable = vc4_txp_encoder_disable, |
---|
383 | 372 | }; |
---|
384 | 373 | |
---|
| 374 | +static int vc4_txp_enable_vblank(struct drm_crtc *crtc) |
---|
| 375 | +{ |
---|
| 376 | + return 0; |
---|
| 377 | +} |
---|
| 378 | + |
---|
| 379 | +static void vc4_txp_disable_vblank(struct drm_crtc *crtc) {} |
---|
| 380 | + |
---|
| 381 | +static const struct drm_crtc_funcs vc4_txp_crtc_funcs = { |
---|
| 382 | + .set_config = drm_atomic_helper_set_config, |
---|
| 383 | + .destroy = vc4_crtc_destroy, |
---|
| 384 | + .page_flip = vc4_page_flip, |
---|
| 385 | + .reset = vc4_crtc_reset, |
---|
| 386 | + .atomic_duplicate_state = vc4_crtc_duplicate_state, |
---|
| 387 | + .atomic_destroy_state = vc4_crtc_destroy_state, |
---|
| 388 | + .gamma_set = drm_atomic_helper_legacy_gamma_set, |
---|
| 389 | + .enable_vblank = vc4_txp_enable_vblank, |
---|
| 390 | + .disable_vblank = vc4_txp_disable_vblank, |
---|
| 391 | +}; |
---|
| 392 | + |
---|
| 393 | +static int vc4_txp_atomic_check(struct drm_crtc *crtc, |
---|
| 394 | + struct drm_crtc_state *state) |
---|
| 395 | +{ |
---|
| 396 | + struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state); |
---|
| 397 | + int ret; |
---|
| 398 | + |
---|
| 399 | + ret = vc4_hvs_atomic_check(crtc, state); |
---|
| 400 | + if (ret) |
---|
| 401 | + return ret; |
---|
| 402 | + |
---|
| 403 | + state->no_vblank = true; |
---|
| 404 | + vc4_state->feed_txp = true; |
---|
| 405 | + |
---|
| 406 | + return 0; |
---|
| 407 | +} |
---|
| 408 | + |
---|
| 409 | +static void vc4_txp_atomic_enable(struct drm_crtc *crtc, |
---|
| 410 | + struct drm_crtc_state *old_state) |
---|
| 411 | +{ |
---|
| 412 | + drm_crtc_vblank_on(crtc); |
---|
| 413 | + vc4_hvs_atomic_enable(crtc, old_state); |
---|
| 414 | +} |
---|
| 415 | + |
---|
| 416 | +static void vc4_txp_atomic_disable(struct drm_crtc *crtc, |
---|
| 417 | + struct drm_crtc_state *old_state) |
---|
| 418 | +{ |
---|
| 419 | + struct drm_device *dev = crtc->dev; |
---|
| 420 | + |
---|
| 421 | + /* Disable vblank irq handling before crtc is disabled. */ |
---|
| 422 | + drm_crtc_vblank_off(crtc); |
---|
| 423 | + |
---|
| 424 | + vc4_hvs_atomic_disable(crtc, old_state); |
---|
| 425 | + |
---|
| 426 | + /* |
---|
| 427 | + * Make sure we issue a vblank event after disabling the CRTC if |
---|
| 428 | + * someone was waiting it. |
---|
| 429 | + */ |
---|
| 430 | + if (crtc->state->event) { |
---|
| 431 | + unsigned long flags; |
---|
| 432 | + |
---|
| 433 | + spin_lock_irqsave(&dev->event_lock, flags); |
---|
| 434 | + drm_crtc_send_vblank_event(crtc, crtc->state->event); |
---|
| 435 | + crtc->state->event = NULL; |
---|
| 436 | + spin_unlock_irqrestore(&dev->event_lock, flags); |
---|
| 437 | + } |
---|
| 438 | +} |
---|
| 439 | + |
---|
| 440 | +static const struct drm_crtc_helper_funcs vc4_txp_crtc_helper_funcs = { |
---|
| 441 | + .atomic_check = vc4_txp_atomic_check, |
---|
| 442 | + .atomic_flush = vc4_hvs_atomic_flush, |
---|
| 443 | + .atomic_enable = vc4_txp_atomic_enable, |
---|
| 444 | + .atomic_disable = vc4_txp_atomic_disable, |
---|
| 445 | +}; |
---|
| 446 | + |
---|
385 | 447 | static irqreturn_t vc4_txp_interrupt(int irq, void *data) |
---|
386 | 448 | { |
---|
387 | 449 | struct vc4_txp *txp = data; |
---|
| 450 | + struct vc4_crtc *vc4_crtc = &txp->base; |
---|
388 | 451 | |
---|
389 | 452 | TXP_WRITE(TXP_DST_CTRL, TXP_READ(TXP_DST_CTRL) & ~TXP_EI); |
---|
390 | | - vc4_crtc_handle_vblank(to_vc4_crtc(txp->connector.base.state->crtc)); |
---|
| 453 | + vc4_crtc_handle_vblank(vc4_crtc); |
---|
391 | 454 | drm_writeback_signal_completion(&txp->connector, 0); |
---|
392 | 455 | |
---|
393 | 456 | return IRQ_HANDLED; |
---|
394 | 457 | } |
---|
| 458 | + |
---|
| 459 | +static const struct vc4_crtc_data vc4_txp_crtc_data = { |
---|
| 460 | + .hvs_available_channels = BIT(2), |
---|
| 461 | + .hvs_output = 2, |
---|
| 462 | +}; |
---|
395 | 463 | |
---|
396 | 464 | static int vc4_txp_bind(struct device *dev, struct device *master, void *data) |
---|
397 | 465 | { |
---|
398 | 466 | struct platform_device *pdev = to_platform_device(dev); |
---|
399 | 467 | struct drm_device *drm = dev_get_drvdata(master); |
---|
400 | 468 | struct vc4_dev *vc4 = to_vc4_dev(drm); |
---|
| 469 | + struct vc4_crtc *vc4_crtc; |
---|
401 | 470 | struct vc4_txp *txp; |
---|
| 471 | + struct drm_crtc *crtc; |
---|
| 472 | + struct drm_encoder *encoder; |
---|
402 | 473 | int ret, irq; |
---|
403 | 474 | |
---|
404 | 475 | irq = platform_get_irq(pdev, 0); |
---|
.. | .. |
---|
408 | 479 | txp = devm_kzalloc(dev, sizeof(*txp), GFP_KERNEL); |
---|
409 | 480 | if (!txp) |
---|
410 | 481 | return -ENOMEM; |
---|
| 482 | + vc4_crtc = &txp->base; |
---|
| 483 | + crtc = &vc4_crtc->base; |
---|
| 484 | + |
---|
| 485 | + vc4_crtc->pdev = pdev; |
---|
| 486 | + vc4_crtc->data = &vc4_txp_crtc_data; |
---|
411 | 487 | |
---|
412 | 488 | txp->pdev = pdev; |
---|
413 | 489 | |
---|
414 | 490 | txp->regs = vc4_ioremap_regs(pdev, 0); |
---|
415 | 491 | if (IS_ERR(txp->regs)) |
---|
416 | 492 | return PTR_ERR(txp->regs); |
---|
| 493 | + txp->regset.base = txp->regs; |
---|
| 494 | + txp->regset.regs = txp_regs; |
---|
| 495 | + txp->regset.nregs = ARRAY_SIZE(txp_regs); |
---|
417 | 496 | |
---|
418 | 497 | drm_connector_helper_add(&txp->connector.base, |
---|
419 | 498 | &vc4_txp_connector_helper_funcs); |
---|
.. | .. |
---|
424 | 503 | if (ret) |
---|
425 | 504 | return ret; |
---|
426 | 505 | |
---|
| 506 | + ret = vc4_crtc_init(drm, vc4_crtc, |
---|
| 507 | + &vc4_txp_crtc_funcs, &vc4_txp_crtc_helper_funcs); |
---|
| 508 | + if (ret) |
---|
| 509 | + return ret; |
---|
| 510 | + |
---|
| 511 | + encoder = &txp->connector.encoder; |
---|
| 512 | + encoder->possible_crtcs = drm_crtc_mask(crtc); |
---|
| 513 | + |
---|
427 | 514 | ret = devm_request_irq(dev, irq, vc4_txp_interrupt, 0, |
---|
428 | 515 | dev_name(dev), txp); |
---|
429 | 516 | if (ret) |
---|
.. | .. |
---|
432 | 519 | dev_set_drvdata(dev, txp); |
---|
433 | 520 | vc4->txp = txp; |
---|
434 | 521 | |
---|
| 522 | + vc4_debugfs_add_regset32(drm, "txp_regs", &txp->regset); |
---|
| 523 | + |
---|
435 | 524 | return 0; |
---|
436 | 525 | } |
---|
437 | 526 | |
---|