At Mon, 22 Nov 2010 19:09:44 +0530, ramesh.babu@xxxxxxxxx wrote: > > From: Ramesh Babu K V <ramesh.babu@xxxxxxxxx> > > 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@xxxxxxxxx> > Signed-off-by: Sailaja Bandarupalli <sailaja.bandarupalli@xxxxxxxxx> > --- > .../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@xxxxxxxxx> > + * Ramesh Babu K V <ramesh.babu@xxxxxxxxx> > + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > + * > + * 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@xxxxxxxxx>"); > +MODULE_AUTHOR("Ramesh Babu K V <ramesh.babu@xxxxxxxxx>"); > +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 _______________________________________________ Alsa-devel mailing list Alsa-devel@xxxxxxxxxxxxxxxx http://mailman.alsa-project.org/mailman/listinfo/alsa-devel