Loading drivers/gpu/drm/msm/Makefile +3 −1 Original line number Diff line number Diff line Loading @@ -98,7 +98,9 @@ msm_drm-$(CONFIG_DRM_MSM_DSI_STAGING) += dsi-staging/dsi_phy.o \ dsi-staging/dsi_display_test.o msm_drm-$(CONFIG_DRM_SDE_HDMI) += \ hdmi-staging/sde_hdmi.o hdmi-staging/sde_hdmi.o \ hdmi-staging/sde_hdmi_bridge.o \ hdmi-staging/sde_hdmi_audio.o msm_drm-$(CONFIG_DRM_MSM_DSI_PLL) += dsi/pll/dsi_pll.o \ dsi/pll/dsi_pll_28nm.o Loading drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c +30 −4 Original line number Diff line number Diff line Loading @@ -325,9 +325,9 @@ static int _sde_hdmi_hpd_enable(struct sde_hdmi *sde_hdmi) } } hdmi_set_mode(hdmi, false); sde_hdmi_set_mode(hdmi, false); _sde_hdmi_phy_reset(hdmi); hdmi_set_mode(hdmi, true); sde_hdmi_set_mode(hdmi, true); hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b); Loading Loading @@ -367,7 +367,7 @@ static void _sde_hdmi_hdp_disable(struct sde_hdmi *sde_hdmi) /* Disable HPD interrupt */ hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0); hdmi_set_mode(hdmi, false); sde_hdmi_set_mode(hdmi, false); for (i = 0; i < config->hpd_clk_cnt; i++) clk_disable_unprepare(hdmi->hpd_clks[i]); Loading Loading @@ -460,6 +460,32 @@ static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id) return IRQ_HANDLED; } void sde_hdmi_set_mode(struct hdmi *hdmi, bool power_on) { uint32_t ctrl = 0; unsigned long flags; spin_lock_irqsave(&hdmi->reg_lock, flags); ctrl = hdmi_read(hdmi, REG_HDMI_CTRL); if (power_on) { ctrl |= HDMI_CTRL_ENABLE; if (!hdmi->hdmi_mode) { ctrl |= HDMI_CTRL_HDMI; hdmi_write(hdmi, REG_HDMI_CTRL, ctrl); ctrl &= ~HDMI_CTRL_HDMI; } else { ctrl |= HDMI_CTRL_HDMI; } } else { ctrl &= ~HDMI_CTRL_HDMI; } hdmi_write(hdmi, REG_HDMI_CTRL, ctrl); spin_unlock_irqrestore(&hdmi->reg_lock, flags); DRM_DEBUG("HDMI Core: %s, HDMI_CTRL=0x%08x\n", power_on ? "Enable" : "Disable", ctrl); } int sde_hdmi_get_info(struct msm_display_info *info, void *display) { Loading Loading @@ -1087,7 +1113,7 @@ int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc) hdmi_audio_infoframe_init(&hdmi->audio.infoframe); hdmi->bridge = hdmi_bridge_init(hdmi); hdmi->bridge = sde_hdmi_bridge_init(hdmi); if (IS_ERR(hdmi->bridge)) { rc = PTR_ERR(hdmi->bridge); SDE_ERROR("failed to create HDMI bridge: %d\n", rc); Loading drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h +44 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <linux/bitops.h> #include <linux/debugfs.h> #include <linux/of_device.h> #include <linux/msm_ext_display.h> #include <drm/drmP.h> #include <drm/drm_crtc.h> Loading Loading @@ -224,6 +225,49 @@ int sde_hdmi_drm_deinit(struct sde_hdmi *display); int sde_hdmi_get_info(struct msm_display_info *info, void *display); /** * sde_hdmi_bridge_init() - init sde hdmi bridge * @hdmi: Handle to the hdmi. * * Return: struct drm_bridge *. */ struct drm_bridge *sde_hdmi_bridge_init(struct hdmi *hdmi); /** * sde_hdmi_set_mode() - Set HDMI mode API. * @hdmi: Handle to the hdmi. * @power_on: Power on/off request. * * Return: void. */ void sde_hdmi_set_mode(struct hdmi *hdmi, bool power_on); /** * sde_hdmi_audio_on() - enable hdmi audio. * @hdmi: Handle to the hdmi. * @params: audio setup parameters from codec. * * Return: error code. */ int sde_hdmi_audio_on(struct hdmi *hdmi, struct msm_ext_disp_audio_setup_params *params); /** * sde_hdmi_audio_off() - disable hdmi audio. * @hdmi: Handle to the hdmi. * * Return: void. */ void sde_hdmi_audio_off(struct hdmi *hdmi); /** * sde_hdmi_config_avmute() - mute hdmi. * @hdmi: Handle to the hdmi. * @set: enable/disable avmute. * * Return: error code. */ int sde_hdmi_config_avmute(struct hdmi *hdmi, bool set); #else /*#ifdef CONFIG_DRM_SDE_HDMI*/ static inline u32 sde_hdmi_get_num_of_displays(void) Loading drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c 0 → 100644 +393 −0 Original line number Diff line number Diff line /* Copyright (c) 2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/slab.h> #include <linux/bitops.h> #include <linux/delay.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/iopoll.h> #include <linux/types.h> #include <linux/switch.h> #include <linux/gcd.h> #include "drm_edid.h" #include "sde_kms.h" #include "sde_hdmi.h" #include "sde_hdmi_regs.h" #include "hdmi.h" #define HDMI_AUDIO_INFO_FRAME_PACKET_HEADER 0x84 #define HDMI_AUDIO_INFO_FRAME_PACKET_VERSION 0x1 #define HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH 0x0A #define HDMI_KHZ_TO_HZ 1000 #define HDMI_MHZ_TO_HZ 1000000 #define HDMI_ACR_N_MULTIPLIER 128 #define DEFAULT_AUDIO_SAMPLE_RATE_HZ 48000 /* Supported HDMI Audio channels */ enum hdmi_audio_channels { AUDIO_CHANNEL_2 = 2, AUDIO_CHANNEL_3, AUDIO_CHANNEL_4, AUDIO_CHANNEL_5, AUDIO_CHANNEL_6, AUDIO_CHANNEL_7, AUDIO_CHANNEL_8, }; /* parameters for clock regeneration */ struct hdmi_audio_acr { u32 n; u32 cts; }; enum hdmi_audio_sample_rates { AUDIO_SAMPLE_RATE_32KHZ, AUDIO_SAMPLE_RATE_44_1KHZ, AUDIO_SAMPLE_RATE_48KHZ, AUDIO_SAMPLE_RATE_88_2KHZ, AUDIO_SAMPLE_RATE_96KHZ, AUDIO_SAMPLE_RATE_176_4KHZ, AUDIO_SAMPLE_RATE_192KHZ, AUDIO_SAMPLE_RATE_MAX }; struct sde_hdmi_audio { struct hdmi *hdmi; struct msm_ext_disp_audio_setup_params params; u32 pclk; }; static void _sde_hdmi_audio_get_audio_sample_rate(u32 *sample_rate_hz) { u32 rate = *sample_rate_hz; switch (rate) { case 32000: *sample_rate_hz = AUDIO_SAMPLE_RATE_32KHZ; break; case 44100: *sample_rate_hz = AUDIO_SAMPLE_RATE_44_1KHZ; break; case 48000: *sample_rate_hz = AUDIO_SAMPLE_RATE_48KHZ; break; case 88200: *sample_rate_hz = AUDIO_SAMPLE_RATE_88_2KHZ; break; case 96000: *sample_rate_hz = AUDIO_SAMPLE_RATE_96KHZ; break; case 176400: *sample_rate_hz = AUDIO_SAMPLE_RATE_176_4KHZ; break; case 192000: *sample_rate_hz = AUDIO_SAMPLE_RATE_192KHZ; break; default: SDE_ERROR("%d unchanged\n", rate); break; } } static void _sde_hdmi_audio_get_acr_param(u32 pclk, u32 fs, struct hdmi_audio_acr *acr) { u32 div, mul; if (!acr) { SDE_ERROR("invalid data\n"); return; } /* * as per HDMI specification, N/CTS = (128*fs)/pclk. * get the ratio using this formula. */ acr->n = HDMI_ACR_N_MULTIPLIER * fs; acr->cts = pclk; /* get the greatest common divisor for the ratio */ div = gcd(acr->n, acr->cts); /* get the n and cts values wrt N/CTS formula */ acr->n /= div; acr->cts /= div; /* * as per HDMI specification, 300 <= 128*fs/N <= 1500 * with a target of 128*fs/N = 1000. To get closest * value without truncating fractional values, find * the corresponding multiplier */ mul = ((HDMI_ACR_N_MULTIPLIER * fs / HDMI_KHZ_TO_HZ) + (acr->n - 1)) / acr->n; acr->n *= mul; acr->cts *= mul; } static void _sde_hdmi_audio_acr_enable(struct sde_hdmi_audio *audio) { struct hdmi_audio_acr acr; struct msm_ext_disp_audio_setup_params *params; u32 pclk, layout, multiplier = 1, sample_rate; u32 acr_pkt_ctl, aud_pkt_ctl2, acr_reg_cts, acr_reg_n; struct hdmi *hdmi; hdmi = audio->hdmi; params = &audio->params; pclk = audio->pclk; sample_rate = params->sample_rate_hz; _sde_hdmi_audio_get_acr_param(pclk, sample_rate, &acr); _sde_hdmi_audio_get_audio_sample_rate(&sample_rate); layout = (params->num_of_channels == AUDIO_CHANNEL_2) ? 0 : 1; SDE_DEBUG("n=%u, cts=%u, layout=%u\n", acr.n, acr.cts, layout); /* AUDIO_PRIORITY | SOURCE */ acr_pkt_ctl = BIT(31) | BIT(8); switch (sample_rate) { case AUDIO_SAMPLE_RATE_44_1KHZ: acr_pkt_ctl |= 0x2 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_44_0; acr_reg_n = HDMI_ACR_44_1; break; case AUDIO_SAMPLE_RATE_48KHZ: acr_pkt_ctl |= 0x3 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_48_0; acr_reg_n = HDMI_ACR_48_1; break; case AUDIO_SAMPLE_RATE_192KHZ: multiplier = 4; acr.n >>= 2; acr_pkt_ctl |= 0x3 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_48_0; acr_reg_n = HDMI_ACR_48_1; break; case AUDIO_SAMPLE_RATE_176_4KHZ: multiplier = 4; acr.n >>= 2; acr_pkt_ctl |= 0x2 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_44_0; acr_reg_n = HDMI_ACR_44_1; break; case AUDIO_SAMPLE_RATE_96KHZ: multiplier = 2; acr.n >>= 1; acr_pkt_ctl |= 0x3 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_48_0; acr_reg_n = HDMI_ACR_48_1; break; case AUDIO_SAMPLE_RATE_88_2KHZ: multiplier = 2; acr.n >>= 1; acr_pkt_ctl |= 0x2 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_44_0; acr_reg_n = HDMI_ACR_44_1; break; default: multiplier = 1; acr_pkt_ctl |= 0x1 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_32_0; acr_reg_n = HDMI_ACR_32_1; break; } aud_pkt_ctl2 = BIT(0) | (layout << 1); /* N_MULTIPLE(multiplier) */ acr_pkt_ctl &= ~(7 << 16); acr_pkt_ctl |= (multiplier & 0x7) << 16; /* SEND | CONT */ acr_pkt_ctl |= BIT(0) | BIT(1); hdmi_write(hdmi, acr_reg_cts, acr.cts); hdmi_write(hdmi, acr_reg_n, acr.n); hdmi_write(hdmi, HDMI_ACR_PKT_CTRL, acr_pkt_ctl); hdmi_write(hdmi, HDMI_AUDIO_PKT_CTRL2, aud_pkt_ctl2); } static void _sde_hdmi_audio_acr_setup(struct sde_hdmi_audio *audio, bool on) { if (on) _sde_hdmi_audio_acr_enable(audio); else hdmi_write(audio->hdmi, HDMI_ACR_PKT_CTRL, 0); } static void _sde_hdmi_audio_infoframe_setup(struct sde_hdmi_audio *audio, bool enabled) { struct hdmi *hdmi = audio->hdmi; u32 channels, channel_allocation, level_shift, down_mix, layout; u32 hdmi_debug_reg = 0, audio_info_0_reg = 0, audio_info_1_reg = 0; u32 audio_info_ctrl_reg, aud_pck_ctrl_2_reg; u32 check_sum, sample_present; audio_info_ctrl_reg = hdmi_read(hdmi, HDMI_INFOFRAME_CTRL0); audio_info_ctrl_reg &= ~0xF0; if (!enabled) goto end; channels = audio->params.num_of_channels - 1; channel_allocation = audio->params.channel_allocation; level_shift = audio->params.level_shift; down_mix = audio->params.down_mix; sample_present = audio->params.sample_present; layout = (audio->params.num_of_channels == AUDIO_CHANNEL_2) ? 0 : 1; aud_pck_ctrl_2_reg = BIT(0) | (layout << 1); hdmi_write(hdmi, HDMI_AUDIO_PKT_CTRL2, aud_pck_ctrl_2_reg); audio_info_1_reg |= channel_allocation & 0xFF; audio_info_1_reg |= ((level_shift & 0xF) << 11); audio_info_1_reg |= ((down_mix & 0x1) << 15); check_sum = 0; check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_HEADER; check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_VERSION; check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH; check_sum += channels; check_sum += channel_allocation; check_sum += (level_shift & 0xF) << 3 | (down_mix & 0x1) << 7; check_sum &= 0xFF; check_sum = (u8) (256 - check_sum); audio_info_0_reg |= check_sum & 0xFF; audio_info_0_reg |= ((channels & 0x7) << 8); /* Enable Audio InfoFrame Transmission */ audio_info_ctrl_reg |= 0xF0; if (layout) { /* Set the Layout bit */ hdmi_debug_reg |= BIT(4); /* Set the Sample Present bits */ hdmi_debug_reg |= sample_present & 0xF; } end: hdmi_write(hdmi, HDMI_DEBUG, hdmi_debug_reg); hdmi_write(hdmi, HDMI_AUDIO_INFO0, audio_info_0_reg); hdmi_write(hdmi, HDMI_AUDIO_INFO1, audio_info_1_reg); hdmi_write(hdmi, HDMI_INFOFRAME_CTRL0, audio_info_ctrl_reg); } int sde_hdmi_audio_on(struct hdmi *hdmi, struct msm_ext_disp_audio_setup_params *params) { struct sde_hdmi_audio audio; int rc = 0; if (!hdmi) { SDE_ERROR("invalid HDMI Ctrl\n"); rc = -ENODEV; goto end; } audio.pclk = hdmi->pixclock; audio.params = *params; audio.hdmi = hdmi; if (!audio.params.num_of_channels) { audio.params.sample_rate_hz = DEFAULT_AUDIO_SAMPLE_RATE_HZ; audio.params.num_of_channels = AUDIO_CHANNEL_2; } _sde_hdmi_audio_acr_setup(&audio, true); _sde_hdmi_audio_infoframe_setup(&audio, true); SDE_DEBUG("HDMI Audio: Enabled\n"); end: return rc; } void sde_hdmi_audio_off(struct hdmi *hdmi) { struct sde_hdmi_audio audio; int rc = 0; if (!hdmi) { SDE_ERROR("invalid HDMI Ctrl\n"); rc = -ENODEV; return; } audio.hdmi = hdmi; _sde_hdmi_audio_infoframe_setup(&audio, false); _sde_hdmi_audio_acr_setup(&audio, false); SDE_DEBUG("HDMI Audio: Disabled\n"); } int sde_hdmi_config_avmute(struct hdmi *hdmi, bool set) { u32 av_mute_status; bool av_pkt_en = false; if (!hdmi) { SDE_ERROR("invalid HDMI Ctrl\n"); return -ENODEV; } av_mute_status = hdmi_read(hdmi, HDMI_GC); if (set) { if (!(av_mute_status & BIT(0))) { hdmi_write(hdmi, HDMI_GC, av_mute_status | BIT(0)); av_pkt_en = true; } } else { if (av_mute_status & BIT(0)) { hdmi_write(hdmi, HDMI_GC, av_mute_status & ~BIT(0)); av_pkt_en = true; } } /* Enable AV Mute tranmission here */ if (av_pkt_en) hdmi_write(hdmi, HDMI_VBI_PKT_CTRL, hdmi_read(hdmi, HDMI_VBI_PKT_CTRL) | (BIT(4) & BIT(5))); SDE_DEBUG("AVMUTE %s\n", set ? "set" : "cleared"); return 0; } drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c 0 → 100644 +388 −0 Original line number Diff line number Diff line /* * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * Copyright (C) 2013 Red Hat * Author: Rob Clark <robdclark@gmail.com> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ #include "drm_edid.h" #include "sde_kms.h" #include "sde_hdmi.h" #include "hdmi.h" struct sde_hdmi_bridge { struct drm_bridge base; struct hdmi *hdmi; }; #define to_hdmi_bridge(x) container_of(x, struct sde_hdmi_bridge, base) /* for AVI program */ #define HDMI_AVI_INFOFRAME_BUFFER_SIZE \ (HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE) #define HDMI_VS_INFOFRAME_BUFFER_SIZE (HDMI_INFOFRAME_HEADER_SIZE + 6) #define HDMI_SPD_INFOFRAME_BUFFER_SIZE \ (HDMI_INFOFRAME_HEADER_SIZE + HDMI_SPD_INFOFRAME_SIZE) #define HDMI_DEFAULT_VENDOR_NAME "unknown" #define HDMI_DEFAULT_PRODUCT_NAME "msm" #define LEFT_SHIFT_BYTE(x) ((x) << 8) #define LEFT_SHIFT_WORD(x) ((x) << 16) #define LEFT_SHIFT_24BITS(x) ((x) << 24) #define HDMI_AVI_IFRAME_LINE_NUMBER 1 #define HDMI_VENDOR_IFRAME_LINE_NUMBER 3 void _sde_hdmi_bridge_destroy(struct drm_bridge *bridge) { } static void _sde_hdmi_bridge_power_on(struct drm_bridge *bridge) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; const struct hdmi_platform_config *config = hdmi->config; int i, ret; for (i = 0; i < config->pwr_reg_cnt; i++) { ret = regulator_enable(hdmi->pwr_regs[i]); if (ret) { SDE_ERROR("failed to enable pwr regulator: %s (%d)\n", config->pwr_reg_names[i], ret); } } if (config->pwr_clk_cnt > 0) { DRM_DEBUG("pixclock: %lu", hdmi->pixclock); ret = clk_set_rate(hdmi->pwr_clks[0], hdmi->pixclock); if (ret) { SDE_ERROR("failed to set pixel clk: %s (%d)\n", config->pwr_clk_names[0], ret); } } for (i = 0; i < config->pwr_clk_cnt; i++) { ret = clk_prepare_enable(hdmi->pwr_clks[i]); if (ret) { SDE_ERROR("failed to enable pwr clk: %s (%d)\n", config->pwr_clk_names[i], ret); } } } static void _sde_hdmi_bridge_power_off(struct drm_bridge *bridge) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; const struct hdmi_platform_config *config = hdmi->config; int i, ret; /* Wait for vsync */ msleep(20); for (i = 0; i < config->pwr_clk_cnt; i++) clk_disable_unprepare(hdmi->pwr_clks[i]); for (i = 0; i < config->pwr_reg_cnt; i++) { ret = regulator_disable(hdmi->pwr_regs[i]); if (ret) { SDE_ERROR("failed to disable pwr regulator: %s (%d)\n", config->pwr_reg_names[i], ret); } } } static void _sde_hdmi_bridge_pre_enable(struct drm_bridge *bridge) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; struct hdmi_phy *phy = hdmi->phy; DRM_DEBUG("power up"); if (!hdmi->power_on) { _sde_hdmi_bridge_power_on(bridge); hdmi->power_on = true; hdmi_audio_update(hdmi); } if (phy) phy->funcs->powerup(phy, hdmi->pixclock); sde_hdmi_set_mode(hdmi, true); if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported) hdmi_hdcp_ctrl_on(hdmi->hdcp_ctrl); } static void _sde_hdmi_bridge_enable(struct drm_bridge *bridge) { } static void _sde_hdmi_bridge_disable(struct drm_bridge *bridge) { } static void _sde_hdmi_bridge_post_disable(struct drm_bridge *bridge) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; struct hdmi_phy *phy = hdmi->phy; if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported) hdmi_hdcp_ctrl_off(hdmi->hdcp_ctrl); DRM_DEBUG("power down"); sde_hdmi_set_mode(hdmi, false); if (phy) phy->funcs->powerdown(phy); if (hdmi->power_on) { _sde_hdmi_bridge_power_off(bridge); hdmi->power_on = false; hdmi_audio_update(hdmi); } } static void _sde_hdmi_bridge_set_avi_infoframe(struct hdmi *hdmi, const struct drm_display_mode *mode) { u8 avi_iframe[HDMI_AVI_INFOFRAME_BUFFER_SIZE] = {0}; u8 *avi_frame = &avi_iframe[HDMI_INFOFRAME_HEADER_SIZE]; u8 checksum; u32 reg_val; struct hdmi_avi_infoframe info; drm_hdmi_avi_infoframe_from_display_mode(&info, mode); hdmi_avi_infoframe_pack(&info, avi_iframe, sizeof(avi_iframe)); checksum = avi_iframe[HDMI_INFOFRAME_HEADER_SIZE - 1]; reg_val = checksum | LEFT_SHIFT_BYTE(avi_frame[0]) | LEFT_SHIFT_WORD(avi_frame[1]) | LEFT_SHIFT_24BITS(avi_frame[2]); hdmi_write(hdmi, REG_HDMI_AVI_INFO(0), reg_val); reg_val = avi_frame[3] | LEFT_SHIFT_BYTE(avi_frame[4]) | LEFT_SHIFT_WORD(avi_frame[5]) | LEFT_SHIFT_24BITS(avi_frame[6]); hdmi_write(hdmi, REG_HDMI_AVI_INFO(1), reg_val); reg_val = avi_frame[7] | LEFT_SHIFT_BYTE(avi_frame[8]) | LEFT_SHIFT_WORD(avi_frame[9]) | LEFT_SHIFT_24BITS(avi_frame[10]); hdmi_write(hdmi, REG_HDMI_AVI_INFO(2), reg_val); reg_val = avi_frame[11] | LEFT_SHIFT_BYTE(avi_frame[12]) | LEFT_SHIFT_24BITS(avi_iframe[1]); hdmi_write(hdmi, REG_HDMI_AVI_INFO(3), reg_val); /* AVI InfFrame enable (every frame) */ hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0) | BIT(1) | BIT(0)); reg_val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); reg_val &= ~0x3F; reg_val |= HDMI_AVI_IFRAME_LINE_NUMBER; hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, reg_val); } static void _sde_hdmi_bridge_set_vs_infoframe(struct hdmi *hdmi, const struct drm_display_mode *mode) { u8 vs_iframe[HDMI_VS_INFOFRAME_BUFFER_SIZE] = {0}; u32 reg_val; struct hdmi_vendor_infoframe info; int rc = 0; rc = drm_hdmi_vendor_infoframe_from_display_mode(&info, mode); if (rc < 0) { SDE_DEBUG("don't send vendor infoframe\n"); return; } hdmi_vendor_infoframe_pack(&info, vs_iframe, sizeof(vs_iframe)); reg_val = (info.s3d_struct << 24) | (info.vic << 16) | (vs_iframe[3] << 8) | (vs_iframe[7] << 5) | vs_iframe[2]; hdmi_write(hdmi, REG_HDMI_VENSPEC_INFO0, reg_val); /* vendor specific info-frame enable (every frame) */ hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0) | BIT(13) | BIT(12)); reg_val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); reg_val &= ~0x3F000000; reg_val |= (HDMI_VENDOR_IFRAME_LINE_NUMBER << 24); hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, reg_val); } static void _sde_hdmi_bridge_set_spd_infoframe(struct hdmi *hdmi, const struct drm_display_mode *mode) { u8 spd_iframe[HDMI_SPD_INFOFRAME_BUFFER_SIZE] = {0}; u32 packet_payload, packet_control, packet_header; struct hdmi_spd_infoframe info; int i; /* Need to query vendor and product name from platform setup */ hdmi_spd_infoframe_init(&info, HDMI_DEFAULT_VENDOR_NAME, HDMI_DEFAULT_PRODUCT_NAME); hdmi_spd_infoframe_pack(&info, spd_iframe, sizeof(spd_iframe)); packet_header = spd_iframe[0] | LEFT_SHIFT_BYTE(spd_iframe[1] & 0x7f) | LEFT_SHIFT_WORD(spd_iframe[2] & 0x7f); hdmi_write(hdmi, REG_HDMI_GENERIC1_HDR, packet_header); for (i = 0; i < MAX_REG_HDMI_GENERIC1_INDEX; i++) { packet_payload = spd_iframe[3 + i * 4] | LEFT_SHIFT_BYTE(spd_iframe[4 + i * 4] & 0x7f) | LEFT_SHIFT_WORD(spd_iframe[5 + i * 4] & 0x7f) | LEFT_SHIFT_24BITS(spd_iframe[6 + i * 4] & 0x7f); hdmi_write(hdmi, REG_HDMI_GENERIC1(i), packet_payload); } packet_payload = (spd_iframe[27] & 0x7f) | LEFT_SHIFT_BYTE(spd_iframe[28] & 0x7f); hdmi_write(hdmi, REG_HDMI_GENERIC1(MAX_REG_HDMI_GENERIC1_INDEX), packet_payload); /* * GENERIC1_LINE | GENERIC1_CONT | GENERIC1_SEND * Setup HDMI TX generic packet control * Enable this packet to transmit every frame * Enable HDMI TX engine to transmit Generic packet 1 */ packet_control = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); packet_control |= ((0x1 << 24) | (1 << 5) | (1 << 4)); hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, packet_control); } static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; int hstart, hend, vstart, vend; uint32_t frame_ctrl; mode = adjusted_mode; hdmi->pixclock = mode->clock * 1000; hstart = mode->htotal - mode->hsync_start; hend = mode->htotal - mode->hsync_start + mode->hdisplay; vstart = mode->vtotal - mode->vsync_start - 1; vend = mode->vtotal - mode->vsync_start + mode->vdisplay - 1; DRM_DEBUG( "htotal=%d, vtotal=%d, hstart=%d, hend=%d, vstart=%d, vend=%d", mode->htotal, mode->vtotal, hstart, hend, vstart, vend); hdmi_write(hdmi, REG_HDMI_TOTAL, HDMI_TOTAL_H_TOTAL(mode->htotal - 1) | HDMI_TOTAL_V_TOTAL(mode->vtotal - 1)); hdmi_write(hdmi, REG_HDMI_ACTIVE_HSYNC, HDMI_ACTIVE_HSYNC_START(hstart) | HDMI_ACTIVE_HSYNC_END(hend)); hdmi_write(hdmi, REG_HDMI_ACTIVE_VSYNC, HDMI_ACTIVE_VSYNC_START(vstart) | HDMI_ACTIVE_VSYNC_END(vend)); if (mode->flags & DRM_MODE_FLAG_INTERLACE) { hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2, HDMI_VSYNC_TOTAL_F2_V_TOTAL(mode->vtotal)); hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2, HDMI_VSYNC_ACTIVE_F2_START(vstart + 1) | HDMI_VSYNC_ACTIVE_F2_END(vend + 1)); } else { hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2, HDMI_VSYNC_TOTAL_F2_V_TOTAL(0)); hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2, HDMI_VSYNC_ACTIVE_F2_START(0) | HDMI_VSYNC_ACTIVE_F2_END(0)); } frame_ctrl = 0; if (mode->flags & DRM_MODE_FLAG_NHSYNC) frame_ctrl |= HDMI_FRAME_CTRL_HSYNC_LOW; if (mode->flags & DRM_MODE_FLAG_NVSYNC) frame_ctrl |= HDMI_FRAME_CTRL_VSYNC_LOW; if (mode->flags & DRM_MODE_FLAG_INTERLACE) frame_ctrl |= HDMI_FRAME_CTRL_INTERLACED_EN; DRM_DEBUG("frame_ctrl=%08x\n", frame_ctrl); hdmi_write(hdmi, REG_HDMI_FRAME_CTRL, frame_ctrl); /* * Setup info frame * Current drm_edid driver doesn't have all CEA formats defined in * latest CEA-861(CTA-861) spec. So, don't check if mode is CEA mode * in here. Once core framework is updated, the check needs to be * added back. */ if (hdmi->hdmi_mode) { _sde_hdmi_bridge_set_avi_infoframe(hdmi, mode); _sde_hdmi_bridge_set_vs_infoframe(hdmi, mode); _sde_hdmi_bridge_set_spd_infoframe(hdmi, mode); DRM_DEBUG("hdmi setup info frame\n"); } hdmi_audio_update(hdmi); } static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = { .pre_enable = _sde_hdmi_bridge_pre_enable, .enable = _sde_hdmi_bridge_enable, .disable = _sde_hdmi_bridge_disable, .post_disable = _sde_hdmi_bridge_post_disable, .mode_set = _sde_hdmi_bridge_mode_set, }; /* initialize bridge */ struct drm_bridge *sde_hdmi_bridge_init(struct hdmi *hdmi) { struct drm_bridge *bridge = NULL; struct sde_hdmi_bridge *sde_hdmi_bridge; int ret; sde_hdmi_bridge = devm_kzalloc(hdmi->dev->dev, sizeof(*sde_hdmi_bridge), GFP_KERNEL); if (!sde_hdmi_bridge) { ret = -ENOMEM; goto fail; } sde_hdmi_bridge->hdmi = hdmi; bridge = &sde_hdmi_bridge->base; bridge->funcs = &_sde_hdmi_bridge_funcs; ret = drm_bridge_attach(hdmi->dev, bridge); if (ret) goto fail; return bridge; fail: if (bridge) _sde_hdmi_bridge_destroy(bridge); return ERR_PTR(ret); } Loading
drivers/gpu/drm/msm/Makefile +3 −1 Original line number Diff line number Diff line Loading @@ -98,7 +98,9 @@ msm_drm-$(CONFIG_DRM_MSM_DSI_STAGING) += dsi-staging/dsi_phy.o \ dsi-staging/dsi_display_test.o msm_drm-$(CONFIG_DRM_SDE_HDMI) += \ hdmi-staging/sde_hdmi.o hdmi-staging/sde_hdmi.o \ hdmi-staging/sde_hdmi_bridge.o \ hdmi-staging/sde_hdmi_audio.o msm_drm-$(CONFIG_DRM_MSM_DSI_PLL) += dsi/pll/dsi_pll.o \ dsi/pll/dsi_pll_28nm.o Loading
drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c +30 −4 Original line number Diff line number Diff line Loading @@ -325,9 +325,9 @@ static int _sde_hdmi_hpd_enable(struct sde_hdmi *sde_hdmi) } } hdmi_set_mode(hdmi, false); sde_hdmi_set_mode(hdmi, false); _sde_hdmi_phy_reset(hdmi); hdmi_set_mode(hdmi, true); sde_hdmi_set_mode(hdmi, true); hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b); Loading Loading @@ -367,7 +367,7 @@ static void _sde_hdmi_hdp_disable(struct sde_hdmi *sde_hdmi) /* Disable HPD interrupt */ hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0); hdmi_set_mode(hdmi, false); sde_hdmi_set_mode(hdmi, false); for (i = 0; i < config->hpd_clk_cnt; i++) clk_disable_unprepare(hdmi->hpd_clks[i]); Loading Loading @@ -460,6 +460,32 @@ static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id) return IRQ_HANDLED; } void sde_hdmi_set_mode(struct hdmi *hdmi, bool power_on) { uint32_t ctrl = 0; unsigned long flags; spin_lock_irqsave(&hdmi->reg_lock, flags); ctrl = hdmi_read(hdmi, REG_HDMI_CTRL); if (power_on) { ctrl |= HDMI_CTRL_ENABLE; if (!hdmi->hdmi_mode) { ctrl |= HDMI_CTRL_HDMI; hdmi_write(hdmi, REG_HDMI_CTRL, ctrl); ctrl &= ~HDMI_CTRL_HDMI; } else { ctrl |= HDMI_CTRL_HDMI; } } else { ctrl &= ~HDMI_CTRL_HDMI; } hdmi_write(hdmi, REG_HDMI_CTRL, ctrl); spin_unlock_irqrestore(&hdmi->reg_lock, flags); DRM_DEBUG("HDMI Core: %s, HDMI_CTRL=0x%08x\n", power_on ? "Enable" : "Disable", ctrl); } int sde_hdmi_get_info(struct msm_display_info *info, void *display) { Loading Loading @@ -1087,7 +1113,7 @@ int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc) hdmi_audio_infoframe_init(&hdmi->audio.infoframe); hdmi->bridge = hdmi_bridge_init(hdmi); hdmi->bridge = sde_hdmi_bridge_init(hdmi); if (IS_ERR(hdmi->bridge)) { rc = PTR_ERR(hdmi->bridge); SDE_ERROR("failed to create HDMI bridge: %d\n", rc); Loading
drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h +44 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <linux/bitops.h> #include <linux/debugfs.h> #include <linux/of_device.h> #include <linux/msm_ext_display.h> #include <drm/drmP.h> #include <drm/drm_crtc.h> Loading Loading @@ -224,6 +225,49 @@ int sde_hdmi_drm_deinit(struct sde_hdmi *display); int sde_hdmi_get_info(struct msm_display_info *info, void *display); /** * sde_hdmi_bridge_init() - init sde hdmi bridge * @hdmi: Handle to the hdmi. * * Return: struct drm_bridge *. */ struct drm_bridge *sde_hdmi_bridge_init(struct hdmi *hdmi); /** * sde_hdmi_set_mode() - Set HDMI mode API. * @hdmi: Handle to the hdmi. * @power_on: Power on/off request. * * Return: void. */ void sde_hdmi_set_mode(struct hdmi *hdmi, bool power_on); /** * sde_hdmi_audio_on() - enable hdmi audio. * @hdmi: Handle to the hdmi. * @params: audio setup parameters from codec. * * Return: error code. */ int sde_hdmi_audio_on(struct hdmi *hdmi, struct msm_ext_disp_audio_setup_params *params); /** * sde_hdmi_audio_off() - disable hdmi audio. * @hdmi: Handle to the hdmi. * * Return: void. */ void sde_hdmi_audio_off(struct hdmi *hdmi); /** * sde_hdmi_config_avmute() - mute hdmi. * @hdmi: Handle to the hdmi. * @set: enable/disable avmute. * * Return: error code. */ int sde_hdmi_config_avmute(struct hdmi *hdmi, bool set); #else /*#ifdef CONFIG_DRM_SDE_HDMI*/ static inline u32 sde_hdmi_get_num_of_displays(void) Loading
drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c 0 → 100644 +393 −0 Original line number Diff line number Diff line /* Copyright (c) 2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/slab.h> #include <linux/bitops.h> #include <linux/delay.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/iopoll.h> #include <linux/types.h> #include <linux/switch.h> #include <linux/gcd.h> #include "drm_edid.h" #include "sde_kms.h" #include "sde_hdmi.h" #include "sde_hdmi_regs.h" #include "hdmi.h" #define HDMI_AUDIO_INFO_FRAME_PACKET_HEADER 0x84 #define HDMI_AUDIO_INFO_FRAME_PACKET_VERSION 0x1 #define HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH 0x0A #define HDMI_KHZ_TO_HZ 1000 #define HDMI_MHZ_TO_HZ 1000000 #define HDMI_ACR_N_MULTIPLIER 128 #define DEFAULT_AUDIO_SAMPLE_RATE_HZ 48000 /* Supported HDMI Audio channels */ enum hdmi_audio_channels { AUDIO_CHANNEL_2 = 2, AUDIO_CHANNEL_3, AUDIO_CHANNEL_4, AUDIO_CHANNEL_5, AUDIO_CHANNEL_6, AUDIO_CHANNEL_7, AUDIO_CHANNEL_8, }; /* parameters for clock regeneration */ struct hdmi_audio_acr { u32 n; u32 cts; }; enum hdmi_audio_sample_rates { AUDIO_SAMPLE_RATE_32KHZ, AUDIO_SAMPLE_RATE_44_1KHZ, AUDIO_SAMPLE_RATE_48KHZ, AUDIO_SAMPLE_RATE_88_2KHZ, AUDIO_SAMPLE_RATE_96KHZ, AUDIO_SAMPLE_RATE_176_4KHZ, AUDIO_SAMPLE_RATE_192KHZ, AUDIO_SAMPLE_RATE_MAX }; struct sde_hdmi_audio { struct hdmi *hdmi; struct msm_ext_disp_audio_setup_params params; u32 pclk; }; static void _sde_hdmi_audio_get_audio_sample_rate(u32 *sample_rate_hz) { u32 rate = *sample_rate_hz; switch (rate) { case 32000: *sample_rate_hz = AUDIO_SAMPLE_RATE_32KHZ; break; case 44100: *sample_rate_hz = AUDIO_SAMPLE_RATE_44_1KHZ; break; case 48000: *sample_rate_hz = AUDIO_SAMPLE_RATE_48KHZ; break; case 88200: *sample_rate_hz = AUDIO_SAMPLE_RATE_88_2KHZ; break; case 96000: *sample_rate_hz = AUDIO_SAMPLE_RATE_96KHZ; break; case 176400: *sample_rate_hz = AUDIO_SAMPLE_RATE_176_4KHZ; break; case 192000: *sample_rate_hz = AUDIO_SAMPLE_RATE_192KHZ; break; default: SDE_ERROR("%d unchanged\n", rate); break; } } static void _sde_hdmi_audio_get_acr_param(u32 pclk, u32 fs, struct hdmi_audio_acr *acr) { u32 div, mul; if (!acr) { SDE_ERROR("invalid data\n"); return; } /* * as per HDMI specification, N/CTS = (128*fs)/pclk. * get the ratio using this formula. */ acr->n = HDMI_ACR_N_MULTIPLIER * fs; acr->cts = pclk; /* get the greatest common divisor for the ratio */ div = gcd(acr->n, acr->cts); /* get the n and cts values wrt N/CTS formula */ acr->n /= div; acr->cts /= div; /* * as per HDMI specification, 300 <= 128*fs/N <= 1500 * with a target of 128*fs/N = 1000. To get closest * value without truncating fractional values, find * the corresponding multiplier */ mul = ((HDMI_ACR_N_MULTIPLIER * fs / HDMI_KHZ_TO_HZ) + (acr->n - 1)) / acr->n; acr->n *= mul; acr->cts *= mul; } static void _sde_hdmi_audio_acr_enable(struct sde_hdmi_audio *audio) { struct hdmi_audio_acr acr; struct msm_ext_disp_audio_setup_params *params; u32 pclk, layout, multiplier = 1, sample_rate; u32 acr_pkt_ctl, aud_pkt_ctl2, acr_reg_cts, acr_reg_n; struct hdmi *hdmi; hdmi = audio->hdmi; params = &audio->params; pclk = audio->pclk; sample_rate = params->sample_rate_hz; _sde_hdmi_audio_get_acr_param(pclk, sample_rate, &acr); _sde_hdmi_audio_get_audio_sample_rate(&sample_rate); layout = (params->num_of_channels == AUDIO_CHANNEL_2) ? 0 : 1; SDE_DEBUG("n=%u, cts=%u, layout=%u\n", acr.n, acr.cts, layout); /* AUDIO_PRIORITY | SOURCE */ acr_pkt_ctl = BIT(31) | BIT(8); switch (sample_rate) { case AUDIO_SAMPLE_RATE_44_1KHZ: acr_pkt_ctl |= 0x2 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_44_0; acr_reg_n = HDMI_ACR_44_1; break; case AUDIO_SAMPLE_RATE_48KHZ: acr_pkt_ctl |= 0x3 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_48_0; acr_reg_n = HDMI_ACR_48_1; break; case AUDIO_SAMPLE_RATE_192KHZ: multiplier = 4; acr.n >>= 2; acr_pkt_ctl |= 0x3 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_48_0; acr_reg_n = HDMI_ACR_48_1; break; case AUDIO_SAMPLE_RATE_176_4KHZ: multiplier = 4; acr.n >>= 2; acr_pkt_ctl |= 0x2 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_44_0; acr_reg_n = HDMI_ACR_44_1; break; case AUDIO_SAMPLE_RATE_96KHZ: multiplier = 2; acr.n >>= 1; acr_pkt_ctl |= 0x3 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_48_0; acr_reg_n = HDMI_ACR_48_1; break; case AUDIO_SAMPLE_RATE_88_2KHZ: multiplier = 2; acr.n >>= 1; acr_pkt_ctl |= 0x2 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_44_0; acr_reg_n = HDMI_ACR_44_1; break; default: multiplier = 1; acr_pkt_ctl |= 0x1 << 4; acr.cts <<= 12; acr_reg_cts = HDMI_ACR_32_0; acr_reg_n = HDMI_ACR_32_1; break; } aud_pkt_ctl2 = BIT(0) | (layout << 1); /* N_MULTIPLE(multiplier) */ acr_pkt_ctl &= ~(7 << 16); acr_pkt_ctl |= (multiplier & 0x7) << 16; /* SEND | CONT */ acr_pkt_ctl |= BIT(0) | BIT(1); hdmi_write(hdmi, acr_reg_cts, acr.cts); hdmi_write(hdmi, acr_reg_n, acr.n); hdmi_write(hdmi, HDMI_ACR_PKT_CTRL, acr_pkt_ctl); hdmi_write(hdmi, HDMI_AUDIO_PKT_CTRL2, aud_pkt_ctl2); } static void _sde_hdmi_audio_acr_setup(struct sde_hdmi_audio *audio, bool on) { if (on) _sde_hdmi_audio_acr_enable(audio); else hdmi_write(audio->hdmi, HDMI_ACR_PKT_CTRL, 0); } static void _sde_hdmi_audio_infoframe_setup(struct sde_hdmi_audio *audio, bool enabled) { struct hdmi *hdmi = audio->hdmi; u32 channels, channel_allocation, level_shift, down_mix, layout; u32 hdmi_debug_reg = 0, audio_info_0_reg = 0, audio_info_1_reg = 0; u32 audio_info_ctrl_reg, aud_pck_ctrl_2_reg; u32 check_sum, sample_present; audio_info_ctrl_reg = hdmi_read(hdmi, HDMI_INFOFRAME_CTRL0); audio_info_ctrl_reg &= ~0xF0; if (!enabled) goto end; channels = audio->params.num_of_channels - 1; channel_allocation = audio->params.channel_allocation; level_shift = audio->params.level_shift; down_mix = audio->params.down_mix; sample_present = audio->params.sample_present; layout = (audio->params.num_of_channels == AUDIO_CHANNEL_2) ? 0 : 1; aud_pck_ctrl_2_reg = BIT(0) | (layout << 1); hdmi_write(hdmi, HDMI_AUDIO_PKT_CTRL2, aud_pck_ctrl_2_reg); audio_info_1_reg |= channel_allocation & 0xFF; audio_info_1_reg |= ((level_shift & 0xF) << 11); audio_info_1_reg |= ((down_mix & 0x1) << 15); check_sum = 0; check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_HEADER; check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_VERSION; check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH; check_sum += channels; check_sum += channel_allocation; check_sum += (level_shift & 0xF) << 3 | (down_mix & 0x1) << 7; check_sum &= 0xFF; check_sum = (u8) (256 - check_sum); audio_info_0_reg |= check_sum & 0xFF; audio_info_0_reg |= ((channels & 0x7) << 8); /* Enable Audio InfoFrame Transmission */ audio_info_ctrl_reg |= 0xF0; if (layout) { /* Set the Layout bit */ hdmi_debug_reg |= BIT(4); /* Set the Sample Present bits */ hdmi_debug_reg |= sample_present & 0xF; } end: hdmi_write(hdmi, HDMI_DEBUG, hdmi_debug_reg); hdmi_write(hdmi, HDMI_AUDIO_INFO0, audio_info_0_reg); hdmi_write(hdmi, HDMI_AUDIO_INFO1, audio_info_1_reg); hdmi_write(hdmi, HDMI_INFOFRAME_CTRL0, audio_info_ctrl_reg); } int sde_hdmi_audio_on(struct hdmi *hdmi, struct msm_ext_disp_audio_setup_params *params) { struct sde_hdmi_audio audio; int rc = 0; if (!hdmi) { SDE_ERROR("invalid HDMI Ctrl\n"); rc = -ENODEV; goto end; } audio.pclk = hdmi->pixclock; audio.params = *params; audio.hdmi = hdmi; if (!audio.params.num_of_channels) { audio.params.sample_rate_hz = DEFAULT_AUDIO_SAMPLE_RATE_HZ; audio.params.num_of_channels = AUDIO_CHANNEL_2; } _sde_hdmi_audio_acr_setup(&audio, true); _sde_hdmi_audio_infoframe_setup(&audio, true); SDE_DEBUG("HDMI Audio: Enabled\n"); end: return rc; } void sde_hdmi_audio_off(struct hdmi *hdmi) { struct sde_hdmi_audio audio; int rc = 0; if (!hdmi) { SDE_ERROR("invalid HDMI Ctrl\n"); rc = -ENODEV; return; } audio.hdmi = hdmi; _sde_hdmi_audio_infoframe_setup(&audio, false); _sde_hdmi_audio_acr_setup(&audio, false); SDE_DEBUG("HDMI Audio: Disabled\n"); } int sde_hdmi_config_avmute(struct hdmi *hdmi, bool set) { u32 av_mute_status; bool av_pkt_en = false; if (!hdmi) { SDE_ERROR("invalid HDMI Ctrl\n"); return -ENODEV; } av_mute_status = hdmi_read(hdmi, HDMI_GC); if (set) { if (!(av_mute_status & BIT(0))) { hdmi_write(hdmi, HDMI_GC, av_mute_status | BIT(0)); av_pkt_en = true; } } else { if (av_mute_status & BIT(0)) { hdmi_write(hdmi, HDMI_GC, av_mute_status & ~BIT(0)); av_pkt_en = true; } } /* Enable AV Mute tranmission here */ if (av_pkt_en) hdmi_write(hdmi, HDMI_VBI_PKT_CTRL, hdmi_read(hdmi, HDMI_VBI_PKT_CTRL) | (BIT(4) & BIT(5))); SDE_DEBUG("AVMUTE %s\n", set ? "set" : "cleared"); return 0; }
drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c 0 → 100644 +388 −0 Original line number Diff line number Diff line /* * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. * Copyright (C) 2013 Red Hat * Author: Rob Clark <robdclark@gmail.com> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ #include "drm_edid.h" #include "sde_kms.h" #include "sde_hdmi.h" #include "hdmi.h" struct sde_hdmi_bridge { struct drm_bridge base; struct hdmi *hdmi; }; #define to_hdmi_bridge(x) container_of(x, struct sde_hdmi_bridge, base) /* for AVI program */ #define HDMI_AVI_INFOFRAME_BUFFER_SIZE \ (HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE) #define HDMI_VS_INFOFRAME_BUFFER_SIZE (HDMI_INFOFRAME_HEADER_SIZE + 6) #define HDMI_SPD_INFOFRAME_BUFFER_SIZE \ (HDMI_INFOFRAME_HEADER_SIZE + HDMI_SPD_INFOFRAME_SIZE) #define HDMI_DEFAULT_VENDOR_NAME "unknown" #define HDMI_DEFAULT_PRODUCT_NAME "msm" #define LEFT_SHIFT_BYTE(x) ((x) << 8) #define LEFT_SHIFT_WORD(x) ((x) << 16) #define LEFT_SHIFT_24BITS(x) ((x) << 24) #define HDMI_AVI_IFRAME_LINE_NUMBER 1 #define HDMI_VENDOR_IFRAME_LINE_NUMBER 3 void _sde_hdmi_bridge_destroy(struct drm_bridge *bridge) { } static void _sde_hdmi_bridge_power_on(struct drm_bridge *bridge) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; const struct hdmi_platform_config *config = hdmi->config; int i, ret; for (i = 0; i < config->pwr_reg_cnt; i++) { ret = regulator_enable(hdmi->pwr_regs[i]); if (ret) { SDE_ERROR("failed to enable pwr regulator: %s (%d)\n", config->pwr_reg_names[i], ret); } } if (config->pwr_clk_cnt > 0) { DRM_DEBUG("pixclock: %lu", hdmi->pixclock); ret = clk_set_rate(hdmi->pwr_clks[0], hdmi->pixclock); if (ret) { SDE_ERROR("failed to set pixel clk: %s (%d)\n", config->pwr_clk_names[0], ret); } } for (i = 0; i < config->pwr_clk_cnt; i++) { ret = clk_prepare_enable(hdmi->pwr_clks[i]); if (ret) { SDE_ERROR("failed to enable pwr clk: %s (%d)\n", config->pwr_clk_names[i], ret); } } } static void _sde_hdmi_bridge_power_off(struct drm_bridge *bridge) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; const struct hdmi_platform_config *config = hdmi->config; int i, ret; /* Wait for vsync */ msleep(20); for (i = 0; i < config->pwr_clk_cnt; i++) clk_disable_unprepare(hdmi->pwr_clks[i]); for (i = 0; i < config->pwr_reg_cnt; i++) { ret = regulator_disable(hdmi->pwr_regs[i]); if (ret) { SDE_ERROR("failed to disable pwr regulator: %s (%d)\n", config->pwr_reg_names[i], ret); } } } static void _sde_hdmi_bridge_pre_enable(struct drm_bridge *bridge) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; struct hdmi_phy *phy = hdmi->phy; DRM_DEBUG("power up"); if (!hdmi->power_on) { _sde_hdmi_bridge_power_on(bridge); hdmi->power_on = true; hdmi_audio_update(hdmi); } if (phy) phy->funcs->powerup(phy, hdmi->pixclock); sde_hdmi_set_mode(hdmi, true); if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported) hdmi_hdcp_ctrl_on(hdmi->hdcp_ctrl); } static void _sde_hdmi_bridge_enable(struct drm_bridge *bridge) { } static void _sde_hdmi_bridge_disable(struct drm_bridge *bridge) { } static void _sde_hdmi_bridge_post_disable(struct drm_bridge *bridge) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; struct hdmi_phy *phy = hdmi->phy; if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported) hdmi_hdcp_ctrl_off(hdmi->hdcp_ctrl); DRM_DEBUG("power down"); sde_hdmi_set_mode(hdmi, false); if (phy) phy->funcs->powerdown(phy); if (hdmi->power_on) { _sde_hdmi_bridge_power_off(bridge); hdmi->power_on = false; hdmi_audio_update(hdmi); } } static void _sde_hdmi_bridge_set_avi_infoframe(struct hdmi *hdmi, const struct drm_display_mode *mode) { u8 avi_iframe[HDMI_AVI_INFOFRAME_BUFFER_SIZE] = {0}; u8 *avi_frame = &avi_iframe[HDMI_INFOFRAME_HEADER_SIZE]; u8 checksum; u32 reg_val; struct hdmi_avi_infoframe info; drm_hdmi_avi_infoframe_from_display_mode(&info, mode); hdmi_avi_infoframe_pack(&info, avi_iframe, sizeof(avi_iframe)); checksum = avi_iframe[HDMI_INFOFRAME_HEADER_SIZE - 1]; reg_val = checksum | LEFT_SHIFT_BYTE(avi_frame[0]) | LEFT_SHIFT_WORD(avi_frame[1]) | LEFT_SHIFT_24BITS(avi_frame[2]); hdmi_write(hdmi, REG_HDMI_AVI_INFO(0), reg_val); reg_val = avi_frame[3] | LEFT_SHIFT_BYTE(avi_frame[4]) | LEFT_SHIFT_WORD(avi_frame[5]) | LEFT_SHIFT_24BITS(avi_frame[6]); hdmi_write(hdmi, REG_HDMI_AVI_INFO(1), reg_val); reg_val = avi_frame[7] | LEFT_SHIFT_BYTE(avi_frame[8]) | LEFT_SHIFT_WORD(avi_frame[9]) | LEFT_SHIFT_24BITS(avi_frame[10]); hdmi_write(hdmi, REG_HDMI_AVI_INFO(2), reg_val); reg_val = avi_frame[11] | LEFT_SHIFT_BYTE(avi_frame[12]) | LEFT_SHIFT_24BITS(avi_iframe[1]); hdmi_write(hdmi, REG_HDMI_AVI_INFO(3), reg_val); /* AVI InfFrame enable (every frame) */ hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0) | BIT(1) | BIT(0)); reg_val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); reg_val &= ~0x3F; reg_val |= HDMI_AVI_IFRAME_LINE_NUMBER; hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, reg_val); } static void _sde_hdmi_bridge_set_vs_infoframe(struct hdmi *hdmi, const struct drm_display_mode *mode) { u8 vs_iframe[HDMI_VS_INFOFRAME_BUFFER_SIZE] = {0}; u32 reg_val; struct hdmi_vendor_infoframe info; int rc = 0; rc = drm_hdmi_vendor_infoframe_from_display_mode(&info, mode); if (rc < 0) { SDE_DEBUG("don't send vendor infoframe\n"); return; } hdmi_vendor_infoframe_pack(&info, vs_iframe, sizeof(vs_iframe)); reg_val = (info.s3d_struct << 24) | (info.vic << 16) | (vs_iframe[3] << 8) | (vs_iframe[7] << 5) | vs_iframe[2]; hdmi_write(hdmi, REG_HDMI_VENSPEC_INFO0, reg_val); /* vendor specific info-frame enable (every frame) */ hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0) | BIT(13) | BIT(12)); reg_val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1); reg_val &= ~0x3F000000; reg_val |= (HDMI_VENDOR_IFRAME_LINE_NUMBER << 24); hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, reg_val); } static void _sde_hdmi_bridge_set_spd_infoframe(struct hdmi *hdmi, const struct drm_display_mode *mode) { u8 spd_iframe[HDMI_SPD_INFOFRAME_BUFFER_SIZE] = {0}; u32 packet_payload, packet_control, packet_header; struct hdmi_spd_infoframe info; int i; /* Need to query vendor and product name from platform setup */ hdmi_spd_infoframe_init(&info, HDMI_DEFAULT_VENDOR_NAME, HDMI_DEFAULT_PRODUCT_NAME); hdmi_spd_infoframe_pack(&info, spd_iframe, sizeof(spd_iframe)); packet_header = spd_iframe[0] | LEFT_SHIFT_BYTE(spd_iframe[1] & 0x7f) | LEFT_SHIFT_WORD(spd_iframe[2] & 0x7f); hdmi_write(hdmi, REG_HDMI_GENERIC1_HDR, packet_header); for (i = 0; i < MAX_REG_HDMI_GENERIC1_INDEX; i++) { packet_payload = spd_iframe[3 + i * 4] | LEFT_SHIFT_BYTE(spd_iframe[4 + i * 4] & 0x7f) | LEFT_SHIFT_WORD(spd_iframe[5 + i * 4] & 0x7f) | LEFT_SHIFT_24BITS(spd_iframe[6 + i * 4] & 0x7f); hdmi_write(hdmi, REG_HDMI_GENERIC1(i), packet_payload); } packet_payload = (spd_iframe[27] & 0x7f) | LEFT_SHIFT_BYTE(spd_iframe[28] & 0x7f); hdmi_write(hdmi, REG_HDMI_GENERIC1(MAX_REG_HDMI_GENERIC1_INDEX), packet_payload); /* * GENERIC1_LINE | GENERIC1_CONT | GENERIC1_SEND * Setup HDMI TX generic packet control * Enable this packet to transmit every frame * Enable HDMI TX engine to transmit Generic packet 1 */ packet_control = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL); packet_control |= ((0x1 << 24) | (1 << 5) | (1 << 4)); hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, packet_control); } static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); struct hdmi *hdmi = sde_hdmi_bridge->hdmi; int hstart, hend, vstart, vend; uint32_t frame_ctrl; mode = adjusted_mode; hdmi->pixclock = mode->clock * 1000; hstart = mode->htotal - mode->hsync_start; hend = mode->htotal - mode->hsync_start + mode->hdisplay; vstart = mode->vtotal - mode->vsync_start - 1; vend = mode->vtotal - mode->vsync_start + mode->vdisplay - 1; DRM_DEBUG( "htotal=%d, vtotal=%d, hstart=%d, hend=%d, vstart=%d, vend=%d", mode->htotal, mode->vtotal, hstart, hend, vstart, vend); hdmi_write(hdmi, REG_HDMI_TOTAL, HDMI_TOTAL_H_TOTAL(mode->htotal - 1) | HDMI_TOTAL_V_TOTAL(mode->vtotal - 1)); hdmi_write(hdmi, REG_HDMI_ACTIVE_HSYNC, HDMI_ACTIVE_HSYNC_START(hstart) | HDMI_ACTIVE_HSYNC_END(hend)); hdmi_write(hdmi, REG_HDMI_ACTIVE_VSYNC, HDMI_ACTIVE_VSYNC_START(vstart) | HDMI_ACTIVE_VSYNC_END(vend)); if (mode->flags & DRM_MODE_FLAG_INTERLACE) { hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2, HDMI_VSYNC_TOTAL_F2_V_TOTAL(mode->vtotal)); hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2, HDMI_VSYNC_ACTIVE_F2_START(vstart + 1) | HDMI_VSYNC_ACTIVE_F2_END(vend + 1)); } else { hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2, HDMI_VSYNC_TOTAL_F2_V_TOTAL(0)); hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2, HDMI_VSYNC_ACTIVE_F2_START(0) | HDMI_VSYNC_ACTIVE_F2_END(0)); } frame_ctrl = 0; if (mode->flags & DRM_MODE_FLAG_NHSYNC) frame_ctrl |= HDMI_FRAME_CTRL_HSYNC_LOW; if (mode->flags & DRM_MODE_FLAG_NVSYNC) frame_ctrl |= HDMI_FRAME_CTRL_VSYNC_LOW; if (mode->flags & DRM_MODE_FLAG_INTERLACE) frame_ctrl |= HDMI_FRAME_CTRL_INTERLACED_EN; DRM_DEBUG("frame_ctrl=%08x\n", frame_ctrl); hdmi_write(hdmi, REG_HDMI_FRAME_CTRL, frame_ctrl); /* * Setup info frame * Current drm_edid driver doesn't have all CEA formats defined in * latest CEA-861(CTA-861) spec. So, don't check if mode is CEA mode * in here. Once core framework is updated, the check needs to be * added back. */ if (hdmi->hdmi_mode) { _sde_hdmi_bridge_set_avi_infoframe(hdmi, mode); _sde_hdmi_bridge_set_vs_infoframe(hdmi, mode); _sde_hdmi_bridge_set_spd_infoframe(hdmi, mode); DRM_DEBUG("hdmi setup info frame\n"); } hdmi_audio_update(hdmi); } static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = { .pre_enable = _sde_hdmi_bridge_pre_enable, .enable = _sde_hdmi_bridge_enable, .disable = _sde_hdmi_bridge_disable, .post_disable = _sde_hdmi_bridge_post_disable, .mode_set = _sde_hdmi_bridge_mode_set, }; /* initialize bridge */ struct drm_bridge *sde_hdmi_bridge_init(struct hdmi *hdmi) { struct drm_bridge *bridge = NULL; struct sde_hdmi_bridge *sde_hdmi_bridge; int ret; sde_hdmi_bridge = devm_kzalloc(hdmi->dev->dev, sizeof(*sde_hdmi_bridge), GFP_KERNEL); if (!sde_hdmi_bridge) { ret = -ENOMEM; goto fail; } sde_hdmi_bridge->hdmi = hdmi; bridge = &sde_hdmi_bridge->base; bridge->funcs = &_sde_hdmi_bridge_funcs; ret = drm_bridge_attach(hdmi->dev, bridge); if (ret) goto fail; return bridge; fail: if (bridge) _sde_hdmi_bridge_destroy(bridge); return ERR_PTR(ret); }