/* Copyright (c) 2013, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "q6voice.h" #define HPCM_MAX_Q_LEN 2 #define HPCM_MIN_VOC_PKT_SIZE 320 #define HPCM_MAX_VOC_PKT_SIZE 640 #define VHPCM_BLOCK_SIZE 4096 #define CACHE_ALIGNMENT_SIZE 128 #define CACHE_ALIGNMENT_MASK 0xFFFFFF80 #define VOICE_TX_CAPTURE_DAI_ID "CS-VOICE HOST TX CAPTURE" #define VOICE_TX_PLAYBACK_DAI_ID "CS-VOICE HOST TX PLAYBACK" #define VOICE_RX_CAPTURE_DAI_ID "CS-VOICE HOST RX CAPTURE" #define VOICE_RX_PLAYBACK_DAI_ID "CS-VOICE HOST RX PLAYBACK" #define VOLTE_TX_CAPTURE_DAI_ID "VOLTE HOST TX CAPTURE" #define VOLTE_TX_PLAYBACK_DAI_ID "VOLTE HOST TX PLAYBACK" #define VOLTE_RX_CAPTURE_DAI_ID "VOLTE HOST RX CAPTURE" #define VOLTE_RX_PLAYBACK_DAI_ID "VOLTE HOST RX PLAYBACK" enum { RX = 1, TX, }; enum { VOICE_INDEX = 0, VOLTE_INDEX, MAX_SESSION }; enum hpcm_state { HPCM_STOPPED = 1, HPCM_CLOSED, HPCM_PREPARED, HPCM_STARTED, }; struct hpcm_frame { uint32_t len; uint8_t voc_pkt[HPCM_MAX_VOC_PKT_SIZE]; }; struct hpcm_buf_node { struct list_head list; struct hpcm_frame frame; }; struct vocpcm_ion_buffer { /* Physical address */ uint32_t paddr; /* Kernel virtual address */ uint32_t kvaddr; }; struct dai_data { enum hpcm_state state; struct snd_pcm_substream *substream; struct list_head filled_queue; struct list_head free_queue; wait_queue_head_t queue_wait; spinlock_t dsp_lock; uint32_t pcm_size; uint32_t pcm_count; /* IRQ position */ uint32_t pcm_irq_pos; /* Position in buffer */ uint32_t pcm_buf_pos; struct vocpcm_ion_buffer vocpcm_ion_buffer; }; struct tap_point { struct dai_data playback_dai_data; struct dai_data capture_dai_data; }; struct session { struct tap_point tx_tap_point; struct tap_point rx_tap_point; uint32_t sess_paddr; uint32_t sess_kvaddr; struct ion_handle *ion_handle; struct mem_map_table tp_mem_table; }; struct tappnt_mxr_data { bool enable; uint16_t direction; uint16_t sample_rate; }; /* Values from mixer ctl are cached in this structure */ struct mixer_conf { int8_t sess_indx; struct tappnt_mxr_data rx; struct tappnt_mxr_data tx; }; struct start_cmd { struct vss_ivpcm_tap_point tap_pnt[2]; uint32_t no_of_tapoints; }; struct hpcm_drv { struct mutex lock; struct session session[MAX_SESSION]; struct mixer_conf mixer_conf; struct ion_client *ion_client; struct start_cmd start_cmd; }; static struct hpcm_drv hpcm_drv; static struct snd_pcm_hardware msm_pcm_hardware = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED), .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_SPECIAL, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, .rate_min = 8000, .rate_max = 16000, .channels_min = 1, .channels_max = 1, .buffer_bytes_max = sizeof(struct hpcm_buf_node) * HPCM_MAX_Q_LEN, .period_bytes_min = HPCM_MIN_VOC_PKT_SIZE, .period_bytes_max = HPCM_MAX_VOC_PKT_SIZE, .periods_min = HPCM_MAX_Q_LEN, .periods_max = HPCM_MAX_Q_LEN, .fifo_size = 0, }; static char *hpcm_get_sess_name(int sess_indx) { char *sess_name = NULL; if (sess_indx == VOICE_INDEX) sess_name = VOICE_SESSION_NAME; else if (sess_indx == VOLTE_INDEX) sess_name = VOLTE_SESSION_NAME; else pr_err("%s:, Invalid sess_index\n", __func__); return sess_name; } static void hpcm_reset_mixer_config(struct hpcm_drv *prtd) { prtd->mixer_conf.sess_indx = -1; prtd->mixer_conf.rx.enable = false; prtd->mixer_conf.rx.direction = -1; prtd->mixer_conf.rx.sample_rate = 0; prtd->mixer_conf.tx.enable = false; prtd->mixer_conf.tx.direction = -1; prtd->mixer_conf.tx.sample_rate = 0; } /* Check for valid mixer control values */ static bool hpcm_is_valid_config(int sess_indx, int tap_point, uint16_t direction, uint16_t samplerate) { if (sess_indx < VOICE_INDEX || sess_indx > VOLTE_INDEX) { pr_err("%s: invalid sess_indx :%d\n", __func__, sess_indx); goto error; } if (samplerate != VSS_IVPCM_SAMPLING_RATE_8K && samplerate != VSS_IVPCM_SAMPLING_RATE_16K) { pr_err("%s: invalid sample rate :%d\n", __func__, samplerate); goto error; } if ((tap_point != RX) && (tap_point != TX)) { pr_err("%s: invalid tappoint :%d\n", __func__, tap_point); goto error; } if ((direction != VSS_IVPCM_TAP_POINT_DIR_IN) && (direction != VSS_IVPCM_TAP_POINT_DIR_OUT) && (direction != VSS_IVPCM_TAP_POINT_DIR_OUT_IN)) { pr_err("%s: invalid direction :%d\n", __func__, direction); goto error; } return true; error: return false; } static struct dai_data *hpcm_get_dai_data(char *pcm_id, struct hpcm_drv *prtd) { struct dai_data *dai_data = NULL; size_t size = 0; if (pcm_id) { size = strlen(pcm_id); /* Check for Voice DAI */ if (strnstr(pcm_id, VOICE_TX_CAPTURE_DAI_ID, size)) { dai_data = &prtd->session[VOICE_INDEX].tx_tap_point.capture_dai_data; } else if (strnstr(pcm_id, VOICE_TX_PLAYBACK_DAI_ID, size)) { dai_data = &prtd->session[VOICE_INDEX].tx_tap_point.playback_dai_data; } else if (strnstr(pcm_id, VOICE_RX_CAPTURE_DAI_ID, size)) { dai_data = &prtd->session[VOICE_INDEX].rx_tap_point.capture_dai_data; } else if (strnstr(pcm_id, VOICE_RX_PLAYBACK_DAI_ID, size)) { dai_data = &prtd->session[VOICE_INDEX].rx_tap_point.playback_dai_data; /* Check for VoLTE DAI */ } else if (strnstr(pcm_id, VOLTE_TX_CAPTURE_DAI_ID, size)) { dai_data = &prtd->session[VOLTE_INDEX].tx_tap_point.capture_dai_data; } else if (strnstr(pcm_id, VOLTE_TX_PLAYBACK_DAI_ID, size)) { dai_data = &prtd->session[VOLTE_INDEX].tx_tap_point.playback_dai_data; } else if (strnstr(pcm_id, VOLTE_RX_CAPTURE_DAI_ID, size)) { dai_data = &prtd->session[VOLTE_INDEX].rx_tap_point.capture_dai_data; } else if (strnstr(pcm_id, VOLTE_RX_PLAYBACK_DAI_ID, size)) { dai_data = &prtd->session[VOLTE_INDEX].rx_tap_point.playback_dai_data; } else { pr_err("%s: Wrong dai id\n", __func__); } } return dai_data; } static struct tap_point *hpcm_get_tappoint_data(char *pcm_id, struct hpcm_drv *prtd) { struct tap_point *tp = NULL; size_t size = 0; if (pcm_id) { size = strlen(pcm_id); /* Check for Voice DAI */ if (strnstr(pcm_id, VOICE_TX_CAPTURE_DAI_ID, size)) { tp = &prtd->session[VOICE_INDEX].tx_tap_point; } else if (strnstr(pcm_id, VOICE_TX_PLAYBACK_DAI_ID, size)) { tp = &prtd->session[VOICE_INDEX].tx_tap_point; } else if (strnstr(pcm_id, VOICE_RX_CAPTURE_DAI_ID, size)) { tp = &prtd->session[VOICE_INDEX].rx_tap_point; } else if (strnstr(pcm_id, VOICE_RX_PLAYBACK_DAI_ID, size)) { tp = &prtd->session[VOICE_INDEX].rx_tap_point; /* Check for VoLTE DAI */ } else if (strnstr(pcm_id, VOLTE_TX_CAPTURE_DAI_ID, size)) { tp = &prtd->session[VOLTE_INDEX].tx_tap_point; } else if (strnstr(pcm_id, VOLTE_TX_PLAYBACK_DAI_ID, size)) { tp = &prtd->session[VOLTE_INDEX].tx_tap_point; } else if (strnstr(pcm_id, VOLTE_RX_CAPTURE_DAI_ID, size)) { tp = &prtd->session[VOLTE_INDEX].rx_tap_point; } else if (strnstr(pcm_id, VOLTE_RX_PLAYBACK_DAI_ID, size)) { tp = &prtd->session[VOLTE_INDEX].rx_tap_point; } else { pr_err("%s: wrong dai id\n", __func__); } } return tp; } static struct tappnt_mxr_data *hpcm_get_tappnt_mixer_data(char *pcm_id, struct hpcm_drv *prtd) { if (strnstr(pcm_id, VOICE_TX_CAPTURE_DAI_ID, strlen(pcm_id)) || strnstr(pcm_id, VOICE_TX_PLAYBACK_DAI_ID, strlen(pcm_id)) || strnstr(pcm_id, VOLTE_TX_CAPTURE_DAI_ID, strlen(pcm_id)) || strnstr(pcm_id, VOLTE_TX_PLAYBACK_DAI_ID, strlen(pcm_id))) { return &prtd->mixer_conf.tx; } else { return &prtd->mixer_conf.rx; } } static int get_tappnt_value(char *pcm_id) { if (strnstr(pcm_id, VOICE_TX_CAPTURE_DAI_ID, strlen(pcm_id)) || strnstr(pcm_id, VOICE_TX_PLAYBACK_DAI_ID, strlen(pcm_id)) || strnstr(pcm_id, VOLTE_TX_CAPTURE_DAI_ID, strlen(pcm_id)) || strnstr(pcm_id, VOLTE_TX_PLAYBACK_DAI_ID, strlen(pcm_id))) { return TX; } else { return RX; } } static bool hpcm_all_dais_are_ready(uint16_t direction, struct tap_point *tp, enum hpcm_state state) { bool dais_started = false; /* * Based on the direction set per tap point in the mixer control, * all the dais per tap point should meet the required state for the * commands such as vpcm_map_memory/vpcm_start to be executed. */ switch (direction) { case VSS_IVPCM_TAP_POINT_DIR_OUT_IN: if ((tp->playback_dai_data.state >= state) && (tp->capture_dai_data.state >= state)) { dais_started = true; } break; case VSS_IVPCM_TAP_POINT_DIR_IN: if (tp->playback_dai_data.state >= state) dais_started = true; break; case VSS_IVPCM_TAP_POINT_DIR_OUT: if (tp->capture_dai_data.state >= state) dais_started = true; break; default: pr_err("invalid direction\n"); } return dais_started; } static void hpcm_create_free_queue(struct snd_dma_buffer *dma_buf, struct dai_data *dai_data) { struct hpcm_buf_node *buf_node = NULL; int i = 0, offset = 0; for (i = 0; i < HPCM_MAX_Q_LEN; i++) { buf_node = (void *)dma_buf->area + offset; list_add_tail(&buf_node->list, &dai_data->free_queue); offset = offset + sizeof(struct hpcm_buf_node); } } static void hpcm_free_allocated_mem(struct hpcm_drv *prtd) { uint32_t paddr = 0; struct tap_point *txtp = NULL; struct tap_point *rxtp = NULL; struct session *sess = NULL; sess = &prtd->session[prtd->mixer_conf.sess_indx]; txtp = &sess->tx_tap_point; rxtp = &sess->rx_tap_point; paddr = sess->sess_paddr; if (paddr) { msm_audio_ion_free(prtd->ion_client, sess->ion_handle); msm_audio_ion_free(sess->tp_mem_table.client, sess->tp_mem_table.handle); sess->sess_paddr = 0; sess->sess_kvaddr = 0; sess->ion_handle = 0; prtd->ion_client = 0; sess->tp_mem_table.client = 0; sess->tp_mem_table.handle = 0; txtp->capture_dai_data.vocpcm_ion_buffer.paddr = 0; txtp->capture_dai_data.vocpcm_ion_buffer.kvaddr = 0; txtp->playback_dai_data.vocpcm_ion_buffer.paddr = 0; txtp->playback_dai_data.vocpcm_ion_buffer.kvaddr = 0; rxtp->capture_dai_data.vocpcm_ion_buffer.paddr = 0; rxtp->capture_dai_data.vocpcm_ion_buffer.kvaddr = 0; rxtp->playback_dai_data.vocpcm_ion_buffer.paddr = 0; rxtp->playback_dai_data.vocpcm_ion_buffer.kvaddr = 0; } else { pr_debug("%s, paddr = 0, nothing to free\n", __func__); } } static void hpcm_unmap_and_free_shared_memory(struct hpcm_drv *prtd) { unsigned long paddr = 0; char *sess_name = hpcm_get_sess_name(prtd->mixer_conf.sess_indx); if (prtd->mixer_conf.sess_indx >= 0) paddr = prtd->session[prtd->mixer_conf.sess_indx].sess_paddr; else paddr = 0; if (paddr) { voc_send_cvp_unmap_vocpcm_memory(voc_get_session_id(sess_name)); hpcm_free_allocated_mem(prtd); } else { pr_debug("%s, paddr = 0, nothing to unmap/free\n", __func__); } } static int hpcm_map_vocpcm_memory(struct hpcm_drv *prtd) { int ret = 0; char *sess_name = hpcm_get_sess_name(prtd->mixer_conf.sess_indx); struct session *sess = NULL; sess = &prtd->session[prtd->mixer_conf.sess_indx]; ret = voc_send_cvp_map_vocpcm_memory(voc_get_session_id(sess_name), &sess->tp_mem_table, sess->sess_paddr, VHPCM_BLOCK_SIZE); return ret; } static int hpcm_allocate_shared_memory(struct hpcm_drv *prtd) { int result; int ret = 0; int mem_len; int len; struct tap_point *txtp = NULL; struct tap_point *rxtp = NULL; struct session *sess = NULL; sess = &prtd->session[prtd->mixer_conf.sess_indx]; txtp = &sess->tx_tap_point; rxtp = &sess->rx_tap_point; result = msm_audio_ion_alloc("host_pcm_buffer", &prtd->ion_client, &sess->ion_handle, VHPCM_BLOCK_SIZE, (ion_phys_addr_t *) &sess->sess_paddr, (size_t *) &mem_len, (void *) &sess->sess_kvaddr); if (result) { pr_err("%s: msm_audio_ion_alloc error, rc = %d\n", __func__, result); sess->sess_paddr = 0; sess->sess_kvaddr = 0; ret = -ENOMEM; goto done; } pr_debug("%s: Host PCM memory block allocated\n", __func__); /* Allocate mem_map_table for tap point */ result = msm_audio_ion_alloc("host_pcm_table", &sess->tp_mem_table.client, &sess->tp_mem_table.handle, sizeof(struct vss_imemory_table_t), (ion_phys_addr_t *) &sess->tp_mem_table.phys, (size_t *) &len, &sess->tp_mem_table.data); if (result) { pr_err("%s: msm_audio_ion_alloc error, rc = %d\n", __func__, result); msm_audio_ion_free(prtd->ion_client, sess->ion_handle); sess->sess_paddr = 0; sess->sess_kvaddr = 0; ret = -ENOMEM; goto done; } pr_debug("%s: Host PCM memory table allocated\n", __func__); memset(sess->tp_mem_table.data, 0, sizeof(struct vss_imemory_table_t)); sess->tp_mem_table.size = sizeof(struct vss_imemory_table_t); pr_debug("%s: data 0x%x phys 0x%x\n", __func__, (unsigned int) sess->tp_mem_table.data, sess->tp_mem_table.phys); /* Split 4096 block into four 1024 byte blocks for each dai */ txtp->capture_dai_data.vocpcm_ion_buffer.paddr = (uint32_t) sess->sess_paddr; txtp->capture_dai_data.vocpcm_ion_buffer.kvaddr = (uint32_t) (uint8_t *) sess->sess_kvaddr; txtp->playback_dai_data.vocpcm_ion_buffer.paddr = (uint32_t) sess->sess_paddr + VHPCM_BLOCK_SIZE/4; txtp->playback_dai_data.vocpcm_ion_buffer.kvaddr = (uint32_t) (uint8_t *) sess->sess_kvaddr + VHPCM_BLOCK_SIZE/4; rxtp->capture_dai_data.vocpcm_ion_buffer.paddr = (uint32_t) sess->sess_paddr + (VHPCM_BLOCK_SIZE/4) * 2; rxtp->capture_dai_data.vocpcm_ion_buffer.kvaddr = (uint32_t) (uint8_t *) sess->sess_kvaddr + (VHPCM_BLOCK_SIZE/4) * 2; rxtp->playback_dai_data.vocpcm_ion_buffer.paddr = (uint32_t) sess->sess_paddr + (VHPCM_BLOCK_SIZE/4) * 3; rxtp->playback_dai_data.vocpcm_ion_buffer.kvaddr = (uint32_t) (uint8_t *) sess->sess_kvaddr + (VHPCM_BLOCK_SIZE/4) * 3; done: return ret; } static int hpcm_start_vocpcm(char *pcm_id, struct hpcm_drv *prtd, struct tap_point *tp) { int indx = prtd->mixer_conf.sess_indx; uint32_t *no_of_tp = &prtd->start_cmd.no_of_tapoints; struct vss_ivpcm_tap_point *tap_pnt = &prtd->start_cmd.tap_pnt[0]; uint32_t no_of_tp_req = 0; char *sess_name = hpcm_get_sess_name(indx); if (prtd->mixer_conf.rx.enable) no_of_tp_req++; if (prtd->mixer_conf.tx.enable) no_of_tp_req++; if (prtd->mixer_conf.rx.enable && (get_tappnt_value(pcm_id) == RX)) { if (hpcm_all_dais_are_ready(prtd->mixer_conf.rx.direction, tp, HPCM_PREPARED)) { pr_debug("%s: RX conditions met\n", __func__); tap_pnt[*no_of_tp].tap_point = VSS_IVPCM_TAP_POINT_RX_DEFAULT; tap_pnt[*no_of_tp].direction = prtd->mixer_conf.rx.direction; tap_pnt[*no_of_tp].sampling_rate = prtd->mixer_conf.rx.sample_rate; (*no_of_tp)++; } } if (prtd->mixer_conf.tx.enable && (get_tappnt_value(pcm_id) == TX)) { if (hpcm_all_dais_are_ready(prtd->mixer_conf.tx.direction, tp, HPCM_PREPARED)) { pr_debug("%s: TX conditions met\n", __func__); tap_pnt[*no_of_tp].tap_point = VSS_IVPCM_TAP_POINT_TX_DEFAULT; tap_pnt[*no_of_tp].direction = prtd->mixer_conf.tx.direction; tap_pnt[*no_of_tp].sampling_rate = prtd->mixer_conf.tx.sample_rate; (*no_of_tp)++; } } if ((prtd->mixer_conf.tx.enable || prtd->mixer_conf.rx.enable) && *no_of_tp == no_of_tp_req) { voc_send_cvp_start_vocpcm(voc_get_session_id(sess_name), tap_pnt, *no_of_tp); /* Reset the start command so that it is not called twice */ memset(&prtd->start_cmd, 0, sizeof(struct start_cmd)); } else { pr_debug("%s: required pcm handles not opened yet\n", __func__); } return 0; } /* Playback path*/ static void hpcm_copy_playback_data_from_queue(struct dai_data *dai_data, uint32_t *len) { struct hpcm_buf_node *buf_node = NULL; unsigned long dsp_flags; if (dai_data->substream == NULL) return; spin_lock_irqsave(&dai_data->dsp_lock, dsp_flags); if (!list_empty(&dai_data->filled_queue)) { buf_node = list_first_entry(&dai_data->filled_queue, struct hpcm_buf_node, list); list_del(&buf_node->list); *len = buf_node->frame.len; memcpy((uint8_t *)dai_data->vocpcm_ion_buffer.kvaddr, &buf_node->frame.voc_pkt[0], buf_node->frame.len); list_add_tail(&buf_node->list, &dai_data->free_queue); dai_data->pcm_irq_pos += dai_data->pcm_count; spin_unlock_irqrestore(&dai_data->dsp_lock, dsp_flags); snd_pcm_period_elapsed(dai_data->substream); } else { *len = 0; spin_unlock_irqrestore(&dai_data->dsp_lock, dsp_flags); pr_err("IN data not available\n"); } wake_up(&dai_data->queue_wait); } /* Capture path*/ static void hpcm_copy_capture_data_to_queue(struct dai_data *dai_data, uint32_t len) { struct hpcm_buf_node *buf_node = NULL; unsigned long dsp_flags; if (dai_data->substream == NULL) return; /* Copy out buffer packet into free_queue */ spin_lock_irqsave(&dai_data->dsp_lock, dsp_flags); if (!list_empty(&dai_data->free_queue)) { buf_node = list_first_entry(&dai_data->free_queue, struct hpcm_buf_node, list); list_del(&buf_node->list); buf_node->frame.len = len; memcpy(&buf_node->frame.voc_pkt[0], (uint8_t *)dai_data->vocpcm_ion_buffer.kvaddr, buf_node->frame.len); list_add_tail(&buf_node->list, &dai_data->filled_queue); dai_data->pcm_irq_pos += dai_data->pcm_count; spin_unlock_irqrestore(&dai_data->dsp_lock, dsp_flags); snd_pcm_period_elapsed(dai_data->substream); } else { spin_unlock_irqrestore(&dai_data->dsp_lock, dsp_flags); pr_err("OUTPUT data dropped\n"); } wake_up(&dai_data->queue_wait); } void hpcm_notify_evt_processing(uint8_t *data, char *session, void *private_data) { struct hpcm_drv *prtd = (struct hpcm_drv *)private_data; struct vss_ivpcm_evt_notify_v2_t *notify_evt = (struct vss_ivpcm_evt_notify_v2_t *)data; struct vss_ivpcm_evt_push_buffer_v2_t push_buff_event; struct tap_point *tp = NULL; int in_buf_len = 0; struct tappnt_mxr_data *tmd = NULL; char *sess_name = hpcm_get_sess_name(prtd->mixer_conf.sess_indx); /* If it's not a timetick, it's a error notification, drop the event */ if ((notify_evt->notify_mask & VSS_IVPCM_NOTIFY_MASK_TIMETICK) == 0) { pr_err("%s: Error notification. mask=%d\n", __func__, notify_evt->notify_mask); return; } if (notify_evt->tap_point == VSS_IVPCM_TAP_POINT_TX_DEFAULT) { tp = &prtd->session[prtd->mixer_conf.sess_indx].tx_tap_point; tmd = &prtd->mixer_conf.tx; } else if (notify_evt->tap_point == VSS_IVPCM_TAP_POINT_RX_DEFAULT) { tp = &prtd->session[prtd->mixer_conf.sess_indx].rx_tap_point; tmd = &prtd->mixer_conf.rx; } if (tp == NULL || tmd == NULL) { pr_err("%s: tp = %x or tmd = %x is null\n", __func__, (uint32_t)tp, (uint32_t)tmd); return; } if (notify_evt->notify_mask & VSS_IVPCM_NOTIFY_MASK_OUTPUT_BUFFER) { hpcm_copy_capture_data_to_queue(&tp->capture_dai_data, notify_evt->filled_out_size); } if (notify_evt->notify_mask & VSS_IVPCM_NOTIFY_MASK_INPUT_BUFFER) { hpcm_copy_playback_data_from_queue(&tp->playback_dai_data, &in_buf_len); } switch (tmd->direction) { /* * When the dir is OUT_IN, for the first notify mask, pushbuf mask * should be set to VSS_IVPCM_PUSH_BUFFER_MASK_OUTPUT_BUFFER since we * atleast need one buffer's worth data before we can send IN buffer. * For the consecutive notify evts, the push buf mask will set for both * VSS_IVPCM_PUSH_BUFFER_MASK_OUTPUT_BUFFER and * VSS_IVPCM_PUSH_BUFFER_MASK_IN_BUFFER. */ case VSS_IVPCM_TAP_POINT_DIR_OUT_IN: if (notify_evt->notify_mask == VSS_IVPCM_NOTIFY_MASK_TIMETICK) { push_buff_event.push_buf_mask = VSS_IVPCM_PUSH_BUFFER_MASK_OUTPUT_BUFFER; } else { push_buff_event.push_buf_mask = VSS_IVPCM_PUSH_BUFFER_MASK_OUTPUT_BUFFER | VSS_IVPCM_PUSH_BUFFER_MASK_INPUT_BUFFER; } break; case VSS_IVPCM_TAP_POINT_DIR_IN: push_buff_event.push_buf_mask = VSS_IVPCM_PUSH_BUFFER_MASK_INPUT_BUFFER; break; case VSS_IVPCM_TAP_POINT_DIR_OUT: push_buff_event.push_buf_mask = VSS_IVPCM_PUSH_BUFFER_MASK_OUTPUT_BUFFER; break; } push_buff_event.tap_point = notify_evt->tap_point; push_buff_event.out_buf_mem_address = tp->capture_dai_data.vocpcm_ion_buffer.paddr; push_buff_event.in_buf_mem_address = tp->playback_dai_data.vocpcm_ion_buffer.paddr; push_buff_event.sampling_rate = notify_evt->sampling_rate; push_buff_event.num_in_channels = 1; /* * ADSP must read and write from a cache aligned (128 byte) location, * and in blocks of the cache alignment size. The 128 byte cache * alignment requirement is guaranteed due to 4096 byte memory * alignment requirement during memory allocation/mapping. The output * buffer (ADSP write) size mask ensures that a 128 byte multiple * worth of will be written. Internally, the input buffer (ADSP read) * size will also be a multiple of 128 bytes. However it is the * application's responsibility to ensure no other data is written in * the specified length of memory. */ push_buff_event.out_buf_mem_size = ((notify_evt->request_buf_size) + CACHE_ALIGNMENT_SIZE) & CACHE_ALIGNMENT_MASK; push_buff_event.in_buf_mem_size = in_buf_len; voc_send_cvp_vocpcm_push_buf_evt(voc_get_session_id(sess_name), &push_buff_event); } static int msm_hpcm_configure_voice_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { int tap_point = ucontrol->value.integer.value[0]; uint16_t direction = ucontrol->value.integer.value[1]; uint16_t sample_rate = ucontrol->value.integer.value[2]; struct tappnt_mxr_data *tmd = NULL; int ret = 0; mutex_lock(&hpcm_drv.lock); pr_debug("%s: tap_point = %d direction = %d sample_rate = %d\n", __func__, tap_point, direction, sample_rate); if (!hpcm_is_valid_config(VOICE_INDEX, tap_point, direction, sample_rate)) { pr_err("Invalid vpcm mixer control voice values\n"); ret = -EINVAL; goto done; } if (tap_point == RX) tmd = &hpcm_drv.mixer_conf.rx; else tmd = &hpcm_drv.mixer_conf.tx; tmd->enable = true; tmd->direction = direction; tmd->sample_rate = sample_rate; hpcm_drv.mixer_conf.sess_indx = VOICE_INDEX; done: mutex_unlock(&hpcm_drv.lock); return ret; } static int msm_hpcm_configure_volte_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { int tap_point = ucontrol->value.integer.value[0]; uint16_t direction = ucontrol->value.integer.value[1]; uint16_t sample_rate = ucontrol->value.integer.value[2]; struct tappnt_mxr_data *tmd = NULL; int ret = 0; mutex_lock(&hpcm_drv.lock); pr_debug("%s: tap_point=%d direction=%d sample_rate=%d\n", __func__, tap_point, direction, sample_rate); if (!hpcm_is_valid_config(VOLTE_INDEX, tap_point, direction, sample_rate)) { pr_err("Invalid vpcm mixer control volte values\n"); ret = -EINVAL; goto done; } if (tap_point == RX) tmd = &hpcm_drv.mixer_conf.rx; else tmd = &hpcm_drv.mixer_conf.tx; tmd->enable = true; tmd->direction = direction; tmd->sample_rate = sample_rate; hpcm_drv.mixer_conf.sess_indx = VOLTE_INDEX; done: mutex_unlock(&hpcm_drv.lock); return ret; } static struct snd_kcontrol_new msm_hpcm_controls[] = { SOC_SINGLE_MULTI_EXT("HPCM_Voice tappoint direction samplerate", SND_SOC_NOPM, 0, 16000 , 0, 3, NULL, msm_hpcm_configure_voice_put), SOC_SINGLE_MULTI_EXT("HPCM_VoLTE tappoint direction samplerate", SND_SOC_NOPM, 0, 16000 , 0, 3, NULL, msm_hpcm_configure_volte_put), }; /* Sample rates supported */ static unsigned int supported_sample_rates[] = {8000, 16000}; static struct snd_pcm_hw_constraint_list constraints_sample_rates = { .count = ARRAY_SIZE(supported_sample_rates), .list = supported_sample_rates, .mask = 0, }; static int msm_pcm_close(struct snd_pcm_substream *substream) { int ret = 0; struct list_head *ptr = NULL; struct list_head *next = NULL; struct hpcm_buf_node *buf_node = NULL; struct snd_dma_buffer *dma_buf; struct snd_pcm_runtime *runtime; struct hpcm_drv *prtd; unsigned long dsp_flags; struct dai_data *dai_data = NULL; struct tap_point *tp = NULL; struct tappnt_mxr_data *tmd = NULL; char *sess_name = NULL; if (substream == NULL) { pr_err("substream is NULL\n"); return -EINVAL; } pr_debug("%s, %s\n", __func__, substream->pcm->id); runtime = substream->runtime; prtd = runtime->private_data; sess_name = hpcm_get_sess_name(prtd->mixer_conf.sess_indx); dai_data = hpcm_get_dai_data(substream->pcm->id, prtd); if (dai_data == NULL) { pr_err("%s, dai_data is NULL\n", __func__); ret = -EINVAL; goto done; } wake_up(&dai_data->queue_wait); mutex_lock(&prtd->lock); tmd = hpcm_get_tappnt_mixer_data(substream->pcm->id, prtd); tp = hpcm_get_tappoint_data(substream->pcm->id, prtd); /* Send stop command */ voc_send_cvp_stop_vocpcm(voc_get_session_id(sess_name)); /* Memory unmap/free takes place only when called the first time */ hpcm_unmap_and_free_shared_memory(prtd); /* Unregister host PCM event callback function */ voc_deregister_hpcm_evt_cb(); /* Reset the cached start cmd */ memset(&prtd->start_cmd, 0, sizeof(struct start_cmd)); /* Release all buffer */ pr_debug("%s: Release all buffer\n", __func__); substream = dai_data->substream; if (substream == NULL) { pr_debug("%s: substream is NULL\n", __func__); goto done; } dma_buf = &substream->dma_buffer; if (dma_buf == NULL) { pr_debug("%s: dma_buf is NULL\n", __func__); goto done; } if (dma_buf->area != NULL) { spin_lock_irqsave(&dai_data->dsp_lock, dsp_flags); list_for_each_safe(ptr, next, &dai_data->filled_queue) { buf_node = list_entry(ptr, struct hpcm_buf_node, list); list_del(&buf_node->list); } list_for_each_safe(ptr, next, &dai_data->free_queue) { buf_node = list_entry(ptr, struct hpcm_buf_node, list); list_del(&buf_node->list); } spin_unlock_irqrestore(&dai_data->dsp_lock, dsp_flags); dma_free_coherent(substream->pcm->card->dev, runtime->hw.buffer_bytes_max, dma_buf->area, dma_buf->addr); dma_buf->area = NULL; } dai_data->substream = NULL; dai_data->pcm_buf_pos = 0; dai_data->pcm_count = 0; dai_data->pcm_irq_pos = 0; dai_data->pcm_size = 0; dai_data->state = HPCM_CLOSED; hpcm_reset_mixer_config(prtd); done: mutex_unlock(&prtd->lock); return ret; } static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) { int ret = 0; struct hpcm_buf_node *buf_node = NULL; struct snd_pcm_runtime *runtime = substream->runtime; struct hpcm_drv *prtd = runtime->private_data; struct dai_data *dai_data = hpcm_get_dai_data(substream->pcm->id, prtd); unsigned long dsp_flags; int count = frames_to_bytes(runtime, frames); if (dai_data == NULL) { pr_err("%s, dai_data is null\n", __func__); ret = -EINVAL; goto done; } ret = wait_event_interruptible_timeout(dai_data->queue_wait, (!list_empty(&dai_data->free_queue) || dai_data->state == HPCM_STOPPED), 1 * HZ); if (ret > 0) { if (count <= HPCM_MAX_VOC_PKT_SIZE) { spin_lock_irqsave(&dai_data->dsp_lock, dsp_flags); buf_node = list_first_entry(&dai_data->free_queue, struct hpcm_buf_node, list); list_del(&buf_node->list); spin_unlock_irqrestore(&dai_data->dsp_lock, dsp_flags); ret = copy_from_user(&buf_node->frame.voc_pkt, buf, count); buf_node->frame.len = count; spin_lock_irqsave(&dai_data->dsp_lock, dsp_flags); list_add_tail(&buf_node->list, &dai_data->filled_queue); spin_unlock_irqrestore(&dai_data->dsp_lock, dsp_flags); } else { pr_err("%s: Write cnt %d is > HPCM_MAX_VOC_PKT_SIZE\n", __func__, count); ret = -ENOMEM; } } else if (ret == 0) { pr_err("%s: No free Playback buffer\n", __func__); ret = -ETIMEDOUT; } else { pr_err("%s: playback copy was interrupted\n", __func__); } done: return ret; } static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) { int ret = 0; int count = 0; struct hpcm_buf_node *buf_node = NULL; struct snd_pcm_runtime *runtime = substream->runtime; struct hpcm_drv *prtd = runtime->private_data; struct dai_data *dai_data = hpcm_get_dai_data(substream->pcm->id, prtd); unsigned long dsp_flags; if (dai_data == NULL) { pr_err("%s, dai_data is null\n", __func__); ret = -EINVAL; goto done; } count = frames_to_bytes(runtime, frames); ret = wait_event_interruptible_timeout(dai_data->queue_wait, (!list_empty(&dai_data->filled_queue) || dai_data->state == HPCM_STOPPED), 1 * HZ); if (ret > 0) { if (count <= HPCM_MAX_VOC_PKT_SIZE) { spin_lock_irqsave(&dai_data->dsp_lock, dsp_flags); buf_node = list_first_entry(&dai_data->filled_queue, struct hpcm_buf_node, list); list_del(&buf_node->list); spin_unlock_irqrestore(&dai_data->dsp_lock, dsp_flags); ret = copy_to_user(buf, &buf_node->frame.voc_pkt, count); if (ret) { pr_err("%s: Copy to user retuned %d\n", __func__, ret); ret = -EFAULT; } spin_lock_irqsave(&dai_data->dsp_lock, dsp_flags); list_add_tail(&buf_node->list, &dai_data->free_queue); spin_unlock_irqrestore(&dai_data->dsp_lock, dsp_flags); } else { pr_err("%s: Read count %d > HPCM_MAX_VOC_PKT_SIZE\n", __func__, count); ret = -ENOMEM; } } else if (ret == 0) { pr_err("%s: No Caputre data available\n", __func__); ret = -ETIMEDOUT; } else { pr_err("%s: Read was interrupted\n", __func__); ret = -ERESTARTSYS; } done: return ret; } static int msm_pcm_copy(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) { int ret = 0; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ret = msm_pcm_playback_copy(substream, channel, hwoff, buf, frames); else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ret = msm_pcm_capture_copy(substream, channel, hwoff, buf, frames); return ret; } static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) { struct dai_data *dai_data = NULL; struct snd_pcm_runtime *runtime = substream->runtime; struct hpcm_drv *prtd = runtime->private_data; snd_pcm_uframes_t ret; dai_data = hpcm_get_dai_data(substream->pcm->id, prtd); if (dai_data == NULL) { pr_err("%s, dai_data is null\n", __func__); ret = 0; goto done; } if (dai_data->pcm_irq_pos >= dai_data->pcm_size) dai_data->pcm_irq_pos = 0; ret = bytes_to_frames(runtime, (dai_data->pcm_irq_pos)); done: return ret; } static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { int ret = 0; struct snd_pcm_runtime *runtime = substream->runtime; struct hpcm_drv *prtd = runtime->private_data; struct dai_data *dai_data = hpcm_get_dai_data(substream->pcm->id, prtd); if (dai_data == NULL) { pr_err("%s, dai_data is null\n", __func__); ret = -EINVAL; goto done; } pr_debug("%s, %s\n", __func__, substream->pcm->id); switch (cmd) { case SNDRV_PCM_TRIGGER_START: pr_debug("SNDRV_PCM_TRIGGER_START\n"); dai_data->state = HPCM_STARTED; break; case SNDRV_PCM_TRIGGER_STOP: pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); dai_data->state = HPCM_STOPPED; break; default: ret = -EINVAL; break; } done: return ret; } static int msm_pcm_prepare(struct snd_pcm_substream *substream) { int ret = 0; struct snd_pcm_runtime *runtime = substream->runtime; struct hpcm_drv *prtd = runtime->private_data; struct dai_data *dai_data = NULL; pr_debug("%s, %s\n", __func__, substream->pcm->id); mutex_lock(&prtd->lock); dai_data = hpcm_get_dai_data(substream->pcm->id, prtd); if (dai_data == NULL) { pr_err("%s, dai_data is null\n", __func__); ret = -EINVAL; goto done; } dai_data->pcm_size = snd_pcm_lib_buffer_bytes(substream); dai_data->pcm_count = snd_pcm_lib_period_bytes(substream); dai_data->pcm_irq_pos = 0; dai_data->pcm_buf_pos = 0; dai_data->state = HPCM_PREPARED; /* Register event notify processing callback in prepare instead of * init() as q6voice module's init() can be called at a later point */ voc_register_hpcm_evt_cb(hpcm_notify_evt_processing, &hpcm_drv); ret = hpcm_start_vocpcm(substream->pcm->id, prtd, hpcm_get_tappoint_data(substream->pcm->id, prtd)); if (ret) { pr_err("error sending start cmd err=%d\n", ret); goto done; } done: mutex_unlock(&prtd->lock); return ret; } static int msm_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dma_buffer *dma_buf = &substream->dma_buffer; struct hpcm_drv *prtd = (struct hpcm_drv *)runtime->private_data; int ret = 0; pr_debug("%s: %s\n", __func__, substream->pcm->id); mutex_lock(&prtd->lock); /* Allocate and map voice host PCM ion buffer */ if (prtd->session[prtd->mixer_conf.sess_indx].sess_paddr == 0) { ret = hpcm_allocate_shared_memory(prtd); if (ret) { pr_err("error creating shared memory err=%d\n", ret); goto done; } ret = hpcm_map_vocpcm_memory(prtd); if (ret) { pr_err("error mapping shared memory err=%d\n", ret); hpcm_free_allocated_mem(prtd); goto done; } } else { pr_debug("%s, VHPCM memory allocation/mapping not performed\n" , __func__); } dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; dma_buf->dev.dev = substream->pcm->card->dev; dma_buf->private_data = NULL; dma_buf->area = dma_alloc_coherent(substream->pcm->card->dev, runtime->hw.buffer_bytes_max, &dma_buf->addr, GFP_KERNEL); if (!dma_buf->area) { pr_err("%s:MSM dma_alloc failed\n", __func__); ret = -ENOMEM; goto done; } dma_buf->bytes = runtime->hw.buffer_bytes_max; memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max); hpcm_create_free_queue(dma_buf, hpcm_get_dai_data(substream->pcm->id, prtd)); snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); done: mutex_unlock(&prtd->lock); return ret; } static int msm_pcm_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct hpcm_drv *prtd = &hpcm_drv; struct tappnt_mxr_data *tmd = NULL; struct dai_data *dai_data = NULL; int ret = 0; int tp_val = 0; pr_debug("%s, %s\n", __func__, substream->pcm->id); mutex_lock(&prtd->lock); dai_data = hpcm_get_dai_data(substream->pcm->id, prtd); if (dai_data == NULL) { pr_err("%s, dai_data is null\n", __func__); ret = -EINVAL; goto done; } runtime->hw = msm_pcm_hardware; ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_sample_rates); if (ret < 0) pr_debug("snd_pcm_hw_constraint_list failed\n"); ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); if (ret < 0) { pr_debug("snd_pcm_hw_constraint_integer failed\n"); goto done; } tp_val = get_tappnt_value(substream->pcm->id); tmd = hpcm_get_tappnt_mixer_data(substream->pcm->id, prtd); /* Check wheather the kcontrol values set are valid */ if (!tmd || !(tmd->enable) || !hpcm_is_valid_config(prtd->mixer_conf.sess_indx, tp_val, tmd->direction, tmd->sample_rate)) { ret = -EINVAL; goto done; } dai_data->substream = substream; runtime->private_data = prtd; done: mutex_unlock(&prtd->lock); return ret; } static struct snd_pcm_ops msm_pcm_ops = { .open = msm_pcm_open, .hw_params = msm_pcm_hw_params, .prepare = msm_pcm_prepare, .trigger = msm_pcm_trigger, .pointer = msm_pcm_pointer, .copy = msm_pcm_copy, .close = msm_pcm_close, }; static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) { struct snd_card *card = rtd->card->snd_card; pr_debug("%s:\n", __func__); if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = DMA_BIT_MASK(32); return 0; } static int msm_pcm_hpcm_probe(struct snd_soc_platform *platform) { snd_soc_add_platform_controls(platform, msm_hpcm_controls, ARRAY_SIZE(msm_hpcm_controls)); return 0; } static struct snd_soc_platform_driver msm_soc_platform = { .ops = &msm_pcm_ops, .pcm_new = msm_asoc_pcm_new, .probe = msm_pcm_hpcm_probe, }; static __devinit int msm_pcm_probe(struct platform_device *pdev) { if (pdev->dev.of_node) dev_set_name(&pdev->dev, "%s", "msm-voice-host-pcm"); pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); return snd_soc_register_platform(&pdev->dev, &msm_soc_platform); } static int msm_pcm_remove(struct platform_device *pdev) { snd_soc_unregister_platform(&pdev->dev); return 0; } static const struct of_device_id msm_voice_host_pcm_dt_match[] = { {.compatible = "qcom,msm-voice-host-pcm"}, {} }; MODULE_DEVICE_TABLE(of, msm_voice_host_pcm_dt_match); static struct platform_driver msm_pcm_driver = { .driver = { .name = "msm-voice-host-pcm", .owner = THIS_MODULE, .of_match_table = msm_voice_host_pcm_dt_match, }, .probe = msm_pcm_probe, .remove = __devexit_p(msm_pcm_remove), }; static int __init msm_soc_platform_init(void) { int i = 0; struct session *s = NULL; memset(&hpcm_drv, 0, sizeof(hpcm_drv)); mutex_init(&hpcm_drv.lock); for (i = 0; i < MAX_SESSION; i++) { s = &hpcm_drv.session[i]; spin_lock_init(&s->rx_tap_point.capture_dai_data.dsp_lock); spin_lock_init(&s->rx_tap_point.playback_dai_data.dsp_lock); spin_lock_init(&s->tx_tap_point.capture_dai_data.dsp_lock); spin_lock_init(&s->tx_tap_point.playback_dai_data.dsp_lock); init_waitqueue_head( &s->rx_tap_point.capture_dai_data.queue_wait); init_waitqueue_head( &s->rx_tap_point.playback_dai_data.queue_wait); init_waitqueue_head( &s->tx_tap_point.capture_dai_data.queue_wait); init_waitqueue_head( &s->tx_tap_point.playback_dai_data.queue_wait); INIT_LIST_HEAD(&s->rx_tap_point.capture_dai_data.filled_queue); INIT_LIST_HEAD(&s->rx_tap_point.capture_dai_data.free_queue); INIT_LIST_HEAD(&s->rx_tap_point.playback_dai_data.filled_queue); INIT_LIST_HEAD(&s->rx_tap_point.playback_dai_data.free_queue); INIT_LIST_HEAD(&s->tx_tap_point.capture_dai_data.filled_queue); INIT_LIST_HEAD(&s->tx_tap_point.capture_dai_data.free_queue); INIT_LIST_HEAD(&s->tx_tap_point.playback_dai_data.filled_queue); INIT_LIST_HEAD(&s->tx_tap_point.playback_dai_data.free_queue); } return platform_driver_register(&msm_pcm_driver); } module_init(msm_soc_platform_init); static void __exit msm_soc_platform_exit(void) { platform_driver_unregister(&msm_pcm_driver); } module_exit(msm_soc_platform_exit); MODULE_DESCRIPTION("PCM module platform driver"); MODULE_LICENSE("GPL v2");