At Mon, 22 Nov 2010 19:09:44 +0530, ramesh.babu@intel.com wrote:
From: Ramesh Babu K V ramesh.babu@intel.com
This patch creates intel_mid_hdmi_audio.c file. It intereacts with ALSA framework to enable the audio playback through HDMI interface. This code uses standard ALSA APIs.
Signed-off-by: Ramesh Babu K V ramesh.babu@intel.com Signed-off-by: Sailaja Bandarupalli sailaja.bandarupalli@intel.com
.../drivers/intel_mid_hdmi/intel_mid_hdmi_audio.c | 546 ++++++++++++++++++++ 1 files changed, 546 insertions(+), 0 deletions(-) create mode 100644 sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.c
diff --git a/sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.c b/sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.c new file mode 100644 index 0000000..3ef4d48 --- /dev/null +++ b/sound/drivers/intel_mid_hdmi/intel_mid_hdmi_audio.c @@ -0,0 +1,546 @@ +/*
- intel_mid_hdmi_audio.c - Intel HDMI audio driver for MID
- Copyright (C) 2010 Intel Corp
- Authors: Sailaja Bandarupalli sailaja.bandarupalli@intel.com
Ramesh Babu K V <ramesh.babu@intel.com>
- 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.
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- ALSA driver for Intel MID HDMI audio controller
- */
+#include <linux/io.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/control.h> +#include <sound/initval.h> +#include "intel_mid_hdmi_audio.h"
+MODULE_AUTHOR("Sailaja Bandarupalli sailaja.bandarupalli@intel.com"); +MODULE_AUTHOR("Ramesh Babu K V ramesh.babu@intel.com"); +MODULE_DESCRIPTION("Intel HDMI Audio driver"); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{Intel,Intel_HAD}");
+#define INFO_FRAME_WORD1 0x000a0184 +#define FIFO_THRESHOLD 0xFE +#define BYTES_PER_WORD 0x4 +#define CH_STATUS_MAP_32KHZ 0x3 +#define CH_STATUS_MAP_44KHZ 0x0 +#define CH_STATUS_MAP_48KHZ 0x2 +#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 SET_BYTE0 0x000000FF +#define VALID_DIP_WORDS 3 +#define LAYOUT0 0 +#define LAYOUT1 1
+/*standard module options for ALSA. This module supports only one card*/ +int hdmi_card_index = SNDRV_DEFAULT_IDX1; +char *hdmi_card_id = SNDRV_DEFAULT_STR1;
+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.");
Drivers should expose the standard module options "index" and "id" like others. Also they should be static.
+/* hardware capability structure */ +static const struct snd_pcm_hardware snd_intel_hadstream = {
- .info = (SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_DOUBLE |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
You have to implement corresponding stuff if you mark the driver as RESUME-able.
SNDRV_PCM_INFO_MMAP|
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_SYNC_START),
Is sync stuff implemented?
- .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_64000 |
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_PERIOD_BYTES,
- .period_bytes_min = HAD_MIN_PERIOD_BYTES,
- .period_bytes_max = HAD_MAX_BUFFER,
- .periods_min = HAD_MIN_PERIODS,
- .periods_max = HAD_MAX_PERIODS,
- .fifo_size = HAD_FIFO_SIZE,
+};
+struct snd_intelhad *intelhaddata;
+/** +* 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;
- int retval;
- BUG_ON(!substream);
These BUG_ON() are superfluous. Better to remove.
- pr_debug("had: snd_intelhad_open called\n");
- intelhaddata = snd_pcm_substream_chip(substream);
So... you assign the global variable at this open callback? Any race?
- runtime = substream->runtime;
- /* set the runtime hw parameter with local snd_pcm_hardware struct */
- runtime->hw = snd_intel_hadstream;
- stream = kzalloc(sizeof(*stream), GFP_KERNEL);
- if (!stream)
return -ENOMEM;
- stream->stream_status = STREAM_INIT;
- runtime->private_data = stream;
- intelhaddata->reg_ops->hdmi_audio_write_register(AUD_HDMI_STATUS, 0);
- retval = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
stream is leaked when the error returned here.
- 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: calling period elapsed\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);
- return;
+}
+/** +* 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("had: 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 had_stream_pvt *stream;
- BUG_ON(!substream);
- stream = substream->runtime->private_data;
- pr_debug("had: snd_intelhad_close called\n");
- intelhaddata = snd_pcm_substream_chip(substream);
- if (intelhaddata->stream_info.str_id) {
intelhaddata->playback_cnt--;
intelhaddata->stream_info.str_id = 0;
- }
- intelhaddata->stream_info.buffer_rendered = 0;
- intelhaddata->stream_info.buffer_ptr = 0;
- kfree(substream->runtime->private_data);
- 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)
+{
- int retval;
- struct snd_pcm_runtime *runtime;
- BUG_ON(!substream);
- BUG_ON(!hw_params);
- pr_debug("had: snd_intelhad_hw_params called\n");
- runtime = substream->runtime;
- retval = snd_pcm_lib_malloc_pages(substream,
params_buffer_bytes(hw_params));
- if (retval < 0)
return -ENOMEM;
- pr_debug("had: allocated memory = %d\n",
params_buffer_bytes(hw_params));
- memset(substream->runtime->dma_area, 0,
params_buffer_bytes(hw_params));
- pr_debug("had: snd_intelhad_hw_params exited\n");
- 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) +{
- BUG_ON(!substream);
- pr_debug("had: snd_intelhad_hw_free called\n");
- return snd_pcm_lib_free_pages(substream);
+}
+/** +* 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 i, caps, retval = 0;
- u32 regval = 0;
- struct snd_intelhad *intelhaddata;
- struct had_stream_pvt *stream;
- struct hdmi_audio_registers_ops *reg_ops;
- struct hdmi_audio_query_set_ops *query_ops;
- BUG_ON(!substream);
- intelhaddata = snd_pcm_substream_chip(substream);
- stream = substream->runtime->private_data;
- reg_ops = intelhaddata->reg_ops;
- query_ops = intelhaddata->query_ops;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
pr_debug("had: Trigger Start\n");
caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
retval = query_ops->hdmi_audio_set_caps(
HAD_SET_ENABLE_AUDIO_INT, &caps);
if (retval)
return retval;
retval = query_ops->hdmi_audio_set_caps(
HAD_SET_ENABLE_AUDIO, NULL);
if (retval)
return retval;
Don't you need to reset audio_caps at error?
retval = reg_ops->hdmi_audio_read_modify(AUD_CONFIG,
SET_BIT0, REG_BIT_0);
stream->substream = substream;
stream->stream_status = STREAM_RUNNING;
break;
- case SNDRV_PCM_TRIGGER_STOP:
pr_debug("had: Trigger Stop\n");
caps = HDMI_AUDIO_UNDERRUN | HDMI_AUDIO_BUFFER_DONE;
retval = query_ops->hdmi_audio_set_caps(
HAD_SET_DISABLE_AUDIO_INT, &caps);
if (retval)
return retval;
retval = query_ops->hdmi_audio_set_caps(
HAD_SET_DISABLE_AUDIO, NULL);
if (retval)
return retval;
stream->stream_status = STREAM_DROPPED;
break;
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pr_debug("had: Trigger Pause\n");
/* disable the validity bits */
for (i = 0; i < MAX_HW_BUFS; i++) {
retval = reg_ops->hdmi_audio_read_register(
AUD_BUF_A_ADDR+(i*REG_WIDTH), ®val);
if (retval)
return retval;
if (regval & REG_BIT_0) {
retval = reg_ops->hdmi_audio_read_modify(
AUD_BUF_A_ADDR+(i * REG_WIDTH),
0, REG_BIT_0);
if (retval)
return retval;
intelhaddata->buf_info[HAD_BUF_TYPE_A + i].
is_valid = true;
}
}
stream->stream_status = STREAM_PAUSED;
break;
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
pr_debug("had: Trigger Resume\n");
/* enable the validity bits */
for (i = 0; i < MAX_HW_BUFS; i++) {
if (intelhaddata->buf_info[HAD_BUF_TYPE_A + i].is_valid)
retval = reg_ops->hdmi_audio_read_modify(
AUD_BUF_A_ADDR + (i * REG_WIDTH),
SET_BIT0, REG_BIT_0);
}
stream->stream_status = STREAM_RUNNING;
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) +{
- struct had_stream_pvt *stream;
- int retval;
- u32 disp_samp_freq, n_param;
- struct snd_intelhad *intelhaddata;
- struct snd_pcm_runtime *runtime;
- pr_debug("had: pcm_prepare called\n");
- BUG_ON(!substream);
- runtime = substream->runtime;
- pr_debug("had:period_size=%d\n",
frames_to_bytes(runtime, runtime->period_size));
- pr_debug("had:periods=%d\n", runtime->periods);
- pr_debug("had:buffer_size=%d\n", snd_pcm_lib_buffer_bytes(substream));
- pr_debug("had:rate=%d\n", runtime->rate);
- pr_debug("had:channels=%d\n", runtime->channels);
- stream = substream->runtime->private_data;
- intelhaddata = snd_pcm_substream_chip(substream);
- if (intelhaddata->stream_info.str_id) {
pr_debug("had:_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;
- }
- intelhaddata->playback_cnt++;
- intelhaddata->stream_info.str_id = intelhaddata->playback_cnt;
Note that the prepare callback might be called multiple times before triggering. This counter doesn't work. If any, it should be counted in the open/close callback.
- snprintf(substream->pcm->id, sizeof(substream->pcm->id),
"%d", intelhaddata->stream_info.str_id);
- retval = snd_intelhad_init_stream(substream);
- if (retval)
goto prep_end;
- /* Get N value in KHz */
- retval = intelhaddata->query_ops->hdmi_audio_get_caps(
HAD_GET_SAMPLING_FREQ, &disp_samp_freq);
- if (retval) {
pr_debug("had: querying display sampling freq failed\n");
goto prep_end;
- } else
pr_debug("had: TMDS freq = %d kHz\n", disp_samp_freq);
- retval = snd_intelhad_prog_n(substream->runtime->rate, &n_param);
- if (retval) {
pr_debug("had: programming N value failed\n");
goto prep_end;
- }
- retval = snd_intelhad_prog_cts(
substream->runtime->rate/1000, disp_samp_freq, n_param);
- if (retval) {
pr_debug("had: programming CTS value failed\n");
goto prep_end;
- }
- retval = snd_intelhad_prog_DIP(substream);
- if (retval) {
pr_debug("had: programming DIP values failed\n");
goto prep_end;
- }
- retval = snd_intelhad_init_audio_ctrl(substream);
- if (retval) {
pr_debug("had: initializing audio controller regs failed\n");
goto prep_end;
- }
- retval = snd_intelhad_prog_ring_buf(substream) ;
- if (retval)
pr_debug("had: initializing ring buffer regs failed\n");
+prep_end:
- return retval;
+}
+/**
- snd_intelhad_pcm_pointer- to send the current buffer pointer processed 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 had_stream_pvt *stream;
- struct snd_intelhad *intelhaddata;
- u32 bytes_rendered;
- pr_debug("had: Called snd_intelhad_pcm_pointer\n");
- BUG_ON(!substream);
- intelhaddata = snd_pcm_substream_chip(substream);
- stream = substream->runtime->private_data;
- 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);
- pr_debug("had:pcm_pointer = %#x\n",
intelhaddata->stream_info.buffer_ptr);
- return intelhaddata->stream_info.buffer_ptr;
+}
+/*PCM operations structure and the calls back for the same */ +struct snd_pcm_ops snd_intelhad_playback_ops = {
Does it need to be global?
- .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,
+};
+/* +* 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;
- struct had_callback_ops ops_cb;
- pr_debug("had: init called\n");
- pr_info(KERN_INFO "INFO: ******** HAD DRIVER loading.. Ver: %s\n",
HAD_DRIVER_VERSION);
- /* allocate memory for saving internal context and working */
- intelhaddata = kzalloc(sizeof(*intelhaddata), GFP_KERNEL);
- if (!intelhaddata) {
pr_debug("had: mem alloc failed\n");
return -ENOMEM;
- }
- /* allocate memory for display driver api set */
- intelhaddata->reg_ops = kzalloc(
sizeof(struct hdmi_audio_registers_ops),
GFP_KERNEL);
- if (!intelhaddata->reg_ops) {
pr_debug("had: mem alloc failed\n");
retval = -ENOMEM;
goto free_context;
- }
- intelhaddata->query_ops = kzalloc(
sizeof(struct hdmi_audio_query_set_ops),
GFP_KERNEL);
- if (!intelhaddata->query_ops) {
pr_debug("had: mem alloc failed\n");
retval = -ENOMEM;
goto free_regops;
- }
Any reason to allocate extra two *_ops instead of just put these flat in the structure itself? I mean, struct intelhda { ... struct hdmi_audio_registers_ops reg_ops; struct hdmi_audio_query_set_ops query_ops; ... }
then you don't need to malloc them.
- ops_cb.intel_had_event_call_back = had_event_handler;
- /* registering with display driver to get access to display APIs */
- retval = intel_hdmi_audio_query_capabilities(
ops_cb.intel_had_event_call_back,
&intelhaddata->reg_ops,
&intelhaddata->query_ops);
- if (retval) {
pr_debug("had: registering with display driver failed\n");
goto free_allocs;
- }
- pr_debug("had:...init complete\n");
- return retval;
+free_allocs:
- kfree(intelhaddata->query_ops);
+free_regops:
- kfree(intelhaddata->reg_ops);
+free_context:
- kfree(intelhaddata);
- pr_err("had: driver init failed\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:had_exit called\n");
- if (intelhaddata) {
kfree(intelhaddata->query_ops);
kfree(intelhaddata->reg_ops);
kfree(intelhaddata);
- }
+} +late_initcall(alsa_card_intelhad_init); +module_exit(alsa_card_intelhad_exit);
Overall I feel uneasy about the handling of global variables. They should be cleaned up more...
thanks,
Takashi