/* sound/soc/samsung/compr.c * * ALSA SoC Audio Layer - Samsung Compress platform driver * * Copyright (c) 2014 Samsung Electronics Co. Ltd. * Yeongman Seo * Lee Tae Ho * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "compr.h" #include "./seiren/seiren.h" #ifdef CONFIG_SND_ESA_SA_EFFECT #include "esa_sa_effect.h" #endif static struct snd_compr_caps compr_cap = { .direction = SND_COMPRESS_PLAYBACK, .min_fragment_size = 4 * 1024, .max_fragment_size = 32 * 1024, .min_fragments = 1, .max_fragments = 5, .num_codecs = 2, .codecs[COMPR_MP3] = SND_AUDIOCODEC_MP3, .codecs[COMPR_AAC] = SND_AUDIOCODEC_AAC, }; struct audio_processor* compr_audio_processor_alloc(seiren_ops ops, void* priv) { struct audio_processor *ap; ap = kzalloc(sizeof(struct audio_processor), GFP_KERNEL); if (!ap) return NULL; ap->ops = ops; ap->priv = priv; ap->reg_ack = esa_compr_get_mem() + COMPR_ACK; return ap; } #ifdef AUDIO_PERF enum CHECK_TIMES { OPEN_T = 0x0, WRITE_T, POINTER_T, DRAIN_T, ISR_T, TOTAL_TIMES, }; #endif struct runtime_data { struct snd_compr_stream *cstream; struct snd_compr_caps *compr_cap; struct snd_compr_params codec_param; spinlock_t lock; int state; struct snd_soc_dai *cpu_dai; struct snd_soc_dai *codec_dai; struct snd_pcm_substream substream; struct snd_pcm_hw_params hw_params; uint32_t byte_offset; u64 copied_total; u64 received_total; u64 app_pointer; void *buffer; #ifdef AUDIO_PERF uint32_t start_time[TOTAL_TIMES]; uint32_t end_time[TOTAL_TIMES]; u64 total_time[TOTAL_TIMES]; #endif atomic_t start; atomic_t eos; atomic_t created; wait_queue_head_t flush_wait; wait_queue_head_t exit_wait; uint32_t stop_ack; uint32_t exit_ack; struct audio_processor* ap; }; int compr_dai_cmd(struct runtime_data *prtd, int cmd); static int compr_event_handler(uint32_t cmd, uint32_t size, void* priv) { struct runtime_data *prtd = priv; struct snd_compr_runtime *runtime = prtd->cstream->runtime; u64 bytes_available; int ret; pr_debug("%s: event handler cmd(%x)\n", __func__, cmd); #ifdef AUDIO_PERF prtd->start_time[ISR_T] = sched_clock(); #endif switch(cmd) { case INTR_CREATED: pr_debug("%s: offload instance is created\n", __func__); break; case INTR_DECODED: spin_lock(&prtd->lock); /* update copied total bytes */ prtd->copied_total += size; prtd->byte_offset += size; if (prtd->byte_offset >= runtime->buffer_size) prtd->byte_offset -= runtime->buffer_size; snd_compr_fragment_elapsed(prtd->cstream); if (!atomic_read(&prtd->start) && runtime->state != SNDRV_PCM_STATE_PAUSED) { /* Writes must be restarted from _copy() */ pr_err("%s: write_done received while not started(%d)", __func__, runtime->state); spin_unlock(&prtd->lock); return -EIO; } bytes_available = prtd->received_total - prtd->copied_total; pr_debug("%s: current free bufsize(%llu)\n", __func__, runtime->buffer_size - bytes_available); if (bytes_available < runtime->fragment_size) { pr_debug("%s: WRITE_DONE Insufficient data to send.(avail:%llu)\n", __func__, bytes_available); } spin_unlock(&prtd->lock); break; case INTR_FLUSH: prtd->stop_ack = 1; wake_up(&prtd->flush_wait); break; case INTR_PAUSED: ret = compr_dai_cmd(prtd, cmd); if (ret) pr_err("%s: compr_dai_cmd fail(%d)\n", __func__, ret); break; case INTR_EOS: if (atomic_read(&prtd->eos)) { if (prtd->copied_total != prtd->received_total) pr_err("%s: EOS is not sync!(%llu/%llu)\n", __func__, prtd->copied_total, prtd->received_total); /* ALSA Framework callback to notify drain complete */ snd_compr_drain_notify(prtd->cstream); atomic_set(&prtd->eos, 0); pr_info("%s: DATA_CMD_EOS wake up\n", __func__); } break; case INTR_DESTROY: prtd->exit_ack = 1; wake_up(&prtd->exit_wait); break; default: pr_err("%s: unknown command(%x)\n", __func__, cmd); break; } #ifdef AUDIO_PERF prtd->end_time[ISR_T] = sched_clock(); prtd->total_time[ISR_T] += prtd->end_time[ISR_T] - prtd->start_time[ISR_T]; #endif return 0; } static int compr_config_substream(struct snd_compr_stream *cstream, struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; int ret; pr_debug("%s\n", __func__); substream->pid = get_task_pid(current, PIDTYPE_PID); substream->private_data = rtd; substream->stream = (cstream->direction == SND_COMPRESS_PLAYBACK) ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; substream->runtime = kzalloc(sizeof(struct snd_pcm_runtime), GFP_KERNEL); if (substream->runtime == NULL) { ret = -ENOMEM; goto config_substream_runtime_err; } substream->runtime->hw_constraints.rules_num = 0; substream->runtime->hw_constraints.rules_all = 1; substream->runtime->hw_constraints.rules = kzalloc(sizeof(struct snd_pcm_hw_rule), GFP_KERNEL); if (substream->runtime->hw_constraints.rules == NULL) { ret = -ENOMEM; goto config_substream_runtime_rules_err; } return 0; config_substream_runtime_rules_err: kfree(substream->runtime); config_substream_runtime_err: return ret; } static int compr_dai_setup(struct runtime_data *prtd, struct snd_soc_pcm_runtime *rtd) { struct snd_pcm_substream *substream = &prtd->substream; struct snd_soc_dai *cpu_dai = prtd->cpu_dai; struct snd_soc_dai *codec_dai = prtd->codec_dai; const struct snd_soc_dai_ops *cpu_dai_ops = cpu_dai->driver->ops; const struct snd_soc_dai_ops *codec_dai_ops = codec_dai->driver->ops; struct snd_pcm_hw_params *params = &prtd->hw_params; int ret; if (cpu_dai_ops->startup) { ret = (*cpu_dai_ops->startup)(substream, cpu_dai); if (ret < 0) { dev_err(cpu_dai->dev, "can't open interface" " %s: %d\n", cpu_dai->name, ret); goto cpu_dai_err; } } if (codec_dai_ops->startup) { ret = (*codec_dai_ops->startup)(substream, codec_dai); if (ret < 0) { dev_err(codec_dai->dev, "can't open codec" " %s: %d\n", codec_dai->name, ret); goto codec_dai_err; } } if (rtd->dai_link->ops->hw_params) { ret = (*rtd->dai_link->ops->hw_params)(substream, params); if (ret < 0) { pr_err("%s: hw_params err(%d)\n", __func__, ret); goto hw_params_err; } } if (codec_dai_ops->hw_params) { ret = (*codec_dai_ops->hw_params)(substream, params, codec_dai); if (ret < 0) { dev_err(codec_dai->dev, "can't set %s hw params:" " %d\n", codec_dai->name, ret); goto codec_dai_hw_param_err; } } if (cpu_dai_ops->hw_params) { ret = (*cpu_dai_ops->hw_params)(substream, params, cpu_dai); if (ret < 0) { dev_err(cpu_dai->dev, "can't set %s hw params:" " %d\n", cpu_dai->name, ret); goto cpu_dai_hw_param_err; } } prtd->cpu_dai->rate = params_rate(params); prtd->codec_dai->rate = params_rate(params); return 0; cpu_dai_hw_param_err: if (cpu_dai_ops->hw_free) (*cpu_dai_ops->hw_free)(substream, prtd->cpu_dai); codec_dai_hw_param_err: if (codec_dai_ops->hw_free) (*codec_dai_ops->hw_free)(substream, prtd->codec_dai); hw_params_err: codec_dai_err: if (codec_dai_ops->shutdown) (*codec_dai_ops->shutdown)(substream, codec_dai); cpu_dai_err: if (cpu_dai_ops->shutdown) (*cpu_dai_ops->shutdown)(substream, cpu_dai); return ret; } int compr_dai_cmd(struct runtime_data *prtd, int cmd) { struct snd_pcm_substream *substream = &prtd->substream; struct snd_soc_dai *cpu_dai = prtd->cpu_dai; struct snd_soc_dai *codec_dai = prtd->codec_dai; const struct snd_soc_dai_ops *cpu_dai_ops = cpu_dai->driver->ops; const struct snd_soc_dai_ops *codec_dai_ops = codec_dai->driver->ops; int ret; if (codec_dai_ops->trigger) { ret = (*codec_dai_ops->trigger)(substream, cmd, codec_dai); if (ret < 0) { pr_err("%s: error codec_dai trigger(%d)\n", __func__, cmd); goto trigger_err; } } if (cpu_dai_ops->trigger) { ret = (*cpu_dai_ops->trigger)(substream, cmd, cpu_dai); if (ret < 0) { pr_err("%s: error cpu_dai trigger(%d)\n", __func__, cmd); goto trigger_err; } } return 0; trigger_err: return ret; } static int compr_dai_prepare(struct runtime_data *prtd) { struct snd_pcm_substream *substream = &prtd->substream; struct snd_soc_dai *cpu_dai = prtd->cpu_dai; struct snd_soc_dai *codec_dai = prtd->codec_dai; const struct snd_soc_dai_ops *cpu_dai_ops = cpu_dai->driver->ops; const struct snd_soc_dai_ops *codec_dai_ops = codec_dai->driver->ops; int ret; if (codec_dai_ops->prepare) { ret = (*codec_dai_ops->prepare)(substream, codec_dai); if (ret < 0) { dev_err(codec_dai->dev, "DAI prepare error: %d\n", ret); goto prepare_err; } } if (cpu_dai_ops->prepare) { ret = (*cpu_dai_ops->prepare)(substream, cpu_dai); if (ret < 0) { dev_err(codec_dai->dev, "DAI prepare error: %d\n", ret); goto prepare_err; } } return 0; prepare_err: return ret; } static void compr_config_hw_params(struct snd_pcm_hw_params *params, struct snd_compr_params *compr_params) { u64 fmt; int acodec_rate = 48000; pr_debug("%s\n", __func__); fmt = ffs(SNDRV_PCM_FMTBIT_S16_LE) - 1; snd_mask_set(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), fmt); hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min = 16; hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS)->min = 32; hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = 2; #ifdef CONFIG_SND_ESA_SA_EFFECT acodec_rate = esa_compr_get_sample_rate(); if (!acodec_rate) acodec_rate = 48000; #endif pr_info("%s input_SR %d PCM_HW_PARAM_RATE %d \n", __func__, compr_params->codec.sample_rate, acodec_rate); hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = acodec_rate; } static int compr_open(struct snd_compr_stream *cstream) { struct snd_compr_runtime *runtime = cstream->runtime; struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct runtime_data *prtd; struct snd_pcm_substream *substream; int ret; pr_debug("%s\n", __func__); prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL); if (prtd == NULL) return -ENOMEM; spin_lock_init(&prtd->lock); esa_compr_set_state(true); prtd->ap = compr_audio_processor_alloc((seiren_ops)compr_event_handler, prtd); if(!prtd->ap) { pr_err("%s: could not allocate memory\n", __func__); ret = -ENOMEM; goto compr_audio_processor_alloc_err; } #ifdef CONFIG_SND_ESA_SA_EFFECT aud_vol.ap[COMPR_DAI_MULTIMEDIA_1] = prtd->ap; #endif runtime->private_data = prtd; prtd->cpu_dai = rtd->cpu_dai; prtd->codec_dai = rtd->codec_dai; substream = &prtd->substream; ret = compr_config_substream(cstream, substream); if (ret) { pr_err("%s: could not config substream(%d)\n", __func__, ret); goto compr_audio_processor_alloc_err; } /* init runtime data */ prtd->cstream = cstream; prtd->byte_offset = 0; prtd->app_pointer = 0; prtd->copied_total = 0; prtd->received_total = 0; prtd->compr_cap = &compr_cap; prtd->ap->sample_rate = 44100; prtd->ap->num_channels = 3; /* stereo channel */ atomic_set(&prtd->eos, 0); atomic_set(&prtd->start, 0); atomic_set(&prtd->created, 0); init_waitqueue_head(&prtd->flush_wait); init_waitqueue_head(&prtd->exit_wait); #ifdef AUDIO_PERF prtd->start_time[OPEN_T] = sched_clock(); #endif esa_compr_open(); return 0; compr_audio_processor_alloc_err: kfree(prtd); esa_compr_set_state(false); return ret; } static int compr_free(struct snd_compr_stream *cstream) { struct snd_compr_runtime *runtime = cstream->runtime; struct runtime_data *prtd = runtime->private_data; struct snd_pcm_substream *substream; struct snd_soc_dai *cpu_dai; struct snd_soc_dai *codec_dai; const struct snd_soc_dai_ops *cpu_dai_ops; const struct snd_soc_dai_ops *codec_dai_ops; unsigned long flags; int ret; #ifdef AUDIO_PERF u64 playback_time, total_time = 0; int idx; #endif pr_debug("%s\n", __func__); if (!prtd) { pr_info("compress dai has already freed.\n"); return 0; } substream = &prtd->substream; cpu_dai = prtd->cpu_dai; codec_dai = prtd->codec_dai; cpu_dai_ops = cpu_dai->driver->ops; codec_dai_ops = codec_dai->driver->ops; if (atomic_read(&prtd->eos)) { /* ALSA Framework callback to notify drain complete */ snd_compr_drain_notify(cstream); atomic_set(&prtd->eos, 0); pr_debug("%s Call Drain notify to wakeup\n", __func__); } if (atomic_read(&prtd->created)) { spin_lock_irqsave(&prtd->lock, flags); atomic_set(&prtd->created, 0); prtd->exit_ack = 0; ret = esa_compr_send_cmd(CMD_COMPR_DESTROY, prtd->ap); if (ret) { esa_err("%s: can't send CMD_COMPR_DESTROY (%d)\n", __func__, ret); spin_unlock_irqrestore(&prtd->lock, flags); } else { spin_unlock_irqrestore(&prtd->lock, flags); ret = wait_event_interruptible_timeout(prtd->exit_wait, prtd->exit_ack, 1 * HZ); if (!ret) pr_err("%s: CMD_DESTROY timed out!!!\n", __func__); } } #ifdef CONFIG_SND_ESA_SA_EFFECT aud_vol.ap[COMPR_DAI_MULTIMEDIA_1] = NULL; #endif esa_compr_set_state(false); /* codec hw_free -> cpu hw_free -> cpu shutdown -> codec shutdown */ if (codec_dai_ops->hw_free) (*codec_dai_ops->hw_free)(substream, codec_dai); if (cpu_dai_ops->hw_free) (*cpu_dai_ops->hw_free)(substream, cpu_dai); if (cpu_dai_ops->shutdown) (*cpu_dai_ops->shutdown)(substream, cpu_dai); if (codec_dai_ops->shutdown) (*codec_dai_ops->shutdown)(substream, codec_dai); if (substream->runtime) kfree(substream->runtime->hw_constraints.rules); kfree(substream->runtime); esa_compr_close(); #ifdef AUDIO_PERF prtd->end_time[OPEN_T] = sched_clock(); playback_time = prtd->end_time[OPEN_T] - prtd->start_time[OPEN_T]; for (idx = 0; idx < TOTAL_TIMES; idx++) { total_time += prtd->total_time[idx]; } pr_debug("%s: measure the audio waken time : %llu\n", __func__, total_time); pr_debug("%s: may be the ap sleep time : (%llu/%llu)\n", __func__, playback_time - total_time, playback_time); #endif kfree(prtd->ap); kfree(prtd); return 0; } static int compr_set_params(struct snd_compr_stream *cstream, struct snd_compr_params *params) { struct snd_compr_runtime *runtime = cstream->runtime; struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct runtime_data *prtd = runtime->private_data; unsigned long flags; int ret; pr_debug("%s\n", __func__); compr_config_hw_params(&prtd->hw_params, params); pr_debug("%s, cpu_dai name = %s\n", __func__, rtd->cpu_dai->name); /* startup -> hw_params */ ret = compr_dai_setup(prtd, rtd); if (ret) { pr_err("%s: could not setup compr_dai(%d)\n", __func__, ret); return -ENXIO; } ret = compr_dai_prepare(prtd); if (ret) { pr_err("%s: compr_dai_prepare() fail(%d)\n", __func__, ret); return -ENXIO; } /* COMPR set_params */ memcpy(&prtd->codec_param, params, sizeof(struct snd_compr_params)); prtd->byte_offset = 0; prtd->app_pointer = 0; prtd->copied_total = 0; prtd->ap->buffer_size = runtime->fragments * runtime->fragment_size; prtd->ap->num_channels = prtd->codec_param.codec.ch_in; prtd->ap->sample_rate = prtd->codec_param.codec.sample_rate; if (prtd->ap->sample_rate == 0 || prtd->ap->num_channels == 0) { pr_err("%s: invalid parameters: sample(%ld), ch(%ld)\n", __func__, prtd->ap->sample_rate, prtd->ap->num_channels); return -EINVAL; } switch (prtd->codec_param.codec.id) { case SND_AUDIOCODEC_MP3: prtd->ap->codec_id = COMPR_MP3; break; case SND_AUDIOCODEC_AAC: prtd->ap->codec_id = COMPR_AAC; break; default: pr_err("%s: unknown codec id %d\n", __func__, prtd->codec_param.codec.id); break; } ret = esa_compr_set_param(prtd->ap, (uint8_t**)&prtd->buffer); if (ret) { pr_err("%s: esa_compr_set_param fail(%d)\n", __func__, ret); return ret; } spin_lock_irqsave(&prtd->lock, flags); atomic_set(&prtd->created, 1); spin_unlock_irqrestore(&prtd->lock, flags); pr_info("%s: sample rate:%ld, channels:%ld\n", __func__, prtd->ap->sample_rate, prtd->ap->num_channels); return 0; } static int compr_set_metadata(struct snd_compr_stream *cstream, struct snd_compr_metadata *metadata) { pr_debug("%s\n", __func__); if (!metadata || !cstream) return -EINVAL; if (metadata->key == SNDRV_COMPRESS_ENCODER_PADDING) { pr_debug("%s, got encoder padding %u", __func__, metadata->value[0]); } else if (metadata->key == SNDRV_COMPRESS_ENCODER_DELAY) { pr_debug("%s, got encoder delay %u", __func__, metadata->value[0]); } return 0; } static int compr_trigger(struct snd_compr_stream *cstream, int cmd) { struct snd_compr_runtime *runtime = cstream->runtime; struct runtime_data *prtd = runtime->private_data; unsigned long flags; int ret; pr_debug("%s: trigger cmd(%d)\n", __func__, cmd); /* platform -> codec -> cpu */ if (cstream->direction != SND_COMPRESS_PLAYBACK) { pr_err("%s: Unsupported stream type\n", __func__); return -EINVAL; } switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_PUSH: pr_info("%s: SNDRV_PCM_TRIGGER_PAUSE_PUSH\n", __func__); spin_lock_irqsave(&prtd->lock, flags); ret = esa_compr_send_cmd(CMD_COMPR_PAUSE, prtd->ap); if (ret) { pr_err("%s: pause cmd failed(%d)\n", __func__, ret); spin_unlock_irqrestore(&prtd->lock, flags); return ret; } spin_unlock_irqrestore(&prtd->lock, flags); atomic_set(&prtd->start, 0); break; case SNDRV_PCM_TRIGGER_STOP: pr_info("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); spin_lock_irqsave(&prtd->lock, flags); if (atomic_read(&prtd->eos)) { /* ALSA Framework callback to notify drain complete */ snd_compr_drain_notify(cstream); atomic_set(&prtd->eos, 0); pr_debug("%s: interrupt drain and eos wait queues", __func__); } pr_debug("CMD_STOP\n"); prtd->stop_ack = 0; ret = esa_compr_send_cmd(CMD_COMPR_STOP, prtd->ap); if (ret) { pr_err("%s: stop cmd failed (%d)\n", __func__, ret); spin_unlock_irqrestore(&prtd->lock, flags); return ret; } spin_unlock_irqrestore(&prtd->lock, flags); ret = wait_event_interruptible_timeout(prtd->flush_wait, prtd->stop_ack, 1 * HZ); if (!ret) { pr_err("CMD_STOP cmd timeout(%d)\n", ret); ret = -ETIMEDOUT; } else ret = 0; ret = compr_dai_cmd(prtd, cmd); if (ret) { pr_err("%s: compr_dai_cmd fail(%d)\n", __func__, ret); return ret; } atomic_set(&prtd->start, 0); /* reset */ prtd->stop_ack = 0; prtd->byte_offset = 0; prtd->app_pointer = 0; prtd->copied_total = 0; prtd->received_total = 0; break; case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (SNDRV_PCM_TRIGGER_START == cmd) pr_info("%s: SNDRV_PCM_TRIGGER_START\n", __func__); else if (SNDRV_PCM_TRIGGER_PAUSE_RELEASE == cmd) pr_info("%s: SNDRV_PCM_TRIGGER_PAUSE_RELEASE\n", __func__); ret = compr_dai_cmd(prtd, cmd); if (ret) { pr_err("%s: compr_dai_cmd fail(%d)\n", __func__, ret); return ret; } atomic_set(&prtd->start, 1); spin_lock_irqsave(&prtd->lock, flags); ret = esa_compr_send_cmd(CMD_COMPR_START, prtd->ap); if (ret) { pr_err("%s: start cmd failed\n", __func__); spin_unlock_irqrestore(&prtd->lock, flags); return ret; } spin_unlock_irqrestore(&prtd->lock, flags); break; case SND_COMPR_TRIGGER_NEXT_TRACK: pr_info("%s: SND_COMPR_TRIGGER_NEXT_TRACK\n", __func__); break; case SND_COMPR_TRIGGER_PARTIAL_DRAIN: pr_info("%s: SND_COMPR_TRIGGER_PARTIAL_DRAIN\n", __func__); case SND_COMPR_TRIGGER_DRAIN: if (SND_COMPR_TRIGGER_DRAIN == cmd) pr_info("%s: SND_COMPR_TRIGGER_DRAIN\n", __func__); /* Make sure all the data is sent to F/W before sending EOS */ spin_lock_irqsave(&prtd->lock, flags); #ifdef AUDIO_PERF prtd->start_time[DRAIN_T] = sched_clock(); #endif if (!atomic_read(&prtd->start)) { pr_err("%s: stream is not in started state\n", __func__); ret = -EPERM; spin_unlock_irqrestore(&prtd->lock, flags); break; } atomic_set(&prtd->eos, 1); pr_debug("%s: CMD_EOS\n", __func__); ret = esa_compr_send_cmd(CMD_COMPR_EOS, prtd->ap); if (ret) { pr_err("%s: can't send eos (%d)\n", __func__, ret); spin_unlock_irqrestore(&prtd->lock, flags); return ret; } spin_unlock_irqrestore(&prtd->lock, flags); #ifdef AUDIO_PERF prtd->end_time[DRAIN_T] = sched_clock(); prtd->total_time[DRAIN_T] += prtd->end_time[DRAIN_T] - prtd->start_time[DRAIN_T]; #endif pr_info("%s: Out of %s Drain", __func__, (cmd == SND_COMPR_TRIGGER_DRAIN ? "Full" : "Partial")); break; default: break; } return 0; } static int compr_pointer(struct snd_compr_stream *cstream, struct snd_compr_tstamp *tstamp) { struct snd_compr_runtime *runtime = cstream->runtime; struct runtime_data *prtd = runtime->private_data; struct snd_compr_tstamp timestamp; unsigned long flags; int pcm_size, bytes_available; int num_channel; pr_debug("%s\n", __func__); #ifdef AUDIO_PERF prtd->start_time[POINTER_T] = sched_clock(); #endif memset(×tamp, 0x0, sizeof(struct snd_compr_tstamp)); spin_lock_irqsave(&prtd->lock, flags); timestamp.sampling_rate = prtd->ap->sample_rate; timestamp.byte_offset = prtd->byte_offset; timestamp.copied_total = prtd->copied_total; pcm_size = esa_compr_pcm_size(); /* set the number of channels */ if (prtd->ap->num_channels == 1 || prtd->ap->num_channels == 2) num_channel = 1; else if (prtd->ap->num_channels == 3) num_channel = 2; else num_channel = 2; spin_unlock_irqrestore(&prtd->lock, flags); if (pcm_size) { bytes_available = prtd->received_total - prtd->copied_total; timestamp.pcm_io_frames = (snd_pcm_uframes_t)div64_u64(pcm_size, 2 * num_channel); pr_debug("%s: pcm_size(%u), frame_count(%u), copied_total(%llu), \ free_size(%llu)\n", __func__, pcm_size, timestamp.pcm_io_frames, prtd->copied_total, runtime->buffer_size - bytes_available); } memcpy(tstamp, ×tamp, sizeof(struct snd_compr_tstamp)); #ifdef AUDIO_PERF prtd->end_time[POINTER_T] = sched_clock(); prtd->total_time[POINTER_T] += prtd->end_time[POINTER_T] - prtd->start_time[POINTER_T]; #endif return 0; } static int compr_copy(struct snd_compr_stream *cstream, char __user* buf, size_t bytes) { struct snd_compr_runtime *runtime = cstream->runtime; struct runtime_data *prtd = runtime->private_data; u64 bytes_available; unsigned long flags; unsigned long copy; void *dstn; int ret; pr_debug("%s\n", __func__); #ifdef AUDIO_PERF prtd->start_time[WRITE_T] = sched_clock(); #endif if (!prtd->buffer) { pr_err("%s: Buffer is not allocated yet ??", __func__); return -ENOMEM; } /* check the free area */ if (bytes <= 0) { pr_err("%s: Buffer size is zero(%ld)\n", __func__, bytes); return 0; } pr_debug("copying %ld at %lld\n", (unsigned long)bytes, prtd->app_pointer); dstn = prtd->buffer + prtd->app_pointer; if (bytes < runtime->buffer_size - prtd->app_pointer) { if (copy_from_user(dstn, buf, bytes)) return -EFAULT; prtd->app_pointer += bytes; } else { copy = runtime->buffer_size - prtd->app_pointer; if (copy_from_user(dstn, buf, copy)) return -EFAULT; if (copy_from_user(prtd->buffer, buf + copy, bytes - copy)) return -EFAULT; prtd->app_pointer = bytes - copy; } /* * since the available bytes fits fragment_size, copy the data right away */ spin_lock_irqsave(&prtd->lock, flags); prtd->received_total += bytes; bytes_available = prtd->received_total - prtd->copied_total; spin_unlock_irqrestore(&prtd->lock, flags); pr_debug("%s: bytes_received(%llu), free_size(%llu)\n", __func__, prtd->received_total, runtime->buffer_size - bytes_available); /* get the bytes to write */ if (bytes_available > 0) { //TODO: issue: unknown mp3 fragment should be checked #if 0 u64 pointer = div64_u64(prtd->copied_total, runtime->buffer_size); pointer = prtd->copied_total - (pointer * runtime->buffer_size); pr_info("%s: bytes to write offset in buffer(%d/%llu)\n", __func__, prtd->byte_offset, prtd->app_pointer); pr_info("%s: [%2llx][%2llx][%2llx][%2llx] (%d)\n", __func__, (u64)(((char*)prtd->buffer)[pointer]), (u64)(((char*)prtd->buffer)[pointer + 1]), (u64)(((char*)prtd->buffer)[pointer + 2]), (u64)(((char*)prtd->buffer)[pointer + 3]), bytes); #endif pr_debug("%s: needs to be copied to the buffer = %llu\n", __func__, bytes_available); ret = esa_compr_send_buffer(bytes, prtd->ap); if (ret) { pr_err("%s: can't send buffer %ld bytes (%d)", __func__, bytes, ret); return -EFAULT; } } #ifdef AUDIO_PERF prtd->end_time[WRITE_T] = sched_clock(); prtd->total_time[WRITE_T] += prtd->end_time[WRITE_T] - prtd->start_time[WRITE_T]; #endif return bytes; } static int compr_get_caps(struct snd_compr_stream *cstream, struct snd_compr_caps *caps) { struct snd_compr_runtime *runtime = cstream->runtime; struct runtime_data *prtd = runtime->private_data; pr_debug("%s\n", __func__); memcpy(caps, prtd->compr_cap, sizeof(struct snd_compr_caps)); return 0; } static int compr_get_codec_caps(struct snd_compr_stream *cstream, struct snd_compr_codec_caps *codec) { pr_debug("%s\n", __func__); return 0; } static struct snd_compr_ops compr_ops = { .open = compr_open, .free = compr_free, .set_params = compr_set_params, .set_metadata = compr_set_metadata, .trigger = compr_trigger, .pointer = compr_pointer, .copy = compr_copy, .get_caps = compr_get_caps, .get_codec_caps = compr_get_codec_caps, }; static struct snd_soc_platform_driver samsung_compr_platform = { .compr_ops = &compr_ops, }; int asoc_compr_platform_register(struct device *dev) { return snd_soc_register_platform(dev, &samsung_compr_platform); } EXPORT_SYMBOL_GPL(asoc_compr_platform_register); void asoc_compr_platform_unregister(struct device *dev) { snd_soc_unregister_platform(dev); } EXPORT_SYMBOL_GPL(asoc_compr_platform_unregister); MODULE_AUTHOR("Yeongman Seo, "); MODULE_AUTHOR("Taeho Lee "); MODULE_DESCRIPTION("Samsung ASoC Compress Driver"); MODULE_LICENSE("GPL");