diff --git a/projects/Generic/linux/linux.x86_64.conf b/projects/Generic/linux/linux.x86_64.conf index 8a78117616..c32dcdb00c 100644 --- a/projects/Generic/linux/linux.x86_64.conf +++ b/projects/Generic/linux/linux.x86_64.conf @@ -3512,6 +3512,7 @@ CONFIG_SND_SOC_TS3A227E=m CONFIG_SND_SIMPLE_CARD=m # CONFIG_SOUND_PRIME is not set CONFIG_AC97_BUS=m +CONFIG_SUPPORT_HDMI=y # # HID support diff --git a/projects/Generic/patches/linux/linux-030-BYT-CHT-SOC-audio-support.patch b/projects/Generic/patches/linux/linux-030-BYT-CHT-SOC-audio-support.patch new file mode 100644 index 0000000000..6c67e87f48 --- /dev/null +++ b/projects/Generic/patches/linux/linux-030-BYT-CHT-SOC-audio-support.patch @@ -0,0 +1,5713 @@ +From d7c07ade8f6b82a3fd299e749f98381f3c3c2057 Mon Sep 17 00:00:00 2001 +From: Pierre-Louis Bossart +Date: Wed, 2 Mar 2016 14:30:44 -0600 +Subject: [PATCH 01/12] drm: i915: remove intel_hdmi variable declaration + +'intel_hdmi' variable is redeclared, use same variable declared in +function scope. + +Signed-off-by: Pierre-Louis Bossart +--- + drivers/gpu/drm/i915/intel_hdmi.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c +index a884470..2adcc15 100644 +--- a/drivers/gpu/drm/i915/intel_hdmi.c ++++ b/drivers/gpu/drm/i915/intel_hdmi.c +@@ -1514,7 +1514,7 @@ intel_hdmi_detect(struct drm_connector *connector, bool force) + intel_hdmi_unset_edid(connector); + + if (intel_hdmi_set_edid(connector, live_status)) { +- struct intel_hdmi *intel_hdmi = intel_attached_hdmi(connector); ++ intel_hdmi = intel_attached_hdmi(connector); + + hdmi_to_dig_port(intel_hdmi)->base.type = INTEL_OUTPUT_HDMI; + status = connector_status_connected; + +From 0dffccdf5025a92d38f18c1698e542a8bd2de903 Mon Sep 17 00:00:00 2001 +From: Pierre-Louis Bossart +Date: Tue, 1 Mar 2016 16:25:04 -0600 +Subject: [PATCH 02/12] drm/i915: Add headers for non-HDAudio HDMI interface + +Add header files for interface available on Baytrail and CherryTrail + +Initial code was downloaded from https://github.com/01org/baytrailaudio/ +...and had the changes to .config stripped and the revert on sound/init.c +done by David Henningson + +Clean-up, port to 4.5 and intel-drm by Pierre Bossart +CherryTrail support by Jerome Anand + +Signed-off-by: David Henningsson +Signed-off-by: Pierre-Louis Bossart +--- + drivers/gpu/drm/i915/hdmi_audio_if.h | 125 +++++++++++++++++++++++++++++++++++ + drivers/gpu/drm/i915/i915_drv.h | 32 +++++++++ + drivers/gpu/drm/i915/i915_reg.h | 22 ++++++ + drivers/gpu/drm/i915/intel_drv.h | 13 ++++ + 4 files changed, 192 insertions(+) + create mode 100644 drivers/gpu/drm/i915/hdmi_audio_if.h + +diff --git a/drivers/gpu/drm/i915/hdmi_audio_if.h b/drivers/gpu/drm/i915/hdmi_audio_if.h +new file mode 100644 +index 0000000..165bba5 +--- /dev/null ++++ b/drivers/gpu/drm/i915/hdmi_audio_if.h +@@ -0,0 +1,125 @@ ++/* ++ * Copyright (c) 2010, Intel Corporation. ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope 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. ++ * ++ * Authors: ++ * jim liu ++ * Uma Shankar ++ */ ++ ++ ++#ifndef __HDMI_AUDIO_IF_H ++#define __HDMI_AUDIO_IF_H ++ ++#include ++#include ++ ++/* HDMI AUDIO INTERRUPT TYPE */ ++#define HDMI_AUDIO_UNDERRUN (1UL<<0) ++#define HDMI_AUDIO_BUFFER_DONE (1UL<<1) ++ ++/* the monitor type HDMI or DVI */ ++#define MONITOR_TYPE_HDMI 1 ++#define MONITOR_TYPE_DVI 2 ++ ++extern int i915_hdmi_state; ++ ++enum had_caps_list { ++ HAD_GET_ELD = 1, ++ HAD_GET_SAMPLING_FREQ, ++ HAD_GET_DISPLAY_RATE, ++ HAD_GET_HDCP_STATUS, ++ HAD_GET_AUDIO_STATUS, ++ HAD_SET_ENABLE_AUDIO, ++ HAD_SET_DISABLE_AUDIO, ++ HAD_SET_ENABLE_AUDIO_INT, ++ HAD_SET_DISABLE_AUDIO_INT, ++ OTHERS_TBD, ++}; ++ ++enum had_event_type { ++ HAD_EVENT_HOT_PLUG = 1, ++ HAD_EVENT_HOT_UNPLUG, ++ HAD_EVENT_MODE_CHANGING, ++ HAD_EVENT_PM_CHANGING, ++ HAD_EVENT_AUDIO_BUFFER_DONE, ++ HAD_EVENT_AUDIO_BUFFER_UNDERRUN, ++ HAD_EVENT_QUERY_IS_AUDIO_BUSY, ++ HAD_EVENT_QUERY_IS_AUDIO_SUSPENDED, ++}; ++ ++/* ++ * HDMI Display Controller Audio Interface ++ * ++ */ ++typedef int (*had_event_call_back) (enum had_event_type event_type, ++ void *ctxt_info); ++ ++struct hdmi_audio_registers_ops { ++ int (*hdmi_audio_get_register_base)(uint32_t *reg_base); ++ int (*hdmi_audio_read_register)(uint32_t reg_addr, uint32_t *data); ++ int (*hdmi_audio_write_register)(uint32_t reg_addr, uint32_t data); ++ int (*hdmi_audio_read_modify)(uint32_t reg_addr, uint32_t data, ++ uint32_t mask); ++}; ++ ++struct hdmi_audio_query_set_ops { ++ int (*hdmi_audio_get_caps)(enum had_caps_list query_element, ++ void *capabilties); ++ int (*hdmi_audio_set_caps)(enum had_caps_list set_element, ++ void *capabilties); ++}; ++ ++typedef struct hdmi_audio_event { ++ int type; ++} hdmi_audio_event_t; ++ ++struct snd_intel_had_interface { ++ const char *name; ++ int (*query)(void *had_data, hdmi_audio_event_t event); ++ int (*suspend)(void *had_data, hdmi_audio_event_t event); ++ int (*resume)(void *had_data); ++}; ++ ++struct hdmi_audio_priv { ++ struct drm_device *dev; ++ u32 hdmi_reg; ++ u32 hdmi_lpe_audio_reg; ++ bool is_hdcp_supported; ++ bool hdmi_hpd_connected; ++ int monitor_type; ++ void *context; ++ int pipe; ++}; ++ ++extern void i915_hdmi_audio_init(struct hdmi_audio_priv *p_hdmi_priv); ++ ++extern bool mid_hdmi_audio_is_busy(struct drm_device *dev); ++extern bool mid_hdmi_audio_suspend(struct drm_device *dev); ++extern void mid_hdmi_audio_resume(struct drm_device *dev); ++extern void mid_hdmi_audio_signal_event(struct drm_device *dev, ++ enum had_event_type event); ++ ++/* Added for HDMI Audio */ ++extern void hdmi_get_eld(uint8_t *eld); ++extern struct hdmi_audio_priv *get_hdmi_priv(void); ++extern void hdmi_get_eld(uint8_t *eld); ++extern int i915_enable_hdmi_audio_int(struct drm_device *dev); ++extern int i915_disable_hdmi_audio_int(struct drm_device *dev); ++extern int mid_hdmi_audio_setup( ++ had_event_call_back audio_callbacks, ++ struct hdmi_audio_registers_ops *reg_ops, ++ struct hdmi_audio_query_set_ops *query_ops); ++extern int mid_hdmi_audio_register( ++ struct snd_intel_had_interface *driver, ++ void *had_data); ++ ++#endif /* __HDMI_AUDIO_IF_H */ +diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h +index bc3f2e6..7c2577d 100644 +--- a/drivers/gpu/drm/i915/i915_drv.h ++++ b/drivers/gpu/drm/i915/i915_drv.h +@@ -61,6 +61,8 @@ + #include "i915_gem_gtt.h" + #include "i915_gem_render_state.h" + ++#include "hdmi_audio_if.h" ++ + /* General customization: + */ + +@@ -1165,6 +1167,18 @@ struct intel_gen6_power_mgmt { + struct mutex hw_lock; + }; + ++/* Runtime power management related */ ++struct intel_gen7_rpm { ++ /* To track (num of get calls - num of put calls) ++ * made by procfs ++ */ ++ atomic_t procfs_count; ++ /* To make sure ring get/put are in pair */ ++ bool ring_active; ++ struct proc_dir_entry *i915_proc_dir; ++ struct proc_dir_entry *i915_proc_file; ++}; ++ + /* defined intel_pm.c */ + extern spinlock_t mchdev_lock; + +@@ -1997,6 +2011,19 @@ struct drm_i915_private { + + struct intel_encoder *dig_port_map[I915_MAX_PORTS]; + ++ /* Added for HDMI Audio */ ++ had_event_call_back had_event_callbacks; ++ struct snd_intel_had_interface *had_interface; ++ void *had_pvt_data; ++ int tmds_clock_speed; ++ int hdmi_audio_interrupt_mask; ++ struct work_struct hdmi_audio_wq; ++ ++ u32 hotplug_status; ++ ++ /* Runtime power management related */ ++ struct intel_gen7_rpm rpm; ++ + /* + * NOTE: This is the dri1/ums dungeon, don't add stuff here. Your patch + * will be rejected. Instead look for a better place. +@@ -3634,6 +3661,11 @@ int intel_freq_opcode(struct drm_i915_private *dev_priv, int val); + } while (upper != old_upper && loop++ < 2); \ + (u64)upper << 32 | lower; }) + ++int i915_rpm_get_disp(struct drm_device *dev); ++int i915_rpm_put_disp(struct drm_device *dev); ++ ++bool i915_is_device_active(struct drm_device *dev); ++ + #define POSTING_READ(reg) (void)I915_READ_NOTRACE(reg) + #define POSTING_READ16(reg) (void)I915_READ16_NOTRACE(reg) + +diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h +index 3fcf7dd..d3e5935 100644 +--- a/drivers/gpu/drm/i915/i915_reg.h ++++ b/drivers/gpu/drm/i915/i915_reg.h +@@ -2113,7 +2113,25 @@ enum skl_disp_power_wells { + #define I915_WINVALID_INTERRUPT (1<<1) + #define I915_USER_INTERRUPT (1<<1) + #define I915_ASLE_INTERRUPT (1<<0) ++#define I915_LPE_AUDIO_HDMI_STATUS_A _MMIO(dev_priv->info.display_mmio_offset + 0x65064) ++#define I915_LPE_AUDIO_HDMI_STATUS_B _MMIO(dev_priv->info.display_mmio_offset + 0x65864) ++#define I915_LPE_AUDIO_HDMI_STATUS_C _MMIO(dev_priv->info.display_mmio_offset + 0x65964) ++#define I915_HDMI_AUDIO_UNDERRUN (1UL<<31) ++#define I915_HDMI_AUDIO_BUFFER_DONE (1UL<<29) + #define I915_BSD_USER_INTERRUPT (1<<25) ++#define I915_HDMI_AUDIO_UNDERRUN_ENABLE (1UL<<15) ++ ++#define I915_HDMI_AUDIO_LPE_C_CONFIG 0x65900 ++#define I915_HDMI_AUDIO_LPE_B_CONFIG 0x65800 ++#define I915_HDMI_AUDIO_LPE_A_CONFIG 0x65000 ++ ++#define HDMI_LPE_AUDIO_PIPE_OFFSET 0x100 ++#define HDMI_LPE_AUDIO_PIPE_BC_OFFSET(pipe) \ ++ (I915_LPE_AUDIO_HDMI_STATUS_B + \ ++ (pipe - 1) * HDMI_LPE_AUDIO_PIPE_OFFSET) ++#define I915_LPE_AUDIO_HDMI_STATUS(pipe) \ ++ (pipe ? (HDMI_LPE_AUDIO_PIPE_BC_OFFSET(pipe)) : \ ++ I915_LPE_AUDIO_HDMI_STATUS_A) + + #define GEN6_BSD_RNCID _MMIO(0x12198) + +@@ -3433,6 +3451,9 @@ enum skl_disp_power_wells { + #define _GEN3_SDVOC 0x61160 + #define GEN3_SDVOB _MMIO(_GEN3_SDVOB) + #define GEN3_SDVOC _MMIO(_GEN3_SDVOC) ++#define HDMIB (dev_priv->info.display_mmio_offset + 0x61140) ++#define HDMIC (dev_priv->info.display_mmio_offset + 0x61160) ++#define HDMID (dev_priv->info.display_mmio_offset + 0x6116C) + #define GEN4_HDMIB GEN3_SDVOB + #define GEN4_HDMIC GEN3_SDVOC + #define VLV_HDMIB _MMIO(VLV_DISPLAY_BASE + 0x61140) +@@ -3442,6 +3463,7 @@ enum skl_disp_power_wells { + #define PCH_HDMIB PCH_SDVOB + #define PCH_HDMIC _MMIO(0xe1150) + #define PCH_HDMID _MMIO(0xe1160) ++#define PORT_ENABLE (1 << 31) + + #define PORT_DFT_I9XX _MMIO(0x61150) + #define DC_BALANCE_RESET (1 << 25) +diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h +index f7f0f01..2820ef2 100644 +--- a/drivers/gpu/drm/i915/intel_drv.h ++++ b/drivers/gpu/drm/i915/intel_drv.h +@@ -751,6 +751,14 @@ struct cxsr_latency { + #define to_intel_plane_state(x) container_of(x, struct intel_plane_state, base) + #define intel_fb_obj(x) (x ? to_intel_framebuffer(x)->obj : NULL) + ++/* HDMI bits are shared with the DP bits */ ++#define HDMIB_HOTPLUG_LIVE_STATUS (1 << 29) ++#define HDMIC_HOTPLUG_LIVE_STATUS (1 << 28) ++#define HDMID_HOTPLUG_LIVE_STATUS (1 << 27) ++#define HDMI_LIVE_STATUS_BASE 30 ++#define HDMI_LIVE_STATUS_DELAY_STEP 10 ++#define HDMI_EDID_RETRY_COUNT 3 ++ + struct intel_hdmi { + i915_reg_t hdmi_reg; + int ddc_bus; +@@ -766,6 +774,9 @@ struct intel_hdmi { + bool rgb_quant_range_selectable; + enum hdmi_picture_aspect aspect_ratio; + struct intel_connector *attached_connector; ++ struct edid *edid; ++ uint32_t edid_mode_count; ++ + void (*write_infoframe)(struct drm_encoder *encoder, + enum hdmi_infoframe_type type, + const void *frame, ssize_t len); +@@ -1196,6 +1207,8 @@ intel_rotation_90_or_270(unsigned int rotation) + + void intel_create_rotation_property(struct drm_device *dev, + struct intel_plane *plane); ++void chv_set_lpe_audio_reg_pipe(struct drm_device *dev, ++ int encoder_type, enum port port); + + void assert_pch_transcoder_disabled(struct drm_i915_private *dev_priv, + enum pipe pipe); + +From fdb02b86d0cc7c9ec341efdc3086aa703637f2b6 Mon Sep 17 00:00:00 2001 +From: Pierre-Louis Bossart +Date: Tue, 1 Mar 2016 16:25:04 -0600 +Subject: [PATCH 03/12] drm/i915: changes for non-HDAudio HDMI interface + +Changes to existing code for interface available on Baytrail and +CherryTrail + +This driver was downloaded from https://github.com/01org/baytrailaudio/ + +...and had the changes to .config stripped and the revert on sound/init.c + +Also squashed change from Toyo Abe to fix typos and underrun issues +To enable interrupt, IER, IIR, and IMR must be configured appropriately. +IER setting for hdmi_audio was missing. +This fixes the following warning in dmesg. +[ 302.369965] had: Driver detected 2 missed buffer done interrupt(s)!!!! + +includes fix to handle display resolution change and changes to +account for tmds clock set in vlv/chv_crtc_compute_clock + +Cleanup, correction for PIPE_A/PIPE_B inversions, +port to 4.5 and intel-drm by Pierre Bossart +CherryTrail support by Jerome Anand + +Signed-off-by: David Henningsson +Signed-off-by: Pierre-Louis Bossart +Signed-off-by: Toyo Abe +--- + drivers/gpu/drm/i915/i915_irq.c | 169 +++++++++++++++++++++++++++- + drivers/gpu/drm/i915/intel_display.c | 103 ++++++++++++++++- + drivers/gpu/drm/i915/intel_hdmi.c | 211 ++++++++++++++++++++++++++++++++++- + 3 files changed, 479 insertions(+), 4 deletions(-) + +diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c +index aab47f7..3ef0d14 100644 +--- a/drivers/gpu/drm/i915/i915_irq.c ++++ b/drivers/gpu/drm/i915/i915_irq.c +@@ -603,6 +603,42 @@ i915_disable_pipestat(struct drm_i915_private *dev_priv, enum pipe pipe, + __i915_disable_pipestat(dev_priv, pipe, enable_mask, status_mask); + } + ++/* Added for HDMI AUDIO */ ++void ++i915_enable_lpe_pipestat(struct drm_i915_private *dev_priv, int pipe) ++{ ++ u32 mask; ++ ++ mask = dev_priv->hdmi_audio_interrupt_mask; ++ mask |= I915_HDMI_AUDIO_UNDERRUN | I915_HDMI_AUDIO_BUFFER_DONE; ++ /* Enable the interrupt, clear any pending status */ ++ if (IS_CHERRYVIEW(dev_priv->dev)) { ++ I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_C, mask); ++ POSTING_READ(I915_LPE_AUDIO_HDMI_STATUS_C); ++ } else { ++ I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_A, mask); ++ POSTING_READ(I915_LPE_AUDIO_HDMI_STATUS_A); ++ } ++} ++ ++void ++i915_disable_lpe_pipestat(struct drm_i915_private *dev_priv, int pipe) ++{ ++ u32 mask; ++ ++ mask = dev_priv->hdmi_audio_interrupt_mask; ++ mask |= I915_HDMI_AUDIO_UNDERRUN | I915_HDMI_AUDIO_BUFFER_DONE; ++ /* Disable the interrupt, clear any pending status */ ++ if (IS_CHERRYVIEW(dev_priv->dev)) { ++ I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_C, mask); ++ POSTING_READ(I915_LPE_AUDIO_HDMI_STATUS_C); ++ ++ } else { ++ I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_A, mask); ++ POSTING_READ(I915_LPE_AUDIO_HDMI_STATUS_A); ++ } ++} ++ + /** + * i915_enable_asle_pipestat - enable ASLE pipestat for OpRegion + * @dev: drm device +@@ -1651,6 +1687,24 @@ static bool intel_pipe_handle_vblank(struct drm_device *dev, enum pipe pipe) + return true; + } + ++static inline ++void i915_notify_audio_buffer_status(struct drm_device *dev, const i915_reg_t reg) ++{ ++ u32 lpe_stream = 0; ++ struct drm_i915_private *dev_priv = dev->dev_private; ++ lpe_stream = I915_READ(reg); ++ if (lpe_stream & I915_HDMI_AUDIO_UNDERRUN) { ++ I915_WRITE(reg, I915_HDMI_AUDIO_UNDERRUN); ++ mid_hdmi_audio_signal_event(dev, ++ HAD_EVENT_AUDIO_BUFFER_UNDERRUN); ++ } ++ if (lpe_stream & I915_HDMI_AUDIO_BUFFER_DONE) { ++ I915_WRITE(reg, I915_HDMI_AUDIO_BUFFER_DONE); ++ mid_hdmi_audio_signal_event(dev, ++ HAD_EVENT_AUDIO_BUFFER_DONE); ++ } ++} ++ + static void valleyview_pipestat_irq_ack(struct drm_device *dev, u32 iir, + u32 pipe_stats[I915_MAX_PIPES]) + { +@@ -1853,6 +1907,23 @@ static irqreturn_t valleyview_irq_handler(int irq, void *arg) + i9xx_hpd_irq_handler(dev, hotplug_status); + + valleyview_pipestat_irq_handler(dev, pipe_stats); ++ ++ if (IS_CHERRYVIEW(dev)) { ++ // FIXME: plb: why are pipes and status mapped this way? ++ if (iir & I915_LPE_PIPE_C_INTERRUPT) ++ i915_notify_audio_buffer_status(dev, ++ I915_LPE_AUDIO_HDMI_STATUS_C); ++ if (iir & I915_LPE_PIPE_B_INTERRUPT) ++ i915_notify_audio_buffer_status(dev, ++ I915_LPE_AUDIO_HDMI_STATUS_B); ++ if (iir & I915_LPE_PIPE_A_INTERRUPT) ++ i915_notify_audio_buffer_status(dev, ++ I915_LPE_AUDIO_HDMI_STATUS_A); ++ } else { ++ if (iir & I915_LPE_PIPE_B_INTERRUPT) ++ i915_notify_audio_buffer_status(dev, ++ I915_LPE_AUDIO_HDMI_STATUS_A); ++ } + } while (0); + + enable_rpm_wakeref_asserts(dev_priv); +@@ -1930,6 +2001,23 @@ static irqreturn_t cherryview_irq_handler(int irq, void *arg) + i9xx_hpd_irq_handler(dev, hotplug_status); + + valleyview_pipestat_irq_handler(dev, pipe_stats); ++ ++ if (IS_CHERRYVIEW(dev)) { ++ // FIXME: plb: why are pipes and status mapped this way? ++ if (iir & I915_LPE_PIPE_C_INTERRUPT) ++ i915_notify_audio_buffer_status(dev, ++ I915_LPE_AUDIO_HDMI_STATUS_C); ++ if (iir & I915_LPE_PIPE_B_INTERRUPT) ++ i915_notify_audio_buffer_status(dev, ++ I915_LPE_AUDIO_HDMI_STATUS_B); ++ if (iir & I915_LPE_PIPE_A_INTERRUPT) ++ i915_notify_audio_buffer_status(dev, ++ I915_LPE_AUDIO_HDMI_STATUS_A); ++ } else { ++ if (iir & I915_LPE_PIPE_B_INTERRUPT) ++ i915_notify_audio_buffer_status(dev, ++ I915_LPE_AUDIO_HDMI_STATUS_A); ++ } + } while (0); + + enable_rpm_wakeref_asserts(dev_priv); +@@ -2861,6 +2949,72 @@ static void gen8_disable_vblank(struct drm_device *dev, unsigned int pipe) + spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); + } + ++/* Added for HDMI Audio */ ++int i915_enable_hdmi_audio_int(struct drm_device *dev) ++{ ++ struct drm_i915_private *dev_priv = (struct drm_i915_private *) dev->dev_private; ++ unsigned long irqflags; ++ u32 imr, int_bit; ++ int pipe = 1; ++ ++ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); ++ i915_enable_lpe_pipestat(dev_priv, pipe); ++ ++ imr = I915_READ(VLV_IMR); ++ ++ if (IS_CHERRYVIEW(dev_priv->dev)) { ++ // FIXME: plb: looks wrong ++ ++ //imr &= ~I915_LPE_PIPE_C_INTERRUPT; ++ int_bit = (pipe ? (I915_LPE_PIPE_B_INTERRUPT >> ++ ((pipe - 1) * 9)) : ++ I915_LPE_PIPE_A_INTERRUPT); ++ imr &= ~int_bit; ++ } else { ++ /* Audio is on Stream A but uses HDMI PIPE B */ ++ imr &= ~I915_LPE_PIPE_B_INTERRUPT; ++ } ++ ++ I915_WRITE(VLV_IMR, imr); ++ I915_WRITE(VLV_IER, ~imr); ++ POSTING_READ(VLV_IER); ++ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); ++ ++ return 0; ++} ++ ++/* Added for HDMI Audio */ ++int i915_disable_hdmi_audio_int(struct drm_device *dev) ++{ ++ struct drm_i915_private *dev_priv = (struct drm_i915_private *) dev->dev_private; ++ unsigned long irqflags; ++ u32 imr, int_bit; ++ int pipe = 1; ++ ++ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); ++ imr = I915_READ(VLV_IMR); ++ ++ if (IS_CHERRYVIEW(dev_priv->dev)) { ++ // FIXME: plb: looks wrong, should have other interrupts as well? ++ //imr |= I915_LPE_PIPE_C_INTERRUPT; ++ int_bit = (pipe ? (I915_LPE_PIPE_B_INTERRUPT >> ++ ((pipe - 1) * 9)) : ++ I915_LPE_PIPE_A_INTERRUPT); ++ imr |= int_bit; ++ } ++ else ++ imr |= I915_LPE_PIPE_B_INTERRUPT; ++ ++ I915_WRITE(VLV_IER, ~imr); ++ I915_WRITE(VLV_IMR, imr); ++ POSTING_READ(VLV_IMR); ++ ++ i915_disable_lpe_pipestat(dev_priv, pipe); ++ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); ++ ++ return 0; ++} ++ + static bool + ring_idle(struct intel_engine_cs *engine, u32 seqno) + { +@@ -3371,7 +3525,8 @@ static void vlv_display_irq_postinstall(struct drm_i915_private *dev_priv) + u32 pipestat_mask; + u32 enable_mask; + enum pipe pipe; +- ++ u32 lpe_status_clear; ++ + pipestat_mask = PLANE_FLIP_DONE_INT_STATUS_VLV | + PIPE_CRC_DONE_INTERRUPT_STATUS; + +@@ -3385,6 +3540,18 @@ static void vlv_display_irq_postinstall(struct drm_i915_private *dev_priv) + if (IS_CHERRYVIEW(dev_priv)) + enable_mask |= I915_DISPLAY_PIPE_C_EVENT_INTERRUPT; + ++ if (IS_CHERRYVIEW(dev_priv->dev)) ++ // FIXME: plb: looks wrong: what about other interrupts ++ enable_mask |= I915_LPE_PIPE_C_INTERRUPT; ++ ++ lpe_status_clear = I915_HDMI_AUDIO_UNDERRUN | ++ I915_HDMI_AUDIO_BUFFER_DONE; ++ I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_A, lpe_status_clear); ++ I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_B, lpe_status_clear); ++ if (IS_CHERRYVIEW(dev_priv->dev)) ++ I915_WRITE(I915_LPE_AUDIO_HDMI_STATUS_C, lpe_status_clear); ++ ++ + WARN_ON(dev_priv->irq_mask != ~0); + + dev_priv->irq_mask = ~enable_mask; +diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c +index 3074c56..5d162b8 100644 +--- a/drivers/gpu/drm/i915/intel_display.c ++++ b/drivers/gpu/drm/i915/intel_display.c +@@ -7964,6 +7964,8 @@ static int chv_crtc_compute_clock(struct intel_crtc *crtc, + { + int refclk = 100000; + const intel_limit_t *limit = &intel_limits_chv; ++ struct drm_device *dev = crtc->base.dev; ++ struct drm_i915_private *dev_priv = dev->dev_private; + + memset(&crtc_state->dpll_hw_state, 0, + sizeof(crtc_state->dpll_hw_state)); +@@ -7977,6 +7979,16 @@ static int chv_crtc_compute_clock(struct intel_crtc *crtc, + + chv_compute_dpll(crtc, crtc_state); + ++ /* Added for HDMI Audio */ ++ if ((IS_CHERRYVIEW(dev)) || (IS_VALLEYVIEW(dev))) { ++ if (intel_pipe_will_have_type(crtc_state, INTEL_OUTPUT_HDMI)) { ++ dev_priv->tmds_clock_speed = crtc_state->port_clock; ++ ++ mid_hdmi_audio_signal_event(dev_priv->dev, ++ HAD_EVENT_MODE_CHANGING); ++ } ++ } ++ + return 0; + } + +@@ -7985,7 +7997,9 @@ static int vlv_crtc_compute_clock(struct intel_crtc *crtc, + { + int refclk = 100000; + const intel_limit_t *limit = &intel_limits_vlv; +- ++ struct drm_device *dev = crtc->base.dev; ++ struct drm_i915_private *dev_priv = dev->dev_private; ++ + memset(&crtc_state->dpll_hw_state, 0, + sizeof(crtc_state->dpll_hw_state)); + +@@ -7998,6 +8012,16 @@ static int vlv_crtc_compute_clock(struct intel_crtc *crtc, + + vlv_compute_dpll(crtc, crtc_state); + ++ /* Added for HDMI Audio */ ++ if ((IS_CHERRYVIEW(dev)) || (IS_VALLEYVIEW(dev))) { ++ if (intel_pipe_will_have_type(crtc_state, INTEL_OUTPUT_HDMI)) { ++ dev_priv->tmds_clock_speed = crtc_state->port_clock; ++ ++ mid_hdmi_audio_signal_event(dev_priv->dev, ++ HAD_EVENT_MODE_CHANGING); ++ } ++ } ++ + return 0; + } + +@@ -14678,6 +14702,83 @@ static void intel_setup_outputs(struct drm_device *dev) + drm_helper_move_panel_connectors_to_head(dev); + } + ++void chv_set_lpe_audio_reg_pipe(struct drm_device *dev, ++ int encoder_type, enum port port) ++{ ++ struct drm_i915_private *dev_priv = dev->dev_private; ++ struct intel_encoder *intel_encoder; ++ struct hdmi_audio_priv *hdmi_priv = get_hdmi_priv(); ++ ++ if(!hdmi_priv) { ++ DRM_DEBUG_KMS("hdmi_priv was never allocated\n"); ++ return; ++ } ++ ++ /* ++ * Due to hardware limitaion, Port D will always ++ * be driven by Pipe C. So Port B and Port C will ++ * be driven by either Pipe A or PipeB, depending ++ * on whether the LFP is MIPI or EDP. ++ */ ++ ++ if (port == PORT_D) { ++ hdmi_priv->hdmi_lpe_audio_reg = ++ I915_HDMI_AUDIO_LPE_C_CONFIG; ++ hdmi_priv->pipe = PIPE_C; ++ if (encoder_type == INTEL_OUTPUT_HDMI) ++ hdmi_priv->hdmi_reg = HDMID; ++ //else ++ // hdmi_priv->hdmi_reg = CHV_DP_D; ++ } else { ++#if 0 ++ list_for_each_entry(intel_encoder, &dev-> ++ mode_config.encoder_list, base.head) { ++ ++ /* ++ * MIPI always comes on Pipe A or Pipe B ++ * depending on Port A or Port C and EDP ++ * comes on Pipe B. So the other pipe ++ * will only be able to drive the DP. ++ * MIPI on Port A is driven by Pipe A ++ * and MIPI on Port C is driven by ++ * Pipe B. So the other pipe will ++ * drive DP. ++ */ ++ ++ if (intel_encoder->type == INTEL_OUTPUT_EDP) { ++ hdmi_priv->hdmi_lpe_audio_reg = ++ I915_HDMI_AUDIO_LPE_A_CONFIG; ++ hdmi_priv->pipe = PIPE_A; ++ break; ++ } else if (intel_encoder->type == INTEL_OUTPUT_DSI && ++ dev_priv->vbt.dsi.port == DVO_PORT_MIPIA) { ++ hdmi_priv->hdmi_lpe_audio_reg = ++ I915_HDMI_AUDIO_LPE_B_CONFIG; ++ hdmi_priv->pipe = PIPE_B; ++ break; ++ } else if (intel_encoder->type == INTEL_OUTPUT_DSI && ++ dev_priv->vbt.dsi.port == DVO_PORT_MIPIC) { ++ hdmi_priv->hdmi_lpe_audio_reg = ++ I915_HDMI_AUDIO_LPE_A_CONFIG; ++ hdmi_priv->pipe = PIPE_A; ++ break; ++ } ++ } ++#endif ++ if (port == PORT_B) { ++ if (encoder_type == INTEL_OUTPUT_HDMI) ++ hdmi_priv->hdmi_reg = HDMIB; ++ //else ++ // hdmi_priv->hdmi_reg = VLV_DP_B; ++ } else { ++ if (encoder_type == INTEL_OUTPUT_HDMI) ++ hdmi_priv->hdmi_reg = HDMIC; ++ //else ++ // hdmi_priv->hdmi_reg = VLV_DP_C; ++ } ++ } ++} ++ + static void intel_user_framebuffer_destroy(struct drm_framebuffer *fb) + { + struct drm_device *dev = fb->dev; +diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c +index 2adcc15..c4ec79e 100644 +--- a/drivers/gpu/drm/i915/intel_hdmi.c ++++ b/drivers/gpu/drm/i915/intel_hdmi.c +@@ -38,6 +38,8 @@ + #include + #include "i915_drv.h" + ++static int i915_notify_had; ++ + static struct drm_device *intel_hdmi_to_dev(struct intel_hdmi *intel_hdmi) + { + return hdmi_to_dig_port(intel_hdmi)->base.base.dev; +@@ -1479,6 +1481,124 @@ intel_hdmi_set_edid(struct drm_connector *connector, bool force) + return connected; + } + ++static bool vlv_hdmi_live_status(struct drm_device *dev, ++ struct intel_hdmi *intel_hdmi) ++{ ++ uint32_t bit; ++ struct drm_i915_private *dev_priv = dev->dev_private; ++ struct intel_digital_port *intel_dig_port = ++ hdmi_to_dig_port(intel_hdmi); ++ ++ DRM_DEBUG_KMS("Reading Live status"); ++ switch (intel_dig_port->port) { ++ case PORT_B: ++ bit = HDMIB_HOTPLUG_LIVE_STATUS; ++ break; ++ case PORT_C: ++ bit = HDMIC_HOTPLUG_LIVE_STATUS; ++ break; ++ case PORT_D: ++ bit = HDMID_HOTPLUG_LIVE_STATUS; ++ break; ++ default: ++ bit = 0; ++ } ++ ++ /* Return results in trems of connector */ ++ return I915_READ(PORT_HOTPLUG_STAT) & bit; ++} ++ ++ ++/* ++ * intel_hdmi_live_status: detect live status of HDMI ++ * if device is gen 6 and above, read the live status reg ++ * else, do not block the detection, return true ++ */ ++static bool intel_hdmi_live_status(struct drm_connector *connector) ++{ ++ struct drm_device *dev = connector->dev; ++ struct intel_hdmi *intel_hdmi = intel_attached_hdmi(connector); ++ ++ if (INTEL_INFO(dev)->gen > 6) { ++ /* Todo: Implement for other Gen 6+ archs*/ ++ if (IS_VALLEYVIEW(dev)) ++ return vlv_hdmi_live_status(dev, intel_hdmi); ++ } ++ ++ return true; ++} ++ ++/* Read DDC and get EDID */ ++struct edid *intel_hdmi_get_edid(struct drm_connector *connector, bool force) ++{ ++ bool current_state = false; ++ bool saved_state = false; ++ ++ struct edid *new_edid = NULL; ++ struct i2c_adapter *adapter = NULL; ++ struct drm_device *dev = connector->dev; ++ struct drm_i915_private *dev_priv = dev->dev_private; ++ struct intel_hdmi *intel_hdmi = intel_attached_hdmi(connector); ++ u32 hotplug_status = dev_priv->hotplug_status; ++ enum port hdmi_port = hdmi_to_dig_port(intel_hdmi)->port; ++ unsigned char retry = HDMI_EDID_RETRY_COUNT; ++ ++ if (!intel_hdmi) { ++ DRM_ERROR("Invalid input to get hdmi\n"); ++ return NULL; ++ } ++ ++ /* Get the saved status from top half */ ++ saved_state = hotplug_status & (1 << (HDMI_LIVE_STATUS_BASE - hdmi_port)); ++ ++ /* ++ * Few monitors are slow to respond on EDID and live status, ++ * so read live status multiple times within a max delay of 30ms ++ */ ++ do { ++ mdelay(HDMI_LIVE_STATUS_DELAY_STEP); ++ current_state = intel_hdmi_live_status(connector); ++ if (current_state) ++ break; ++ } while (retry--); ++ ++ /* Compare current status, and saved status in top half */ ++ if (current_state != saved_state) ++ DRM_DEBUG_DRIVER("Warning: Saved HDMI status != current status"); ++ ++ /* Read EDID if live status or saved status is up, or we are forced */ ++ if (current_state || saved_state || force) { ++ ++ adapter = intel_gmbus_get_adapter(dev_priv, ++ intel_hdmi->ddc_bus); ++ if (!adapter) { ++ DRM_ERROR("Get_hdmi cant get adapter\n"); ++ return NULL; ++ } ++ ++ /* ++ * Few monitors issue EDID after some delay, so give them ++ * some chances, but within 30ms ++ */ ++ retry = 3; ++READ_EDID: ++ new_edid = drm_get_edid(connector, adapter); ++ if (!new_edid) { ++ if (retry--) { ++ mdelay(HDMI_LIVE_STATUS_DELAY_STEP); ++ goto READ_EDID; ++ } ++ ++ DRM_ERROR("Get_hdmi cant read edid\n"); ++ return NULL; ++ } ++ ++ DRM_DEBUG_KMS("Live status up, got EDID"); ++ } ++ ++ return new_edid; ++} ++ + static enum drm_connector_status + intel_hdmi_detect(struct drm_connector *connector, bool force) + { +@@ -1487,6 +1607,8 @@ intel_hdmi_detect(struct drm_connector *connector, bool force) + struct drm_i915_private *dev_priv = to_i915(connector->dev); + bool live_status = false; + unsigned int try; ++ bool inform_audio = false; ++ struct drm_device *dev = connector->dev; + + DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); +@@ -1523,6 +1645,31 @@ intel_hdmi_detect(struct drm_connector *connector, bool force) + + intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS); + ++ /* Need to inform audio about the event */ ++ intel_hdmi = intel_attached_hdmi(connector); ++ if (intel_hdmi->has_audio) ++ inform_audio = true; ++ ++ if (status == connector_status_connected) { ++ if (intel_hdmi->has_audio) ++ i915_notify_had = 1; ++ } else { ++ struct intel_digital_port *intel_dig_port = ++ hdmi_to_dig_port(intel_hdmi); ++ ++ chv_set_lpe_audio_reg_pipe(dev, INTEL_OUTPUT_HDMI, ++ intel_dig_port->port); ++ /* Send a disconnect event to audio */ ++ if (inform_audio) { ++ DRM_DEBUG_DRIVER("Sending event to audio"); ++ mid_hdmi_audio_signal_event(dev_priv->dev, ++ HAD_EVENT_HOT_UNPLUG); ++ } ++ } ++ ++ if (IS_VALLEYVIEW(dev)) ++ i915_hdmi_state = status; ++ + return status; + } + +@@ -1546,12 +1693,29 @@ intel_hdmi_force(struct drm_connector *connector) + static int intel_hdmi_get_modes(struct drm_connector *connector) + { + struct edid *edid; ++ struct intel_encoder *intel_encoder = intel_attached_encoder(connector); ++ struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&intel_encoder->base); ++ struct intel_digital_port *intel_dig_port = ++ hdmi_to_dig_port(intel_hdmi); ++ struct drm_device *dev = connector->dev; ++ int ret; ++ struct drm_i915_private *dev_priv = connector->dev->dev_private; + + edid = to_intel_connector(connector)->detect_edid; + if (edid == NULL) + return 0; + +- return intel_connector_update_modes(connector, edid); ++ ret = intel_connector_update_modes(connector, edid); ++ ++ if (i915_notify_had) { ++ chv_set_lpe_audio_reg_pipe(dev, INTEL_OUTPUT_HDMI, ++ intel_dig_port->port); ++ mid_hdmi_audio_signal_event(dev_priv->dev, ++ HAD_EVENT_HOT_PLUG); ++ i915_notify_had = 0; ++ } ++ ++ return ret; + } + + static bool +@@ -2258,6 +2422,20 @@ void intel_hdmi_init_connector(struct intel_digital_port *intel_dig_port, + u32 temp = I915_READ(PEG_BAND_GAP_DATA); + I915_WRITE(PEG_BAND_GAP_DATA, (temp & ~0xf) | 0xd); + } ++ ++ i915_notify_had = 1; ++} ++ ++/* Added for HDMI Audio */ ++void i915_had_wq(struct work_struct *work) ++{ ++ struct drm_i915_private *dev_priv = container_of(work, ++ struct drm_i915_private, hdmi_audio_wq); ++ ++ if (i915_hdmi_state == connector_status_connected) { ++ mid_hdmi_audio_signal_event(dev_priv->dev, ++ HAD_EVENT_HOT_PLUG); ++ } + } + + void intel_hdmi_init(struct drm_device *dev, +@@ -2266,7 +2444,10 @@ void intel_hdmi_init(struct drm_device *dev, + struct intel_digital_port *intel_dig_port; + struct intel_encoder *intel_encoder; + struct intel_connector *intel_connector; +- ++ /* Added for HDMI Audio */ ++ struct hdmi_audio_priv *hdmi_priv; ++ struct drm_i915_private *dev_priv = dev->dev_private; ++ + intel_dig_port = kzalloc(sizeof(*intel_dig_port), GFP_KERNEL); + if (!intel_dig_port) + return; +@@ -2277,6 +2458,7 @@ void intel_hdmi_init(struct drm_device *dev, + return; + } + ++ + intel_encoder = &intel_dig_port->base; + + drm_encoder_init(dev, &intel_encoder->base, &intel_hdmi_enc_funcs, +@@ -2336,4 +2518,29 @@ void intel_hdmi_init(struct drm_device *dev, + intel_dig_port->max_lanes = 4; + + intel_hdmi_init_connector(intel_dig_port, intel_connector); ++ ++ /* Added for HDMI Audio */ ++ /* HDMI private data */ ++ INIT_WORK(&dev_priv->hdmi_audio_wq, i915_had_wq); ++ hdmi_priv = kzalloc(sizeof(struct hdmi_audio_priv), GFP_KERNEL); ++ if (!hdmi_priv) { ++ pr_err("failed to allocate memory"); ++ } else { ++ hdmi_priv->dev = dev; ++ if (IS_CHERRYVIEW(dev)) { ++ // FIXME: plb: looks wrong ++ // mapping between stream and Hdmi port ? ++ hdmi_priv->hdmi_reg = HDMIC; ++ hdmi_priv->hdmi_lpe_audio_reg = ++ I915_HDMI_AUDIO_LPE_C_CONFIG; ++ } else { ++ hdmi_priv->hdmi_reg = HDMIB; ++ hdmi_priv->hdmi_lpe_audio_reg = ++ I915_HDMI_AUDIO_LPE_A_CONFIG; ++ } ++ hdmi_priv->monitor_type = MONITOR_TYPE_HDMI; ++ hdmi_priv->is_hdcp_supported = true; ++ i915_hdmi_audio_init(hdmi_priv); ++ } ++ + } + +From bea9d15e00603196e4d138af691327bbf6a86af0 Mon Sep 17 00:00:00 2001 +From: Pierre-Louis Bossart +Date: Tue, 1 Mar 2016 16:25:04 -0600 +Subject: [PATCH 04/12] drm/i915: power-related changes non-HDAudio HDMI + interface + +PM and RPM changes for interface available on Baytrail and CherryTrail + +This driver was downloaded from https://github.com/01org/baytrailaudio/ + +...and had the changes to .config stripped and the revert on sound/init.c + +Clean-up, port to 4.4 and intel-drm by Pierre Bossart + +Signed-off-by: David Henningsson +Signed-off-by: Pierre-Louis Bossart +--- + drivers/gpu/drm/i915/i915_rpm.c | 476 ++++++++++++++++++++++++++++++++++++++++ + drivers/gpu/drm/i915/intel_pm.c | 53 +++++ + 2 files changed, 529 insertions(+) + create mode 100644 drivers/gpu/drm/i915/i915_rpm.c + +diff --git a/drivers/gpu/drm/i915/i915_rpm.c b/drivers/gpu/drm/i915/i915_rpm.c +new file mode 100644 +index 0000000..511311c +--- /dev/null ++++ b/drivers/gpu/drm/i915/i915_rpm.c +@@ -0,0 +1,476 @@ ++/* ++ * Copyright 2013 Intel Corporation ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a ++ * copy of this software and associated documentation files (the "Software"), ++ * to deal in the Software without restriction, including without limitation ++ * the rights to use, copy, modify, merge, publish, distribute, sublicense, ++ * and/or sell copies of the Software, and to permit persons to whom the ++ * Software is furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice (including the next ++ * paragraph) shall be included in all copies or substantial portions of the ++ * Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ++ * DEALINGS IN THE SOFTWARE. ++ * ++ * Author: ++ * Naresh Kumar Kachhi ++ */ ++ ++#include "i915_drv.h" ++#include "i915_reg.h" ++#include "intel_drv.h" ++#include ++#ifdef CONFIG_PM_RUNTIME ++#include ++#endif ++#include /* Needed for procfs access */ ++#include /* For the basic file system */ ++#include ++ ++#define RPM_AUTOSUSPEND_DELAY 500 ++ ++#ifdef CONFIG_PM_RUNTIME ++ ++/** ++ * - Where should we use get/put? ++ * Get/put should be used very carefully as we might end up in weird states ++ * if not used properly (see the Note below). We want to cover all the ++ * acesses that might result in accessing rings/display/registers/gtt etc ++ * Mostly covering ioctls and drm callbacks should be enough. You can ++ * avoid those which does not access any HW. ++ * ++ * - When should we avoid get/put? ++ * WQ and interrupts should be taken care in suspend path. We should ++ * disable all the interrupts and cancel any pending WQs. Never try to ++ * cover interrupt/WQ with get/put unless you are sure about it. ++ * ++ * Note:Following scenarios should be strictly avoided while using get_sync ++ * 1. Calling get_sync with struct_mutex or mode_config.mutex locked ++ * - we acquire these locks in runtime_resume, so any call to get_sync ++ * with these mutex locked might end up in a dead lock. ++ * check_mutex_current function can be used to debug this scenario. ++ * - Or let's say thread1 has done get_sync and is currently executing ++ * runtime_resume function. Before thread1 is able to acquire these ++ * mutex, thread2 acquires the mutex and does a get_sync. Now thread1 ++ * is waiting for mutex and thread2 is waiting for dev->power.lock ++ * resulting in a deadlock. Use check_mutex to debug this. ++ * 2. Calling get_sync from runtime_resume path ++ * runtime_resume is called with dev->power.lock held. doing get_sync ++ * in same path will end up in deadlock ++ */ ++ ++#define RPM_PROC_ENTRY_FILENAME "i915_rpm_op" ++#define RPM_PROC_ENTRY_DIRECTORY "driver/i915rpm" ++ ++int i915_rpm_get_procfs(struct inode *inode, struct file *file); ++int i915_rpm_put_procfs(struct inode *inode, struct file *file); ++/* proc file operations supported */ ++static const struct file_operations rpm_file_ops = { ++ .owner = THIS_MODULE, ++ .open = i915_rpm_get_procfs, ++ .release = i915_rpm_put_procfs, ++}; ++ ++bool i915_pm_runtime_enabled(struct device *dev) ++{ ++ return pm_runtime_enabled(dev); ++} ++ ++void i915_rpm_enable(struct device *dev) ++{ ++ int cur_status = pm_runtime_enabled(dev); ++ ++ if (!cur_status) { ++ pm_runtime_enable(dev); ++ pm_runtime_allow(dev); ++ } ++} ++ ++void i915_rpm_disable(struct drm_device *drm_dev) ++{ ++ struct device *dev = drm_dev->dev; ++ int cur_status = pm_runtime_enabled(dev); ++ ++ if (cur_status) { ++ pm_runtime_forbid(dev); ++ pm_runtime_disable(dev); ++ } ++} ++ ++static int i915_rpm_procfs_init(struct drm_device *drm_dev) ++{ ++ struct drm_i915_private *dev_priv = drm_dev->dev_private; ++ ++ dev_priv->rpm.i915_proc_dir = NULL; ++ dev_priv->rpm.i915_proc_file = NULL; ++ ++ /** ++ * Create directory for rpm file(s) ++ */ ++ dev_priv->rpm.i915_proc_dir = proc_mkdir(RPM_PROC_ENTRY_DIRECTORY, ++ NULL); ++ if (dev_priv->rpm.i915_proc_dir == NULL) { ++ DRM_ERROR("Could not initialize %s\n", ++ RPM_PROC_ENTRY_DIRECTORY); ++ return -ENOMEM; ++ } ++ /** ++ * Create the /proc file ++ */ ++ dev_priv->rpm.i915_proc_file = proc_create_data( ++ RPM_PROC_ENTRY_FILENAME, ++ S_IRUGO | S_IWUSR, ++ dev_priv->rpm.i915_proc_dir, ++ &rpm_file_ops, ++ drm_dev); ++ /* check if file is created successfuly */ ++ if (dev_priv->rpm.i915_proc_file == NULL) { ++ DRM_ERROR("Could not initialize %s/%s\n", ++ RPM_PROC_ENTRY_DIRECTORY, RPM_PROC_ENTRY_FILENAME); ++ return -ENOMEM; ++ } ++ return 0; ++} ++ ++static int i915_rpm_procfs_deinit(struct drm_device *drm_dev) ++{ ++ struct drm_i915_private *dev_priv = drm_dev->dev_private; ++ /* Clean up proc file */ ++ if (dev_priv->rpm.i915_proc_file) { ++ remove_proc_entry(RPM_PROC_ENTRY_FILENAME, ++ dev_priv->rpm.i915_proc_dir); ++ dev_priv->rpm.i915_proc_file = NULL; ++ } ++ if (dev_priv->rpm.i915_proc_dir) { ++ remove_proc_entry(RPM_PROC_ENTRY_DIRECTORY, NULL); ++ dev_priv->rpm.i915_proc_dir = NULL; ++ } ++ return 0; ++} ++ ++/* RPM init */ ++int i915_rpm_init(struct drm_device *drm_dev) ++{ ++ int ret = 0; ++ struct device *dev = drm_dev->dev; ++ struct drm_i915_private *dev_priv = drm_dev->dev_private; ++ ++ ret = i915_rpm_procfs_init(drm_dev); ++ if (ret) ++ DRM_ERROR("unable to initialize procfs entry"); ++ ret = pm_runtime_set_active(dev); ++ dev_priv->rpm.ring_active = false; ++ atomic_set(&dev_priv->rpm.procfs_count, 0); ++ pm_runtime_allow(dev); ++ /* enable Auto Suspend */ ++ pm_runtime_set_autosuspend_delay(dev, RPM_AUTOSUSPEND_DELAY); ++ pm_runtime_use_autosuspend(dev); ++ if (dev->power.runtime_error) ++ DRM_ERROR("rpm init: error = %d\n", dev->power.runtime_error); ++ ++ return ret; ++} ++ ++int i915_rpm_deinit(struct drm_device *drm_dev) ++{ ++ struct device *dev = drm_dev->dev; ++ ++ pm_runtime_forbid(dev); ++ pm_runtime_set_suspended(dev); ++ pm_runtime_get_noresume(dev); ++ if (dev->power.runtime_error) ++ DRM_ERROR("rpm init: error = %d\n", dev->power.runtime_error); ++ ++ i915_rpm_procfs_deinit(drm_dev); ++ return 0; ++} ++ ++/** ++ * We have different flavour of get/put based on access type (ring/disp/ ++ * vxd etc). this is done based on different requirements and to make ++ * debugging a little easier. Debugfs introduces separate counter for ++ * each type. ++ */ ++ ++/** ++ * Once we have scheduled commands on GPU, it might take a while GPU ++ * to execute them. Following is done to make sure Gfx is in D0i0 while ++ * GPU is executing the commands. ++ * 1. For IOCTLS make sure we are in D0i0 by calling "get_ioctl". ++ * 2. if IOCTL scheudles GPU commands using rings do the following ++ * a. For all ring accesses make sure we add a request in the request ++ * list and schedule a work item to track the "seq no". This ++ * is done by using "i915_add_request" or "i915_add_request_no_flush" ++ * functions. ++ * b. If request list was empty, we do a "get_ring". This will increment ++ * ref count to make sure GPU will be in D0 state. ++ * c. Once the list becomes empty call put_ring ++ * ++ * Note: All the ring accesses are covered with struct_mutex. So we ++ * don't need any synchronization to protect ring_active. ++ */ ++int i915_rpm_get_ring(struct drm_device *drm_dev) ++{ ++ struct intel_engine_cs *ring; ++ struct drm_i915_private *dev_priv = drm_dev->dev_private; ++ int i; ++ bool idle = true; ++ ++ for_each_ring(ring, dev_priv, i) ++ idle &= list_empty(&ring->request_list); ++ ++ if (idle) { ++ if (!dev_priv->rpm.ring_active) { ++ dev_priv->rpm.ring_active = true; ++ pm_runtime_get_noresume(drm_dev->dev); ++ } ++ } ++ ++ return 0; ++} ++ ++int i915_rpm_put_ring(struct drm_device *drm_dev) ++{ ++ struct drm_i915_private *dev_priv = drm_dev->dev_private; ++ ++ if (dev_priv->rpm.ring_active) { ++ /* Mark last time it was busy and schedule a autosuspend */ ++ pm_runtime_mark_last_busy(drm_dev->dev); ++ pm_runtime_put_autosuspend(drm_dev->dev); ++ dev_priv->rpm.ring_active = false; ++ } ++ return 0; ++} ++ ++/** ++ * To cover the function pointers that are assigned to drm structures ++ * and can be called from drm ++ */ ++int i915_rpm_get_callback(struct drm_device *drm_dev) ++{ ++ return pm_runtime_get_sync(drm_dev->dev); ++} ++ ++int i915_rpm_put_callback(struct drm_device *drm_dev) ++{ ++ pm_runtime_mark_last_busy(drm_dev->dev); ++ return pm_runtime_put_autosuspend(drm_dev->dev); ++} ++ ++/** ++ * early_suspend/DSR should call this function to notify PM Core about ++ * display idleness ++ */ ++int i915_rpm_get_disp(struct drm_device *drm_dev) ++{ ++ return pm_runtime_get_sync(drm_dev->dev); ++} ++ ++int i915_rpm_put_disp(struct drm_device *drm_dev) ++{ ++ pm_runtime_mark_last_busy(drm_dev->dev); ++ return pm_runtime_put_autosuspend(drm_dev->dev); ++} ++ ++/** to cover the ioctls with get/put*/ ++int i915_rpm_get_ioctl(struct drm_device *drm_dev) ++{ ++ /* Don't do anything if device is not ready */ ++ if (drm_device_is_unplugged(drm_dev)) ++ return 0; ++ ++ return pm_runtime_get_sync(drm_dev->dev); ++} ++ ++int i915_rpm_put_ioctl(struct drm_device *drm_dev) ++{ ++ /* Don't do anything if device is not ready */ ++ if (drm_device_is_unplugged(drm_dev)) ++ return 0; ++ ++ pm_runtime_mark_last_busy(drm_dev->dev); ++ return pm_runtime_put_autosuspend(drm_dev->dev); ++} ++ ++/* these operations are caled from user mode (CoreU) to make sure ++ * Gfx is up before register accesses from user mode ++ */ ++int i915_rpm_get_procfs(struct inode *inode, struct file *file) ++{ ++ struct drm_device *dev = PDE_DATA(inode); ++ struct drm_i915_private *dev_priv = dev->dev_private; ++ ++ atomic_inc(&dev_priv->rpm.procfs_count); ++ pm_runtime_get_sync(dev->dev); ++ return 0; ++} ++ ++int i915_rpm_put_procfs(struct inode *inode, struct file *file) ++{ ++ struct drm_device *dev = PDE_DATA(inode); ++ struct drm_i915_private *dev_priv = dev->dev_private; ++ ++ pm_runtime_mark_last_busy(dev->dev); ++ pm_runtime_put_autosuspend(dev->dev); ++ atomic_dec(&dev_priv->rpm.procfs_count); ++ return 0; ++} ++ ++/** ++ * VXD driver need to call this to make sure Gfx is in D0i0 ++ * while VXD is on ++ */ ++#ifdef CONFIG_DRM_VXD_BYT ++int i915_rpm_get_vxd(struct drm_device *drm_dev) ++{ ++ return pm_runtime_get_sync(drm_dev->dev); ++} ++EXPORT_SYMBOL(i915_rpm_get_vxd); ++ ++/** ++ * VXD driver need to call this to notify Gfx that it is ++ * done with HW accesses ++ */ ++int i915_rpm_put_vxd(struct drm_device *drm_dev) ++{ ++ pm_runtime_mark_last_busy(drm_dev->dev); ++ return pm_runtime_put_autosuspend(drm_dev->dev); ++} ++EXPORT_SYMBOL(i915_rpm_put_vxd); ++#endif ++ ++/* mainly for debug purpose, check if the access is valid */ ++bool i915_rpm_access_check(struct drm_device *dev) ++{ ++ if (dev->dev->power.runtime_status == RPM_SUSPENDED) { ++ DRM_ERROR("invalid access, will cause Hard Hang\n"); ++ dump_stack(); ++ return false; ++ } ++ return true; ++} ++ ++/* mainly for debug purpose, check if mutex is locked by ++ * current thread ++ */ ++int check_mutex_current(struct drm_device *drm_dev) ++{ ++ int ret = 0; ++ ++ if ((mutex_is_locked(&drm_dev->mode_config.mutex)) && ++ (drm_dev->mode_config.mutex.owner == current)) { ++ DRM_ERROR("config mutex locked by current thread\n"); ++ dump_stack(); ++ ret = -1; ++ } ++ if ((mutex_is_locked(&drm_dev->struct_mutex)) && ++ (drm_dev->struct_mutex.owner == current)) { ++ DRM_ERROR("struct mutex locked by current thread\n"); ++ dump_stack(); ++ ret = -2; ++ } ++ return ret; ++} ++ ++int check_mutex(struct drm_device *drm_dev) ++{ ++ int ret = 0; ++ ++ if (mutex_is_locked(&drm_dev->mode_config.mutex)) { ++ DRM_ERROR("config mutex locked\n"); ++ dump_stack(); ++ ret = -1; ++ } ++ if (mutex_is_locked(&drm_dev->struct_mutex)) { ++ DRM_ERROR("struct mutex locked\n"); ++ dump_stack(); ++ ret = -2; ++ } ++ return ret; ++} ++ ++/* Check for current runtime state */ ++bool i915_is_device_active(struct drm_device *dev) ++{ ++ return (dev->dev->power.runtime_status == RPM_ACTIVE); ++} ++ ++bool i915_is_device_resuming(struct drm_device *dev) ++{ ++ return (dev->dev->power.runtime_status == RPM_RESUMING); ++} ++ ++bool i915_is_device_suspended(struct drm_device *dev) ++{ ++ return (dev->dev->power.runtime_status == RPM_SUSPENDED); ++} ++ ++bool i915_is_device_suspending(struct drm_device *dev) ++{ ++ return (dev->dev->power.runtime_status == RPM_SUSPENDING); ++} ++ ++#else /*CONFIG_PM_RUNTIME*/ ++int i915_rpm_init(struct drm_device *dev) {return 0; } ++int i915_rpm_deinit(struct drm_device *dev) {return 0; } ++int i915_rpm_get(struct drm_device *dev, u32 flags) {return 0; } ++int i915_rpm_put(struct drm_device *dev, u32 flags) {return 0; } ++int i915_rpm_get_ring(struct drm_device *dev) {return 0; } ++int i915_rpm_put_ring(struct drm_device *dev) {return 0; } ++int i915_rpm_get_callback(struct drm_device *dev) {return 0; } ++int i915_rpm_put_callback(struct drm_device *dev) {return 0; } ++int i915_rpm_get_ioctl(struct drm_device *dev) {return 0; } ++int i915_rpm_put_ioctl(struct drm_device *dev) {return 0; } ++int i915_rpm_get_disp(struct drm_device *dev) {return 0; } ++int i915_rpm_put_disp(struct drm_device *dev) {return 0; } ++int i915_rpm_get_procfs(struct inode *inode, ++ struct file *file) {return 0; } ++int i915_rpm_put_procfs(struct inode *inode, ++ struct file *file) {return 0; } ++#ifdef CONFIG_DRM_VXD_BYT ++int i915_rpm_get_vxd(struct drm_device *dev) {return 0; } ++int i915_rpm_put_vxd(struct drm_device *dev) {return 0; } ++#endif ++ ++bool i915_is_device_active(struct drm_device *dev) ++{ ++ return true; ++} ++ ++bool i915_is_device_resuming(struct drm_device *dev) ++{ ++ return false; ++} ++ ++bool i915_is_device_suspended(struct drm_device *dev) ++{ ++ return false; ++} ++ ++bool i915_is_device_suspending(struct drm_device *dev) ++{ ++ return false; ++} ++ ++bool i915_rpm_access_check(struct drm_device *dev) ++{ ++ return true; ++} ++bool i915_pm_runtime_enabled(struct device *dev) ++{ ++ return false; ++} ++ ++void i915_rpm_enable(struct device *dev) {} ++ ++void i915_rpm_disable(struct drm_device *drm_dev) {} ++ ++#endif /*CONFIG_PM_RUNTIME*/ +diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c +index 2863b92..883b13c 100644 +--- a/drivers/gpu/drm/i915/intel_pm.c ++++ b/drivers/gpu/drm/i915/intel_pm.c +@@ -31,6 +31,17 @@ + #include "../../../platform/x86/intel_ips.h" + #include + ++typedef enum _UHBUsage { ++ OSPM_UHB_ONLY_IF_ON = 0, ++ OSPM_UHB_FORCE_POWER_ON, ++} UHBUsage; ++ ++static struct drm_device *gdev; ++ ++#ifdef CONFIG_HAS_EARLYSUSPEND ++ #include ++#endif ++ + /** + * DOC: RC6 + * +@@ -7264,6 +7275,7 @@ void intel_init_clock_gating_hooks(struct drm_i915_private *dev_priv) + void intel_init_pm(struct drm_device *dev) + { + struct drm_i915_private *dev_priv = dev->dev_private; ++ gdev = dev; + + intel_fbc_init(dev_priv); + +@@ -7496,3 +7508,44 @@ void intel_pm_setup(struct drm_device *dev) + atomic_set(&dev_priv->pm.wakeref_count, 0); + atomic_set(&dev_priv->pm.atomic_seq, 0); + } ++ ++bool ospm_power_is_hw_on(int hw_islands) ++{ ++#if 0 ++ struct drm_device *drm_dev = gdev; ++ unsigned long flags; ++ bool ret = false; ++ struct drm_i915_private *dev_priv = drm_dev->dev_private; ++ u32 data = vlv_punit_read(dev_priv, VLV_IOSFSB_PWRGT_STATUS); ++ ++ if ((VLV_POWER_GATE_DISPLAY_MASK & data) ++ == VLV_POWER_GATE_DISPLAY_MASK) { ++ DRM_ERROR("Display Island not ON\n"); ++ return false; ++ } else { ++ return true; ++ } ++#endif ++ return true; ++} ++EXPORT_SYMBOL(ospm_power_is_hw_on); ++ ++/* Dummy Function for HDMI Audio Power management. ++ * Will be updated once S0iX code is integrated ++ */ ++bool ospm_power_using_hw_begin(int hw_island, UHBUsage usage) ++{ ++ struct drm_device *drm_dev = gdev; ++ ++ i915_rpm_get_disp(drm_dev); ++ return i915_is_device_active(drm_dev); ++} ++EXPORT_SYMBOL(ospm_power_using_hw_begin); ++ ++void ospm_power_using_hw_end(int hw_island) ++{ ++ struct drm_device *drm_dev = gdev; ++ ++ i915_rpm_put_disp(drm_dev); ++} ++EXPORT_SYMBOL(ospm_power_using_hw_end); + +From b172cffb536124dba41ff37793a67dc6f9a6a39c Mon Sep 17 00:00:00 2001 +From: Pierre-Louis Bossart +Date: Thu, 3 Mar 2016 11:08:10 -0600 +Subject: [PATCH 05/12] drm/i915: Add API code for non-HDAudio HDMI interface + +Add API code for interface available on Baytrail and CherryTrail + +Initial code was downloaded from https://github.com/01org/baytrailaudio/ +...and had the changes to .config stripped and the revert on sound/init.c +done by David Henningson + +Clean-up, port to 4.5 and intel-drm by Pierre Bossart +CherryTrail support by Jerome Anand + +Signed-off-by: David Henningsson +Signed-off-by: Pierre-Louis Bossart +--- + drivers/gpu/drm/i915/hdmi_audio_if.c | 425 +++++++++++++++++++++++++++++++++++ + 1 file changed, 425 insertions(+) + create mode 100644 drivers/gpu/drm/i915/hdmi_audio_if.c + +diff --git a/drivers/gpu/drm/i915/hdmi_audio_if.c b/drivers/gpu/drm/i915/hdmi_audio_if.c +new file mode 100644 +index 0000000..c7c5f8f +--- /dev/null ++++ b/drivers/gpu/drm/i915/hdmi_audio_if.c +@@ -0,0 +1,425 @@ ++/* ++ * Copyright (c) 2010, Intel Corporation. ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope 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. ++ * ++ * Authors: ++ * jim liu ++ * Uma Shankar ++ */ ++ ++#include ++#include "hdmi_audio_if.h" ++#include "i915_drv.h" ++#include "i915_reg.h" ++ ++#define CONFIG_SUPPORT_HDMI_AUDIO ++#ifdef CONFIG_SUPPORT_HDMI_AUDIO ++ ++int i915_hdmi_state; ++ ++/* ++ * Audio register range 0x65000 to 0x65FFF ++ */ ++ ++#define IS_HDMI_AUDIO_I915(reg) ((reg >= 0x65000) && (reg < 0x65FFF)) ++ ++/* Added for HDMI Audio */ ++#define HAD_MAX_ELD_BYTES 84 ++uint8_t hdmi_eld[HAD_MAX_ELD_BYTES]; ++ ++static struct hdmi_audio_priv *hdmi_priv; ++ ++void i915_hdmi_audio_init(struct hdmi_audio_priv *p_hdmi_priv) ++{ ++ hdmi_priv = p_hdmi_priv; ++} ++ ++/* Added for HDMI Audio */ ++void hdmi_get_eld(uint8_t *eld) ++{ ++ struct drm_device *dev = hdmi_priv->dev; ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ memcpy(hdmi_eld, eld, HAD_MAX_ELD_BYTES); ++ mid_hdmi_audio_signal_event(dev_priv->dev, HAD_EVENT_HOT_PLUG); ++} ++ ++static inline int android_hdmi_get_eld(struct drm_device *dev, void *eld) ++{ ++ memcpy(eld, hdmi_eld, HAD_MAX_ELD_BYTES); ++ return 0; ++} ++ ++struct hdmi_audio_priv *get_hdmi_priv() ++{ ++ return hdmi_priv; ++} ++ ++/* ++ * return whether HDMI audio device is busy. ++ */ ++bool mid_hdmi_audio_is_busy(struct drm_device *dev) ++{ ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ int hdmi_audio_busy = 0; ++ hdmi_audio_event_t hdmi_audio_event; ++ ++ if (i915_hdmi_state == connector_status_disconnected) { ++ /* HDMI is not connected, assuming audio device is idle. */ ++ return false; ++ } ++ ++ if (dev_priv->had_interface) { ++ hdmi_audio_event.type = HAD_EVENT_QUERY_IS_AUDIO_BUSY; ++ hdmi_audio_busy = dev_priv->had_interface->query( ++ dev_priv->had_pvt_data, ++ hdmi_audio_event); ++ return hdmi_audio_busy != 0; ++ } ++ return false; ++} ++ ++/* ++ * return whether HDMI audio device is suspended. ++ */ ++bool mid_hdmi_audio_suspend(struct drm_device *dev) ++{ ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ hdmi_audio_event_t hdmi_audio_event; ++ int ret = 0; ++ ++ if (i915_hdmi_state == connector_status_disconnected) { ++ /* HDMI is not connected, assuming audio device ++ * is suspended already. ++ */ ++ return true; ++ } ++ DRM_DEBUG_DRIVER("%s: i915_hdmi_state %d", __func__, ++ i915_hdmi_state); ++ ++ if (dev_priv->had_interface) { ++ hdmi_audio_event.type = 0; ++ ret = dev_priv->had_interface->suspend(dev_priv->had_pvt_data, ++ hdmi_audio_event); ++ return (ret == 0) ? true : false; ++ } ++ return true; ++} ++ ++void mid_hdmi_audio_resume(struct drm_device *dev) ++{ ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ ++ if (i915_hdmi_state == connector_status_disconnected) { ++ /* HDMI is not connected, there is no need ++ * to resume audio device. ++ */ ++ return; ++ } ++ DRM_DEBUG_DRIVER("%s: i915_hdmi_state %d", __func__, ++ i915_hdmi_state); ++ ++ if (dev_priv->had_interface) ++ dev_priv->had_interface->resume(dev_priv->had_pvt_data); ++} ++ ++void mid_hdmi_audio_signal_event(struct drm_device *dev, ++ enum had_event_type event) ++{ ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ ++ if (dev_priv->had_event_callbacks) ++ (*dev_priv->had_event_callbacks)(event, ++ dev_priv->had_pvt_data); ++} ++ ++/** ++ * hdmi_audio_write: ++ * used to write into display controller HDMI audio registers. ++ * ++ */ ++static int hdmi_audio_write(uint32_t reg, uint32_t val) ++{ ++ struct drm_device *dev = hdmi_priv->dev; ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ int ret = 0; ++ ++ if (hdmi_priv->monitor_type == MONITOR_TYPE_DVI) ++ return 0; ++ ++ if (IS_HDMI_AUDIO_I915(reg)) ++ I915_WRITE(_MMIO(VLV_DISPLAY_BASE + reg), val); ++ else ++ ret = -EINVAL; ++ ++ return ret; ++} ++ ++/** ++ * hdmi_audio_read: ++ * used to get the register value read from ++ * display controller HDMI audio registers. ++ */ ++static int hdmi_audio_read(uint32_t reg, uint32_t *val) ++{ ++ struct drm_device *dev = hdmi_priv->dev; ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ int ret = 0; ++ ++ if (hdmi_priv->monitor_type == MONITOR_TYPE_DVI) ++ return 0; ++ ++ if (IS_HDMI_AUDIO_I915(reg)) ++ *val = I915_READ(_MMIO(VLV_DISPLAY_BASE + reg)); ++ else ++ ret = -EINVAL; ++ ++ return ret; ++} ++ ++/** ++ * hdmi_audio_rmw: ++ * used to update the masked bits in display controller HDMI audio registers . ++ * ++ */ ++static int hdmi_audio_rmw(uint32_t reg, uint32_t val, uint32_t mask) ++{ ++ struct drm_device *dev = hdmi_priv->dev; ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ int ret = 0; ++ uint32_t val_tmp = 0; ++ ++ if (IS_HDMI_AUDIO_I915(reg)) { ++ val_tmp = (val & mask) | ++ (I915_READ(_MMIO(VLV_DISPLAY_BASE + reg)) & ~mask); ++ I915_WRITE(_MMIO(VLV_DISPLAY_BASE + reg), val_tmp); ++ } else { ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++/** ++ * hdmi_audio_get_caps: ++ * used to return the HDMI audio capabilities. ++ * e.g. resolution, frame rate. ++ */ ++static int hdmi_audio_get_caps(enum had_caps_list get_element, ++ void *capabilities) ++{ ++ struct drm_device *dev = hdmi_priv->dev; ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ int ret = 0; ++ ++ DRM_DEBUG_DRIVER("\n"); ++ ++ switch (get_element) { ++ case HAD_GET_ELD: ++ ret = android_hdmi_get_eld(dev, capabilities); ++ break; ++ case HAD_GET_SAMPLING_FREQ: ++ /* ToDo: Verify if sampling freq logic is correct */ ++ memcpy(capabilities, &(dev_priv->tmds_clock_speed), ++ sizeof(uint32_t)); ++ break; ++ default: ++ break; ++ } ++ ++ return ret; ++} ++ ++/** ++ * hdmi_audio_get_register_base ++ * used to get the current hdmi base address ++ */ ++int hdmi_audio_get_register_base(uint32_t *reg_base) ++{ ++ *reg_base = hdmi_priv->hdmi_lpe_audio_reg; ++ return 0; ++} ++ ++/** ++ * hdmi_audio_set_caps: ++ * used to set the HDMI audio capabilities. ++ * e.g. Audio INT. ++ */ ++static int hdmi_audio_set_caps(enum had_caps_list set_element, ++ void *capabilties) ++{ ++ struct drm_device *dev = hdmi_priv->dev; ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ int ret = 0; ++ u32 hdmi_reg; ++ u32 int_masks = 0; ++ ++ DRM_DEBUG_DRIVER("\n"); ++ ++ switch (set_element) { ++ case HAD_SET_ENABLE_AUDIO: ++ hdmi_reg = I915_READ(_MMIO(hdmi_priv->hdmi_reg)); ++ if (hdmi_reg & PORT_ENABLE) ++ hdmi_reg |= SDVO_AUDIO_ENABLE; ++ ++ I915_WRITE(_MMIO(hdmi_priv->hdmi_reg), hdmi_reg); ++ I915_READ(_MMIO(hdmi_priv->hdmi_reg)); ++ break; ++ case HAD_SET_DISABLE_AUDIO: ++ hdmi_reg = I915_READ(_MMIO(hdmi_priv->hdmi_reg)) & ++ ~SDVO_AUDIO_ENABLE; ++ I915_WRITE(_MMIO(hdmi_priv->hdmi_reg), hdmi_reg); ++ I915_READ(_MMIO(hdmi_priv->hdmi_reg)); ++ break; ++ ++ case HAD_SET_ENABLE_AUDIO_INT: ++ if (*((u32 *)capabilties) & HDMI_AUDIO_UNDERRUN) ++ int_masks |= I915_HDMI_AUDIO_UNDERRUN_ENABLE; ++ dev_priv->hdmi_audio_interrupt_mask |= int_masks; ++ i915_enable_hdmi_audio_int(dev); ++ break; ++ case HAD_SET_DISABLE_AUDIO_INT: ++ if (*((u32 *)capabilties) & HDMI_AUDIO_UNDERRUN) ++ int_masks |= I915_HDMI_AUDIO_UNDERRUN_ENABLE; ++ dev_priv->hdmi_audio_interrupt_mask &= ~int_masks; ++ ++ i915_disable_hdmi_audio_int(dev); ++ break; ++ default: ++ break; ++ } ++ ++ return ret; ++} ++ ++static struct hdmi_audio_registers_ops hdmi_audio_reg_ops = { ++ .hdmi_audio_get_register_base = hdmi_audio_get_register_base, ++ .hdmi_audio_read_register = hdmi_audio_read, ++ .hdmi_audio_write_register = hdmi_audio_write, ++ .hdmi_audio_read_modify = hdmi_audio_rmw, ++}; ++ ++static struct hdmi_audio_query_set_ops hdmi_audio_get_set_ops = { ++ .hdmi_audio_get_caps = hdmi_audio_get_caps, ++ .hdmi_audio_set_caps = hdmi_audio_set_caps, ++}; ++ ++int mid_hdmi_audio_setup( ++ had_event_call_back audio_callbacks, ++ struct hdmi_audio_registers_ops *reg_ops, ++ struct hdmi_audio_query_set_ops *query_ops) ++{ ++ struct drm_device *dev = hdmi_priv->dev; ++ struct drm_i915_private *dev_priv = ++ (struct drm_i915_private *) dev->dev_private; ++ int ret = 0; ++ ++ DRM_DEBUG_DRIVER("%s: called\n", __func__); ++ ++ reg_ops->hdmi_audio_get_register_base = ++ (hdmi_audio_reg_ops.hdmi_audio_get_register_base); ++ reg_ops->hdmi_audio_read_register = ++ (hdmi_audio_reg_ops.hdmi_audio_read_register); ++ reg_ops->hdmi_audio_write_register = ++ (hdmi_audio_reg_ops.hdmi_audio_write_register); ++ reg_ops->hdmi_audio_read_modify = ++ (hdmi_audio_reg_ops.hdmi_audio_read_modify); ++ query_ops->hdmi_audio_get_caps = ++ hdmi_audio_get_set_ops.hdmi_audio_get_caps; ++ query_ops->hdmi_audio_set_caps = ++ hdmi_audio_get_set_ops.hdmi_audio_set_caps; ++ ++ dev_priv->had_event_callbacks = audio_callbacks; ++ ++ return ret; ++} ++EXPORT_SYMBOL(mid_hdmi_audio_setup); ++ ++int mid_hdmi_audio_register(struct snd_intel_had_interface *driver, ++ void *had_data) ++{ ++ struct drm_device *dev; ++ struct drm_i915_private *dev_priv; ++ ++ DRM_DEBUG_DRIVER("%s: called\n", __func__); ++ if (!hdmi_priv) ++ return -ENODEV; ++ ++ dev = hdmi_priv->dev; ++ dev_priv = (struct drm_i915_private *) dev->dev_private; ++ dev_priv->had_pvt_data = had_data; ++ dev_priv->had_interface = driver; ++ ++ if (hdmi_priv->monitor_type == MONITOR_TYPE_DVI) ++ return 0; ++ ++ /* The Audio driver is loading now and we need to notify ++ * it if there is an HDMI device attached ++ */ ++ DRM_INFO("%s: Scheduling HDMI audio work queue\n", __func__); ++ schedule_work(&dev_priv->hdmi_audio_wq); ++ ++ return 0; ++} ++EXPORT_SYMBOL(mid_hdmi_audio_register); ++#else ++bool hdmi_audio_is_busy(struct drm_device *dev) ++{ ++ /* always in idle state */ ++ return false; ++} ++ ++bool hdmi_audio_suspend(struct drm_device *dev) ++{ ++ /* always in suspend state */ ++ return true; ++} ++ ++void hdmi_audio_resume(struct drm_device *dev) ++{ ++} ++ ++void hdmi_audio_signal_event(struct drm_device *dev, enum had_event_type event) ++{ ++} ++ ++void i915_hdmi_audio_init(struct hdmi_audio_priv *hdmi_priv) ++{ ++ DRM_INFO("%s: HDMI is not supported.\n", __func__); ++} ++ ++int mid_hdmi_audio_setup( ++ had_event_call_back audio_callbacks, ++ struct hdmi_audio_registers_ops *reg_ops, ++ struct hdmi_audio_query_set_ops *query_ops) ++{ ++ DRM_ERROR("%s: HDMI is not supported.\n", __func__); ++ return -ENODEV; ++} ++EXPORT_SYMBOL(mid_hdmi_audio_setup); ++ ++int mid_hdmi_audio_register(struct snd_intel_had_interface *driver, ++ void *had_data) ++{ ++ DRM_ERROR("%s: HDMI is not supported.\n", __func__); ++ return -ENODEV; ++} ++EXPORT_SYMBOL(mid_hdmi_audio_register); ++#endif + +From 915a5b433454cab54cda47484390e84cfb8c7b5d Mon Sep 17 00:00:00 2001 +From: Pierre-Louis Bossart +Date: Tue, 1 Mar 2016 16:25:04 -0600 +Subject: [PATCH 06/12] drm/i915: enable non-HDAudio HDMI interface Makefile + +Makefile for all previous patches + +This driver was downloaded from https://github.com/01org/baytrailaudio/ + +...and had the changes to .config stripped and the revert on sound/init.c + +clean-up, port to 4.5 and intel-drm by Pierre Bossart + +Signed-off-by: David Henningsson +Signed-off-by: Pierre-Louis Bossart +--- + drivers/gpu/drm/i915/Makefile | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile +index 0b88ba0..57406b2 100644 +--- a/drivers/gpu/drm/i915/Makefile ++++ b/drivers/gpu/drm/i915/Makefile +@@ -14,7 +14,8 @@ i915-y := i915_drv.o \ + i915_sysfs.o \ + intel_csr.o \ + intel_pm.o \ +- intel_runtime_pm.o ++ intel_runtime_pm.o \ ++ hdmi_audio_if.o + + i915-$(CONFIG_COMPAT) += i915_ioc32.o + i915-$(CONFIG_DEBUG_FS) += i915_debugfs.o +@@ -39,6 +40,7 @@ i915-y += i915_cmd_parser.o \ + i915_trace_points.o \ + intel_lrc.o \ + intel_mocs.o \ ++ i915_rpm.o \ + intel_ringbuffer.o \ + intel_uncore.o + + +From 8213760904b873b68e55f5c707921704d0ff4c8f Mon Sep 17 00:00:00 2001 +From: Pierre-Louis Bossart +Date: Thu, 3 Mar 2016 11:09:26 -0600 +Subject: [PATCH 07/12] ALSA: Intel: Atom: add Atom non-HDAudio HDMI interface + +Add support interface available on Baytrail and CherryTrail + +Initial code was downloaded from https://github.com/01org/baytrailaudio/ +...and had the changes to .config stripped and the revert on sound/init.c +and printk->pr_debug done by David Henningson + +Clean-up, port to 4.5 and intel-drm by Pierre Bossart +CherryTrail support by Jerome Anand + +Signed-off-by: David Henningsson +Signed-off-by: Pierre-Louis Bossart +--- + sound/Kconfig | 8 + + sound/Makefile | 1 + + sound/hdmi_audio/Makefile | 9 + + sound/hdmi_audio/intel_mid_hdmi_audio.c | 2027 ++++++++++++++++++++++++++++ + sound/hdmi_audio/intel_mid_hdmi_audio.h | 740 ++++++++++ + sound/hdmi_audio/intel_mid_hdmi_audio_if.c | 533 ++++++++ + 6 files changed, 3318 insertions(+) + create mode 100644 sound/hdmi_audio/Makefile + create mode 100644 sound/hdmi_audio/intel_mid_hdmi_audio.c + create mode 100644 sound/hdmi_audio/intel_mid_hdmi_audio.h + create mode 100644 sound/hdmi_audio/intel_mid_hdmi_audio_if.c + +diff --git a/sound/Kconfig b/sound/Kconfig +index 5a240e0..75c679e 100644 +--- a/sound/Kconfig ++++ b/sound/Kconfig +@@ -134,3 +134,11 @@ config AC97_BUS + sound subsystem and other function drivers completely unrelated to + sound although they're sharing the AC97 bus. Concerned drivers + should "select" this. ++ ++config SUPPORT_HDMI ++ bool "SUPPORT_HDMI" ++ depends on DRM_I915 ++ default n ++ help ++ Choose this option to support HDMI. ++ +diff --git a/sound/Makefile b/sound/Makefile +index 7732070..f2c5e82 100644 +--- a/sound/Makefile ++++ b/sound/Makefile +@@ -8,6 +8,7 @@ obj-$(CONFIG_DMASOUND) += oss/ + obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \ + firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ + obj-$(CONFIG_SND_AOA) += aoa/ ++obj-$(CONFIG_SUPPORT_HDMI) += hdmi_audio/ + + # This one must be compilable even if sound is configured out + obj-$(CONFIG_AC97_BUS) += ac97_bus.o +diff --git a/sound/hdmi_audio/Makefile b/sound/hdmi_audio/Makefile +new file mode 100644 +index 0000000..b28eb3b +--- /dev/null ++++ b/sound/hdmi_audio/Makefile +@@ -0,0 +1,9 @@ ++DRIVER_NAME := hdmi_audio ++ ++ccflags-y += -Idrivers/gpu/drm/i915 ++ ++$(DRIVER_NAME)-objs += \ ++ intel_mid_hdmi_audio.o \ ++ intel_mid_hdmi_audio_if.o ++ ++obj-m += $(DRIVER_NAME).o +diff --git a/sound/hdmi_audio/intel_mid_hdmi_audio.c b/sound/hdmi_audio/intel_mid_hdmi_audio.c +new file mode 100644 +index 0000000..d8c5574 +--- /dev/null ++++ b/sound/hdmi_audio/intel_mid_hdmi_audio.c +@@ -0,0 +1,2027 @@ ++/* ++ * intel_mid_hdmi_audio.c - Intel HDMI audio driver for MID ++ * ++ * Copyright (C) 2010 Intel Corp ++ * Authors: Sailaja Bandarupalli ++ * Ramesh Babu K V ++ * Vaibhav Agarwal ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; version 2 of the License. ++ * ++ * 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. ++ * ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ * ALSA driver for Intel MID HDMI audio controller ++ */ ++ ++#define pr_fmt(fmt) "had: " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "intel_mid_hdmi_audio.h" ++ ++#define PCM_INDEX 0 ++#define MAX_PB_STREAMS 1 ++#define MAX_CAP_STREAMS 0 ++#define HDMI_AUDIO_DRIVER "hdmi-audio" ++static DEFINE_MUTEX(had_mutex); ++ ++/*standard module options for ALSA. This module supports only one card*/ ++static int hdmi_card_index = SNDRV_DEFAULT_IDX1; ++static char *hdmi_card_id = SNDRV_DEFAULT_STR1; ++static struct snd_intelhad *had_data; ++ ++module_param(hdmi_card_index, int, 0444); ++MODULE_PARM_DESC(hdmi_card_index, ++ "Index value for INTEL Intel HDMI Audio controller."); ++module_param(hdmi_card_id, charp, 0444); ++MODULE_PARM_DESC(hdmi_card_id, ++ "ID string for INTEL Intel HDMI Audio controller."); ++MODULE_AUTHOR("Sailaja Bandarupalli "); ++MODULE_AUTHOR("Ramesh Babu K V "); ++MODULE_AUTHOR("Vaibhav Agarwal "); ++MODULE_DESCRIPTION("Intel HDMI Audio driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_SUPPORTED_DEVICE("{Intel,Intel_HAD}"); ++MODULE_VERSION(HAD_DRIVER_VERSION); ++ ++#define INFO_FRAME_WORD1 0x000a0184 ++#define FIFO_THRESHOLD 0xFE ++#define DMA_FIFO_THRESHOLD 0x7 ++#define BYTES_PER_WORD 0x4 ++ ++/* Sampling rate as per IEC60958 Ver 3 */ ++#define CH_STATUS_MAP_32KHZ 0x3 ++#define CH_STATUS_MAP_44KHZ 0x0 ++#define CH_STATUS_MAP_48KHZ 0x2 ++#define CH_STATUS_MAP_88KHZ 0x8 ++#define CH_STATUS_MAP_96KHZ 0xA ++#define CH_STATUS_MAP_176KHZ 0xC ++#define CH_STATUS_MAP_192KHZ 0xE ++ ++#define MAX_SMPL_WIDTH_20 0x0 ++#define MAX_SMPL_WIDTH_24 0x1 ++#define SMPL_WIDTH_16BITS 0x1 ++#define SMPL_WIDTH_24BITS 0x5 ++#define CHANNEL_ALLOCATION 0x1F ++#define MASK_BYTE0 0x000000FF ++#define VALID_DIP_WORDS 3 ++#define LAYOUT0 0 ++#define LAYOUT1 1 ++#define SWAP_LFE_CENTER 0x00fac4c8 ++#define AUD_CONFIG_CH_MASK_V2 0x70 ++ ++/* ++ * ELD SA bits in the CEA Speaker Allocation data block ++*/ ++static int eld_speaker_allocation_bits[] = { ++ [0] = FL | FR, ++ [1] = LFE, ++ [2] = FC, ++ [3] = RL | RR, ++ [4] = RC, ++ [5] = FLC | FRC, ++ [6] = RLC | RRC, ++ /* the following are not defined in ELD yet */ ++ [7] = 0, ++}; ++ ++/* ++ * This is an ordered list! ++ * ++ * The preceding ones have better chances to be selected by ++ * hdmi_channel_allocation(). ++ */ ++static struct cea_channel_speaker_allocation channel_allocations[] = { ++/* channel: 7 6 5 4 3 2 1 0 */ ++{ .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL } }, ++ /* 2.1 */ ++{ .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL } }, ++ /* Dolby Surround */ ++{ .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL } }, ++ /* surround40 */ ++{ .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL } }, ++ /* surround41 */ ++{ .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL } }, ++ /* surround50 */ ++{ .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL } }, ++ /* surround51 */ ++{ .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL } }, ++ /* 6.1 */ ++{ .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL } }, ++ /* surround71 */ ++{ .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL } }, ++ ++{ .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL } }, ++{ .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL } }, ++{ .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL } }, ++{ .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL } }, ++{ .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL } }, ++{ .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL } }, ++{ .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL } }, ++{ .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL } }, ++{ .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL } }, ++{ .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL } }, ++{ .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL } }, ++{ .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL } }, ++{ .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL } }, ++{ .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL } }, ++{ .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL } }, ++{ .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL } }, ++{ .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL } }, ++{ .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL } }, ++{ .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL } }, ++{ .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL } }, ++{ .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL } }, ++{ .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL } }, ++{ .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL } }, ++}; ++ ++static struct channel_map_table map_tables[] = { ++ { SNDRV_CHMAP_FL, 0x00, FL }, ++ { SNDRV_CHMAP_FR, 0x01, FR }, ++ { SNDRV_CHMAP_RL, 0x04, RL }, ++ { SNDRV_CHMAP_RR, 0x05, RR }, ++ { SNDRV_CHMAP_LFE, 0x02, LFE }, ++ { SNDRV_CHMAP_FC, 0x03, FC }, ++ { SNDRV_CHMAP_RLC, 0x06, RLC }, ++ { SNDRV_CHMAP_RRC, 0x07, RRC }, ++ {} /* terminator */ ++}; ++ ++/* hardware capability structure */ ++static const struct snd_pcm_hardware snd_intel_hadstream = { ++ .info = (SNDRV_PCM_INFO_INTERLEAVED | ++ SNDRV_PCM_INFO_DOUBLE | ++ SNDRV_PCM_INFO_MMAP| ++ SNDRV_PCM_INFO_MMAP_VALID | ++ SNDRV_PCM_INFO_BATCH), ++ .formats = (SNDRV_PCM_FMTBIT_S24 | ++ SNDRV_PCM_FMTBIT_U24), ++ .rates = SNDRV_PCM_RATE_32000 | ++ SNDRV_PCM_RATE_44100 | ++ SNDRV_PCM_RATE_48000 | ++ SNDRV_PCM_RATE_88200 | ++ SNDRV_PCM_RATE_96000 | ++ SNDRV_PCM_RATE_176400 | ++ SNDRV_PCM_RATE_192000, ++ .rate_min = HAD_MIN_RATE, ++ .rate_max = HAD_MAX_RATE, ++ .channels_min = HAD_MIN_CHANNEL, ++ .channels_max = HAD_MAX_CHANNEL, ++ .buffer_bytes_max = HAD_MAX_BUFFER, ++ .period_bytes_min = HAD_MIN_PERIOD_BYTES, ++ .period_bytes_max = HAD_MAX_PERIOD_BYTES, ++ .periods_min = HAD_MIN_PERIODS, ++ .periods_max = HAD_MAX_PERIODS, ++ .fifo_size = HAD_FIFO_SIZE, ++}; ++ ++/* Register access functions */ ++ ++inline int had_get_hwstate(struct snd_intelhad *intelhaddata) ++{ ++ /* Check for device presence -SW state */ ++ if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) { ++ pr_debug("%s:Device not connected:%d\n", __func__, ++ intelhaddata->drv_status); ++ return -ENODEV; ++ } ++ ++ /* Check for device presence -HW state */ ++ if (!ospm_power_is_hw_on(OSPM_DISPLAY_ISLAND)) { ++ pr_err("%s:Device not connected\n", __func__); ++ /* HOT_UNPLUG event can be sent to ++ * maintain correct state within HAD ++ * had_event_handler(HAD_EVENT_HOT_UNPLUG, intelhaddata); ++ * Drop all acuired locks before executing this. ++ */ ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++inline int had_get_caps(enum had_caps_list query, void *caps) ++{ ++ int retval; ++ struct snd_intelhad *intelhaddata = had_data; ++ ++ retval = had_get_hwstate(intelhaddata); ++ if (!retval) ++ retval = intelhaddata->query_ops.hdmi_audio_get_caps(query, ++ caps); ++ ++ return retval; ++} ++ ++inline int had_set_caps(enum had_caps_list set_element, void *caps) ++{ ++ int retval; ++ struct snd_intelhad *intelhaddata = had_data; ++ ++ retval = had_get_hwstate(intelhaddata); ++ if (!retval) ++ retval = intelhaddata->query_ops.hdmi_audio_set_caps( ++ set_element, caps); ++ ++ return retval; ++} ++ ++inline int had_read_register(uint32_t offset, uint32_t *data) ++{ ++ int retval; ++ struct snd_intelhad *intelhaddata = had_data; ++ u32 base_addr = intelhaddata->audio_reg_base; ++ ++ retval = had_get_hwstate(intelhaddata); ++ if (!retval) ++ retval = intelhaddata->reg_ops.hdmi_audio_read_register( ++ base_addr + offset, data); ++ ++ return retval; ++} ++ ++inline int had_write_register(uint32_t offset, uint32_t data) ++{ ++ int retval; ++ struct snd_intelhad *intelhaddata = had_data; ++ u32 base_addr = intelhaddata->audio_reg_base; ++ ++ retval = had_get_hwstate(intelhaddata); ++ if (!retval) ++ retval = intelhaddata->reg_ops.hdmi_audio_write_register( ++ base_addr + offset, data); ++ ++ return retval; ++} ++ ++inline int had_read_modify(uint32_t offset, uint32_t data, uint32_t mask) ++{ ++ int retval; ++ struct snd_intelhad *intelhaddata = had_data; ++ u32 base_addr = intelhaddata->audio_reg_base; ++ ++ retval = had_get_hwstate(intelhaddata); ++ if (!retval) ++ retval = intelhaddata->reg_ops.hdmi_audio_read_modify( ++ base_addr + offset, data, mask); ++ ++ return retval; ++} ++/** ++ * had_read_modify_aud_config_v2 - Specific function to read-modify AUD_CONFIG ++ * register on VLV2.The had_read_modify() function should not directly be used ++ * on VLV2 for updating AUD_CONFIG register. ++ * This is because: ++ * Bit6 of AUD_CONFIG register is writeonly due to a silicon bug on VLV2 HDMI IP. ++ * As a result a read-modify of AUD_CONFIG regiter will always clear bit6. ++ * AUD_CONFIG[6:4] represents the "channels" field of the register. ++ * This field should be 1xy binary for configuration with 6 or more channels. ++ * Read-modify of AUD_CONFIG (Eg. for enabling audio) causes the "channels" field ++ * to be updated as 0xy binary resulting in bad audio. ++ * The fix is to always write the AUD_CONFIG[6:4] with appropriate value when ++ * doing read-modify of AUD_CONFIG register. ++ * ++ * @substream: the current substream or NULL if no active substream ++ * @data : data to be written ++ * @mask : mask ++ * ++ */ ++inline int had_read_modify_aud_config_v2(struct snd_pcm_substream *substream, ++ uint32_t data, uint32_t mask) ++{ ++ union aud_cfg cfg_val = {.cfg_regval = 0}; ++ u8 channels; ++ ++ /* ++ * If substream is NULL, there is no active stream. ++ * In this case just set channels to 2 ++ */ ++ if (substream) ++ channels = substream->runtime->channels; ++ else ++ channels = 2; ++ cfg_val.cfg_regx_v2.num_ch = channels - 2; ++ ++ data = data | cfg_val.cfg_regval; ++ mask = mask | AUD_CONFIG_CH_MASK_V2; ++ ++ pr_debug("%s : data = %x, mask =%x\n", __func__, data, mask); ++ ++ return had_read_modify(AUD_CONFIG, data, mask); ++} ++ ++/** ++ * snd_intelhad_enable_audio_v1 - to enable audio ++ * ++ * @substream: Current substream or NULL if no active substream. ++ * @enable: 1 if audio is to be enabled; 0 if audio is to be disabled. ++ * ++ */ ++static void snd_intelhad_enable_audio_v1(struct snd_pcm_substream *substream, ++ u8 enable) ++{ ++ had_read_modify(AUD_CONFIG, enable, BIT(0)); ++} ++ ++/** ++ * snd_intelhad_enable_audio_v2 - to enable audio ++ * ++ * @substream: Current substream or NULL if no active substream. ++ * @enable: 1 if audio is to be enabled; 0 if audio is to be disabled. ++ */ ++static void snd_intelhad_enable_audio_v2(struct snd_pcm_substream *substream, ++ u8 enable) ++{ ++ had_read_modify_aud_config_v2(substream, enable, BIT(0)); ++} ++ ++/** ++ * snd_intelhad_reset_audio_v1 - to reset audio subsystem ++ * ++ * @reset: 1 to reset audio; 0 to bring audio out of reset. ++ * ++ */ ++static void snd_intelhad_reset_audio_v1(u8 reset) ++{ ++ had_write_register(AUD_HDMI_STATUS, reset); ++} ++ ++/** ++ * snd_intelhad_reset_audio_v2 - to reset audio subsystem ++ * ++ * @reset: 1 to reset audio; 0 to bring audio out of reset. ++ * ++ */ ++static void snd_intelhad_reset_audio_v2(u8 reset) ++{ ++ had_write_register(AUD_HDMI_STATUS_v2, reset); ++} ++ ++/** ++ * had_prog_status_reg - to initialize audio channel status registers ++ * ++ * @substream:substream for which the prepare function is called ++ * @intelhaddata:substream private data ++ * ++ * This function is called in the prepare callback ++ */ ++static int had_prog_status_reg(struct snd_pcm_substream *substream, ++ struct snd_intelhad *intelhaddata) ++{ ++ union aud_ch_status_0 ch_stat0 = {.status_0_regval = 0}; ++ union aud_ch_status_1 ch_stat1 = {.status_1_regval = 0}; ++ int format; ++ ++ pr_debug("Entry %s\n", __func__); ++ ++ ch_stat0.status_0_regx.lpcm_id = (intelhaddata->aes_bits & ++ IEC958_AES0_NONAUDIO)>>1; ++ ch_stat0.status_0_regx.clk_acc = (intelhaddata->aes_bits & ++ IEC958_AES3_CON_CLOCK)>>4; ++ switch (substream->runtime->rate) { ++ case AUD_SAMPLE_RATE_32: ++ ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_32KHZ; ++ break; ++ ++ case AUD_SAMPLE_RATE_44_1: ++ ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_44KHZ; ++ break; ++ case AUD_SAMPLE_RATE_48: ++ ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_48KHZ; ++ break; ++ case AUD_SAMPLE_RATE_88_2: ++ ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_88KHZ; ++ break; ++ case AUD_SAMPLE_RATE_96: ++ ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_96KHZ; ++ break; ++ case AUD_SAMPLE_RATE_176_4: ++ ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_176KHZ; ++ break; ++ case AUD_SAMPLE_RATE_192: ++ ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_192KHZ; ++ break; ++ ++ default: ++ /* control should never come here */ ++ return -EINVAL; ++ break; ++ ++ } ++ had_write_register(AUD_CH_STATUS_0, ch_stat0.status_0_regval); ++ ++ format = substream->runtime->format; ++ ++ if (format == SNDRV_PCM_FORMAT_S16_LE) { ++ ch_stat1.status_1_regx.max_wrd_len = MAX_SMPL_WIDTH_20; ++ ch_stat1.status_1_regx.wrd_len = SMPL_WIDTH_16BITS; ++ } else if (format == SNDRV_PCM_FORMAT_S24_LE) { ++ ch_stat1.status_1_regx.max_wrd_len = MAX_SMPL_WIDTH_24; ++ ch_stat1.status_1_regx.wrd_len = SMPL_WIDTH_24BITS; ++ } else { ++ ch_stat1.status_1_regx.max_wrd_len = 0; ++ ch_stat1.status_1_regx.wrd_len = 0; ++ } ++ had_write_register(AUD_CH_STATUS_1, ch_stat1.status_1_regval); ++ return 0; ++} ++ ++/** ++ * snd_intelhad_prog_audio_ctrl_v2 - to initialize audio ++ * registers and buffer confgiuration registers ++ * ++ * @substream:substream for which the prepare function is called ++ * @intelhaddata:substream private data ++ * ++ * This function is called in the prepare callback ++ */ ++int snd_intelhad_prog_audio_ctrl_v2(struct snd_pcm_substream *substream, ++ struct snd_intelhad *intelhaddata) ++{ ++ union aud_cfg cfg_val = {.cfg_regval = 0}; ++ union aud_buf_config buf_cfg = {.buf_cfgval = 0}; ++ u8 channels; ++ ++ had_prog_status_reg(substream, intelhaddata); ++ ++ buf_cfg.buf_cfg_regx_v2.audio_fifo_watermark = FIFO_THRESHOLD; ++ buf_cfg.buf_cfg_regx_v2.dma_fifo_watermark = DMA_FIFO_THRESHOLD; ++ buf_cfg.buf_cfg_regx_v2.aud_delay = 0; ++ had_write_register(AUD_BUF_CONFIG, buf_cfg.buf_cfgval); ++ ++ channels = substream->runtime->channels; ++ cfg_val.cfg_regx_v2.num_ch = channels - 2; ++ if (channels <= 2) ++ cfg_val.cfg_regx_v2.layout = LAYOUT0; ++ else ++ cfg_val.cfg_regx_v2.layout = LAYOUT1; ++ ++ cfg_val.cfg_regx_v2.val_bit = 1; ++ had_write_register(AUD_CONFIG, cfg_val.cfg_regval); ++ return 0; ++} ++ ++/** ++ * snd_intelhad_prog_audio_ctrl_v1 - to initialize audio ++ * registers and buffer confgiuration registers ++ * ++ * @substream:substream for which the prepare function is called ++ * @intelhaddata:substream private data ++ * ++ * This function is called in the prepare callback ++ */ ++int snd_intelhad_prog_audio_ctrl_v1(struct snd_pcm_substream *substream, ++ struct snd_intelhad *intelhaddata) ++{ ++ union aud_cfg cfg_val = {.cfg_regval = 0}; ++ union aud_buf_config buf_cfg = {.buf_cfgval = 0}; ++ u8 channels; ++ ++ had_prog_status_reg(substream, intelhaddata); ++ ++ buf_cfg.buf_cfg_regx.fifo_width = FIFO_THRESHOLD; ++ buf_cfg.buf_cfg_regx.aud_delay = 0; ++ had_write_register(AUD_BUF_CONFIG, buf_cfg.buf_cfgval); ++ ++ channels = substream->runtime->channels; ++ ++ switch (channels) { ++ case 1: ++ case 2: ++ cfg_val.cfg_regx.num_ch = CH_STEREO; ++ cfg_val.cfg_regx.layout = LAYOUT0; ++ break; ++ ++ case 3: ++ case 4: ++ cfg_val.cfg_regx.num_ch = CH_THREE_FOUR; ++ cfg_val.cfg_regx.layout = LAYOUT1; ++ break; ++ ++ case 5: ++ case 6: ++ cfg_val.cfg_regx.num_ch = CH_FIVE_SIX; ++ cfg_val.cfg_regx.layout = LAYOUT1; ++ break; ++ ++ case 7: ++ case 8: ++ cfg_val.cfg_regx.num_ch = CH_SEVEN_EIGHT; ++ cfg_val.cfg_regx.layout = LAYOUT1; ++ break; ++ ++ } ++ ++ cfg_val.cfg_regx.val_bit = 1; ++ had_write_register(AUD_CONFIG, cfg_val.cfg_regval); ++ return 0; ++} ++/* ++ * Compute derived values in channel_allocations[]. ++ */ ++static void init_channel_allocations(void) ++{ ++ int i, j; ++ struct cea_channel_speaker_allocation *p; ++ ++ pr_debug("%s: Enter\n", __func__); ++ ++ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { ++ p = channel_allocations + i; ++ p->channels = 0; ++ p->spk_mask = 0; ++ for (j = 0; j < ARRAY_SIZE(p->speakers); j++) ++ if (p->speakers[j]) { ++ p->channels++; ++ p->spk_mask |= p->speakers[j]; ++ } ++ } ++} ++ ++/* ++ * The transformation takes two steps: ++ * ++ * eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask ++ * spk_mask => (channel_allocations[]) => ai->CA ++ * ++ * TODO: it could select the wrong CA from multiple candidates. ++*/ ++static int snd_intelhad_channel_allocation(struct snd_intelhad *intelhaddata, ++ int channels) ++{ ++ int i; ++ int ca = 0; ++ int spk_mask = 0; ++ ++ /* ++ * CA defaults to 0 for basic stereo audio ++ */ ++ if (channels <= 2) ++ return 0; ++ ++ /* ++ * expand ELD's speaker allocation mask ++ * ++ * ELD tells the speaker mask in a compact(paired) form, ++ * expand ELD's notions to match the ones used by Audio InfoFrame. ++ */ ++ ++ for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { ++ if (intelhaddata->eeld.speaker_allocation_block & (1 << i)) ++ spk_mask |= eld_speaker_allocation_bits[i]; ++ } ++ ++ /* search for the first working match in the CA table */ ++ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { ++ if (channels == channel_allocations[i].channels && ++ (spk_mask & channel_allocations[i].spk_mask) == ++ channel_allocations[i].spk_mask) { ++ ca = channel_allocations[i].ca_index; ++ break; ++ } ++ } ++ ++ pr_debug("HDMI: select CA 0x%x for %d\n", ca, channels); ++ ++ return ca; ++} ++ ++/* from speaker bit mask to ALSA API channel position */ ++static int spk_to_chmap(int spk) ++{ ++ struct channel_map_table *t = map_tables; ++ ++ for (; t->map; t++) { ++ if (t->spk_mask == spk) ++ return t->map; ++ } ++ return 0; ++} ++ ++void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata) ++{ ++ int i = 0, c = 0; ++ int spk_mask = 0; ++ struct snd_pcm_chmap_elem *chmap; ++ uint8_t eld_high, eld_high_mask = 0xF0; ++ uint8_t high_msb; ++ ++ chmap = kzalloc(sizeof(*chmap), GFP_KERNEL); ++ if (chmap == NULL) { ++ pr_err("kzalloc returned null in %s\n", __func__); ++ intelhaddata->chmap->chmap = NULL; ++ return; ++ } ++ ++ had_get_caps(HAD_GET_ELD, &intelhaddata->eeld); ++ ++ pr_debug("eeld.speaker_allocation_block = %x\n", ++ intelhaddata->eeld.speaker_allocation_block); ++ ++ /* WA: Fix the max channel supported to 8 */ ++ ++ /* ++ * Sink may support more than 8 channels, if eld_high has more than ++ * one bit set. SOC supports max 8 channels. ++ * Refer eld_speaker_allocation_bits, for sink speaker allocation ++ */ ++ ++ /* if 0x2F < eld < 0x4F fall back to 0x2f, else fall back to 0x4F */ ++ eld_high = intelhaddata->eeld.speaker_allocation_block & eld_high_mask; ++ if ((eld_high & (eld_high-1)) && (eld_high > 0x1F)) { ++ /* eld_high & (eld_high-1): if more than 1 bit set */ ++ /* 0x1F: 7 channels */ ++ for (i = 1; i < 4; i++) { ++ high_msb = eld_high & (0x80 >> i); ++ if (high_msb) { ++ intelhaddata->eeld.speaker_allocation_block &= ++ high_msb | 0xF; ++ break; ++ } ++ } ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { ++ if (intelhaddata->eeld.speaker_allocation_block & (1 << i)) ++ spk_mask |= eld_speaker_allocation_bits[i]; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { ++ if (spk_mask == channel_allocations[i].spk_mask) { ++ for (c = 0; c < channel_allocations[i].channels; c++) { ++ chmap->map[c] = spk_to_chmap( ++ channel_allocations[i].speakers[(MAX_SPEAKERS - 1)-c]); ++ } ++ chmap->channels = channel_allocations[i].channels; ++ intelhaddata->chmap->chmap = chmap; ++ break; ++ } ++ } ++ if (i >= ARRAY_SIZE(channel_allocations)) ++ kfree(chmap); ++} ++ ++/* ++ ** ALSA API channel-map control callbacks ++ **/ ++static int had_chmap_ctl_info(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_info *uinfo) ++{ ++ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); ++ struct snd_intelhad *intelhaddata = info->private_data; ++ ++ if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) ++ return -ENODEV; ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; ++ uinfo->count = HAD_MAX_CHANNEL; ++ uinfo->value.integer.min = 0; ++ uinfo->value.integer.max = SNDRV_CHMAP_LAST; ++ return 0; ++} ++ ++#ifndef USE_ALSA_DEFAULT_TLV ++static int had_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, ++ unsigned int size, unsigned int __user *tlv) ++{ ++ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); ++ struct snd_intelhad *intelhaddata = info->private_data; ++ ++ if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) ++ return -ENODEV; ++ ++ /* TODO: Fix for query channel map */ ++ return -EPERM; ++} ++#endif ++ ++static int had_chmap_ctl_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); ++ struct snd_intelhad *intelhaddata = info->private_data; ++ int i = 0; ++ const struct snd_pcm_chmap_elem *chmap; ++ ++ if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) ++ return -ENODEV; ++ if (intelhaddata->chmap->chmap == NULL) ++ return -ENODATA; ++ chmap = intelhaddata->chmap->chmap; ++ for (i = 0; i < chmap->channels; i++) { ++ ucontrol->value.integer.value[i] = chmap->map[i]; ++ pr_debug("chmap->map[%d] = %d\n", i, chmap->map[i]); ++ } ++ ++ return 0; ++} ++ ++static int had_chmap_ctl_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ /* TODO: Get channel map and set swap register */ ++ return -EPERM; ++} ++ ++static int had_register_chmap_ctls(struct snd_intelhad *intelhaddata, ++ struct snd_pcm *pcm) ++{ ++ int err = 0; ++ ++ err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, ++ NULL, 0, (unsigned long)intelhaddata, ++ &intelhaddata->chmap); ++ if (err < 0) ++ return err; ++ ++ intelhaddata->chmap->private_data = intelhaddata; ++ intelhaddata->kctl = intelhaddata->chmap->kctl; ++ intelhaddata->kctl->info = had_chmap_ctl_info; ++ intelhaddata->kctl->get = had_chmap_ctl_get; ++ intelhaddata->kctl->put = had_chmap_ctl_put; ++#ifndef USE_ALSA_DEFAULT_TLV ++ intelhaddata->kctl->tlv.c = had_chmap_ctl_tlv; ++#endif ++ intelhaddata->chmap->chmap = NULL; ++ return 0; ++} ++ ++/** ++ * snd_intelhad_prog_dip_v1 - to initialize Data Island Packets registers ++ * ++ * @substream:substream for which the prepare function is called ++ * @intelhaddata:substream private data ++ * ++ * This function is called in the prepare callback ++ */ ++static void snd_intelhad_prog_dip_v1(struct snd_pcm_substream *substream, ++ struct snd_intelhad *intelhaddata) ++{ ++ int i; ++ union aud_ctrl_st ctrl_state = {.ctrl_val = 0}; ++ union aud_info_frame2 frame2 = {.fr2_val = 0}; ++ union aud_info_frame3 frame3 = {.fr3_val = 0}; ++ u8 checksum = 0; ++ int channels; ++ ++ channels = substream->runtime->channels; ++ ++ had_write_register(AUD_CNTL_ST, ctrl_state.ctrl_val); ++ ++ frame2.fr2_regx.chnl_cnt = substream->runtime->channels - 1; ++ ++ frame3.fr3_regx.chnl_alloc = snd_intelhad_channel_allocation( ++ intelhaddata, channels); ++ ++ /*Calculte the byte wide checksum for all valid DIP words*/ ++ for (i = 0; i < BYTES_PER_WORD; i++) ++ checksum += (INFO_FRAME_WORD1 >> i*BITS_PER_BYTE) & MASK_BYTE0; ++ for (i = 0; i < BYTES_PER_WORD; i++) ++ checksum += (frame2.fr2_val >> i*BITS_PER_BYTE) & MASK_BYTE0; ++ for (i = 0; i < BYTES_PER_WORD; i++) ++ checksum += (frame3.fr3_val >> i*BITS_PER_BYTE) & MASK_BYTE0; ++ ++ frame2.fr2_regx.chksum = -(checksum); ++ ++ had_write_register(AUD_HDMIW_INFOFR, INFO_FRAME_WORD1); ++ had_write_register(AUD_HDMIW_INFOFR, frame2.fr2_val); ++ had_write_register(AUD_HDMIW_INFOFR, frame3.fr3_val); ++ ++ /* program remaining DIP words with zero */ ++ for (i = 0; i < HAD_MAX_DIP_WORDS-VALID_DIP_WORDS; i++) ++ had_write_register(AUD_HDMIW_INFOFR, 0x0); ++ ++ ctrl_state.ctrl_regx.dip_freq = 1; ++ ctrl_state.ctrl_regx.dip_en_sta = 1; ++ had_write_register(AUD_CNTL_ST, ctrl_state.ctrl_val); ++} ++ ++/** ++ * snd_intelhad_prog_dip_v2 - to initialize Data Island Packets registers ++ * ++ * @substream:substream for which the prepare function is called ++ * @intelhaddata:substream private data ++ * ++ * This function is called in the prepare callback ++ */ ++static void snd_intelhad_prog_dip_v2(struct snd_pcm_substream *substream, ++ struct snd_intelhad *intelhaddata) ++{ ++ int i; ++ union aud_ctrl_st ctrl_state = {.ctrl_val = 0}; ++ union aud_info_frame2 frame2 = {.fr2_val = 0}; ++ union aud_info_frame3 frame3 = {.fr3_val = 0}; ++ u8 checksum = 0; ++ int channels; ++ ++ channels = substream->runtime->channels; ++ ++ had_write_register(AUD_CNTL_ST, ctrl_state.ctrl_val); ++ ++ frame2.fr2_regx.chnl_cnt = substream->runtime->channels - 1; ++ ++ frame3.fr3_regx.chnl_alloc = snd_intelhad_channel_allocation( ++ intelhaddata, channels); ++ ++ /*Calculte the byte wide checksum for all valid DIP words*/ ++ for (i = 0; i < BYTES_PER_WORD; i++) ++ checksum += (INFO_FRAME_WORD1 >> i*BITS_PER_BYTE) & MASK_BYTE0; ++ for (i = 0; i < BYTES_PER_WORD; i++) ++ checksum += (frame2.fr2_val >> i*BITS_PER_BYTE) & MASK_BYTE0; ++ for (i = 0; i < BYTES_PER_WORD; i++) ++ checksum += (frame3.fr3_val >> i*BITS_PER_BYTE) & MASK_BYTE0; ++ ++ frame2.fr2_regx.chksum = -(checksum); ++ ++ had_write_register(AUD_HDMIW_INFOFR_v2, INFO_FRAME_WORD1); ++ had_write_register(AUD_HDMIW_INFOFR_v2, frame2.fr2_val); ++ had_write_register(AUD_HDMIW_INFOFR_v2, frame3.fr3_val); ++ ++ /* program remaining DIP words with zero */ ++ for (i = 0; i < HAD_MAX_DIP_WORDS-VALID_DIP_WORDS; i++) ++ had_write_register(AUD_HDMIW_INFOFR_v2, 0x0); ++ ++ ctrl_state.ctrl_regx.dip_freq = 1; ++ ctrl_state.ctrl_regx.dip_en_sta = 1; ++ had_write_register(AUD_CNTL_ST, ctrl_state.ctrl_val); ++} ++ ++/** ++ * snd_intelhad_prog_buffer - programs buffer ++ * address and length registers ++ * ++ * @substream:substream for which the prepare function is called ++ * @intelhaddata:substream private data ++ * ++ * This function programs ring buffer address and length into registers. ++ */ ++int snd_intelhad_prog_buffer(struct snd_intelhad *intelhaddata, ++ int start, int end) ++{ ++ u32 ring_buf_addr, ring_buf_size, period_bytes; ++ u8 i, num_periods; ++ struct snd_pcm_substream *substream; ++ ++ substream = intelhaddata->stream_info.had_substream; ++ if (!substream) { ++ pr_err("substream is NULL\n"); ++ dump_stack(); ++ return 0; ++ } ++ ++ ring_buf_addr = substream->runtime->dma_addr; ++ ring_buf_size = snd_pcm_lib_buffer_bytes(substream); ++ intelhaddata->stream_info.ring_buf_size = ring_buf_size; ++ period_bytes = frames_to_bytes(substream->runtime, ++ substream->runtime->period_size); ++ num_periods = substream->runtime->periods; ++ ++ /* ++ * buffer addr should be 64 byte aligned, period bytes ++ * will be used to calculate addr offset ++ */ ++ period_bytes &= ~0x3F; ++ ++ /* Hardware supports MAX_PERIODS buffers */ ++ if (end >= HAD_MAX_PERIODS) ++ return -EINVAL; ++ ++ for (i = start; i <= end; i++) { ++ /* Program the buf registers with addr and len */ ++ intelhaddata->buf_info[i].buf_addr = ring_buf_addr + ++ (i * period_bytes); ++ if (i < num_periods-1) ++ intelhaddata->buf_info[i].buf_size = period_bytes; ++ else ++ intelhaddata->buf_info[i].buf_size = ring_buf_size - ++ (period_bytes*i); ++ ++ had_write_register(AUD_BUF_A_ADDR + (i * HAD_REG_WIDTH), ++ intelhaddata->buf_info[i].buf_addr | ++ BIT(0) | BIT(1)); ++ had_write_register(AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH), ++ period_bytes); ++ intelhaddata->buf_info[i].is_valid = true; ++ } ++ pr_debug("%s:buf[%d-%d] addr=%#x and size=%d\n", __func__, start, end, ++ intelhaddata->buf_info[start].buf_addr, ++ intelhaddata->buf_info[start].buf_size); ++ intelhaddata->valid_buf_cnt = num_periods; ++ return 0; ++} ++ ++inline int snd_intelhad_read_len(struct snd_intelhad *intelhaddata) ++{ ++ int i, retval = 0; ++ u32 len[4]; ++ ++ for (i = 0; i < 4 ; i++) { ++ had_read_register(AUD_BUF_A_LENGTH + (i * HAD_REG_WIDTH), ++ &len[i]); ++ if (!len[i]) ++ retval++; ++ } ++ if (retval != 1) { ++ for (i = 0; i < 4 ; i++) ++ pr_debug("buf[%d] size=%d\n", i, len[i]); ++ } ++ ++ return retval; ++} ++ ++/** ++ * snd_intelhad_prog_cts_v1 - Program HDMI audio CTS value ++ * ++ * @aud_samp_freq: sampling frequency of audio data ++ * @tmds: sampling frequency of the display data ++ * @n_param: N value, depends on aud_samp_freq ++ * @intelhaddata:substream private data ++ * ++ * Program CTS register based on the audio and display sampling frequency ++ */ ++static void snd_intelhad_prog_cts_v1(u32 aud_samp_freq, u32 tmds, u32 n_param, ++ struct snd_intelhad *intelhaddata) ++{ ++ u32 cts_val; ++ u64 dividend, divisor; ++ ++ /* Calculate CTS according to HDMI 1.3a spec*/ ++ dividend = (u64)tmds * n_param*1000; ++ divisor = 128 * aud_samp_freq; ++ cts_val = div64_u64(dividend, divisor); ++ pr_debug("TMDS value=%d, N value=%d, CTS Value=%d\n", ++ tmds, n_param, cts_val); ++ had_write_register(AUD_HDMI_CTS, (BIT(20) | cts_val)); ++} ++ ++/** ++ * snd_intelhad_prog_cts_v2 - Program HDMI audio CTS value ++ * ++ * @aud_samp_freq: sampling frequency of audio data ++ * @tmds: sampling frequency of the display data ++ * @n_param: N value, depends on aud_samp_freq ++ * @intelhaddata:substream private data ++ * ++ * Program CTS register based on the audio and display sampling frequency ++ */ ++static void snd_intelhad_prog_cts_v2(u32 aud_samp_freq, u32 tmds, u32 n_param, ++ struct snd_intelhad *intelhaddata) ++{ ++ u32 cts_val; ++ u64 dividend, divisor; ++ ++ /* Calculate CTS according to HDMI 1.3a spec*/ ++ dividend = (u64)tmds * n_param*1000; ++ divisor = 128 * aud_samp_freq; ++ cts_val = div64_u64(dividend, divisor); ++ pr_debug("TMDS value=%d, N value=%d, CTS Value=%d\n", ++ tmds, n_param, cts_val); ++ had_write_register(AUD_HDMI_CTS, (BIT(24) | cts_val)); ++} ++ ++static int had_calculate_n_value(u32 aud_samp_freq) ++{ ++ s32 n_val; ++ ++ /* Select N according to HDMI 1.3a spec*/ ++ switch (aud_samp_freq) { ++ case AUD_SAMPLE_RATE_32: ++ n_val = 4096; ++ break; ++ ++ case AUD_SAMPLE_RATE_44_1: ++ n_val = 6272; ++ break; ++ ++ case AUD_SAMPLE_RATE_48: ++ n_val = 6144; ++ break; ++ ++ case AUD_SAMPLE_RATE_88_2: ++ n_val = 12544; ++ break; ++ ++ case AUD_SAMPLE_RATE_96: ++ n_val = 12288; ++ break; ++ ++ case AUD_SAMPLE_RATE_176_4: ++ n_val = 25088; ++ break; ++ ++ case HAD_MAX_RATE: ++ n_val = 24576; ++ break; ++ ++ default: ++ n_val = -EINVAL; ++ break; ++ } ++ return n_val; ++} ++ ++/** ++ * snd_intelhad_prog_n_v1 - Program HDMI audio N value ++ * ++ * @aud_samp_freq: sampling frequency of audio data ++ * @n_param: N value, depends on aud_samp_freq ++ * @intelhaddata:substream private data ++ * ++ * This function is called in the prepare callback. ++ * It programs based on the audio and display sampling frequency ++ */ ++static int snd_intelhad_prog_n_v1(u32 aud_samp_freq, u32 *n_param, ++ struct snd_intelhad *intelhaddata) ++{ ++ s32 n_val; ++ ++ n_val = had_calculate_n_value(aud_samp_freq); ++ ++ if (n_val < 0) ++ return n_val; ++ ++ had_write_register(AUD_N_ENABLE, (BIT(20) | n_val)); ++ *n_param = n_val; ++ return 0; ++} ++ ++/** ++ * snd_intelhad_prog_n_v2 - Program HDMI audio N value ++ * ++ * @aud_samp_freq: sampling frequency of audio data ++ * @n_param: N value, depends on aud_samp_freq ++ * @intelhaddata:substream private data ++ * ++ * This function is called in the prepare callback. ++ * It programs based on the audio and display sampling frequency ++ */ ++static int snd_intelhad_prog_n_v2(u32 aud_samp_freq, u32 *n_param, ++ struct snd_intelhad *intelhaddata) ++{ ++ s32 n_val; ++ ++ n_val = had_calculate_n_value(aud_samp_freq); ++ ++ if (n_val < 0) ++ return n_val; ++ ++ had_write_register(AUD_N_ENABLE, (BIT(24) | n_val)); ++ *n_param = n_val; ++ return 0; ++} ++ ++static void had_clear_underrun_intr_v1(struct snd_intelhad *intelhaddata) ++{ ++ u32 hdmi_status, i = 0; ++ ++ /* Handle Underrun interrupt within Audio Unit */ ++ had_write_register(AUD_CONFIG, 0); ++ /* Reset buffer pointers */ ++ had_write_register(AUD_HDMI_STATUS, 1); ++ had_write_register(AUD_HDMI_STATUS, 0); ++ /** ++ * The interrupt status 'sticky' bits might not be cleared by ++ * setting '1' to that bit once... ++ */ ++ do { /* clear bit30, 31 AUD_HDMI_STATUS */ ++ had_read_register(AUD_HDMI_STATUS, &hdmi_status); ++ pr_debug("HDMI status =0x%x\n", hdmi_status); ++ if (hdmi_status & AUD_CONFIG_MASK_UNDERRUN) { ++ i++; ++ hdmi_status &= (AUD_CONFIG_MASK_SRDBG | ++ AUD_CONFIG_MASK_FUNCRST); ++ hdmi_status |= ~AUD_CONFIG_MASK_UNDERRUN; ++ had_write_register(AUD_HDMI_STATUS, hdmi_status); ++ } else ++ break; ++ } while (i < MAX_CNT); ++ if (i >= MAX_CNT) ++ pr_err("Unable to clear UNDERRUN bits\n"); ++} ++ ++static void had_clear_underrun_intr_v2(struct snd_intelhad *intelhaddata) ++{ ++ u32 hdmi_status, i = 0; ++ ++ /* Handle Underrun interrupt within Audio Unit */ ++ had_write_register(AUD_CONFIG, 0); ++ /* Reset buffer pointers */ ++ had_write_register(AUD_HDMI_STATUS_v2, 1); ++ had_write_register(AUD_HDMI_STATUS_v2, 0); ++ /** ++ * The interrupt status 'sticky' bits might not be cleared by ++ * setting '1' to that bit once... ++ */ ++ do { /* clear bit30, 31 AUD_HDMI_STATUS */ ++ had_read_register(AUD_HDMI_STATUS_v2, &hdmi_status); ++ pr_debug("HDMI status =0x%x\n", hdmi_status); ++ if (hdmi_status & AUD_CONFIG_MASK_UNDERRUN) { ++ i++; ++ hdmi_status &= ~AUD_CONFIG_MASK_UNDERRUN; ++ had_write_register(AUD_HDMI_STATUS_v2, hdmi_status); ++ } else ++ break; ++ } while (i < MAX_CNT); ++ if (i >= MAX_CNT) ++ pr_err("Unable to clear UNDERRUN bits\n"); ++} ++ ++/** ++* snd_intelhad_open - stream initializations are done here ++* @substream:substream for which the stream function is called ++* ++* This function is called whenever a PCM stream is opened ++*/ ++static int snd_intelhad_open(struct snd_pcm_substream *substream) ++{ ++ struct snd_intelhad *intelhaddata; ++ struct snd_pcm_runtime *runtime; ++ struct had_stream_pvt *stream; ++ struct had_pvt_data *had_stream; ++ int retval; ++ ++ pr_debug("snd_intelhad_open called\n"); ++ intelhaddata = snd_pcm_substream_chip(substream); ++ had_stream = intelhaddata->private_data; ++ runtime = substream->runtime; ++ ++ pm_runtime_get(intelhaddata->dev); ++ ++ /* ++ * HDMI driver might suspend the device already, ++ * so we return it on ++ */ ++ if (!ospm_power_using_hw_begin(OSPM_DISPLAY_ISLAND, ++ OSPM_UHB_FORCE_POWER_ON)) { ++ pr_err("HDMI device can't be turned on\n"); ++ retval = -ENODEV; ++ goto exit_put_handle; ++ } ++ ++ if (had_get_hwstate(intelhaddata)) { ++ pr_err("%s: HDMI cable plugged-out\n", __func__); ++ retval = -ENODEV; ++ goto exit_ospm_handle; ++ } ++ ++ /* Check, if device already in use */ ++ if (runtime->private_data) { ++ pr_err("Device already in use\n"); ++ retval = -EBUSY; ++ goto exit_ospm_handle; ++ } ++ ++ /* set the runtime hw parameter with local snd_pcm_hardware struct */ ++ runtime->hw = snd_intel_hadstream; ++ ++ stream = kzalloc(sizeof(*stream), GFP_KERNEL); ++ if (!stream) { ++ retval = -ENOMEM; ++ goto exit_ospm_handle; ++ } ++ stream->stream_status = STREAM_INIT; ++ runtime->private_data = stream; ++ ++ retval = snd_pcm_hw_constraint_integer(runtime, ++ SNDRV_PCM_HW_PARAM_PERIODS); ++ if (retval < 0) { ++ goto exit_err; ++ } ++ ++ /* Make sure, that the period size is always aligned ++ * 64byte boundary ++ */ ++ retval = snd_pcm_hw_constraint_step(substream->runtime, 0, ++ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); ++ if (retval < 0) { ++ pr_err("%s:step_size=64 failed,err=%d\n", __func__, retval); ++ goto exit_err; ++ } ++ ++ return retval; ++exit_err: ++ kfree(stream); ++exit_ospm_handle: ++ ospm_power_using_hw_end(OSPM_DISPLAY_ISLAND); ++exit_put_handle: ++ pm_runtime_put(intelhaddata->dev); ++ runtime->private_data = NULL; ++ return retval; ++} ++ ++/** ++* had_period_elapsed - updates the hardware pointer status ++* @had_substream:substream for which the stream function is called ++* ++*/ ++static void had_period_elapsed(void *had_substream) ++{ ++ struct snd_pcm_substream *substream = had_substream; ++ struct had_stream_pvt *stream; ++ ++ /* pr_debug("had_period_elapsed called\n"); */ ++ ++ if (!substream || !substream->runtime) ++ return; ++ stream = substream->runtime->private_data; ++ if (!stream) ++ return; ++ ++ if (stream->stream_status != STREAM_RUNNING) ++ return; ++ snd_pcm_period_elapsed(substream); ++} ++ ++/** ++* snd_intelhad_init_stream - internal function to initialize stream info ++* @substream:substream for which the stream function is called ++* ++*/ ++static int snd_intelhad_init_stream(struct snd_pcm_substream *substream) ++{ ++ struct snd_intelhad *intelhaddata = snd_pcm_substream_chip(substream); ++ ++ pr_debug("snd_intelhad_init_stream called\n"); ++ ++ pr_debug("setting buffer ptr param\n"); ++ intelhaddata->stream_info.period_elapsed = had_period_elapsed; ++ intelhaddata->stream_info.had_substream = substream; ++ intelhaddata->stream_info.buffer_ptr = 0; ++ intelhaddata->stream_info.buffer_rendered = 0; ++ intelhaddata->stream_info.sfreq = substream->runtime->rate; ++ return 0; ++} ++ ++/** ++ * snd_intelhad_close- to free parameteres when stream is stopped ++ * ++ * @substream: substream for which the function is called ++ * ++ * This function is called by ALSA framework when stream is stopped ++ */ ++static int snd_intelhad_close(struct snd_pcm_substream *substream) ++{ ++ struct snd_intelhad *intelhaddata; ++ struct snd_pcm_runtime *runtime; ++ ++ pr_debug("snd_intelhad_close called\n"); ++ ++ intelhaddata = snd_pcm_substream_chip(substream); ++ runtime = substream->runtime; ++ ++ if (!runtime->private_data) { ++ pr_debug("close() might have called after failed open"); ++ return 0; ++ } ++ ++ intelhaddata->stream_info.buffer_rendered = 0; ++ intelhaddata->stream_info.buffer_ptr = 0; ++ intelhaddata->stream_info.str_id = 0; ++ intelhaddata->stream_info.had_substream = NULL; ++ ++ /* Check if following drv_status modification is required - VA */ ++ if (intelhaddata->drv_status != HAD_DRV_DISCONNECTED) ++ intelhaddata->drv_status = HAD_DRV_CONNECTED; ++ kfree(runtime->private_data); ++ runtime->private_data = NULL; ++ ospm_power_using_hw_end(OSPM_DISPLAY_ISLAND); ++ pm_runtime_put(intelhaddata->dev); ++ return 0; ++} ++ ++/** ++ * snd_intelhad_hw_params- to setup the hardware parameters ++ * like allocating the buffers ++ * ++ * @substream: substream for which the function is called ++ * @hw_params: hardware parameters ++ * ++ * This function is called by ALSA framework when hardware params are set ++ */ ++static int snd_intelhad_hw_params(struct snd_pcm_substream *substream, ++ struct snd_pcm_hw_params *hw_params) ++{ ++ unsigned long addr; ++ int pages, buf_size, retval; ++ ++ pr_debug("snd_intelhad_hw_params called\n"); ++ ++ BUG_ON(!hw_params); ++ ++ buf_size = params_buffer_bytes(hw_params); ++ retval = snd_pcm_lib_malloc_pages(substream, buf_size); ++ if (retval < 0) ++ return retval; ++ pr_debug("%s:allocated memory = %d\n", __func__, buf_size); ++ /* mark the pages as uncached region */ ++ addr = (unsigned long) substream->runtime->dma_area; ++ pages = (substream->runtime->dma_bytes + PAGE_SIZE - 1) / PAGE_SIZE; ++ retval = set_memory_uc(addr, pages); ++ if (retval) { ++ pr_err("set_memory_uc failed.Error:%d\n", retval); ++ return retval; ++ } ++ memset(substream->runtime->dma_area, 0, buf_size); ++ ++ return retval; ++} ++ ++/** ++ * snd_intelhad_hw_free- to release the resources allocated during ++ * hardware params setup ++ * ++ * @substream: substream for which the function is called ++ * ++ * This function is called by ALSA framework before close callback. ++ * ++ */ ++static int snd_intelhad_hw_free(struct snd_pcm_substream *substream) ++{ ++ unsigned long addr; ++ u32 pages; ++ ++ pr_debug("snd_intelhad_hw_free called\n"); ++ ++ /* mark back the pages as cached/writeback region before the free */ ++ if (substream->runtime->dma_area != NULL) { ++ addr = (unsigned long) substream->runtime->dma_area; ++ pages = (substream->runtime->dma_bytes + PAGE_SIZE - 1) / ++ PAGE_SIZE; ++ set_memory_wb(addr, pages); ++ return snd_pcm_lib_free_pages(substream); ++ } ++ return 0; ++} ++ ++/** ++* snd_intelhad_pcm_trigger - stream activities are handled here ++* @substream:substream for which the stream function is called ++* @cmd:the stream commamd thats requested from upper layer ++* This function is called whenever an a stream activity is invoked ++*/ ++static int snd_intelhad_pcm_trigger(struct snd_pcm_substream *substream, ++ int cmd) ++{ ++ int caps, retval = 0; ++ unsigned long flag_irq; ++ struct snd_intelhad *intelhaddata; ++ struct had_stream_pvt *stream; ++ struct had_pvt_data *had_stream; ++ ++ pr_debug("snd_intelhad_pcm_trigger called\n"); ++ ++ intelhaddata = snd_pcm_substream_chip(substream); ++ stream = substream->runtime->private_data; ++ had_stream = intelhaddata->private_data; ++ ++ switch (cmd) { ++ case SNDRV_PCM_TRIGGER_START: ++ pr_debug("Trigger Start\n"); ++ ++ /* Disable local INTRs till register prgmng is done */ ++ if (had_get_hwstate(intelhaddata)) { ++ pr_err("_START: HDMI cable plugged-out\n"); ++ retval = -ENODEV; ++ break; ++ } ++ stream->stream_status = STREAM_RUNNING; ++ ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irq); ++ had_stream->stream_type = HAD_RUNNING_STREAM; ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irq); ++ ++ /* Enable Audio */ ++ /* ++ * ToDo: Need to enable UNDERRUN interrupts as well ++ * caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE; ++ */ ++ caps = HDMI_AUDIO_BUFFER_DONE; ++ retval = had_set_caps(HAD_SET_ENABLE_AUDIO_INT, &caps); ++ retval = had_set_caps(HAD_SET_ENABLE_AUDIO, NULL); ++ intelhaddata->ops->enable_audio(substream, 1); ++ ++ pr_debug("Processed _Start\n"); ++ ++ break; ++ ++ case SNDRV_PCM_TRIGGER_STOP: ++ pr_debug("Trigger Stop\n"); ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irq); ++ intelhaddata->stream_info.str_id = 0; ++ intelhaddata->curr_buf = 0; ++ ++ /* Stop reporting BUFFER_DONE/UNDERRUN to above layers*/ ++ ++ had_stream->stream_type = HAD_INIT; ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irq); ++ /* Disable Audio */ ++ /* ++ * ToDo: Need to disable UNDERRUN interrupts as well ++ * caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE; ++ */ ++ caps = HDMI_AUDIO_BUFFER_DONE; ++ had_set_caps(HAD_SET_DISABLE_AUDIO_INT, &caps); ++ intelhaddata->ops->enable_audio(substream, 0); ++ /* Reset buffer pointers */ ++ intelhaddata->ops->reset_audio(1); ++ intelhaddata->ops->reset_audio(0); ++ stream->stream_status = STREAM_DROPPED; ++ had_set_caps(HAD_SET_DISABLE_AUDIO, NULL); ++ break; ++ ++ default: ++ retval = -EINVAL; ++ } ++ return retval; ++} ++ ++/** ++* snd_intelhad_pcm_prepare- internal preparation before starting a stream ++* ++* @substream: substream for which the function is called ++* ++* This function is called when a stream is started for internal preparation. ++*/ ++static int snd_intelhad_pcm_prepare(struct snd_pcm_substream *substream) ++{ ++ int retval; ++ u32 disp_samp_freq, n_param; ++ struct snd_intelhad *intelhaddata; ++ struct snd_pcm_runtime *runtime; ++ struct had_pvt_data *had_stream; ++ ++ pr_debug("snd_intelhad_pcm_prepare called\n"); ++ ++ intelhaddata = snd_pcm_substream_chip(substream); ++ runtime = substream->runtime; ++ had_stream = intelhaddata->private_data; ++ ++ if (had_get_hwstate(intelhaddata)) { ++ pr_err("%s: HDMI cable plugged-out\n", __func__); ++ snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); ++ retval = -ENODEV; ++ goto prep_end; ++ } ++ ++ pr_debug("period_size=%d\n", ++ frames_to_bytes(runtime, runtime->period_size)); ++ pr_debug("periods=%d\n", runtime->periods); ++ pr_debug("buffer_size=%d\n", snd_pcm_lib_buffer_bytes(substream)); ++ pr_debug("rate=%d\n", runtime->rate); ++ pr_debug("channels=%d\n", runtime->channels); ++ ++ if (intelhaddata->stream_info.str_id) { ++ pr_debug("_prepare is called for existing str_id#%d\n", ++ intelhaddata->stream_info.str_id); ++ retval = snd_intelhad_pcm_trigger(substream, ++ SNDRV_PCM_TRIGGER_STOP); ++ return retval; ++ } ++ ++ retval = snd_intelhad_init_stream(substream); ++ if (retval) ++ goto prep_end; ++ ++ ++ /* Get N value in KHz */ ++ retval = had_get_caps(HAD_GET_SAMPLING_FREQ, &disp_samp_freq); ++ if (retval) { ++ pr_err("querying display sampling freq failed %#x\n", retval); ++ goto prep_end; ++ } ++ ++ had_get_caps(HAD_GET_ELD, &intelhaddata->eeld); ++ ++ retval = intelhaddata->ops->prog_n(substream->runtime->rate, &n_param, ++ intelhaddata); ++ if (retval) { ++ pr_err("programming N value failed %#x\n", retval); ++ goto prep_end; ++ } ++ intelhaddata->ops->prog_cts(substream->runtime->rate, ++ disp_samp_freq, n_param, intelhaddata); ++ ++ intelhaddata->ops->prog_dip(substream, intelhaddata); ++ ++ retval = intelhaddata->ops->audio_ctrl(substream, intelhaddata); ++ ++ /* Prog buffer address */ ++ retval = snd_intelhad_prog_buffer(intelhaddata, ++ HAD_BUF_TYPE_A, HAD_BUF_TYPE_D); ++ ++ /* ++ * Program channel mapping in following order: ++ * FL, FR, C, LFE, RL, RR ++ */ ++ ++ had_write_register(AUD_BUF_CH_SWAP, SWAP_LFE_CENTER); ++ ++prep_end: ++ return retval; ++} ++ ++/** ++ * snd_intelhad_pcm_pointer- to send the current buffer pointerprocessed by hw ++ * ++ * @substream: substream for which the function is called ++ * ++ * This function is called by ALSA framework to get the current hw buffer ptr ++ * when a period is elapsed ++ */ ++static snd_pcm_uframes_t snd_intelhad_pcm_pointer( ++ struct snd_pcm_substream *substream) ++{ ++ struct snd_intelhad *intelhaddata; ++ u32 bytes_rendered = 0; ++ ++ /* pr_debug("snd_intelhad_pcm_pointer called\n"); */ ++ ++ intelhaddata = snd_pcm_substream_chip(substream); ++ ++ if (intelhaddata->flag_underrun) { ++ intelhaddata->flag_underrun = 0; ++ return SNDRV_PCM_POS_XRUN; ++ } ++ ++ if (intelhaddata->stream_info.buffer_rendered) ++ div_u64_rem(intelhaddata->stream_info.buffer_rendered, ++ intelhaddata->stream_info.ring_buf_size, ++ &(bytes_rendered)); ++ ++ intelhaddata->stream_info.buffer_ptr = bytes_to_frames( ++ substream->runtime, ++ bytes_rendered); ++ return intelhaddata->stream_info.buffer_ptr; ++} ++ ++/** ++* snd_intelhad_pcm_mmap- mmaps a kernel buffer to user space for copying data ++* ++* @substream: substream for which the function is called ++* @vma: struct instance of memory VMM memory area ++* ++* This function is called by OS when a user space component ++* tries to get mmap memory from driver ++*/ ++static int snd_intelhad_pcm_mmap(struct snd_pcm_substream *substream, ++ struct vm_area_struct *vma) ++{ ++ ++ pr_debug("snd_intelhad_pcm_mmap called\n"); ++ ++ pr_debug("entry with prot:%s\n", __func__); ++ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); ++ return remap_pfn_range(vma, vma->vm_start, ++ substream->dma_buffer.addr >> PAGE_SHIFT, ++ vma->vm_end - vma->vm_start, vma->vm_page_prot); ++} ++ ++int hdmi_audio_mode_change(struct snd_pcm_substream *substream) ++{ ++ int retval = 0; ++ u32 disp_samp_freq, n_param; ++ struct snd_intelhad *intelhaddata; ++ ++ intelhaddata = snd_pcm_substream_chip(substream); ++ ++ /* Disable Audio */ ++ intelhaddata->ops->enable_audio(substream, 0); ++ ++ /* Update CTS value */ ++ retval = had_get_caps(HAD_GET_SAMPLING_FREQ, &disp_samp_freq); ++ if (retval) { ++ pr_err("querying display sampling freq failed %#x\n", retval); ++ goto out; ++ } ++ ++ retval = intelhaddata->ops->prog_n(substream->runtime->rate, &n_param, ++ intelhaddata); ++ if (retval) { ++ pr_err("programming N value failed %#x\n", retval); ++ goto out; ++ } ++ intelhaddata->ops->prog_cts(substream->runtime->rate, ++ disp_samp_freq, n_param, intelhaddata); ++ ++ /* Enable Audio */ ++ intelhaddata->ops->enable_audio(substream, 1); ++ ++out: ++ return retval; ++} ++ ++/*PCM operations structure and the calls back for the same */ ++struct snd_pcm_ops snd_intelhad_playback_ops = { ++ .open = snd_intelhad_open, ++ .close = snd_intelhad_close, ++ .ioctl = snd_pcm_lib_ioctl, ++ .hw_params = snd_intelhad_hw_params, ++ .hw_free = snd_intelhad_hw_free, ++ .prepare = snd_intelhad_pcm_prepare, ++ .trigger = snd_intelhad_pcm_trigger, ++ .pointer = snd_intelhad_pcm_pointer, ++ .mmap = snd_intelhad_pcm_mmap, ++}; ++ ++/** ++ * snd_intelhad_create - to crete alsa card instance ++ * ++ * @intelhaddata: pointer to internal context ++ * @card: pointer to card ++ * ++ * This function is called when the hdmi cable is plugged in ++ */ ++static int snd_intelhad_create( ++ struct snd_intelhad *intelhaddata, ++ struct snd_card *card) ++{ ++ int retval; ++ static struct snd_device_ops ops = { ++ }; ++ ++ BUG_ON(!intelhaddata); ++ /* ALSA api to register the device */ ++ retval = snd_device_new(card, SNDRV_DEV_LOWLEVEL, intelhaddata, &ops); ++ return retval; ++} ++/** ++ * snd_intelhad_pcm_free - to free the memory allocated ++ * ++ * @pcm: pointer to pcm instance ++ * This function is called when the device is removed ++ */ ++static void snd_intelhad_pcm_free(struct snd_pcm *pcm) ++{ ++ pr_debug("Freeing PCM preallocated pages\n"); ++ snd_pcm_lib_preallocate_free_for_all(pcm); ++} ++ ++static int had_iec958_info(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_info *uinfo) ++{ ++ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; ++ uinfo->count = 1; ++ return 0; ++} ++ ++static int had_iec958_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ struct snd_intelhad *intelhaddata = snd_kcontrol_chip(kcontrol); ++ ++ ucontrol->value.iec958.status[0] = (intelhaddata->aes_bits >> 0) & 0xff; ++ ucontrol->value.iec958.status[1] = (intelhaddata->aes_bits >> 8) & 0xff; ++ ucontrol->value.iec958.status[2] = ++ (intelhaddata->aes_bits >> 16) & 0xff; ++ ucontrol->value.iec958.status[3] = ++ (intelhaddata->aes_bits >> 24) & 0xff; ++ return 0; ++} ++static int had_iec958_mask_get(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ ucontrol->value.iec958.status[0] = 0xff; ++ ucontrol->value.iec958.status[1] = 0xff; ++ ucontrol->value.iec958.status[2] = 0xff; ++ ucontrol->value.iec958.status[3] = 0xff; ++ return 0; ++} ++static int had_iec958_put(struct snd_kcontrol *kcontrol, ++ struct snd_ctl_elem_value *ucontrol) ++{ ++ unsigned int val; ++ struct snd_intelhad *intelhaddata = snd_kcontrol_chip(kcontrol); ++ ++ pr_debug("entered had_iec958_put\n"); ++ val = (ucontrol->value.iec958.status[0] << 0) | ++ (ucontrol->value.iec958.status[1] << 8) | ++ (ucontrol->value.iec958.status[2] << 16) | ++ (ucontrol->value.iec958.status[3] << 24); ++ if (intelhaddata->aes_bits != val) { ++ intelhaddata->aes_bits = val; ++ return 1; ++ } ++ return 1; ++} ++ ++static struct snd_kcontrol_new had_control_iec958_mask = { ++ .access = SNDRV_CTL_ELEM_ACCESS_READ, ++ .iface = SNDRV_CTL_ELEM_IFACE_PCM, ++ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), ++ .info = had_iec958_info, /* shared */ ++ .get = had_iec958_mask_get, ++}; ++ ++static struct snd_kcontrol_new had_control_iec958 = { ++ .iface = SNDRV_CTL_ELEM_IFACE_PCM, ++ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), ++ .info = had_iec958_info, ++ .get = had_iec958_get, ++ .put = had_iec958_put ++}; ++ ++static struct snd_intel_had_interface had_interface = { ++ .name = "hdmi-audio", ++ .query = hdmi_audio_query, ++ .suspend = hdmi_audio_suspend, ++ .resume = hdmi_audio_resume, ++}; ++ ++static struct had_ops had_ops_v1 = { ++ .enable_audio = snd_intelhad_enable_audio_v1, ++ .reset_audio = snd_intelhad_reset_audio_v1, ++ .prog_n = snd_intelhad_prog_n_v1, ++ .prog_cts = snd_intelhad_prog_cts_v1, ++ .audio_ctrl = snd_intelhad_prog_audio_ctrl_v1, ++ .prog_dip = snd_intelhad_prog_dip_v1, ++ .handle_underrun = had_clear_underrun_intr_v1, ++}; ++ ++static struct had_ops had_ops_v2 = { ++ .enable_audio = snd_intelhad_enable_audio_v2, ++ .reset_audio = snd_intelhad_reset_audio_v2, ++ .prog_n = snd_intelhad_prog_n_v2, ++ .prog_cts = snd_intelhad_prog_cts_v2, ++ .audio_ctrl = snd_intelhad_prog_audio_ctrl_v2, ++ .prog_dip = snd_intelhad_prog_dip_v2, ++ .handle_underrun = had_clear_underrun_intr_v2, ++}; ++/** ++ * hdmi_audio_probe - to create sound card instance for HDMI audio playabck ++ * ++ *@haddata: pointer to HAD private data ++ *@card_id: card for which probe is called ++ * ++ * This function is called when the hdmi cable is plugged in. This function ++ * creates and registers the sound card with ALSA ++ */ ++static int hdmi_audio_probe(struct platform_device *devptr) ++{ ++ ++ int retval; ++ struct snd_pcm *pcm; ++ struct snd_card *card; ++ struct had_callback_ops ops_cb; ++ struct snd_intelhad *intelhaddata; ++ struct had_pvt_data *had_stream; ++ ++ pr_debug("Enter %s\n", __func__); ++ ++ pr_debug("hdmi_audio_probe dma_mask: %d\n", devptr->dev.dma_mask); ++ ++ /* allocate memory for saving internal context and working */ ++ intelhaddata = kzalloc(sizeof(*intelhaddata), GFP_KERNEL); ++ if (!intelhaddata) { ++ pr_err("mem alloc failed\n"); ++ return -ENOMEM; ++ } ++ ++ had_stream = kzalloc(sizeof(*had_stream), GFP_KERNEL); ++ if (!had_stream) { ++ pr_err("mem alloc failed\n"); ++ retval = -ENOMEM; ++ goto free_haddata; ++ } ++ ++ had_data = intelhaddata; ++ ops_cb.intel_had_event_call_back = had_event_handler; ++ ++ /* registering with display driver to get access to display APIs */ ++ ++ retval = mid_hdmi_audio_setup( ++ ops_cb.intel_had_event_call_back, ++ &(intelhaddata->reg_ops), ++ &(intelhaddata->query_ops)); ++ if (retval) { ++ pr_err("querying display driver APIs failed %#x\n", retval); ++ goto free_hadstream; ++ } ++ mutex_lock(&had_mutex); ++ spin_lock_init(&intelhaddata->had_spinlock); ++ intelhaddata->drv_status = HAD_DRV_DISCONNECTED; ++ /* create a card instance with ALSA framework */ ++ retval = snd_card_new(&devptr->dev, hdmi_card_index, hdmi_card_id, ++ THIS_MODULE, 0, &card); ++ ++ if (retval) ++ goto unlock_mutex; ++ intelhaddata->card = card; ++ intelhaddata->card_id = hdmi_card_id; ++ intelhaddata->card_index = card->number; ++ intelhaddata->private_data = had_stream; ++ intelhaddata->flag_underrun = 0; ++ intelhaddata->aes_bits = SNDRV_PCM_DEFAULT_CON_SPDIF; ++ strncpy(card->driver, INTEL_HAD, strlen(INTEL_HAD)); ++ strncpy(card->shortname, INTEL_HAD, strlen(INTEL_HAD)); ++ ++ retval = snd_pcm_new(card, INTEL_HAD, PCM_INDEX, MAX_PB_STREAMS, ++ MAX_CAP_STREAMS, &pcm); ++ if (retval) ++ goto err; ++ ++ /* setup private data which can be retrieved when required */ ++ pcm->private_data = intelhaddata; ++ pcm->private_free = snd_intelhad_pcm_free; ++ pcm->info_flags = 0; ++ strncpy(pcm->name, card->shortname, strlen(card->shortname)); ++ /* setup the ops for palyabck */ ++ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ++ &snd_intelhad_playback_ops); ++ /* allocate dma pages for ALSA stream operations ++ * memory allocated is based on size, not max value ++ * thus using same argument for max & size ++ */ ++ retval = snd_pcm_lib_preallocate_pages_for_all(pcm, ++ SNDRV_DMA_TYPE_DEV, NULL, ++ HAD_MAX_BUFFER, HAD_MAX_BUFFER); ++ ++ if (card->dev == NULL) ++ pr_debug("card->dev is NULL!!!!! Should not be this case\n"); ++ else if (card->dev->dma_mask == NULL) ++ pr_debug("hdmi_audio_probe dma_mask is NULL!!!!!\n"); ++ else ++ pr_debug("hdmi_audio_probe dma_mask is : %d\n", card->dev->dma_mask); ++ ++ if (retval) ++ goto err; ++ ++ /* internal function call to register device with ALSA */ ++ retval = snd_intelhad_create(intelhaddata, card); ++ if (retval) ++ goto err; ++ ++ card->private_data = &intelhaddata; ++ retval = snd_card_register(card); ++ if (retval) ++ goto err; ++ ++ /* IEC958 controls */ ++ retval = snd_ctl_add(card, snd_ctl_new1(&had_control_iec958_mask, ++ intelhaddata)); ++ if (retval < 0) ++ goto err; ++ retval = snd_ctl_add(card, snd_ctl_new1(&had_control_iec958, ++ intelhaddata)); ++ if (retval < 0) ++ goto err; ++ ++ init_channel_allocations(); ++ ++ /* Register channel map controls */ ++ retval = had_register_chmap_ctls(intelhaddata, pcm); ++ if (retval < 0) ++ goto err; ++ ++ intelhaddata->dev = &devptr->dev; ++ pm_runtime_set_active(intelhaddata->dev); ++ pm_runtime_enable(intelhaddata->dev); ++ ++ mutex_unlock(&had_mutex); ++ retval = mid_hdmi_audio_register(&had_interface, intelhaddata); ++ if (retval) { ++ pr_err("registering with display driver failed %#x\n", retval); ++ snd_card_free(card); ++ goto free_hadstream; ++ } ++ ++ intelhaddata->hw_silence = 1; ++ intelhaddata->ops = &had_ops_v2; ++ ++ return retval; ++err: ++ snd_card_free(card); ++unlock_mutex: ++ mutex_unlock(&had_mutex); ++free_hadstream: ++ kfree(had_stream); ++ pm_runtime_disable(intelhaddata->dev); ++ intelhaddata->dev = NULL; ++free_haddata: ++ kfree(intelhaddata); ++ intelhaddata = NULL; ++ pr_err("Error returned from %s api %#x\n", __func__, retval); ++ return retval; ++} ++ ++/** ++ * hdmi_audio_remove - removes the alsa card ++ * ++ *@haddata: pointer to HAD private data ++ * ++ * This function is called when the hdmi cable is un-plugged. This function ++ * free the sound card. ++ */ ++static int hdmi_audio_remove(struct platform_device *devptr) ++{ ++ struct snd_intelhad *intelhaddata = had_data; ++ int caps; ++ ++ pr_debug("Enter %s\n", __func__); ++ ++ if (!intelhaddata) ++ return 0; ++ ++ if (intelhaddata->drv_status != HAD_DRV_DISCONNECTED) { ++ caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE; ++ had_set_caps(HAD_SET_DISABLE_AUDIO_INT, &caps); ++ had_set_caps(HAD_SET_DISABLE_AUDIO, NULL); ++ } ++ snd_card_free(intelhaddata->card); ++ kfree(intelhaddata->private_data); ++ kfree(intelhaddata); ++ return 0; ++} ++ ++static int had_pm_runtime_suspend(struct device *dev) ++{ ++ return 0; ++} ++ ++static int had_pm_runtime_resume(struct device *dev) ++{ ++ return 0; ++} ++ ++static int had_pm_runtime_idle(struct device *dev) ++{ ++ return pm_schedule_suspend(dev, HAD_SUSPEND_DELAY); ++} ++ ++const struct dev_pm_ops had_pm_ops = { ++ .runtime_idle = had_pm_runtime_idle, ++ .runtime_suspend = had_pm_runtime_suspend, ++ .runtime_resume = had_pm_runtime_resume, ++}; ++ ++static const struct acpi_device_id had_acpi_ids[] = { ++ { "HAD0F28", 0 }, ++ { "HAD022A8", 0 }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(acpi, had_acpi_ids); ++ ++static struct platform_driver had_driver = { ++ .probe = hdmi_audio_probe, ++ .remove = hdmi_audio_remove, ++ .suspend = NULL, ++ .resume = NULL, ++ .driver = { ++ .name = HDMI_AUDIO_DRIVER, ++#ifdef CONFIG_PM ++ .pm = &had_pm_ops, ++#endif ++ .acpi_match_table = ACPI_PTR(had_acpi_ids), ++ }, ++}; ++ ++/* ++* alsa_card_intelhad_init- driver init function ++* This function is called when driver module is inserted ++*/ ++static int __init alsa_card_intelhad_init(void) ++{ ++ int retval; ++ ++ pr_debug("Enter %s\n", __func__); ++ ++ pr_info("******** HAD DRIVER loading.. Ver: %s\n", ++ HAD_DRIVER_VERSION); ++ ++ retval = platform_driver_register(&had_driver); ++ if (retval < 0) { ++ pr_err("Platform driver register failed\n"); ++ return retval; ++ } ++ ++ pr_debug("init complete\n"); ++ return retval; ++} ++ ++/** ++* alsa_card_intelhad_exit- driver exit function ++* This function is called when driver module is removed ++*/ ++static void __exit alsa_card_intelhad_exit(void) ++{ ++ pr_debug("had_exit called\n"); ++ platform_driver_unregister(&had_driver); ++} ++late_initcall(alsa_card_intelhad_init); ++module_exit(alsa_card_intelhad_exit); +diff --git a/sound/hdmi_audio/intel_mid_hdmi_audio.h b/sound/hdmi_audio/intel_mid_hdmi_audio.h +new file mode 100644 +index 0000000..7c54b97 +--- /dev/null ++++ b/sound/hdmi_audio/intel_mid_hdmi_audio.h +@@ -0,0 +1,740 @@ ++/* ++ * intel_mid_hdmi_audio.h - Intel HDMI audio driver for MID ++ * ++ * Copyright (C) 2010 Intel Corp ++ * Authors: Sailaja Bandarupalli ++ * Ramesh Babu K V ++ * Vaibhav Agarwal ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; version 2 of the License. ++ * ++ * 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. ++ * ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ * ALSA driver for Intel MID HDMI audio controller ++ */ ++#ifndef __INTEL_MID_HDMI_AUDIO_H ++#define __INTEL_MID_HDMI_AUDIO_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define OSPM_DISPLAY_ISLAND 0x40 ++ ++typedef enum _UHBUsage { ++ OSPM_UHB_ONLY_IF_ON = 0, ++ OSPM_UHB_FORCE_POWER_ON, ++} UHBUsage; ++ ++bool ospm_power_using_hw_begin(int hw_island, UHBUsage usage); ++void ospm_power_using_hw_end(int hw_island); ++ ++/* ++ * Use this function to do an instantaneous check for if the hw is on. ++ * Only use this in cases where you know the g_state_change_mutex ++ * is already held such as in irq install/uninstall and you need to ++ * prevent a deadlock situation. Otherwise use ospm_power_using_hw_begin(). ++ */ ++bool ospm_power_is_hw_on(int hw_islands); ++ ++#define HAD_DRIVER_VERSION "0.01.003" ++#define HAD_MAX_DEVICES 1 ++#define HAD_MIN_CHANNEL 2 ++#define HAD_MAX_CHANNEL 8 ++#define HAD_NUM_OF_RING_BUFS 4 ++ ++/* Assume 192KHz, 8channel, 25msec period */ ++#define HAD_MAX_BUFFER (600*1024) ++#define HAD_MIN_BUFFER (32*1024) ++#define HAD_MAX_PERIODS 4 ++#define HAD_MIN_PERIODS 4 ++#define HAD_MAX_PERIOD_BYTES (HAD_MAX_BUFFER/HAD_MIN_PERIODS) ++#define HAD_MIN_PERIOD_BYTES 256 ++#define HAD_FIFO_SIZE 0 /* fifo not being used */ ++#define MAX_SPEAKERS 8 ++/* TODO: Add own tlv when channel map is ported for user space */ ++#define USE_ALSA_DEFAULT_TLV ++ ++#define AUD_SAMPLE_RATE_32 32000 ++#define AUD_SAMPLE_RATE_44_1 44100 ++#define AUD_SAMPLE_RATE_48 48000 ++#define AUD_SAMPLE_RATE_88_2 88200 ++#define AUD_SAMPLE_RATE_96 96000 ++#define AUD_SAMPLE_RATE_176_4 176400 ++#define AUD_SAMPLE_RATE_192 192000 ++ ++#define HAD_MIN_RATE AUD_SAMPLE_RATE_32 ++#define HAD_MAX_RATE AUD_SAMPLE_RATE_192 ++ ++#define DRIVER_NAME "intelmid_hdmi_audio" ++#define DIS_SAMPLE_RATE_25_2 25200 ++#define DIS_SAMPLE_RATE_27 27000 ++#define DIS_SAMPLE_RATE_54 54000 ++#define DIS_SAMPLE_RATE_74_25 74250 ++#define DIS_SAMPLE_RATE_148_5 148500 ++#define HAD_REG_WIDTH 0x08 ++#define HAD_MAX_HW_BUFS 0x04 ++#define HAD_MAX_DIP_WORDS 16 ++#define INTEL_HAD "IntelHDMI" ++ ++/* _AUD_CONFIG register MASK */ ++#define AUD_CONFIG_MASK_UNDERRUN 0xC0000000 ++#define AUD_CONFIG_MASK_SRDBG 0x00000002 ++#define AUD_CONFIG_MASK_FUNCRST 0x00000001 ++ ++#define MAX_CNT 0xFF ++#define HAD_SUSPEND_DELAY 1000 ++ ++#define OTM_HDMI_ELD_SIZE 84 ++ ++typedef union { ++ uint8_t eld_data[OTM_HDMI_ELD_SIZE]; ++ #pragma pack(1) ++ struct { ++ /* Byte[0] = ELD Version Number */ ++ union { ++ uint8_t byte0; ++ struct { ++ uint8_t reserved:3; /* Reserf */ ++ uint8_t eld_ver:5; /* ELD Version Number */ ++ /* 00000b - reserved ++ * 00001b - first rev, obsoleted ++ * 00010b - version 2, supporting CEA version 861D or below ++ * 00011b:11111b - reserved ++ * for future ++ */ ++ }; ++ }; ++ ++ /* Byte[1] = Vendor Version Field */ ++ union { ++ uint8_t vendor_version; ++ struct { ++ uint8_t reserved1:3; ++ uint8_t veld_ver:5; /* Version number of the ELD ++ * extension. This value is ++ * provisioned and unique to ++ * each vendor. ++ */ ++ }; ++ }; ++ ++ /* Byte[2] = Baseline Length field */ ++ uint8_t baseline_eld_length; /* Length of the Baseline structure ++ * divided by Four. ++ */ ++ ++ /* Byte [3] = Reserved for future use */ ++ uint8_t byte3; ++ ++ /* Starting of the BaseLine EELD structure ++ * Byte[4] = Monitor Name Length ++ */ ++ union { ++ uint8_t byte4; ++ struct { ++ uint8_t mnl:5; ++ uint8_t cea_edid_rev_id:3; ++ }; ++ }; ++ ++ /* Byte[5] = Capabilities */ ++ union { ++ uint8_t capabilities; ++ struct { ++ uint8_t hdcp:1; /* HDCP support */ ++ uint8_t ai_support:1; /* AI support */ ++ uint8_t connection_type:2; /* Connection type ++ * 00 - HDMI ++ * 01 - DP ++ * 10 -11 Reserved ++ * for future ++ * connection types ++ */ ++ uint8_t sadc:4; /* Indicates number of 3 bytes ++ * Short Audio Descriptors. ++ */ ++ }; ++ }; ++ ++ /* Byte[6] = Audio Synch Delay */ ++ uint8_t audio_synch_delay; /* Amount of time reported by the ++ * sink that the video trails audio ++ * in milliseconds. ++ */ ++ ++ /* Byte[7] = Speaker Allocation Block */ ++ union { ++ uint8_t speaker_allocation_block; ++ struct { ++ uint8_t flr:1; /*Front Left and Right channels*/ ++ uint8_t lfe:1; /*Low Frequency Effect channel*/ ++ uint8_t fc:1; /*Center transmission channel*/ ++ uint8_t rlr:1; /*Rear Left and Right channels*/ ++ uint8_t rc:1; /*Rear Center channel*/ ++ uint8_t flrc:1; /*Front left and Right of Center ++ *transmission channels ++ */ ++ uint8_t rlrc:1; /*Rear left and Right of Center ++ *transmission channels ++ */ ++ uint8_t reserved3:1; /* Reserved */ ++ }; ++ }; ++ ++ /* Byte[8 - 15] - 8 Byte port identification value */ ++ uint8_t port_id_value[8]; ++ ++ /* Byte[16 - 17] - 2 Byte Manufacturer ID */ ++ uint8_t manufacturer_id[2]; ++ ++ /* Byte[18 - 19] - 2 Byte Product ID */ ++ uint8_t product_id[2]; ++ ++ /* Byte [20-83] - 64 Bytes of BaseLine Data */ ++ uint8_t mn_sand_sads[64]; /* This will include ++ * - ASCII string of Monitor name ++ * - List of 3 byte SADs ++ * - Zero padding ++ */ ++ ++ /* Vendor ELD Block should continue here! ++ * No Vendor ELD block defined as of now. ++ */ ++ }; ++ #pragma pack() ++} otm_hdmi_eld_t; ++ ++/** ++ * enum had_status - Audio stream states ++ * ++ * @STREAM_INIT: Stream initialized ++ * @STREAM_RUNNING: Stream running ++ * @STREAM_PAUSED: Stream paused ++ * @STREAM_DROPPED: Stream dropped ++ */ ++enum had_stream_status { ++ STREAM_INIT = 0, ++ STREAM_RUNNING = 1, ++ STREAM_PAUSED = 2, ++ STREAM_DROPPED = 3 ++}; ++ ++/** ++ * enum had_status_stream - HAD stream states ++ */ ++enum had_status_stream { ++ HAD_INIT = 0, ++ HAD_RUNNING_STREAM, ++}; ++ ++enum had_drv_status { ++ HAD_DRV_CONNECTED, ++ HAD_DRV_RUNNING, ++ HAD_DRV_DISCONNECTED, ++ HAD_DRV_SUSPENDED, ++ HAD_DRV_ERR, ++}; ++ ++/* enum intel_had_aud_buf_type - HDMI controller ring buffer types */ ++enum intel_had_aud_buf_type { ++ HAD_BUF_TYPE_A = 0, ++ HAD_BUF_TYPE_B = 1, ++ HAD_BUF_TYPE_C = 2, ++ HAD_BUF_TYPE_D = 3, ++}; ++ ++enum num_aud_ch { ++ CH_STEREO = 0, ++ CH_THREE_FOUR = 1, ++ CH_FIVE_SIX = 2, ++ CH_SEVEN_EIGHT = 3 ++}; ++ ++ ++/* HDMI controller register offsets */ ++enum hdmi_ctrl_reg_offset_v1 { ++ AUD_CONFIG = 0x0, ++ AUD_CH_STATUS_0 = 0x08, ++ AUD_CH_STATUS_1 = 0x0C, ++ AUD_HDMI_CTS = 0x10, ++ AUD_N_ENABLE = 0x14, ++ AUD_SAMPLE_RATE = 0x18, ++ AUD_BUF_CONFIG = 0x20, ++ AUD_BUF_CH_SWAP = 0x24, ++ AUD_BUF_A_ADDR = 0x40, ++ AUD_BUF_A_LENGTH = 0x44, ++ AUD_BUF_B_ADDR = 0x48, ++ AUD_BUF_B_LENGTH = 0x4c, ++ AUD_BUF_C_ADDR = 0x50, ++ AUD_BUF_C_LENGTH = 0x54, ++ AUD_BUF_D_ADDR = 0x58, ++ AUD_BUF_D_LENGTH = 0x5c, ++ AUD_CNTL_ST = 0x60, ++ AUD_HDMI_STATUS = 0x68, ++ AUD_HDMIW_INFOFR = 0x114, ++}; ++ ++/* ++ * Delta changes in HDMI controller register offsets ++ * compare to v1 version ++ */ ++ ++enum hdmi_ctrl_reg_offset_v2 { ++ AUD_HDMI_STATUS_v2 = 0x64, ++ AUD_HDMIW_INFOFR_v2 = 0x68, ++}; ++ ++/* ++ * CEA speaker placement: ++ * ++ * FL FLC FC FRC FR ++ * ++ * LFE ++ * ++ * RL RLC RC RRC RR ++ * ++ * The Left/Right Surround channel _notions_ LS/RS in SMPTE 320M corresponds to ++ * CEA RL/RR; The SMPTE channel _assignment_ C/LFE is swapped to CEA LFE/FC. ++ */ ++enum cea_speaker_placement { ++ FL = (1 << 0), /* Front Left */ ++ FC = (1 << 1), /* Front Center */ ++ FR = (1 << 2), /* Front Right */ ++ FLC = (1 << 3), /* Front Left Center */ ++ FRC = (1 << 4), /* Front Right Center */ ++ RL = (1 << 5), /* Rear Left */ ++ RC = (1 << 6), /* Rear Center */ ++ RR = (1 << 7), /* Rear Right */ ++ RLC = (1 << 8), /* Rear Left Center */ ++ RRC = (1 << 9), /* Rear Right Center */ ++ LFE = (1 << 10), /* Low Frequency Effect */ ++}; ++ ++struct cea_channel_speaker_allocation { ++ int ca_index; ++ int speakers[8]; ++ ++ /* derived values, just for convenience */ ++ int channels; ++ int spk_mask; ++}; ++ ++struct channel_map_table { ++ unsigned char map; /* ALSA API channel map position */ ++ unsigned char cea_slot; /* CEA slot value */ ++ int spk_mask; /* speaker position bit mask */ ++}; ++ ++/** ++ * union aud_cfg - Audio configuration ++ * ++ * @cfg_regx: individual register bits ++ * @cfg_regval: full register value ++ * ++ */ ++union aud_cfg { ++ struct { ++ u32 aud_en:1; ++ u32 layout:1; ++ u32 fmt:2; ++ u32 num_ch:2; ++ u32 rsvd0:1; ++ u32 set:1; ++ u32 flat:1; ++ u32 val_bit:1; ++ u32 user_bit:1; ++ u32 underrun:1; ++ u32 rsvd1:20; ++ } cfg_regx; ++ struct { ++ u32 aud_en:1; ++ u32 layout:1; ++ u32 fmt:2; ++ u32 num_ch:3; ++ u32 set:1; ++ u32 flat:1; ++ u32 val_bit:1; ++ u32 user_bit:1; ++ u32 underrun:1; ++ u32 packet_mode:1; ++ u32 left_align:1; ++ u32 bogus_sample:1; ++ u32 dp_modei:1; ++ u32 rsvd:16; ++ } cfg_regx_v2; ++ u32 cfg_regval; ++}; ++ ++/** ++ * union aud_ch_status_0 - Audio Channel Status 0 Attributes ++ * ++ * @status_0_regx:individual register bits ++ * @status_0_regval:full register value ++ * ++ */ ++union aud_ch_status_0 { ++ struct { ++ u32 ch_status:1; ++ u32 lpcm_id:1; ++ u32 cp_info:1; ++ u32 format:3; ++ u32 mode:2; ++ u32 ctg_code:8; ++ u32 src_num:4; ++ u32 ch_num:4; ++ u32 samp_freq:4; ++ u32 clk_acc:2; ++ u32 rsvd:2; ++ } status_0_regx; ++ u32 status_0_regval; ++}; ++ ++/** ++ * union aud_ch_status_1 - Audio Channel Status 1 Attributes ++ * ++ * @status_1_regx: individual register bits ++ * @status_1_regval: full register value ++ * ++ */ ++union aud_ch_status_1 { ++ struct { ++ u32 max_wrd_len:1; ++ u32 wrd_len:3; ++ u32 rsvd:28; ++ } status_1_regx; ++ u32 status_1_regval; ++}; ++ ++/** ++ * union aud_hdmi_cts - CTS register ++ * ++ * @cts_regx: individual register bits ++ * @cts_regval: full register value ++ * ++ */ ++union aud_hdmi_cts { ++ struct { ++ u32 cts_val:20; ++ u32 en_cts_prog:1; ++ u32 rsvd:11; ++ } cts_regx; ++ struct { ++ u32 cts_val:24; ++ u32 en_cts_prog:1; ++ u32 rsvd:7; ++ } cts_regx_v2; ++ u32 cts_regval; ++}; ++ ++/** ++ * union aud_hdmi_n_enable - N register ++ * ++ * @n_regx: individual register bits ++ * @n_regval: full register value ++ * ++ */ ++union aud_hdmi_n_enable { ++ struct { ++ u32 n_val:20; ++ u32 en_n_prog:1; ++ u32 rsvd:11; ++ } n_regx; ++ struct { ++ u32 n_val:24; ++ u32 en_n_prog:1; ++ u32 rsvd:7; ++ } n_regx_v2; ++ u32 n_regval; ++}; ++ ++/** ++ * union aud_buf_config - Audio Buffer configurations ++ * ++ * @buf_cfg_regx: individual register bits ++ * @buf_cfgval: full register value ++ * ++ */ ++union aud_buf_config { ++ struct { ++ u32 fifo_width:8; ++ u32 rsvd0:8; ++ u32 aud_delay:8; ++ u32 rsvd1:8; ++ } buf_cfg_regx; ++ struct { ++ u32 audio_fifo_watermark:8; ++ u32 dma_fifo_watermark:3; ++ u32 rsvd0:5; ++ u32 aud_delay:8; ++ u32 rsvd1:8; ++ } buf_cfg_regx_v2; ++ u32 buf_cfgval; ++}; ++ ++/** ++ * union aud_buf_ch_swap - Audio Sample Swapping offset ++ * ++ * @buf_ch_swap_regx: individual register bits ++ * @buf_ch_swap_val: full register value ++ * ++ */ ++union aud_buf_ch_swap { ++ struct { ++ u32 first_0:3; ++ u32 second_0:3; ++ u32 first_1:3; ++ u32 second_1:3; ++ u32 first_2:3; ++ u32 second_2:3; ++ u32 first_3:3; ++ u32 second_3:3; ++ u32 rsvd:8; ++ } buf_ch_swap_regx; ++ u32 buf_ch_swap_val; ++}; ++ ++/** ++ * union aud_buf_addr - Address for Audio Buffer ++ * ++ * @buf_addr_regx: individual register bits ++ * @buf_addr_val: full register value ++ * ++ */ ++union aud_buf_addr { ++ struct { ++ u32 valid:1; ++ u32 intr_en:1; ++ u32 rsvd:4; ++ u32 addr:26; ++ } buf_addr_regx; ++ u32 buf_addr_val; ++}; ++ ++/** ++ * union aud_buf_len - Length of Audio Buffer ++ * ++ * @buf_len_regx: individual register bits ++ * @buf_len_val: full register value ++ * ++ */ ++union aud_buf_len { ++ struct { ++ u32 buf_len:20; ++ u32 rsvd:12; ++ } buf_len_regx; ++ u32 buf_len_val; ++}; ++ ++/** ++ * union aud_ctrl_st - Audio Control State Register offset ++ * ++ * @ctrl_regx: individual register bits ++ * @ctrl_val: full register value ++ * ++ */ ++union aud_ctrl_st { ++ struct { ++ u32 ram_addr:4; ++ u32 eld_ack:1; ++ u32 eld_addr:4; ++ u32 eld_buf_size:5; ++ u32 eld_valid:1; ++ u32 cp_ready:1; ++ u32 dip_freq:2; ++ u32 dip_idx:3; ++ u32 dip_en_sta:4; ++ u32 rsvd:7; ++ } ctrl_regx; ++ u32 ctrl_val; ++}; ++ ++/** ++ * union aud_info_frame1 - Audio HDMI Widget Data Island Packet offset ++ * ++ * @fr1_regx: individual register bits ++ * @fr1_val: full register value ++ * ++ */ ++union aud_info_frame1 { ++ struct { ++ u32 pkt_type:8; ++ u32 ver_num:8; ++ u32 len:5; ++ u32 rsvd:11; ++ } fr1_regx; ++ u32 fr1_val; ++}; ++ ++/** ++ * union aud_info_frame2 - DIP frame 2 ++ * ++ * @fr2_regx: individual register bits ++ * @fr2_val: full register value ++ * ++ */ ++union aud_info_frame2 { ++ struct { ++ u32 chksum:8; ++ u32 chnl_cnt:3; ++ u32 rsvd0:1; ++ u32 coding_type:4; ++ u32 smpl_size:2; ++ u32 smpl_freq:3; ++ u32 rsvd1:3; ++ u32 format:8; ++ } fr2_regx; ++ u32 fr2_val; ++}; ++ ++/** ++ * union aud_info_frame3 - DIP frame 3 ++ * ++ * @fr3_regx: individual register bits ++ * @fr3_val: full register value ++ * ++ */ ++union aud_info_frame3 { ++ struct { ++ u32 chnl_alloc:8; ++ u32 rsvd0:3; ++ u32 lsv:4; ++ u32 dm_inh:1; ++ u32 rsvd1:16; ++ } fr3_regx; ++ u32 fr3_val; ++}; ++ ++ ++struct pcm_stream_info { ++ int str_id; ++ void *had_substream; ++ void (*period_elapsed)(void *had_substream); ++ u32 buffer_ptr; ++ u64 buffer_rendered; ++ u32 ring_buf_size; ++ int sfreq; ++}; ++ ++struct ring_buf_info { ++ uint32_t buf_addr; ++ uint32_t buf_size; ++ uint8_t is_valid; ++}; ++ ++struct had_stream_pvt { ++ enum had_stream_status stream_status; ++ int stream_ops; ++ ssize_t dbg_cum_bytes; ++}; ++ ++struct had_pvt_data { ++ enum had_status_stream stream_type; ++}; ++ ++struct had_callback_ops { ++ had_event_call_back intel_had_event_call_back; ++}; ++ ++/** ++ * struct snd_intelhad - intelhad driver structure ++ * ++ * @card: ptr to hold card details ++ * @card_index: sound card index ++ * @card_id: detected sound card id ++ * @reg_ops: register operations to program registers ++ * @query_ops: caps call backs for get/set operations ++ * @drv_status: driver status ++ * @buf_info: ring buffer info ++ * @stream_info: stream information ++ * @eeld: holds EELD info ++ * @curr_buf: pointer to hold current active ring buf ++ * @valid_buf_cnt: ring buffer count for stream ++ * @had_spinlock: driver lock ++ * @aes_bits: IEC958 status bits ++ * @buff_done: id of current buffer done intr ++ * @dev: platoform device handle ++ * @kctl: holds kctl ptrs used for channel map ++ * @chmap: holds channel map info ++ * @audio_reg_base: hdmi audio register base offset ++ * @hw_silence: flag indicates SoC support for HW silence/Keep alive ++ * @ops: holds ops functions based on platform ++ */ ++struct snd_intelhad { ++ struct snd_card *card; ++ int card_index; ++ char *card_id; ++ struct hdmi_audio_registers_ops reg_ops; ++ struct hdmi_audio_query_set_ops query_ops; ++ enum had_drv_status drv_status; ++ struct ring_buf_info buf_info[HAD_NUM_OF_RING_BUFS]; ++ struct pcm_stream_info stream_info; ++ otm_hdmi_eld_t eeld; ++ enum intel_had_aud_buf_type curr_buf; ++ int valid_buf_cnt; ++ unsigned int aes_bits; ++ int flag_underrun; ++ struct had_pvt_data *private_data; ++ spinlock_t had_spinlock; ++ enum intel_had_aud_buf_type buff_done; ++ struct device *dev; ++ struct snd_kcontrol *kctl; ++ struct snd_pcm_chmap *chmap; ++ unsigned int audio_reg_base; ++ bool hw_silence; ++ struct had_ops *ops; ++}; ++ ++struct had_ops { ++ void (*enable_audio)(struct snd_pcm_substream *substream, ++ u8 enable); ++ void (*reset_audio)(u8 reset); ++ int (*prog_n)(u32 aud_samp_freq, u32 *n_param, ++ struct snd_intelhad *intelhaddata); ++ void (*prog_cts)(u32 aud_samp_freq, u32 tmds, u32 n_param, ++ struct snd_intelhad *intelhaddata); ++ int (*audio_ctrl)(struct snd_pcm_substream *substream, ++ struct snd_intelhad *intelhaddata); ++ void (*prog_dip)(struct snd_pcm_substream *substream, ++ struct snd_intelhad *intelhaddata); ++ void (*handle_underrun)(struct snd_intelhad *intelhaddata); ++}; ++ ++ ++int had_event_handler(enum had_event_type event_type, void *data); ++ ++int hdmi_audio_query(void *drv_data, hdmi_audio_event_t event); ++int hdmi_audio_suspend(void *drv_data, hdmi_audio_event_t event); ++int hdmi_audio_resume(void *drv_data); ++int hdmi_audio_mode_change(struct snd_pcm_substream *substream); ++extern struct snd_pcm_ops snd_intelhad_playback_ops; ++ ++int snd_intelhad_init_audio_ctrl(struct snd_pcm_substream *substream, ++ struct snd_intelhad *intelhaddata, ++ int flag_silence); ++int snd_intelhad_prog_buffer(struct snd_intelhad *intelhaddata, ++ int start, int end); ++int snd_intelhad_invd_buffer(int start, int end); ++inline int snd_intelhad_read_len(struct snd_intelhad *intelhaddata); ++void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata); ++ ++/* Register access functions */ ++inline int had_get_hwstate(struct snd_intelhad *intelhaddata); ++inline int had_get_caps(enum had_caps_list query_element, void *capabilties); ++inline int had_set_caps(enum had_caps_list set_element, void *capabilties); ++inline int had_read_register(uint32_t reg_addr, uint32_t *data); ++inline int had_write_register(uint32_t reg_addr, uint32_t data); ++inline int had_read_modify(uint32_t reg_addr, uint32_t data, uint32_t mask); ++#endif +diff --git a/sound/hdmi_audio/intel_mid_hdmi_audio_if.c b/sound/hdmi_audio/intel_mid_hdmi_audio_if.c +new file mode 100644 +index 0000000..acc407d +--- /dev/null ++++ b/sound/hdmi_audio/intel_mid_hdmi_audio_if.c +@@ -0,0 +1,533 @@ ++/* ++ * intel_mid_hdmi_audio_if.c - Intel HDMI audio driver for MID ++ * ++ * Copyright (C) 2010 Intel Corp ++ * Authors: Sailaja Bandarupalli ++ * Ramesh Babu K V ++ * Vaibhav Agarwal ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; version 2 of the License. ++ * ++ * 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. ++ * ++ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++ * ALSA driver for Intel MID HDMI audio controller. This file contains ++ * interface functions exposed to HDMI Display driver and code to register ++ * with ALSA framework.. ++ */ ++ ++#define pr_fmt(fmt) "had: " fmt ++ ++#include ++#include ++#include ++#include ++#include "intel_mid_hdmi_audio.h" ++ ++ ++/** ++ * hdmi_audio_query - hdmi audio query function ++ * ++ *@haddata: pointer to HAD private data ++ *@event: audio event for which this method is invoked ++ * ++ * This function is called by client driver to query the ++ * hdmi audio. ++ */ ++int hdmi_audio_query(void *haddata, hdmi_audio_event_t event) ++{ ++ struct snd_pcm_substream *substream = NULL; ++ struct had_pvt_data *had_stream; ++ unsigned long flag_irqs; ++ struct snd_intelhad *intelhaddata = (struct snd_intelhad *)haddata; ++ ++ if (intelhaddata->stream_info.had_substream) ++ substream = intelhaddata->stream_info.had_substream; ++ had_stream = intelhaddata->private_data; ++ switch (event.type) { ++ case HAD_EVENT_QUERY_IS_AUDIO_BUSY: ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ ++ if ((had_stream->stream_type == HAD_RUNNING_STREAM) || ++ substream) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, ++ flag_irqs); ++ pr_debug("Audio stream active\n"); ++ return -EBUSY; ++ } ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ break; ++ ++ case HAD_EVENT_QUERY_IS_AUDIO_SUSPENDED: ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ if (intelhaddata->drv_status == HAD_DRV_SUSPENDED) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, ++ flag_irqs); ++ pr_debug("Audio is suspended\n"); ++ return 1; ++ } ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ break; ++ ++ default: ++ pr_debug("error un-handled event !!\n"); ++ return -EINVAL; ++ break; ++ ++ } ++ ++ return 0; ++} ++ ++/** ++ * hdmi_audio_suspend - power management suspend function ++ * ++ *@haddata: pointer to HAD private data ++ *@event: pm event for which this method is invoked ++ * ++ * This function is called by client driver to suspend the ++ * hdmi audio. ++ */ ++int hdmi_audio_suspend(void *haddata, hdmi_audio_event_t event) ++{ ++ int caps, retval = 0; ++ struct had_pvt_data *had_stream; ++ unsigned long flag_irqs; ++ struct snd_pcm_substream *substream; ++ struct snd_intelhad *intelhaddata = (struct snd_intelhad *)haddata; ++ ++ pr_debug("Enter:%s", __func__); ++ ++ had_stream = intelhaddata->private_data; ++ substream = intelhaddata->stream_info.had_substream; ++ ++ if (intelhaddata->dev->power.runtime_status != RPM_SUSPENDED) { ++ pr_err("audio stream is active\n"); ++ return -EAGAIN; ++ } ++ ++ ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ pr_debug("had not connected\n"); ++ return retval; ++ } ++ ++ if (intelhaddata->drv_status == HAD_DRV_SUSPENDED) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ pr_debug("had already suspended\n"); ++ return retval; ++ } ++ ++ intelhaddata->drv_status = HAD_DRV_SUSPENDED; ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ /* ++ * ToDo: Need to disable UNDERRUN interrupts as well ++ * caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE; ++ */ ++ caps = HDMI_AUDIO_BUFFER_DONE; ++ had_set_caps(HAD_SET_DISABLE_AUDIO_INT, &caps); ++ had_set_caps(HAD_SET_DISABLE_AUDIO, NULL); ++ pr_debug("Exit:%s", __func__); ++ return retval; ++} ++ ++/** ++ * hdmi_audio_resume - power management resume function ++ * ++ *@haddata: pointer to HAD private data ++ * ++ * This function is called by client driver to resume the ++ * hdmi audio. ++ */ ++int hdmi_audio_resume(void *haddata) ++{ ++ int caps, retval = 0; ++ struct snd_intelhad *intelhaddata = (struct snd_intelhad *)haddata; ++ unsigned long flag_irqs; ++ ++ pr_debug("Enter:%s", __func__); ++ ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ pr_debug("had not connected\n"); ++ return 0; ++ } ++ ++ if (intelhaddata->drv_status != HAD_DRV_SUSPENDED) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ pr_err("had is not in suspended state\n"); ++ return 0; ++ } ++ ++ if (had_get_hwstate(intelhaddata)) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ pr_err("Failed to resume. Device not accessible\n"); ++ return -ENODEV; ++ } ++ ++ intelhaddata->drv_status = HAD_DRV_CONNECTED; ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ /* ++ * ToDo: Need to enable UNDERRUN interrupts as well ++ * caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE; ++ */ ++ caps = HDMI_AUDIO_BUFFER_DONE; ++ retval = had_set_caps(HAD_SET_ENABLE_AUDIO_INT, &caps); ++ retval = had_set_caps(HAD_SET_ENABLE_AUDIO, NULL); ++ pr_debug("Exit:%s", __func__); ++ return retval; ++} ++ ++static inline int had_chk_intrmiss(struct snd_intelhad *intelhaddata, ++ enum intel_had_aud_buf_type buf_id) ++{ ++ int i, intr_count = 0; ++ enum intel_had_aud_buf_type buff_done; ++ u32 buf_size, buf_addr; ++ struct had_pvt_data *had_stream; ++ unsigned long flag_irqs; ++ ++ had_stream = intelhaddata->private_data; ++ ++ buff_done = buf_id; ++ ++ intr_count = snd_intelhad_read_len(intelhaddata); ++ if (intr_count > 1) { ++ /* In case of active playback */ ++ pr_err("Driver detected %d missed buffer done interrupt(s)!!!!\n", ++ (intr_count - 1)); ++ if (intr_count > 3) ++ return intr_count; ++ ++ buf_id += (intr_count - 1); ++ /* Reprogram registers*/ ++ for (i = buff_done; i < buf_id; i++) { ++ int j = i % 4; ++ ++ buf_size = intelhaddata->buf_info[j].buf_size; ++ buf_addr = intelhaddata->buf_info[j].buf_addr; ++ had_write_register(AUD_BUF_A_LENGTH + ++ (j * HAD_REG_WIDTH), buf_size); ++ had_write_register( ++ AUD_BUF_A_ADDR+(j * HAD_REG_WIDTH), ++ (buf_addr | BIT(0) | BIT(1))); ++ } ++ buf_id = buf_id % 4; ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ intelhaddata->buff_done = buf_id; ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ } ++ ++ return intr_count; ++} ++ ++int had_process_buffer_done(struct snd_intelhad *intelhaddata) ++{ ++ int retval = 0; ++ u32 len = 1; ++ enum intel_had_aud_buf_type buf_id; ++ enum intel_had_aud_buf_type buff_done; ++ struct pcm_stream_info *stream; ++ u32 buf_size; ++ struct had_pvt_data *had_stream; ++ int intr_count; ++ enum had_status_stream stream_type; ++ unsigned long flag_irqs; ++ ++ had_stream = intelhaddata->private_data; ++ stream = &intelhaddata->stream_info; ++ intr_count = 1; ++ ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ pr_err("%s:Device already disconnected\n", __func__); ++ return retval; ++ } ++ buf_id = intelhaddata->curr_buf; ++ intelhaddata->buff_done = buf_id; ++ buff_done = intelhaddata->buff_done; ++ buf_size = intelhaddata->buf_info[buf_id].buf_size; ++ stream_type = had_stream->stream_type; ++ ++ pr_debug("Enter:%s buf_id=%d", __func__, buf_id); ++ ++ /* Every debug statement has an implication ++ * of ~5msec. Thus, avoid having >3 debug statements ++ * for each buffer_done handling. ++ */ ++ ++ /* Check for any intr_miss in case of active playback */ ++ if (had_stream->stream_type == HAD_RUNNING_STREAM) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ intr_count = had_chk_intrmiss(intelhaddata, buf_id); ++ if (!intr_count || (intr_count > 3)) { ++ pr_err("HAD SW state in non-recoverable!!! mode\n"); ++ pr_err("Already played stale data\n"); ++ return retval; ++ } ++ buf_id += (intr_count - 1); ++ buf_id = buf_id % 4; ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ } ++ ++ intelhaddata->buf_info[buf_id].is_valid = true; ++ if (intelhaddata->valid_buf_cnt-1 == buf_id) { ++ if (had_stream->stream_type >= HAD_RUNNING_STREAM) ++ intelhaddata->curr_buf = HAD_BUF_TYPE_A; ++ } else ++ intelhaddata->curr_buf = buf_id + 1; ++ ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ ++ if (had_get_hwstate(intelhaddata)) { ++ pr_err("HDMI cable plugged-out\n"); ++ return retval; ++ } ++ ++ /*Reprogram the registers with addr and length*/ ++ had_write_register(AUD_BUF_A_LENGTH + ++ (buf_id * HAD_REG_WIDTH), buf_size); ++ had_write_register(AUD_BUF_A_ADDR+(buf_id * HAD_REG_WIDTH), ++ intelhaddata->buf_info[buf_id].buf_addr| ++ BIT(0) | BIT(1)); ++ ++ had_read_register(AUD_BUF_A_LENGTH + (buf_id * HAD_REG_WIDTH), ++ &len); ++ pr_debug("%s:Enabled buf[%d]\n", __func__, buf_id); ++ ++ /* In case of actual data, ++ * report buffer_done to above ALSA layer ++ */ ++ buf_size = intelhaddata->buf_info[buf_id].buf_size; ++ if (stream_type >= HAD_RUNNING_STREAM) { ++ intelhaddata->stream_info.buffer_rendered += ++ (intr_count * buf_size); ++ stream->period_elapsed(stream->had_substream); ++ } ++ ++ return retval; ++} ++ ++int had_process_buffer_underrun(struct snd_intelhad *intelhaddata) ++{ ++ int retval = 0; ++ enum intel_had_aud_buf_type buf_id; ++ struct pcm_stream_info *stream; ++ struct had_pvt_data *had_stream; ++ enum had_status_stream stream_type; ++ unsigned long flag_irqs; ++ int drv_status; ++ ++ had_stream = intelhaddata->private_data; ++ stream = &intelhaddata->stream_info; ++ ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ buf_id = intelhaddata->curr_buf; ++ stream_type = had_stream->stream_type; ++ intelhaddata->buff_done = buf_id; ++ drv_status = intelhaddata->drv_status; ++ if (stream_type == HAD_RUNNING_STREAM) ++ intelhaddata->curr_buf = HAD_BUF_TYPE_A; ++ ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ ++ pr_debug("Enter:%s buf_id=%d, stream_type=%d\n", ++ __func__, buf_id, stream_type); ++ ++ intelhaddata->ops->handle_underrun(intelhaddata); ++ ++ if (drv_status == HAD_DRV_DISCONNECTED) { ++ pr_err("%s:Device already disconnected\n", __func__); ++ return retval; ++ } ++ ++ if (stream_type == HAD_RUNNING_STREAM) { ++ /* Report UNDERRUN error to above layers */ ++ intelhaddata->flag_underrun = 1; ++ stream->period_elapsed(stream->had_substream); ++ } ++ ++ return retval; ++} ++ ++int had_process_hot_plug(struct snd_intelhad *intelhaddata) ++{ ++ int retval = 0; ++ enum intel_had_aud_buf_type buf_id; ++ struct snd_pcm_substream *substream; ++ struct had_pvt_data *had_stream; ++ unsigned long flag_irqs; ++ ++ pr_debug("Enter:%s", __func__); ++ ++ substream = intelhaddata->stream_info.had_substream; ++ had_stream = intelhaddata->private_data; ++ ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ if (intelhaddata->drv_status == HAD_DRV_CONNECTED) { ++ pr_debug("Device already connected\n"); ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ return retval; ++ } ++ buf_id = intelhaddata->curr_buf; ++ intelhaddata->buff_done = buf_id; ++ intelhaddata->drv_status = HAD_DRV_CONNECTED; ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ ++ pr_debug("Processing HOT_PLUG, buf_id = %d\n", buf_id); ++ ++ /* Query display driver for audio register base */ ++ if (intelhaddata->reg_ops.hdmi_audio_get_register_base ++ (&intelhaddata->audio_reg_base)) { ++ pr_err("Unable to get audio reg base from Display driver\n"); ++ goto err; ++ } ++ if(intelhaddata->audio_reg_base == 0){ ++ pr_err("audio reg base value is NULL\n"); ++ goto err; ++ } ++ ++ pr_debug("%s audio_reg_base = %x\n",__func__, intelhaddata->audio_reg_base); ++ ++ /* Safety check */ ++ if (substream) { ++ pr_debug("There should not be active PB from ALSA\n"); ++ pr_debug("Signifies, cable is plugged-in even before\n"); ++ pr_debug("processing snd_pcm_disconnect\n"); ++ /* Set runtime->state to hw_params done */ ++ snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); ++ } ++ ++ had_build_channel_allocation_map(intelhaddata); ++ ++ return retval; ++ ++err: ++ pm_runtime_disable(intelhaddata->dev); ++ intelhaddata->dev = NULL; ++ return retval; ++} ++ ++int had_process_hot_unplug(struct snd_intelhad *intelhaddata) ++{ ++ int caps, retval = 0; ++ enum intel_had_aud_buf_type buf_id; ++ struct had_pvt_data *had_stream; ++ unsigned long flag_irqs; ++ ++ pr_debug("Enter:%s", __func__); ++ ++ had_stream = intelhaddata->private_data; ++ buf_id = intelhaddata->curr_buf; ++ ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ if (intelhaddata->drv_status == HAD_DRV_DISCONNECTED) { ++ pr_debug("Device already disconnected\n"); ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ return retval; ++ } else { ++ /* Disable Audio */ ++ caps = HDMI_AUDIO_BUFFER_DONE; ++ retval = had_set_caps(HAD_SET_DISABLE_AUDIO_INT, &caps); ++ retval = had_set_caps(HAD_SET_DISABLE_AUDIO, NULL); ++ intelhaddata->ops->enable_audio(intelhaddata->stream_info.had_substream, 0); ++ } ++ ++ intelhaddata->drv_status = HAD_DRV_DISCONNECTED; ++ /* Report to above ALSA layer */ ++ if (intelhaddata->stream_info.had_substream != NULL) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ pr_debug("%s: unlock -> sending pcm_stop -> lock\n", __func__); ++ snd_pcm_stop(intelhaddata->stream_info.had_substream, ++ SNDRV_PCM_STATE_DISCONNECTED); ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ } ++ ++ had_stream->stream_type = HAD_INIT; ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ kfree(intelhaddata->chmap->chmap); ++ intelhaddata->chmap->chmap = NULL; ++ intelhaddata->audio_reg_base = 0; ++ pr_debug("%s: unlocked -> returned\n", __func__); ++ ++ return retval; ++} ++ ++/** ++ * had_event_handler - Call back function to handle events ++ * ++ * @event_type: Event type to handle ++ * @data: data related to the event_type ++ * ++ * This function is invoked to handle HDMI events from client driver. ++ */ ++int had_event_handler(enum had_event_type event_type, void *data) ++{ ++ int retval = 0; ++ struct snd_intelhad *intelhaddata = data; ++ enum intel_had_aud_buf_type buf_id; ++ struct snd_pcm_substream *substream; ++ struct had_pvt_data *had_stream; ++ unsigned long flag_irqs; ++ ++ buf_id = intelhaddata->curr_buf; ++ had_stream = intelhaddata->private_data; ++ ++ /* Switching to a function can drop atomicity even in INTR context. ++ * Thus, a big lock is acquired to maintain atomicity. ++ * This can be optimized later. ++ * Currently, only buffer_done/_underrun executes in INTR context. ++ * Also, locking is implemented separately to avoid real contention ++ * of data(struct intelhaddata) between IRQ/SOFT_IRQ/PROCESS context. ++ */ ++ substream = intelhaddata->stream_info.had_substream; ++ switch (event_type) { ++ case HAD_EVENT_AUDIO_BUFFER_DONE: ++ retval = had_process_buffer_done(intelhaddata); ++ break; ++ ++ case HAD_EVENT_AUDIO_BUFFER_UNDERRUN: ++ retval = had_process_buffer_underrun(intelhaddata); ++ break; ++ ++ case HAD_EVENT_HOT_PLUG: ++ retval = had_process_hot_plug(intelhaddata); ++ break; ++ ++ case HAD_EVENT_HOT_UNPLUG: ++ retval = had_process_hot_unplug(intelhaddata); ++ break; ++ ++ case HAD_EVENT_MODE_CHANGING: ++ pr_debug(" called _event_handler with _MODE_CHANGE event\n"); ++ /* Process only if stream is active & cable Plugged-in */ ++ spin_lock_irqsave(&intelhaddata->had_spinlock, flag_irqs); ++ if (intelhaddata->drv_status >= HAD_DRV_DISCONNECTED) { ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, ++ flag_irqs); ++ break; ++ } ++ spin_unlock_irqrestore(&intelhaddata->had_spinlock, flag_irqs); ++ if ((had_stream->stream_type == HAD_RUNNING_STREAM) ++ && substream) ++ retval = hdmi_audio_mode_change(substream); ++ break; ++ ++ default: ++ pr_debug("error un-handled event !!\n"); ++ retval = -EINVAL; ++ break; ++ ++ } ++ return retval; ++} + +From 16eb2207449a5bf5115345384e78033cb13a0f3c Mon Sep 17 00:00:00 2001 +From: Pierre-Louis Bossart +Date: Sat, 20 Feb 2016 18:08:41 -0600 +Subject: [PATCH 08/12] add dependency on PM_RUNTIME + +Signed-off-by: Pierre-Louis Bossart +--- + sound/Kconfig | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/sound/Kconfig b/sound/Kconfig +index 75c679e..b8b4fce 100644 +--- a/sound/Kconfig ++++ b/sound/Kconfig +@@ -138,6 +138,7 @@ config AC97_BUS + config SUPPORT_HDMI + bool "SUPPORT_HDMI" + depends on DRM_I915 ++ select PM_RUNTIME + default n + help + Choose this option to support HDMI. + +From 6f73b34ca585c052de0732c02dece07126d36dbe Mon Sep 17 00:00:00 2001 +From: David Henningsson +Date: Fri, 21 Aug 2015 11:08:47 +0200 +Subject: [PATCH 09/12] hdmi_audio: Improve position reporting + +Using a hw register to calculate sub-period position reports. + +This makes PulseAudio happier. + +Signed-off-by: David Henningsson +Signed-off-by: Pierre-Louis Bossart +--- + sound/hdmi_audio/intel_mid_hdmi_audio.c | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/sound/hdmi_audio/intel_mid_hdmi_audio.c b/sound/hdmi_audio/intel_mid_hdmi_audio.c +index d8c5574..b2337c3 100644 +--- a/sound/hdmi_audio/intel_mid_hdmi_audio.c ++++ b/sound/hdmi_audio/intel_mid_hdmi_audio.c +@@ -1550,6 +1550,8 @@ static snd_pcm_uframes_t snd_intelhad_pcm_pointer( + { + struct snd_intelhad *intelhaddata; + u32 bytes_rendered = 0; ++ u32 t; ++ int buf_id; + + /* pr_debug("snd_intelhad_pcm_pointer called\n"); */ + +@@ -1560,6 +1562,14 @@ static snd_pcm_uframes_t snd_intelhad_pcm_pointer( + return SNDRV_PCM_POS_XRUN; + } + ++ buf_id = intelhaddata->curr_buf % 4; ++ had_read_register(AUD_BUF_A_LENGTH + (buf_id * HAD_REG_WIDTH), &t); ++ if (t == 0) { ++ pr_debug("discovered buffer done for buf %d\n", buf_id); ++ /* had_process_buffer_done(intelhaddata); */ ++ } ++ t = intelhaddata->buf_info[buf_id].buf_size - t; ++ + if (intelhaddata->stream_info.buffer_rendered) + div_u64_rem(intelhaddata->stream_info.buffer_rendered, + intelhaddata->stream_info.ring_buf_size, +@@ -1567,7 +1577,7 @@ static snd_pcm_uframes_t snd_intelhad_pcm_pointer( + + intelhaddata->stream_info.buffer_ptr = bytes_to_frames( + substream->runtime, +- bytes_rendered); ++ bytes_rendered + t); + return intelhaddata->stream_info.buffer_ptr; + } + + +From 3265fa5f1bea0e7820718a1c14528ba8620ee70a Mon Sep 17 00:00:00 2001 +From: David Henningsson +Date: Fri, 21 Aug 2015 11:18:19 +0200 +Subject: [PATCH 10/12] hdmi_audio: Fixup some monitor + +I think this change was given to us, and they claimed it fixed an issue +on some monitor brand. I'm not sure what this patch actually does. + +Signed-off-by: David Henningsson +Signed-off-by: Pierre-Louis Bossart +--- + sound/hdmi_audio/intel_mid_hdmi_audio.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/sound/hdmi_audio/intel_mid_hdmi_audio.c b/sound/hdmi_audio/intel_mid_hdmi_audio.c +index b2337c3..1667748 100644 +--- a/sound/hdmi_audio/intel_mid_hdmi_audio.c ++++ b/sound/hdmi_audio/intel_mid_hdmi_audio.c +@@ -386,6 +386,7 @@ static void snd_intelhad_reset_audio_v2(u8 reset) + static int had_prog_status_reg(struct snd_pcm_substream *substream, + struct snd_intelhad *intelhaddata) + { ++ union aud_cfg cfg_val = {.cfg_regval = 0}; + union aud_ch_status_0 ch_stat0 = {.status_0_regval = 0}; + union aud_ch_status_1 ch_stat1 = {.status_1_regval = 0}; + int format; +@@ -396,6 +397,8 @@ static int had_prog_status_reg(struct snd_pcm_substream *substream, + IEC958_AES0_NONAUDIO)>>1; + ch_stat0.status_0_regx.clk_acc = (intelhaddata->aes_bits & + IEC958_AES3_CON_CLOCK)>>4; ++ cfg_val.cfg_regx.val_bit = ch_stat0.status_0_regx.lpcm_id; ++ + switch (substream->runtime->rate) { + case AUD_SAMPLE_RATE_32: + ch_stat0.status_0_regx.samp_freq = CH_STATUS_MAP_32KHZ; +@@ -474,7 +477,6 @@ int snd_intelhad_prog_audio_ctrl_v2(struct snd_pcm_substream *substream, + else + cfg_val.cfg_regx_v2.layout = LAYOUT1; + +- cfg_val.cfg_regx_v2.val_bit = 1; + had_write_register(AUD_CONFIG, cfg_val.cfg_regval); + return 0; + } +@@ -530,7 +532,6 @@ int snd_intelhad_prog_audio_ctrl_v1(struct snd_pcm_substream *substream, + + } + +- cfg_val.cfg_regx.val_bit = 1; + had_write_register(AUD_CONFIG, cfg_val.cfg_regval); + return 0; + } + +From fbad36dca91688d71593b61bd170cf4c39eb24fb Mon Sep 17 00:00:00 2001 +From: Toyo Abe +Date: Thu, 3 Mar 2016 12:57:41 +0900 +Subject: [PATCH 11/12] hdmi_audio: Fix mishandling of AUD_HDMI_STATUS_v2 + register. + +According to the datasheet, write one to clear these UNDERRUN flag bits. +This fixes the following warning in dmesg. + +[15357.574902] had: Unable to clear UNDERRUN bits + +Signed-off-by: Toyo Abe +Signed-off-by: Pierre-Louis Bossart +--- + sound/hdmi_audio/intel_mid_hdmi_audio.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/sound/hdmi_audio/intel_mid_hdmi_audio.c b/sound/hdmi_audio/intel_mid_hdmi_audio.c +index 1667748..86db38e 100644 +--- a/sound/hdmi_audio/intel_mid_hdmi_audio.c ++++ b/sound/hdmi_audio/intel_mid_hdmi_audio.c +@@ -1135,7 +1135,6 @@ static void had_clear_underrun_intr_v2(struct snd_intelhad *intelhaddata) + pr_debug("HDMI status =0x%x\n", hdmi_status); + if (hdmi_status & AUD_CONFIG_MASK_UNDERRUN) { + i++; +- hdmi_status &= ~AUD_CONFIG_MASK_UNDERRUN; + had_write_register(AUD_HDMI_STATUS_v2, hdmi_status); + } else + break; + +From 9c175c10b955b2d6f2865bb4a1c2dcda6d57dbf6 Mon Sep 17 00:00:00 2001 +From: Jerome Anand +Date: Fri, 1 Apr 2016 11:07:48 +0530 +Subject: [PATCH 12/12] Create a platform device for hdmi audio driver and + allocate full resources + +Signed-off-by: Pierre-Louis Bossart +--- + sound/hdmi_audio/intel_mid_hdmi_audio.c | 22 +++++++++++++++++++--- + 1 file changed, 19 insertions(+), 3 deletions(-) + +diff --git a/sound/hdmi_audio/intel_mid_hdmi_audio.c b/sound/hdmi_audio/intel_mid_hdmi_audio.c +index 86db38e..6497b6f 100644 +--- a/sound/hdmi_audio/intel_mid_hdmi_audio.c ++++ b/sound/hdmi_audio/intel_mid_hdmi_audio.c +@@ -47,6 +47,8 @@ static int hdmi_card_index = SNDRV_DEFAULT_IDX1; + static char *hdmi_card_id = SNDRV_DEFAULT_STR1; + static struct snd_intelhad *had_data; + ++struct platform_device *gpdev; ++ + module_param(hdmi_card_index, int, 0444); + MODULE_PARM_DESC(hdmi_card_index, + "Index value for INTEL Intel HDMI Audio controller."); +@@ -1481,9 +1483,9 @@ static int snd_intelhad_pcm_prepare(struct snd_pcm_substream *substream) + } + + pr_debug("period_size=%d\n", +- frames_to_bytes(runtime, runtime->period_size)); ++ (int)frames_to_bytes(runtime, runtime->period_size)); + pr_debug("periods=%d\n", runtime->periods); +- pr_debug("buffer_size=%d\n", snd_pcm_lib_buffer_bytes(substream)); ++ pr_debug("buffer_size=%d\n", (int)snd_pcm_lib_buffer_bytes(substream)); + pr_debug("rate=%d\n", runtime->rate); + pr_debug("channels=%d\n", runtime->channels); + +@@ -1997,10 +1999,16 @@ static struct platform_driver had_driver = { + #ifdef CONFIG_PM + .pm = &had_pm_ops, + #endif +- .acpi_match_table = ACPI_PTR(had_acpi_ids), ++ //.acpi_match_table = ACPI_PTR(had_acpi_ids), + }, + }; + ++static const struct platform_device_info hdmi_audio_dev_info = { ++ .name = HDMI_AUDIO_DRIVER, ++ .id = -1, ++ .dma_mask = DMA_BIT_MASK(32), ++}; ++ + /* + * alsa_card_intelhad_init- driver init function + * This function is called when driver module is inserted +@@ -2020,6 +2028,13 @@ static int __init alsa_card_intelhad_init(void) + return retval; + } + ++ //gpdev = platform_device_register_simple(HDMI_AUDIO_DRIVER, -1, NULL, 0); ++ gpdev = platform_device_register_full(&hdmi_audio_dev_info); ++ if (!gpdev) { ++ pr_err("[jerome] Failed to register platform device \n"); ++ return -1; ++ } ++ + pr_debug("init complete\n"); + return retval; + } +@@ -2032,6 +2047,7 @@ static void __exit alsa_card_intelhad_exit(void) + { + pr_debug("had_exit called\n"); + platform_driver_unregister(&had_driver); ++ platform_device_unregister(gpdev); + } + late_initcall(alsa_card_intelhad_init); + module_exit(alsa_card_intelhad_exit);