linux/sound/x86/intel_hdmi_audio.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 *   intel_hdmi_audio.c - Intel HDMI audio driver
 *
 *  Copyright (C) 2016 Intel Corp
 *  Authors:	Sailaja Bandarupalli <[email protected]>
 *		Ramesh Babu K V	<[email protected]>
 *		Vaibhav Agarwal <[email protected]>
 *		Jerome Anand <[email protected]>
 *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * ALSA driver for Intel HDMI audio
 */

#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <sound/core.h>
#include <sound/asoundef.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/control.h>
#include <sound/jack.h>
#include <drm/drm_edid.h>
#include <drm/drm_eld.h>
#include <drm/intel/intel_lpe_audio.h>
#include "intel_hdmi_audio.h"

#define INTEL_HDMI_AUDIO_SUSPEND_DELAY_MS

#define for_each_pipe(card_ctx, pipe)
#define for_each_port(card_ctx, port)

/*standard module options for ALSA. This module supports only one card*/
static int hdmi_card_index =;
static char *hdmi_card_id =;
static bool single_port;

module_param_named(index, hdmi_card_index, int, 0444);
MODULE_PARM_DESC();
module_param_named(id, hdmi_card_id, charp, 0444);
MODULE_PARM_DESC();
module_param(single_port, bool, 0444);
MODULE_PARM_DESC();

/*
 * ELD SA bits in the CEA Speaker Allocation data block
 */
static const int eld_speaker_allocation_bits[] =;

/*
 * 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[] =;

static const struct channel_map_table map_tables[] =;

/* hardware capability structure */
static const struct snd_pcm_hardware had_pcm_hardware =;

/* Get the active PCM substream;
 * Call had_substream_put() for unreferecing.
 * Don't call this inside had_spinlock, as it takes by itself
 */
static struct snd_pcm_substream *
had_substream_get(struct snd_intelhad *intelhaddata)
{}

/* Unref the active PCM substream;
 * Don't call this inside had_spinlock, as it takes by itself
 */
static void had_substream_put(struct snd_intelhad *intelhaddata)
{}

static u32 had_config_offset(int pipe)
{}

/* Register access functions */
static u32 had_read_register_raw(struct snd_intelhad_card *card_ctx,
				 int pipe, u32 reg)
{}

static void had_write_register_raw(struct snd_intelhad_card *card_ctx,
				   int pipe, u32 reg, u32 val)
{}

static void had_read_register(struct snd_intelhad *ctx, u32 reg, u32 *val)
{}

static void had_write_register(struct snd_intelhad *ctx, u32 reg, u32 val)
{}

/*
 * enable / disable audio configuration
 *
 * The normal read/modify 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 register 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.
 */
static void had_enable_audio(struct snd_intelhad *intelhaddata,
			     bool enable)
{}

/* forcibly ACKs to both BUFFER_DONE and BUFFER_UNDERRUN interrupts */
static void had_ack_irqs(struct snd_intelhad *ctx)
{}

/* Reset buffer pointers */
static void had_reset_audio(struct snd_intelhad *intelhaddata)
{}

/*
 * initialize audio channel status registers
 * This function is called in the prepare callback
 */
static int had_prog_status_reg(struct snd_pcm_substream *substream,
			struct snd_intelhad *intelhaddata)
{}

/*
 * function to initialize audio
 * registers and buffer configuration registers
 * This function is called in the prepare callback
 */
static int had_init_audio_ctrl(struct snd_pcm_substream *substream,
			       struct snd_intelhad *intelhaddata)
{}

/*
 * Compute derived values in channel_allocations[].
 */
static void init_channel_allocations(void)
{}

/*
 * 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 had_channel_allocation(struct snd_intelhad *intelhaddata,
				  int channels)
{}

/* from speaker bit mask to ALSA API channel position */
static int spk_to_chmap(int spk)
{}

static void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata)
{}

/*
 * ALSA API channel-map control callbacks
 */
static int had_chmap_ctl_info(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_info *uinfo)
{}

static int had_chmap_ctl_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{}

static int had_register_chmap_ctls(struct snd_intelhad *intelhaddata,
						struct snd_pcm *pcm)
{}

/*
 * Initialize Data Island Packets registers
 * This function is called in the prepare callback
 */
static void had_prog_dip(struct snd_pcm_substream *substream,
			 struct snd_intelhad *intelhaddata)
{}

static int had_calculate_maud_value(u32 aud_samp_freq, u32 link_rate)
{}

/*
 * Program HDMI audio CTS value
 *
 * @aud_samp_freq: sampling frequency of audio data
 * @tmds: sampling frequency of the display data
 * @link_rate: DP link rate
 * @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 had_prog_cts(u32 aud_samp_freq, u32 tmds, u32 link_rate,
			 u32 n_param, struct snd_intelhad *intelhaddata)
{}

static int had_calculate_n_value(u32 aud_samp_freq)
{}

/*
 * 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 had_prog_n(u32 aud_samp_freq, u32 *n_param,
		      struct snd_intelhad *intelhaddata)
{}

/*
 * PCM ring buffer handling
 *
 * The hardware provides a ring buffer with the fixed 4 buffer descriptors
 * (BDs).  The driver maps these 4 BDs onto the PCM ring buffer.  The mapping
 * moves at each period elapsed.  The below illustrates how it works:
 *
 * At time=0
 *  PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1|
 *  BD  | 0 | 1 | 2 | 3 |
 *
 * At time=1 (period elapsed)
 *  PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1|
 *  BD      | 1 | 2 | 3 | 0 |
 *
 * At time=2 (second period elapsed)
 *  PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1|
 *  BD          | 2 | 3 | 0 | 1 |
 *
 * The bd_head field points to the index of the BD to be read.  It's also the
 * position to be filled at next.  The pcm_head and the pcm_filled fields
 * point to the indices of the current position and of the next position to
 * be filled, respectively.  For PCM buffer there are both _head and _filled
 * because they may be difference when nperiods > 4.  For example, in the
 * example above at t=1, bd_head=1 and pcm_head=1 while pcm_filled=5:
 *
 * pcm_head (=1) --v               v-- pcm_filled (=5)
 *       PCM | 0 | 1 | 2 | 3 | 4 | 5 | .... |n-1|
 *       BD      | 1 | 2 | 3 | 0 |
 *  bd_head (=1) --^               ^-- next to fill (= bd_head)
 *
 * For nperiods < 4, the remaining BDs out of 4 are marked as invalid, so that
 * the hardware skips those BDs in the loop.
 *
 * An exceptional setup is the case with nperiods=1.  Since we have to update
 * BDs after finishing one BD processing, we'd need at least two BDs, where
 * both BDs point to the same content, the same address, the same size of the
 * whole PCM buffer.
 */

#define AUD_BUF_ADDR(x)
#define AUD_BUF_LEN(x)

/* Set up a buffer descriptor at the "filled" position */
static void had_prog_bd(struct snd_pcm_substream *substream,
			struct snd_intelhad *intelhaddata)
{}

/* invalidate a buffer descriptor with the given index */
static void had_invalidate_bd(struct snd_intelhad *intelhaddata,
			      int idx)
{}

/* Initial programming of ring buffer */
static void had_init_ringbuf(struct snd_pcm_substream *substream,
			     struct snd_intelhad *intelhaddata)
{}

/* process a bd, advance to the next */
static void had_advance_ringbuf(struct snd_pcm_substream *substream,
				struct snd_intelhad *intelhaddata)
{}

/* process the current BD(s);
 * returns the current PCM buffer byte position, or -EPIPE for underrun.
 */
static int had_process_ringbuf(struct snd_pcm_substream *substream,
			       struct snd_intelhad *intelhaddata)
{}

/* called from irq handler */
static void had_process_buffer_done(struct snd_intelhad *intelhaddata)
{}

/*
 * The interrupt status 'sticky' bits might not be cleared by
 * setting '1' to that bit once...
 */
static void wait_clear_underrun_bit(struct snd_intelhad *intelhaddata)
{}

/* Perform some reset procedure after stopping the stream;
 * this is called from prepare or hw_free callbacks once after trigger STOP
 * or underrun has been processed in order to settle down the h/w state.
 */
static int had_pcm_sync_stop(struct snd_pcm_substream *substream)
{}

/* called from irq handler */
static void had_process_buffer_underrun(struct snd_intelhad *intelhaddata)
{}

/*
 * ALSA PCM open callback
 */
static int had_pcm_open(struct snd_pcm_substream *substream)
{}

/*
 * ALSA PCM close callback
 */
static int had_pcm_close(struct snd_pcm_substream *substream)
{}

/*
 * ALSA PCM hw_params callback
 */
static int had_pcm_hw_params(struct snd_pcm_substream *substream,
			     struct snd_pcm_hw_params *hw_params)
{}

/*
 * ALSA PCM trigger callback
 */
static int had_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{}

/*
 * ALSA PCM prepare callback
 */
static int had_pcm_prepare(struct snd_pcm_substream *substream)
{}

/*
 * ALSA PCM pointer callback
 */
static snd_pcm_uframes_t had_pcm_pointer(struct snd_pcm_substream *substream)
{}

/*
 * ALSA PCM ops
 */
static const struct snd_pcm_ops had_pcm_ops =;

/* process mode change of the running stream; called in mutex */
static int had_process_mode_change(struct snd_intelhad *intelhaddata)
{}

/* process hot plug, called from wq with mutex locked */
static void had_process_hot_plug(struct snd_intelhad *intelhaddata)
{}

/* process hot unplug, called from wq with mutex locked */
static void had_process_hot_unplug(struct snd_intelhad *intelhaddata)
{}

/*
 * ALSA iec958 and ELD controls
 */

static int had_iec958_info(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_info *uinfo)
{}

static int had_iec958_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{}

static int had_iec958_mask_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{}

static int had_iec958_put(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{}

static int had_ctl_eld_info(struct snd_kcontrol *kcontrol,
			    struct snd_ctl_elem_info *uinfo)
{}

static int had_ctl_eld_get(struct snd_kcontrol *kcontrol,
			   struct snd_ctl_elem_value *ucontrol)
{}

static const struct snd_kcontrol_new had_controls[] =;

/*
 * audio interrupt handler
 */
static irqreturn_t display_pipe_interrupt_handler(int irq, void *dev_id)
{}

/*
 * monitor plug/unplug notification from i915; just kick off the work
 */
static void notify_audio_lpe(struct platform_device *pdev, int port)
{}

/* the work to handle monitor hot plug/unplug */
static void had_audio_wq(struct work_struct *work)
{}

/*
 * Jack interface
 */
static int had_create_jack(struct snd_intelhad *ctx,
			   struct snd_pcm *pcm)
{}

/*
 * PM callbacks
 */

static int __maybe_unused hdmi_lpe_audio_suspend(struct device *dev)
{}

static int __maybe_unused hdmi_lpe_audio_resume(struct device *dev)
{}

/* release resources */
static void hdmi_lpe_audio_free(struct snd_card *card)
{}

/*
 * hdmi_lpe_audio_probe - start bridge with i915
 *
 * This function is called when the i915 driver creates the
 * hdmi-lpe-audio platform device.
 */
static int __hdmi_lpe_audio_probe(struct platform_device *pdev)
{}

static int hdmi_lpe_audio_probe(struct platform_device *pdev)
{}

static const struct dev_pm_ops hdmi_lpe_audio_pm =;

static struct platform_driver hdmi_lpe_audio_driver =;

module_platform_driver();
MODULE_ALIAS();

MODULE_AUTHOR();
MODULE_AUTHOR();
MODULE_AUTHOR();
MODULE_AUTHOR();
MODULE_DESCRIPTION();
MODULE_LICENSE();