Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm: zte: support hdmi audio through spdif

It enables HDMI audio support through SPDIF interface based on generic
hdmi-audio-codec driver. The HDMI hardware supports more audio
interfaces than SPDIF, like I2S, which may be added later.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>

+184
+1
drivers/gpu/drm/zte/Kconfig
··· 4 4 select DRM_KMS_CMA_HELPER 5 5 select DRM_KMS_FB_HELPER 6 6 select DRM_KMS_HELPER 7 + select SND_SOC_HDMI_CODEC if SND_SOC 7 8 select VIDEOMODE_HELPERS 8 9 help 9 10 Choose this option to enable DRM on ZTE ZX SoCs.
+148
drivers/gpu/drm/zte/zx_hdmi.c
··· 25 25 #include <drm/drm_of.h> 26 26 #include <drm/drmP.h> 27 27 28 + #include <sound/hdmi-codec.h> 29 + 28 30 #include "zx_hdmi_regs.h" 29 31 #include "zx_vou.h" 30 32 ··· 51 49 bool sink_is_hdmi; 52 50 bool sink_has_audio; 53 51 const struct vou_inf *inf; 52 + struct platform_device *audio_pdev; 54 53 }; 55 54 56 55 #define to_zx_hdmi(x) container_of(x, struct zx_hdmi, x) ··· 369 366 return IRQ_NONE; 370 367 } 371 368 369 + static int zx_hdmi_audio_startup(struct device *dev, void *data) 370 + { 371 + struct zx_hdmi *hdmi = dev_get_drvdata(dev); 372 + struct drm_encoder *encoder = &hdmi->encoder; 373 + 374 + vou_inf_hdmi_audio_sel(encoder->crtc, VOU_HDMI_AUD_SPDIF); 375 + 376 + return 0; 377 + } 378 + 379 + static void zx_hdmi_audio_shutdown(struct device *dev, void *data) 380 + { 381 + struct zx_hdmi *hdmi = dev_get_drvdata(dev); 382 + 383 + /* Disable audio input */ 384 + hdmi_writeb_mask(hdmi, AUD_EN, AUD_IN_EN, 0); 385 + } 386 + 387 + static inline int zx_hdmi_audio_get_n(unsigned int fs) 388 + { 389 + unsigned int n; 390 + 391 + if (fs && (fs % 44100) == 0) 392 + n = 6272 * (fs / 44100); 393 + else 394 + n = fs * 128 / 1000; 395 + 396 + return n; 397 + } 398 + 399 + static int zx_hdmi_audio_hw_params(struct device *dev, 400 + void *data, 401 + struct hdmi_codec_daifmt *daifmt, 402 + struct hdmi_codec_params *params) 403 + { 404 + struct zx_hdmi *hdmi = dev_get_drvdata(dev); 405 + struct hdmi_audio_infoframe *cea = &params->cea; 406 + union hdmi_infoframe frame; 407 + int n; 408 + 409 + /* We only support spdif for now */ 410 + if (daifmt->fmt != HDMI_SPDIF) { 411 + DRM_DEV_ERROR(hdmi->dev, "invalid daifmt %d\n", daifmt->fmt); 412 + return -EINVAL; 413 + } 414 + 415 + switch (params->sample_width) { 416 + case 16: 417 + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK, 418 + SPDIF_SAMPLE_SIZE_16BIT); 419 + break; 420 + case 20: 421 + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK, 422 + SPDIF_SAMPLE_SIZE_20BIT); 423 + break; 424 + case 24: 425 + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK, 426 + SPDIF_SAMPLE_SIZE_24BIT); 427 + break; 428 + default: 429 + DRM_DEV_ERROR(hdmi->dev, "invalid sample width %d\n", 430 + params->sample_width); 431 + return -EINVAL; 432 + } 433 + 434 + /* CTS is calculated by hardware, and we only need to take care of N */ 435 + n = zx_hdmi_audio_get_n(params->sample_rate); 436 + hdmi_writeb(hdmi, N_SVAL1, n & 0xff); 437 + hdmi_writeb(hdmi, N_SVAL2, (n >> 8) & 0xff); 438 + hdmi_writeb(hdmi, N_SVAL3, (n >> 16) & 0xf); 439 + 440 + /* Enable spdif mode */ 441 + hdmi_writeb_mask(hdmi, AUD_MODE, SPDIF_EN, SPDIF_EN); 442 + 443 + /* Enable audio input */ 444 + hdmi_writeb_mask(hdmi, AUD_EN, AUD_IN_EN, AUD_IN_EN); 445 + 446 + memcpy(&frame.audio, cea, sizeof(*cea)); 447 + 448 + return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_AUDIO); 449 + } 450 + 451 + static int zx_hdmi_audio_digital_mute(struct device *dev, void *data, 452 + bool enable) 453 + { 454 + struct zx_hdmi *hdmi = dev_get_drvdata(dev); 455 + 456 + if (enable) 457 + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, TPI_AUD_MUTE, 458 + TPI_AUD_MUTE); 459 + else 460 + hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, TPI_AUD_MUTE, 0); 461 + 462 + return 0; 463 + } 464 + 465 + static int zx_hdmi_audio_get_eld(struct device *dev, void *data, 466 + uint8_t *buf, size_t len) 467 + { 468 + struct zx_hdmi *hdmi = dev_get_drvdata(dev); 469 + struct drm_connector *connector = &hdmi->connector; 470 + 471 + memcpy(buf, connector->eld, min(sizeof(connector->eld), len)); 472 + 473 + return 0; 474 + } 475 + 476 + static const struct hdmi_codec_ops zx_hdmi_codec_ops = { 477 + .audio_startup = zx_hdmi_audio_startup, 478 + .hw_params = zx_hdmi_audio_hw_params, 479 + .audio_shutdown = zx_hdmi_audio_shutdown, 480 + .digital_mute = zx_hdmi_audio_digital_mute, 481 + .get_eld = zx_hdmi_audio_get_eld, 482 + }; 483 + 484 + static struct hdmi_codec_pdata zx_hdmi_codec_pdata = { 485 + .ops = &zx_hdmi_codec_ops, 486 + .spdif = 1, 487 + }; 488 + 489 + static int zx_hdmi_audio_register(struct zx_hdmi *hdmi) 490 + { 491 + struct platform_device *pdev; 492 + 493 + pdev = platform_device_register_data(hdmi->dev, HDMI_CODEC_DRV_NAME, 494 + PLATFORM_DEVID_AUTO, 495 + &zx_hdmi_codec_pdata, 496 + sizeof(zx_hdmi_codec_pdata)); 497 + if (IS_ERR(pdev)) 498 + return PTR_ERR(pdev); 499 + 500 + hdmi->audio_pdev = pdev; 501 + 502 + return 0; 503 + } 504 + 372 505 static int zx_hdmi_i2c_read(struct zx_hdmi *hdmi, struct i2c_msg *msg) 373 506 { 374 507 int len = msg->len; ··· 705 566 return ret; 706 567 } 707 568 569 + ret = zx_hdmi_audio_register(hdmi); 570 + if (ret) { 571 + DRM_DEV_ERROR(dev, "failed to register audio: %d\n", ret); 572 + return ret; 573 + } 574 + 708 575 ret = zx_hdmi_register(drm, hdmi); 709 576 if (ret) { 710 577 DRM_DEV_ERROR(dev, "failed to register hdmi: %d\n", ret); ··· 735 590 736 591 hdmi->connector.funcs->destroy(&hdmi->connector); 737 592 hdmi->encoder.funcs->destroy(&hdmi->encoder); 593 + 594 + if (hdmi->audio_pdev) 595 + platform_device_unregister(hdmi->audio_pdev); 738 596 } 739 597 740 598 static const struct component_ops zx_hdmi_component_ops = {
+14
drivers/gpu/drm/zte/zx_hdmi_regs.h
··· 52 52 #define TPI_INFO_TRANS_RPT BIT(6) 53 53 #define TPI_DDC_MASTER_EN 0x06f8 54 54 #define HW_DDC_MASTER BIT(7) 55 + #define N_SVAL1 0xa03 56 + #define N_SVAL2 0xa04 57 + #define N_SVAL3 0xa05 58 + #define AUD_EN 0xa13 59 + #define AUD_IN_EN BIT(0) 60 + #define AUD_MODE 0xa14 61 + #define SPDIF_EN BIT(1) 62 + #define TPI_AUD_CONFIG 0xa62 63 + #define SPDIF_SAMPLE_SIZE_SHIFT 6 64 + #define SPDIF_SAMPLE_SIZE_MASK (0x3 << SPDIF_SAMPLE_SIZE_SHIFT) 65 + #define SPDIF_SAMPLE_SIZE_16BIT (0x1 << SPDIF_SAMPLE_SIZE_SHIFT) 66 + #define SPDIF_SAMPLE_SIZE_20BIT (0x2 << SPDIF_SAMPLE_SIZE_SHIFT) 67 + #define SPDIF_SAMPLE_SIZE_24BIT (0x3 << SPDIF_SAMPLE_SIZE_SHIFT) 68 + #define TPI_AUD_MUTE BIT(4) 55 69 56 70 #endif /* __ZX_HDMI_REGS_H__ */
+9
drivers/gpu/drm/zte/zx_vou.c
··· 119 119 return zcrtc->vou; 120 120 } 121 121 122 + void vou_inf_hdmi_audio_sel(struct drm_crtc *crtc, 123 + enum vou_inf_hdmi_audio aud) 124 + { 125 + struct zx_crtc *zcrtc = to_zx_crtc(crtc); 126 + struct zx_vou_hw *vou = zcrtc->vou; 127 + 128 + zx_writel_mask(vou->vouctl + VOU_INF_HDMI_CTRL, VOU_HDMI_AUD_MASK, aud); 129 + } 130 + 122 131 void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc) 123 132 { 124 133 struct zx_crtc *zcrtc = to_zx_crtc(crtc);
+10
drivers/gpu/drm/zte/zx_vou.h
··· 30 30 VOU_RGB_666 = 3, 31 31 }; 32 32 33 + enum vou_inf_hdmi_audio { 34 + VOU_HDMI_AUD_SPDIF = BIT(0), 35 + VOU_HDMI_AUD_I2S = BIT(1), 36 + VOU_HDMI_AUD_DSD = BIT(2), 37 + VOU_HDMI_AUD_HBR = BIT(3), 38 + VOU_HDMI_AUD_PARALLEL = BIT(4), 39 + }; 40 + 33 41 struct vou_inf { 34 42 enum vou_inf_id id; 35 43 enum vou_inf_data_sel data_sel; ··· 45 37 u32 clocks_sel_bits; 46 38 }; 47 39 40 + void vou_inf_hdmi_audio_sel(struct drm_crtc *crtc, 41 + enum vou_inf_hdmi_audio aud); 48 42 void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc); 49 43 void vou_inf_disable(const struct vou_inf *inf, struct drm_crtc *crtc); 50 44
+2
drivers/gpu/drm/zte/zx_vou_regs.h
··· 150 150 #define VOU_CLK_GL0_SEL BIT(4) 151 151 #define VOU_CLK_REQEN 0x20 152 152 #define VOU_CLK_EN 0x24 153 + #define VOU_INF_HDMI_CTRL 0x30 154 + #define VOU_HDMI_AUD_MASK 0x1f 153 155 154 156 /* OTFPPU_CTRL registers */ 155 157 #define OTFPPU_RSZ_DATA_SOURCE 0x04