| .. | .. |
|---|
| 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 | |
|---|