| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd |
|---|
| 3 | 4 | * Zheng Yang <zhengyang@rock-chips.com> |
|---|
| 4 | 5 | * Yakir Yang <ykk@rock-chips.com> |
|---|
| 5 | | - * |
|---|
| 6 | | - * This software is licensed under the terms of the GNU General Public |
|---|
| 7 | | - * License version 2, as published by the Free Software Foundation, and |
|---|
| 8 | | - * may be copied, distributed, and modified under those terms. |
|---|
| 9 | | - * |
|---|
| 10 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 11 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 13 | | - * GNU General Public License for more details. |
|---|
| 14 | 6 | */ |
|---|
| 15 | 7 | |
|---|
| 16 | 8 | #include <linux/irq.h> |
|---|
| .. | .. |
|---|
| 23 | 15 | #include <linux/mutex.h> |
|---|
| 24 | 16 | #include <linux/of_device.h> |
|---|
| 25 | 17 | |
|---|
| 26 | | -#include <drm/drm_of.h> |
|---|
| 27 | | -#include <drm/drmP.h> |
|---|
| 28 | 18 | #include <drm/drm_atomic_helper.h> |
|---|
| 29 | | -#include <drm/drm_crtc_helper.h> |
|---|
| 30 | 19 | #include <drm/drm_edid.h> |
|---|
| 20 | +#include <drm/drm_of.h> |
|---|
| 21 | +#include <drm/drm_probe_helper.h> |
|---|
| 22 | +#include <drm/drm_simple_kms_helper.h> |
|---|
| 23 | + |
|---|
| 24 | +#include <sound/hdmi-codec.h> |
|---|
| 31 | 25 | |
|---|
| 32 | 26 | #include "rockchip_drm_drv.h" |
|---|
| 33 | 27 | #include "rockchip_drm_vop.h" |
|---|
| .. | .. |
|---|
| 35 | 29 | #include "inno_hdmi.h" |
|---|
| 36 | 30 | |
|---|
| 37 | 31 | #define to_inno_hdmi(x) container_of(x, struct inno_hdmi, x) |
|---|
| 32 | + |
|---|
| 33 | +struct audio_info { |
|---|
| 34 | + int sample_rate; |
|---|
| 35 | + int channels; |
|---|
| 36 | + int sample_width; |
|---|
| 37 | +}; |
|---|
| 38 | 38 | |
|---|
| 39 | 39 | struct hdmi_data_info { |
|---|
| 40 | 40 | int vic; |
|---|
| .. | .. |
|---|
| 87 | 87 | struct i2c_adapter *ddc; |
|---|
| 88 | 88 | |
|---|
| 89 | 89 | unsigned int tmds_rate; |
|---|
| 90 | + struct platform_device *audio_pdev; |
|---|
| 91 | + bool audio_enable; |
|---|
| 90 | 92 | const struct inno_hdmi_plat_data *plat_data; |
|---|
| 91 | 93 | |
|---|
| 92 | 94 | struct hdmi_data_info hdmi_data; |
|---|
| .. | .. |
|---|
| 315 | 317 | m_PACKET_VSI_EN, v_PACKET_VSI_EN(0), v_PACKET_VSI_EN(1)); |
|---|
| 316 | 318 | } |
|---|
| 317 | 319 | |
|---|
| 320 | +static int inno_hdmi_config_audio_aai(struct inno_hdmi *hdmi, |
|---|
| 321 | + struct audio_info *audio) |
|---|
| 322 | +{ |
|---|
| 323 | + struct hdmi_audio_infoframe *faudio; |
|---|
| 324 | + union hdmi_infoframe frame; |
|---|
| 325 | + int rc; |
|---|
| 326 | + |
|---|
| 327 | + rc = hdmi_audio_infoframe_init(&frame.audio); |
|---|
| 328 | + faudio = (struct hdmi_audio_infoframe *)&frame; |
|---|
| 329 | + |
|---|
| 330 | + faudio->channels = audio->channels; |
|---|
| 331 | + |
|---|
| 332 | + return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_AAI, 0, 0, 0); |
|---|
| 333 | +} |
|---|
| 334 | + |
|---|
| 318 | 335 | static int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi, |
|---|
| 319 | 336 | struct drm_display_mode *mode) |
|---|
| 320 | 337 | { |
|---|
| 321 | 338 | union hdmi_infoframe frame; |
|---|
| 322 | 339 | int rc; |
|---|
| 323 | 340 | |
|---|
| 324 | | - rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode, false); |
|---|
| 341 | + rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, |
|---|
| 342 | + &hdmi->connector, |
|---|
| 343 | + mode); |
|---|
| 325 | 344 | |
|---|
| 326 | 345 | if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV444) |
|---|
| 327 | 346 | frame.avi.colorspace = HDMI_COLORSPACE_YUV444; |
|---|
| .. | .. |
|---|
| 511 | 530 | inno_hdmi_i2c_init(hdmi); |
|---|
| 512 | 531 | |
|---|
| 513 | 532 | /* Unmute video and audio output */ |
|---|
| 514 | | - hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, |
|---|
| 515 | | - v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0)); |
|---|
| 533 | + hdmi_modb(hdmi, HDMI_AV_MUTE, m_VIDEO_BLACK, v_VIDEO_MUTE(0)); |
|---|
| 534 | + if (hdmi->audio_enable) |
|---|
| 535 | + hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE, v_AUDIO_MUTE(0)); |
|---|
| 516 | 536 | |
|---|
| 517 | 537 | return 0; |
|---|
| 518 | 538 | } |
|---|
| .. | .. |
|---|
| 526 | 546 | inno_hdmi_setup(hdmi, adj_mode); |
|---|
| 527 | 547 | |
|---|
| 528 | 548 | /* Store the display mode for plugin/DPMS poweron events */ |
|---|
| 529 | | - memcpy(&hdmi->previous_mode, adj_mode, sizeof(hdmi->previous_mode)); |
|---|
| 549 | + drm_mode_copy(&hdmi->previous_mode, adj_mode); |
|---|
| 530 | 550 | } |
|---|
| 531 | 551 | |
|---|
| 532 | 552 | static void inno_hdmi_encoder_enable(struct drm_encoder *encoder) |
|---|
| .. | .. |
|---|
| 571 | 591 | .atomic_check = inno_hdmi_encoder_atomic_check, |
|---|
| 572 | 592 | }; |
|---|
| 573 | 593 | |
|---|
| 574 | | -static struct drm_encoder_funcs inno_hdmi_encoder_funcs = { |
|---|
| 575 | | - .destroy = drm_encoder_cleanup, |
|---|
| 576 | | -}; |
|---|
| 577 | | - |
|---|
| 578 | 594 | static enum drm_connector_status |
|---|
| 579 | 595 | inno_hdmi_connector_detect(struct drm_connector *connector, bool force) |
|---|
| 580 | 596 | { |
|---|
| .. | .. |
|---|
| 588 | 604 | { |
|---|
| 589 | 605 | struct inno_hdmi *hdmi = to_inno_hdmi(connector); |
|---|
| 590 | 606 | struct edid *edid; |
|---|
| 591 | | - struct drm_display_info *info = &connector->display_info; |
|---|
| 592 | 607 | int ret = 0; |
|---|
| 593 | 608 | |
|---|
| 594 | 609 | if (!hdmi->ddc) |
|---|
| .. | .. |
|---|
| 601 | 616 | drm_connector_update_edid_property(connector, edid); |
|---|
| 602 | 617 | ret = drm_add_edid_modes(connector, edid); |
|---|
| 603 | 618 | kfree(edid); |
|---|
| 604 | | - } else { |
|---|
| 605 | | - hdmi->hdmi_data.sink_is_hdmi = true; |
|---|
| 606 | | - hdmi->hdmi_data.sink_has_audio = true; |
|---|
| 607 | | - ret = rockchip_drm_add_modes_noedid(connector); |
|---|
| 608 | | - info->edid_hdmi_dc_modes = 0; |
|---|
| 609 | | - info->hdmi.y420_dc_modes = 0; |
|---|
| 610 | | - info->color_formats = 0; |
|---|
| 611 | | - |
|---|
| 612 | | - dev_info(hdmi->dev, "failed to get edid\n"); |
|---|
| 613 | 619 | } |
|---|
| 614 | 620 | |
|---|
| 615 | 621 | return ret; |
|---|
| .. | .. |
|---|
| 649 | 655 | .mode_valid = inno_hdmi_connector_mode_valid, |
|---|
| 650 | 656 | }; |
|---|
| 651 | 657 | |
|---|
| 658 | +static int |
|---|
| 659 | +inno_hdmi_audio_config_set(struct inno_hdmi *hdmi, |
|---|
| 660 | + struct hdmi_codec_daifmt *daifmt, |
|---|
| 661 | + struct audio_info *audio) |
|---|
| 662 | +{ |
|---|
| 663 | + int rate, N, channel; |
|---|
| 664 | + |
|---|
| 665 | + if (audio->channels < 3) |
|---|
| 666 | + channel = I2S_CHANNEL_1_2; |
|---|
| 667 | + else if (audio->channels < 5) |
|---|
| 668 | + channel = I2S_CHANNEL_3_4; |
|---|
| 669 | + else if (audio->channels < 7) |
|---|
| 670 | + channel = I2S_CHANNEL_5_6; |
|---|
| 671 | + else |
|---|
| 672 | + channel = I2S_CHANNEL_7_8; |
|---|
| 673 | + |
|---|
| 674 | + switch (audio->sample_rate) { |
|---|
| 675 | + case 32000: |
|---|
| 676 | + rate = AUDIO_32K; |
|---|
| 677 | + N = N_32K; |
|---|
| 678 | + break; |
|---|
| 679 | + case 44100: |
|---|
| 680 | + rate = AUDIO_441K; |
|---|
| 681 | + N = N_441K; |
|---|
| 682 | + break; |
|---|
| 683 | + case 48000: |
|---|
| 684 | + rate = AUDIO_48K; |
|---|
| 685 | + N = N_48K; |
|---|
| 686 | + break; |
|---|
| 687 | + case 88200: |
|---|
| 688 | + rate = AUDIO_882K; |
|---|
| 689 | + N = N_882K; |
|---|
| 690 | + break; |
|---|
| 691 | + case 96000: |
|---|
| 692 | + rate = AUDIO_96K; |
|---|
| 693 | + N = N_96K; |
|---|
| 694 | + break; |
|---|
| 695 | + case 176400: |
|---|
| 696 | + rate = AUDIO_1764K; |
|---|
| 697 | + N = N_1764K; |
|---|
| 698 | + break; |
|---|
| 699 | + case 192000: |
|---|
| 700 | + rate = AUDIO_192K; |
|---|
| 701 | + N = N_192K; |
|---|
| 702 | + break; |
|---|
| 703 | + default: |
|---|
| 704 | + dev_err(hdmi->dev, "[%s] not support such sample rate %d\n", |
|---|
| 705 | + __func__, audio->sample_rate); |
|---|
| 706 | + return -ENOENT; |
|---|
| 707 | + } |
|---|
| 708 | + |
|---|
| 709 | + if (daifmt->fmt == HDMI_SPDIF) { |
|---|
| 710 | + /* set_audio source SPDIF */ |
|---|
| 711 | + hdmi_writeb(hdmi, HDMI_AUDIO_CTRL1, 0x09); |
|---|
| 712 | + } else { |
|---|
| 713 | + /* set_audio source I2S */ |
|---|
| 714 | + hdmi_writeb(hdmi, HDMI_AUDIO_CTRL1, 0x01); |
|---|
| 715 | + } |
|---|
| 716 | + hdmi_writeb(hdmi, AUDIO_SAMPLE_RATE, rate); |
|---|
| 717 | + hdmi_writeb(hdmi, AUDIO_I2S_MODE, v_I2S_MODE(I2S_STANDARD) | |
|---|
| 718 | + v_I2S_CHANNEL(channel)); |
|---|
| 719 | + |
|---|
| 720 | + hdmi_writeb(hdmi, AUDIO_I2S_MAP, 0x00); |
|---|
| 721 | + hdmi_writeb(hdmi, AUDIO_I2S_SWAPS_SPDIF, rate); |
|---|
| 722 | + |
|---|
| 723 | + /* Set N value */ |
|---|
| 724 | + hdmi_writeb(hdmi, AUDIO_N_H, (N >> 16) & 0x0F); |
|---|
| 725 | + hdmi_writeb(hdmi, AUDIO_N_M, (N >> 8) & 0xFF); |
|---|
| 726 | + hdmi_writeb(hdmi, AUDIO_N_L, N & 0xFF); |
|---|
| 727 | + |
|---|
| 728 | + /* Set hdmi nlpcm mode to support hdmi bitstream */ |
|---|
| 729 | + hdmi_writeb(hdmi, HDMI_AUDIO_CHANNEL_STATUS, v_AUDIO_STATUS_NLPCM(0)); |
|---|
| 730 | + |
|---|
| 731 | + return inno_hdmi_config_audio_aai(hdmi, audio); |
|---|
| 732 | +} |
|---|
| 733 | + |
|---|
| 734 | +static int inno_hdmi_audio_prepare(struct device *dev, void *data, |
|---|
| 735 | + struct hdmi_codec_daifmt *fmt, |
|---|
| 736 | + struct hdmi_codec_params *hparms) |
|---|
| 737 | +{ |
|---|
| 738 | + struct inno_hdmi *hdmi = dev_get_drvdata(dev); |
|---|
| 739 | + |
|---|
| 740 | + if (!hdmi->hdmi_data.sink_has_audio) { |
|---|
| 741 | + dev_err(hdmi->dev, "Sink do not support audio!\n"); |
|---|
| 742 | + return -ENODEV; |
|---|
| 743 | + } |
|---|
| 744 | + |
|---|
| 745 | + hdmi->audio_enable = 0; |
|---|
| 746 | + hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_PD, v_AUDIO_PD(1)); |
|---|
| 747 | + return 0; |
|---|
| 748 | +} |
|---|
| 749 | + |
|---|
| 750 | +static int inno_hdmi_audio_hw_params(struct device *dev, void *d, |
|---|
| 751 | + struct hdmi_codec_daifmt *daifmt, |
|---|
| 752 | + struct hdmi_codec_params *params) |
|---|
| 753 | +{ |
|---|
| 754 | + struct inno_hdmi *hdmi = dev_get_drvdata(dev); |
|---|
| 755 | + struct audio_info audio = { |
|---|
| 756 | + .sample_width = params->sample_width, |
|---|
| 757 | + .sample_rate = params->sample_rate, |
|---|
| 758 | + .channels = params->channels, |
|---|
| 759 | + }; |
|---|
| 760 | + |
|---|
| 761 | + if (!hdmi->hdmi_data.sink_has_audio) { |
|---|
| 762 | + dev_err(hdmi->dev, "Sink do not support audio!\n"); |
|---|
| 763 | + return -ENODEV; |
|---|
| 764 | + } |
|---|
| 765 | + |
|---|
| 766 | + if (!hdmi->encoder.crtc) |
|---|
| 767 | + return -ENODEV; |
|---|
| 768 | + |
|---|
| 769 | + switch (daifmt->fmt) { |
|---|
| 770 | + case HDMI_I2S: |
|---|
| 771 | + break; |
|---|
| 772 | + case HDMI_SPDIF: |
|---|
| 773 | + break; |
|---|
| 774 | + default: |
|---|
| 775 | + dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt); |
|---|
| 776 | + return -EINVAL; |
|---|
| 777 | + } |
|---|
| 778 | + |
|---|
| 779 | + return inno_hdmi_audio_config_set(hdmi, daifmt, &audio); |
|---|
| 780 | +} |
|---|
| 781 | + |
|---|
| 782 | +static void inno_hdmi_audio_shutdown(struct device *dev, void *d) |
|---|
| 783 | +{ |
|---|
| 784 | + /* do nothing */ |
|---|
| 785 | +} |
|---|
| 786 | + |
|---|
| 787 | +static int inno_hdmi_audio_mute(struct device *dev, void *data, bool mute, int direction) |
|---|
| 788 | +{ |
|---|
| 789 | + struct inno_hdmi *hdmi = dev_get_drvdata(dev); |
|---|
| 790 | + |
|---|
| 791 | + if (!hdmi->hdmi_data.sink_has_audio) { |
|---|
| 792 | + dev_err(hdmi->dev, "Sink do not support audio!\n"); |
|---|
| 793 | + return -ENODEV; |
|---|
| 794 | + } |
|---|
| 795 | + |
|---|
| 796 | + hdmi->audio_enable = !mute; |
|---|
| 797 | + |
|---|
| 798 | + if (mute) |
|---|
| 799 | + hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_AUDIO_PD, |
|---|
| 800 | + v_AUDIO_MUTE(1) | v_AUDIO_PD(1)); |
|---|
| 801 | + else |
|---|
| 802 | + hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_AUDIO_PD, |
|---|
| 803 | + v_AUDIO_MUTE(0) | v_AUDIO_PD(0)); |
|---|
| 804 | + |
|---|
| 805 | + return 0; |
|---|
| 806 | +} |
|---|
| 807 | + |
|---|
| 808 | +static int inno_hdmi_audio_get_eld(struct device *dev, void *d, |
|---|
| 809 | + uint8_t *buf, size_t len) |
|---|
| 810 | +{ |
|---|
| 811 | + struct inno_hdmi *hdmi = dev_get_drvdata(dev); |
|---|
| 812 | + struct drm_mode_config *config = &hdmi->encoder.dev->mode_config; |
|---|
| 813 | + struct drm_connector *connector; |
|---|
| 814 | + int ret = -ENODEV; |
|---|
| 815 | + |
|---|
| 816 | + mutex_lock(&config->mutex); |
|---|
| 817 | + list_for_each_entry(connector, &config->connector_list, head) { |
|---|
| 818 | + if (&hdmi->encoder == connector->encoder) { |
|---|
| 819 | + memcpy(buf, connector->eld, |
|---|
| 820 | + min(sizeof(connector->eld), len)); |
|---|
| 821 | + ret = 0; |
|---|
| 822 | + } |
|---|
| 823 | + } |
|---|
| 824 | + mutex_unlock(&config->mutex); |
|---|
| 825 | + |
|---|
| 826 | + return ret; |
|---|
| 827 | +} |
|---|
| 828 | + |
|---|
| 829 | +static const struct hdmi_codec_ops audio_codec_ops = { |
|---|
| 830 | + .hw_params = inno_hdmi_audio_hw_params, |
|---|
| 831 | + .prepare = inno_hdmi_audio_prepare, |
|---|
| 832 | + .audio_shutdown = inno_hdmi_audio_shutdown, |
|---|
| 833 | + .mute_stream = inno_hdmi_audio_mute, |
|---|
| 834 | + .get_eld = inno_hdmi_audio_get_eld, |
|---|
| 835 | +}; |
|---|
| 836 | + |
|---|
| 837 | +static int inno_hdmi_audio_codec_init(struct inno_hdmi *hdmi, |
|---|
| 838 | + struct device *dev) |
|---|
| 839 | +{ |
|---|
| 840 | + const char *str = "i2s"; |
|---|
| 841 | + struct hdmi_codec_pdata codec_data = { |
|---|
| 842 | + .i2s = 1, |
|---|
| 843 | + .spdif = 0, |
|---|
| 844 | + .ops = &audio_codec_ops, |
|---|
| 845 | + .max_i2s_channels = 8, |
|---|
| 846 | + }; |
|---|
| 847 | + |
|---|
| 848 | + if (device_property_read_string(dev, "rockchip,format", &str)) |
|---|
| 849 | + dev_warn(dev, "can not get rockchip,format\n"); |
|---|
| 850 | + |
|---|
| 851 | + if (strstr(str, "spdif")) { |
|---|
| 852 | + codec_data.i2s = 0; |
|---|
| 853 | + codec_data.spdif = 1; |
|---|
| 854 | + } |
|---|
| 855 | + |
|---|
| 856 | + hdmi->audio_enable = false; |
|---|
| 857 | + hdmi->audio_pdev = platform_device_register_data( |
|---|
| 858 | + dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_NONE, |
|---|
| 859 | + &codec_data, sizeof(codec_data)); |
|---|
| 860 | + |
|---|
| 861 | + return PTR_ERR_OR_ZERO(hdmi->audio_pdev); |
|---|
| 862 | +} |
|---|
| 863 | + |
|---|
| 652 | 864 | static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi) |
|---|
| 653 | 865 | { |
|---|
| 654 | 866 | struct drm_encoder *encoder = &hdmi->encoder; |
|---|
| 655 | 867 | struct device *dev = hdmi->dev; |
|---|
| 656 | 868 | |
|---|
| 657 | | - encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); |
|---|
| 869 | + encoder->possible_crtcs = rockchip_drm_of_find_possible_crtcs(drm, dev->of_node); |
|---|
| 658 | 870 | |
|---|
| 659 | 871 | /* |
|---|
| 660 | 872 | * If we failed to find the CRTC(s) which this encoder is |
|---|
| .. | .. |
|---|
| 666 | 878 | return -EPROBE_DEFER; |
|---|
| 667 | 879 | |
|---|
| 668 | 880 | drm_encoder_helper_add(encoder, &inno_hdmi_encoder_helper_funcs); |
|---|
| 669 | | - drm_encoder_init(drm, encoder, &inno_hdmi_encoder_funcs, |
|---|
| 670 | | - DRM_MODE_ENCODER_TMDS, NULL); |
|---|
| 881 | + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); |
|---|
| 671 | 882 | |
|---|
| 672 | 883 | hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD; |
|---|
| 673 | 884 | |
|---|
| 674 | 885 | drm_connector_helper_add(&hdmi->connector, |
|---|
| 675 | 886 | &inno_hdmi_connector_helper_funcs); |
|---|
| 676 | | - drm_connector_init(drm, &hdmi->connector, &inno_hdmi_connector_funcs, |
|---|
| 677 | | - DRM_MODE_CONNECTOR_HDMIA); |
|---|
| 887 | + drm_connector_init_with_ddc(drm, &hdmi->connector, |
|---|
| 888 | + &inno_hdmi_connector_funcs, |
|---|
| 889 | + DRM_MODE_CONNECTOR_HDMIA, |
|---|
| 890 | + hdmi->ddc); |
|---|
| 678 | 891 | |
|---|
| 679 | 892 | drm_connector_attach_encoder(&hdmi->connector, encoder); |
|---|
| 893 | + inno_hdmi_audio_codec_init(hdmi, dev); |
|---|
| 680 | 894 | |
|---|
| 681 | 895 | return 0; |
|---|
| 682 | 896 | } |
|---|