/* * Copyright (c) 2020 Actions Semiconductor Co., Ltd * * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @brief Audio IN(ADC/I2S-RX/SPDIF-RX) core management layer */ #include #include #include #include #include #include #include #include #include "phy_audio_common.h" #include LOG_MODULE_REGISTER(ain, CONFIG_LOG_DEFAULT_LEVEL); #define AIN_SESSION_MAGIC (0x11223344) #define AIN_SESSION_CHECK_MAGIC(x) ((x) == AIN_SESSION_MAGIC) #ifdef CONFIG_AUDIO_IN_ADC_SUPPORT #define AIN_ADC_SESSION_MAX (1) /* 1 ADC channels */ #else #define AIN_ADC_SESSION_MAX (1) #endif #ifdef CONFIG_AUDIO_IN_I2SRX_SUPPORT #define AIN_I2SRX_SESSION_MAX (1) /* 1 I2SRX channel */ #else #define AIN_I2SRX_SESSION_MAX (0) #endif #ifdef CONFIG_AUDIO_IN_SPDIFRX_SUPPORT #define AIN_SPDIFRX_SESSION_MAX (1) /* 1 SPDIFRX channel */ #else #define AIN_SPDIFRX_SESSION_MAX (0) #endif #define AIN_SESSION_MAX (AIN_ADC_SESSION_MAX + AIN_I2SRX_SESSION_MAX + AIN_SPDIFRX_SESSION_MAX) /* totally max sessions */ #define AIN_SESSION_OPEN (1 << 0) /* Session open flag */ #define AIN_SESSION_CONFIG (1 << 1) /* Session config flag */ #define AIN_SESSION_START (1 << 2) /* Session start flag */ #define AIN_SESSION_BIND (1 << 3) /* Session start flag */ #define DMA_IRQ_TC (0) /* DMA completion flag */ #define DMA_IRQ_HF (1) /* DMA half-full flag */ /** * struct ain_dynamic_debug_t * @brief audio in session dynamic debug structure */ typedef struct { uint32_t timestamp; /* record the per-second timestamp */ uint32_t per_second_size; /* record the per-second play size */ uint8_t dump_len; /* dump the length of buffer per-second */ uint8_t performance: 1; /* enable flag of showing the play performance */ } ain_dynamic_debug_t; /* * struct ain_session_t * @brief audio in session structure */ typedef struct { int magic; /* session magic value as AIN_SESSION_MAGIC */ int dma_chan; /* DMA channel handle */ uint16_t input_dev; /* input audio device */ uint16_t channel_type; /* Indicates the channel type selection */ uint8_t flags; /* session flags */ uint8_t id; /* the session id */ uint8_t dma_width; /* dma width */ int (*callback)(void *cb_data, uint32_t reason); /* The callback function which called when #AIN_DMA_IRQ_HF or #AIN_DMA_IRQ_TC events happened */ void *cb_data; /* callback user data */ uint8_t *reload_addr; /* Reload buffer address to transfer */ uint32_t reload_len; /* The length of the reload buffer */ #ifdef CONFIG_AUDIO_DYNAMIC_DEBUG ain_dynamic_debug_t debug; /* dynamic debug */ #endif uint8_t dma_separated_en : 1; /* the flag of DMA interleaved mode enable or not */ uint8_t dma_separated_mode : 2; /* the DMA interleaved mode which specifies the data format stored in memory */ } ain_session_t; /* * struct aout_drv_data * @brief audio in driver data which are software related */ struct ain_drv_data { struct k_sem lock; struct device *dma_dev; struct device *adc0_dev; /* ADC0 device handler */ struct device *i2srx0_dev; /* I2SRX0 device handler */ struct device *spdifrx0_dev; /* SPFIRX0 device handler */ struct device *anc0_dev; /* ANC0 device handler */ uint32_t adc_sess_stack[AIN_ADC_SESSION_MAX + 1]; /* stack to store sync wait sessions */ uint8_t adc_sess_stack_cur; /* current curtor of adc session stack */ uint8_t adc_ref_counter; /* ADC channels start reference counter */ uint8_t anc_en_flag : 1; /* the flag of ANC enable */ }; /* * struct aout_config_data * @brief audio in config data which are the hardward related */ struct ain_config_data { const char *adc0_name; /* physical ADC0 device name */ const char *i2srx0_name; /* physical I2SRX0 device name */ const char *spdifrx0_name; /* physical SPDIFRX0 device name */ }; static ain_session_t ain_sessions[AIN_SESSION_MAX]; /* * @brief checkout if there is the same channel within audio in sessions */ static bool audio_in_session_check(uint16_t type) { int i; ain_session_t *sess = ain_sessions; uint8_t max_sess, sess_opened = 0; if (AUDIO_CHANNEL_ADC & type) { max_sess = AIN_ADC_SESSION_MAX; } else if (AUDIO_CHANNEL_I2SRX & type) { max_sess = AIN_I2SRX_SESSION_MAX; } else if (AUDIO_CHANNEL_SPDIFRX & type) { max_sess = AIN_SPDIFRX_SESSION_MAX; } else { LOG_ERR("Invalid session type %d", type); return true; } for (i = 0; i < AIN_SESSION_MAX; i++, sess++) { if (AIN_SESSION_CHECK_MAGIC(sess->magic) && (sess->flags & AIN_SESSION_OPEN) && (sess->channel_type & type)) { sess_opened++; LOG_DBG("Found aout channel type %d in use", type); } } /* check if the number of opened sessions is bigger than the max limit */ if (sess_opened >= max_sess) { LOG_ERR("channel type:%d session max %d and opened %d", type, max_sess, sess_opened); return true; } return false; } /* * @brief Get audio in session by specified channel */ static ain_session_t *audio_in_session_get(uint16_t type) { ain_session_t *sess = ain_sessions; int i; if (audio_in_session_check(type)) return NULL; for (i = 0; i < AIN_SESSION_MAX; i++, sess++) { if (!(sess->flags & AIN_SESSION_OPEN)) { memset(sess, 0, sizeof(ain_session_t)); sess->magic = AIN_SESSION_MAGIC; sess->flags = AIN_SESSION_OPEN; sess->channel_type = type; sess->id = i; return sess; } } return NULL; } /* * @brief Put audio in session by given session address */ static void audio_in_session_put(ain_session_t *s) { ain_session_t *sess = ain_sessions; int i; for (i = 0; i < AIN_SESSION_MAX; i++, sess++) { if ((uint32_t)s == (uint32_t)sess) { memset(s, 0, sizeof(ain_session_t)); break; } } } /* @brief audio in session lock */ static inline void audio_in_lock(struct device *dev) { struct ain_drv_data *data = dev->data; k_sem_take(&data->lock, K_FOREVER); } /* @brief audio in session unlock */ static inline void audio_in_unlock(struct device *dev) { struct ain_drv_data *data = dev->data; k_sem_give(&data->lock); } /* @brief bind the audio in session to dma start at the same time */ static int audio_in_session_bind(struct device *dev, ain_session_t *this, ain_session_t *bind) { struct ain_drv_data *data = dev->data; if ((!bind) || (!AIN_SESSION_CHECK_MAGIC(bind->magic))) { LOG_ERR("Invalid session:%p", bind); return -EINVAL; } /* check if the session does not config or already start */ if (!(bind->flags & AIN_SESSION_CONFIG) || (bind->flags & AIN_SESSION_START)) { LOG_ERR("Invalid session status:0x%x", bind->flags); return -EPERM; } if (this == bind) { LOG_ERR("Bind the same session@%p", this); return -EPERM; } if (bind->flags & AIN_SESSION_BIND) { LOG_INF("Already bind session@%p", bind); return 0; } if (bind->channel_type != AUDIO_CHANNEL_ADC) { LOG_ERR("For now only support ADC to bind channels"); return -EPERM; } bind->flags |= AIN_SESSION_BIND; ++data->adc_ref_counter; LOG_INF("session@%p binded", bind); return 0; } #ifdef CONFIG_AUDIO_DYNAMIC_DEBUG /* @brief show the audio in performance */ static void audio_in_debug_perf(ain_session_t *session, uint8_t *buffer, uint32_t len) { uint32_t delta; if (session->debug.performance) { delta = (uint32_t)k_cyc_to_ns_floor64(k_cycle_get_32() - session->debug.timestamp); session->debug.per_second_size += len; if (delta > 1000000000UL) { session->debug.timestamp = k_cycle_get_32(); printk("** in session@%p - %dB/s **\n", session, session->debug.per_second_size); session->debug.per_second_size = 0; if (session->debug.dump_len) { AUDIO_DUMP_MEM(buffer, session->debug.dump_len); } } } } /* @brief control all sessions to enable or disable performance statistics */ static int audio_in_debug_perf_all_sessions(uint8_t is_en) { int i; ain_session_t *sess = ain_sessions; for (i = 0; i < AIN_SESSION_MAX; i++, sess++) { if (AIN_SESSION_CHECK_MAGIC(sess->magic) && (sess->flags & AIN_SESSION_OPEN)) { LOG_INF("session@%p perf debug status %d", sess, is_en); sess->debug.performance = is_en; sess->debug.per_second_size = 0; sess->debug.timestamp = 0; sess->debug.dump_len = 0; } } return 0; } /* @brief control all sessions to set the dump length per second */ static int audio_in_debug_dump_length(uint8_t length) { int i; ain_session_t *sess = ain_sessions; for (i = 0; i < AIN_SESSION_MAX; i++, sess++) { if (AIN_SESSION_CHECK_MAGIC(sess->magic) && (sess->flags & AIN_SESSION_OPEN)) { sess->debug.dump_len = length; } } return 0; } #endif /* @brief Enable the ADC channel */ #ifdef CONFIG_AUDIO_IN_ADC_SUPPORT static int audio_in_enable_adc(struct device *dev, ain_param_t *param) { struct ain_drv_data *data = dev->data; struct device *adc = data->adc0_dev; if (!adc) { LOG_ERR("Physical ADC device is not esixted"); return -EFAULT; } if ((param->channel_width != CHANNEL_WIDTH_16BITS) && (param->channel_width != CHANNEL_WIDTH_24BITS)) { LOG_ERR("Invalid channel width %d", param->channel_width); return -EINVAL; } return phy_audio_enable(adc, (void *)param); } #endif /* @brief Enable the I2SRX channel */ #ifdef CONFIG_AUDIO_IN_I2SRX_SUPPORT static int audio_in_enable_i2srx(struct device *dev, ain_param_t *param) { struct ain_drv_data *data = dev->data; struct device *i2srx = data->i2srx0_dev; if (!i2srx) { LOG_ERR("Physical I2SRX device is not esixted"); return -EFAULT; } if ((param->channel_width != CHANNEL_WIDTH_16BITS) && (param->channel_width != CHANNEL_WIDTH_20BITS) && (param->channel_width != CHANNEL_WIDTH_24BITS)) { LOG_ERR("Invalid channel width %d", param->channel_width); return -EINVAL; } return phy_audio_enable(i2srx, (void *)param); } #endif /* @brief Enable the SPDIFRX channel */ #ifdef CONFIG_AUDIO_IN_SPDIFRX_SUPPORT static int audio_in_enable_spdifrx(struct device *dev, ain_param_t *param) { struct ain_drv_data *data = dev->data; struct device *spdifrx = data->spdifrx0_dev; if (!spdifrx) { LOG_ERR("Physical SPDIFRX device is not esixted"); return -EFAULT; } if ((param->channel_width != CHANNEL_WIDTH_16BITS) && (param->channel_width != CHANNEL_WIDTH_24BITS)) { LOG_ERR("Invalid channel width %d", param->channel_width); return -EINVAL; } return phy_audio_enable(spdifrx, (void *)param); } #endif /* @brief DMA irq callback on reload method */ static void audio_in_dma_reload(const struct device *dev, void *user_data, uint32_t channel, int status) { ain_session_t *session = (ain_session_t *)user_data; uint32_t ret_reson; uint32_t key, delta, len; uint16_t time = CONFIG_ADC_STARTUP_DISCARD_TIME; static uint32_t timestamp = 0; static bool already_discard = false; ARG_UNUSED(dev); ARG_UNUSED(channel); if(session ==NULL) { LOG_WRN("session is NULL"); return; } len = session->reload_len / 2; if (!timestamp) { timestamp = k_cycle_get_32(); LOG_DBG("discard data start time:%u ns",k_cyc_to_ns_floor32(timestamp)); } if ((!already_discard) && time) { delta = (uint32_t)k_cyc_to_ns_floor64(k_cycle_get_32() - timestamp); if (delta > (time * 1000000UL)) { already_discard = true; } LOG_DBG("discard data end time:%u ns", k_cyc_to_ns_floor32(k_cycle_get_32())); } if (session && AIN_SESSION_CHECK_MAGIC(session->magic)) { if (session->callback) { key = irq_lock(); if (status == DMA_IRQ_HF) { ret_reson = AIN_DMA_IRQ_HF; if (!already_discard && time) memset(session->reload_addr, 0, len); } else { ret_reson = AIN_DMA_IRQ_TC; if (!already_discard && time) memset(session->reload_addr + len, 0, len); } session->callback(session->cb_data, ret_reson); irq_unlock(key); #ifdef CONFIG_AUDIO_DYNAMIC_DEBUG audio_in_debug_perf(session, session->reload_addr, session->reload_len / 2); #endif } } } /* @brief audio out dma enable separated mode */ static int audio_in_dma_separated_enable(struct device *dev, ain_session_t *session, audio_interleave_mode_e *p_mode) { ARG_UNUSED(dev); if (!session || !p_mode) { LOG_ERR("Invalid parameter"); return -EINVAL; } session->dma_separated_en = 1; session->dma_separated_mode = *p_mode; LOG_INF("Enable AIN DMA separated mode:%d", session->dma_separated_mode); return 0; } /* @brief prepare the DMA configuration for the audio in transfer */ static int audio_in_dma_prepare(struct device *dev, ain_session_t *session) { struct ain_drv_data *data = dev->data; struct dma_config dma_cfg = {0}; struct dma_block_config dma_block_cfg = {0}; struct audio_in_dma_info info = {0}; dma_cfg.channel_direction = PERIPHERAL_TO_MEMORY; /* Only support the DMA reload mode */ dma_block_cfg.dest_reload_en = 1; dma_cfg.head_block = &dma_block_cfg; dma_cfg.source_data_size = session->dma_width; if (AUDIO_CHANNEL_ADC == session->channel_type) { info.input_dev = session->input_dev; if (phy_audio_control(data->adc0_dev, PHY_CMD_GET_AIN_DMA_INFO, &info)) { LOG_ERR("Failed to get ADC DMA info"); return -EFAULT; } uint32_t level; uint32_t fifo_cmd = PHY_FIFO_CMD(session->input_dev, 0); if (!phy_audio_control(data->adc0_dev, PHY_CMD_FIFO_DRQ_LEVEL_GET, &fifo_cmd)) { level = PHY_GET_FIFO_CMD_VAL(fifo_cmd); LOG_DBG("DRQ level %d", level); /* When DRQ level < 8 levels shall use DMA single mode */ if (level < 8) dma_cfg.source_burst_length = 1; } else { LOG_ERR("Failed to get DRQ level"); return -EFAULT; } } else if (AUDIO_CHANNEL_I2SRX == session->channel_type) { if (phy_audio_control(data->i2srx0_dev, PHY_CMD_GET_AIN_DMA_INFO, &info)) { LOG_ERR("Failed to get I2SRX DMA info"); return -EFAULT; } } else if (AUDIO_CHANNEL_SPDIFRX == session->channel_type) { if (phy_audio_control(data->spdifrx0_dev, PHY_CMD_GET_AIN_DMA_INFO, &info)) { LOG_ERR("Failed to get SPDIFRX DMA info"); return -EFAULT; } } else { LOG_ERR("invalid channel type 0x%x\n", session->channel_type); return -EINVAL; } if (!data->dma_dev) { data->dma_dev = (struct device *)device_get_binding(info.dma_info.dma_dev_name); if (!data->dma_dev) { LOG_ERR("Bind DMA device %s error", info.dma_info.dma_dev_name); return -ENOENT; } } /* request dma channel handle */ session->dma_chan = dma_request(data->dma_dev, 0xFF); if (session->dma_chan < 0) { LOG_ERR("Failed to request dma channel"); return -ENXIO; } dma_cfg.dma_slot = info.dma_info.dma_id; /* Use the DMA irq to notify the status of transfer */ if (session->callback) { dma_cfg.dma_callback = audio_in_dma_reload; dma_cfg.user_data = (void *)session; dma_cfg.complete_callback_en = 1; } if (session->dma_separated_en) { dma_cfg.reserved = 1; } if (dma_config(data->dma_dev, session->dma_chan, &dma_cfg)) { LOG_ERR("DMA config error"); return -EFAULT; } if (session->reload_addr) { LOG_INF("reload addr:0x%x, reload len:%d\n", (uint32_t)session->reload_addr, session->reload_len); if (session->dma_separated_en) { uint32_t dma_length = (session->reload_len << 1); if (session->dma_separated_mode == LEFT_MONO_RIGHT_MUTE_MODE) { dma_reload(data->dma_dev, session->dma_chan, AUDIO_IN_DMA_RESERVED_ADDRESS, (uint32_t)session->reload_addr, dma_length); } else if (session->dma_separated_mode == LEFT_RIGHT_SEPERATE){ dma_reload(data->dma_dev, session->dma_chan, (uint32_t)(session->reload_addr + session->reload_len), (uint32_t)session->reload_addr, dma_length); } else if (session->dma_separated_mode == LEFT_MUTE_RIGHT_MONO_MODE){ dma_reload(data->dma_dev, session->dma_chan, (uint32_t)session->reload_addr, AUDIO_IN_DMA_RESERVED_ADDRESS, dma_length); } } else { dma_reload(data->dma_dev, session->dma_chan, 0, (uint32_t)session->reload_addr, session->reload_len); } } return 0; } /* @brief audio out enable and return the session handler */ static void *acts_audio_in_open(struct device *dev, ain_param_t *param) { ain_session_t * session = NULL; uint8_t channel_type; int ret; void *rtn_sess = NULL; if (!param) { LOG_ERR("NULL parameter"); return NULL; } audio_in_lock(dev); channel_type = param->channel_type; session = audio_in_session_get(channel_type); if (!session) { LOG_ERR("Failed to get audio session (type:%d)", channel_type); goto out; } if (param->reload_setting.reload_addr && !param->reload_setting.reload_len) { LOG_ERR("Reload parameter error addr:0x%x, len:0x%x", (uint32_t)param->reload_setting.reload_addr, param->reload_setting.reload_len); audio_in_session_put(session); goto out; } session->callback = param->callback; session->cb_data = param->cb_data; session->reload_addr = param->reload_setting.reload_addr; session->reload_len = param->reload_setting.reload_len; session->dma_width = (CHANNEL_WIDTH_16BITS == param->channel_width) ? 2 : 4; if (channel_type == AUDIO_CHANNEL_ADC) { #ifdef CONFIG_AUDIO_IN_ADC_SUPPORT ret = audio_in_enable_adc(dev, param); if (!ret) session->input_dev = param->adc_setting->device; #else ret = -ENXIO; #endif } else if (channel_type == AUDIO_CHANNEL_I2SRX) { #ifdef CONFIG_AUDIO_IN_I2SRX_SUPPORT ret = audio_in_enable_i2srx(dev, param); #else ret = -ENXIO; #endif } else if (channel_type == AUDIO_CHANNEL_SPDIFRX) { #ifdef CONFIG_AUDIO_IN_SPDIFRX_SUPPORT ret = audio_in_enable_spdifrx(dev, param); #else ret = -ENXIO; #endif } else { LOG_ERR("Invalid channel type %d", channel_type); audio_in_session_put(session); goto out; } if (ret) { LOG_ERR("Enable channel type %d error:%d", channel_type, ret); audio_in_session_put(session); goto out; } session->flags |= AIN_SESSION_CONFIG; /* The session address is the audio in handler */ rtn_sess = (void *)session; LOG_INF("channel@%d sess:%p type:%d input_dev:0x%x opened", session->id, session, channel_type, session->input_dev); out: audio_in_unlock(dev); return rtn_sess; } /* @brief Disable the ADC channel */ #ifdef CONFIG_AUDIO_IN_ADC_SUPPORT static int audio_in_disable_adc(struct device *dev, ain_session_t *session) { struct ain_drv_data *data = dev->data; struct device *adc = data->adc0_dev; uint16_t input_dev = session->input_dev; if (!adc) { LOG_ERR("Physical ADC device is not esixted"); return -EFAULT; } if ((session->flags & AIN_SESSION_BIND) && !(session->flags & AIN_SESSION_START)) { /* the session already bind but not start yet */ if (data->adc_ref_counter) --data->adc_ref_counter; } if (!data->adc_ref_counter) data->adc_sess_stack_cur = 0; return phy_audio_disable(adc, &input_dev); } #endif /* @brief Disable the I2SRX channel */ #ifdef CONFIG_AUDIO_IN_I2SRX_SUPPORT static int audio_in_disable_i2srx(struct device *dev, ain_session_t *session) { struct ain_drv_data *data = dev->data; struct device *i2srx = data->i2srx0_dev; if (!i2srx) { LOG_ERR("Physical I2SRX device is not esixted"); return -EFAULT; } return phy_audio_disable(i2srx, NULL); } #endif /* @brief Disable the SPDIFRX channel */ #ifdef CONFIG_AUDIO_IN_SPDIFRX_SUPPORT static int audio_in_disable_spdifrx(struct device *dev, ain_session_t *session) { struct ain_drv_data *data = dev->data; struct device *spdifrx = data->spdifrx0_dev; if (!spdifrx) { LOG_ERR("Physical SPDIFRX device is not esixted"); return -EFAULT; } return phy_audio_disable(spdifrx, NULL); } #endif /* @brief Disable audio in channel by specified session handler */ static int acts_audio_in_close(struct device *dev, void *handle) { struct ain_drv_data *data = dev->data; uint8_t channel_type; int ret; ain_session_t *session = (ain_session_t *)handle; if (!handle) { LOG_ERR("Invalid handle"); return -EINVAL; } audio_in_lock(dev); if (!AIN_SESSION_CHECK_MAGIC(session->magic)) { LOG_ERR("Session magic error:0x%x", session->magic); ret = -EFAULT; goto out; } channel_type = session->channel_type; if (channel_type == AUDIO_CHANNEL_ADC) { #ifdef CONFIG_AUDIO_IN_ADC_SUPPORT ret = audio_in_disable_adc(dev, session); #else ret = -ENXIO; #endif } else if (channel_type == AUDIO_CHANNEL_I2SRX) { #ifdef CONFIG_AUDIO_IN_I2SRX_SUPPORT ret = audio_in_disable_i2srx(dev, session); #else ret = -ENXIO; #endif } else if (channel_type == AUDIO_CHANNEL_SPDIFRX) { #ifdef CONFIG_AUDIO_IN_SPDIFRX_SUPPORT ret = audio_in_disable_spdifrx(dev, session); #else ret = -ENXIO; #endif } else { LOG_ERR("Invalid channel type %d", channel_type); ret = -EINVAL; goto out; } if (ret) { LOG_ERR("Disable channel type %d error:%d", channel_type, ret); goto out; } /* stop and free dma channel resource */ if (data->dma_dev && session->dma_chan) { dma_stop(data->dma_dev, session->dma_chan); dma_free(data->dma_dev, session->dma_chan); } LOG_INF("session#%d@%p closed", session->id, session); audio_in_session_put(session); out: audio_in_unlock(dev); return ret; } static int acts_audio_in_control(struct device *dev, void *handle, int cmd, void *param) { struct ain_drv_data *data = dev->data; ain_session_t *session = (ain_session_t *)handle; uint8_t channel_type = session->channel_type; int ret; LOG_DBG("session#0x%x cmd 0x%x", (uint32_t)handle, cmd); if (cmd == AIN_CMD_ANC_CONTROL) { audio_in_lock(dev); ret = phy_audio_control(data->adc0_dev, AIN_CMD_ANC_CONTROL, param); audio_in_unlock(dev); return ret; } #ifdef CONFIG_AUDIO_DYNAMIC_DEBUG if (AIN_CMD_DEBUG_PERFORMANCE_CTL_ALL == cmd) return audio_in_debug_perf_all_sessions(*(uint8_t *)param); if (AIN_CMD_DEBUG_DUMP_LENGTH_ALL == cmd) return audio_in_debug_dump_length(*(uint8_t *)param); #endif if (!AIN_SESSION_CHECK_MAGIC(session->magic)) { LOG_ERR("Session magic error:0x%x", session->magic); return -EFAULT; } #ifdef CONFIG_AUDIO_DYNAMIC_DEBUG if (AIN_CMD_DEBUG_PERFORMANCE_CTL == cmd) { session->debug.performance = *(uint8_t *)param; session->debug.per_second_size = 0; session->debug.timestamp = 0; session->debug.dump_len = 0; return 0; } if (AIN_CMD_DEBUG_DUMP_LENGTH == cmd) { session->debug.dump_len = *(uint8_t *)param; return 0; } #endif if (cmd == AIN_CMD_BIND_CHANNEL) return audio_in_session_bind(dev, session, (ain_session_t *)param); if (cmd == AIN_CMD_SET_SEPARATED_MODE) { return audio_in_dma_separated_enable(dev, session, (audio_interleave_mode_e *)param); } if (channel_type == AUDIO_CHANNEL_ADC) { #ifdef CONFIG_AUDIO_IN_ADC_SUPPORT if (AIN_CMD_SET_ADC_GAIN == cmd) { adc_setting_t setting = {0}; adc_gain *gain = (adc_gain *)param; setting.device = session->input_dev; memcpy(&setting.gain, gain, sizeof(adc_gain)); ret = phy_audio_control(data->adc0_dev, PHY_CMD_ADC_GAIN_CONFIG, &setting); } else if ((AIN_CMD_GET_ADC_LEFT_GAIN_RANGE == cmd) || (AIN_CMD_GET_ADC_RIGHT_GAIN_RANGE == cmd)) { adc_gain_range *range = (adc_gain_range *)param; /* use the 'min' to send the input device info to phy-adc device */ range->min = (int16_t)session->input_dev; ret = phy_audio_control(data->adc0_dev, cmd, param); } else if (AIN_CMD_GET_ADC_FIFO_DRQ_LEVEL == cmd) { uint32_t fifo_cmd = PHY_FIFO_CMD(session->input_dev, 0); ret = phy_audio_control(data->adc0_dev, PHY_CMD_FIFO_DRQ_LEVEL_GET, &fifo_cmd); if (!ret) *(uint8_t *)param = PHY_GET_FIFO_CMD_VAL(fifo_cmd); } else if (AIN_CMD_SET_ADC_FIFO_DRQ_LEVEL == cmd) { uint8_t level = *(uint8_t *)param; uint32_t fifo_cmd = PHY_FIFO_CMD(session->input_dev, level); ret = phy_audio_control(data->adc0_dev, PHY_CMD_FIFO_DRQ_LEVEL_SET, &fifo_cmd); } else { ret = phy_audio_control(data->adc0_dev, cmd, param); } #else ret = -ENXIO; #endif } else if (channel_type == AUDIO_CHANNEL_I2SRX) { #ifdef CONFIG_AUDIO_IN_I2SRX_SUPPORT ret = phy_audio_control(data->i2srx0_dev, cmd, param); #else ret = -ENXIO; #endif } else if (channel_type == AUDIO_CHANNEL_SPDIFRX) { #ifdef CONFIG_AUDIO_IN_SPDIFRX_SUPPORT ret = phy_audio_control(data->spdifrx0_dev, cmd, param); #else ret = -ENXIO; #endif } else { LOG_ERR("Invalid channel type %d", channel_type); ret = -EINVAL; } return ret; } /* @brief Start the audio input transfering */ static int acts_audio_in_start(struct device *dev, void *handle) { struct ain_drv_data *data = dev->data; ain_session_t *session = (ain_session_t *)handle; int ret; if (!handle) { LOG_ERR("Invalid handle"); return -EINVAL; } if (!AIN_SESSION_CHECK_MAGIC(session->magic)) { LOG_ERR("Session magic error:0x%x", session->magic); return -EFAULT; } /* In DMA reload mode only start one time is enough */ if (session->flags & AIN_SESSION_START) { LOG_DBG("session%p already started", session); return 0; } if (AUDIO_CHANNEL_ADC == session->channel_type) { if (data->adc_ref_counter) { if (data->adc_sess_stack_cur >= AIN_ADC_SESSION_MAX) { /* unlikely branch */ LOG_ERR("FIXME: invlaid stack cur%d", data->adc_sess_stack_cur); return -EFAULT; } data->adc_sess_stack[data->adc_sess_stack_cur++] = (uint32_t)session; --data->adc_ref_counter; LOG_INF("session@%p save to wait stack[%d]", session, data->adc_ref_counter); return 0; } struct aduio_in_adc_en adc_en = {0}; uint16_t input_dev[AIN_ADC_SESSION_MAX] = {0}; adc_en.input_dev_array = input_dev; if (data->adc_sess_stack_cur > 0) { uint8_t i; ain_session_t *_sess = NULL; data->adc_sess_stack[data->adc_sess_stack_cur++] = (uint32_t)session; for (i = 0; i < data->adc_sess_stack_cur; i++) { _sess = (ain_session_t *)data->adc_sess_stack[i]; if (!(_sess->flags & AIN_SESSION_CONFIG) || (_sess->flags & AIN_SESSION_START) || (_sess->channel_type != AUDIO_CHANNEL_ADC)) { /* unlikely branch */ LOG_ERR("FIXME: invalid session@%p status:0x%x", _sess, _sess->flags); data->adc_sess_stack_cur = 0; return -ESRCH; } input_dev[i] = _sess->input_dev; } adc_en.input_dev_num = data->adc_sess_stack_cur; /* always set start flag regardless of the result of start */ session->flags |= AIN_SESSION_START; ret = phy_audio_control(data->adc0_dev, PHY_CMD_ADC_DIGITAL_ENABLE, &adc_en); if (ret) { LOG_ERR("Failed to enable ADC err=%d", ret); data->adc_sess_stack_cur = 0; return ret; } for (i = 0; i < data->adc_sess_stack_cur; i++) { _sess = (ain_session_t *)data->adc_sess_stack[i]; ret = audio_in_dma_prepare(dev, _sess); if (ret) { LOG_ERR("session@%p DMA prepare error=%d", session, ret); continue; } ret = dma_start(data->dma_dev, _sess->dma_chan); if (ret) { LOG_ERR("session@%p DMA start error:%d", _sess, ret); } else { LOG_INF("session@%p started", _sess); } } data->adc_sess_stack_cur = 0; } else { input_dev[0] = session->input_dev; adc_en.input_dev_num = 1; ret = phy_audio_control(data->adc0_dev, PHY_CMD_ADC_DIGITAL_ENABLE, &adc_en); if (ret) { LOG_ERR("Failed to enable ADC err=%d", ret); return ret; } ret = audio_in_dma_prepare(dev, session); if (ret) { LOG_ERR("session@%p DMA prepare error=%d", session, ret); return ret; } ret = dma_start(data->dma_dev, session->dma_chan); if (ret) { LOG_ERR("session@%p DMA start error:%d", session, ret); } else { LOG_INF("session@%p started", session); } } } else { session->flags |= AIN_SESSION_START; ret = audio_in_dma_prepare(dev, session); if (ret) { LOG_ERR("session@%p DMA prepare error=%d", session, ret); return ret; } ret = dma_start(data->dma_dev, session->dma_chan); if (ret) { LOG_ERR("session@%p DMA start error:%d", session, ret); } else { LOG_INF("session@%p started", session); } } return ret; } static int acts_audio_in_stop(struct device *dev, void *handle) { struct ain_drv_data *data = dev->data; ain_session_t *session = (ain_session_t *)handle; int ret = 0; if (session && AIN_SESSION_CHECK_MAGIC(session->magic)) { LOG_INF("session#%p, audio in stop", session); if (data->dma_dev && session->dma_chan) ret = dma_stop(data->dma_dev, session->dma_chan); } return ret; } const struct ain_driver_api ain_drv_api = { .ain_open = acts_audio_in_open, .ain_close = acts_audio_in_close, .ain_control = acts_audio_in_control, .ain_start = acts_audio_in_start, .ain_stop = acts_audio_in_stop }; /* @brief audio-in channles initialization */ static int audio_in_init(const struct device *dev) { const struct ain_config_data *cfg = dev->config; struct ain_drv_data *data = dev->data; memset(data, 0, sizeof(struct ain_drv_data)); k_sem_init(&data->lock, 1, 1); #ifdef CONFIG_AUDIO_IN_ADC_SUPPORT data->adc0_dev = (struct device *)device_get_binding(cfg->adc0_name); if (!data->adc0_dev) { LOG_ERR("no ADC device(%s)", cfg->adc0_name); } #endif #ifdef CONFIG_AUDIO_IN_I2SRX_SUPPORT data->i2srx0_dev = (struct device *)device_get_binding(cfg->i2srx0_name); if (!data->i2srx0_dev) { LOG_ERR("no I2SRX device(%s)", cfg->i2srx0_name); } #endif #ifdef CONFIG_AUDIO_IN_SPDIFRX_SUPPORT data->spdifrx0_dev = (struct device *)device_get_binding(cfg->spdifrx0_name); if (!data->spdifrx0_dev) { LOG_ERR("no SPDIFRX device(%s)", cfg->spdifrx0_name); } #endif /* disable AVDD pulldown */ sys_write32(sys_read32(AUDIOLDO_CTL) & ~(1 << 4), AUDIOLDO_CTL); printk("ACTS-AUDIO IN initialized\n"); return 0; } static struct ain_drv_data audio_in_drv_data; static struct ain_config_data audio_in_config_data = { #ifdef CONFIG_AUDIO_IN_ADC_SUPPORT .adc0_name = CONFIG_AUDIO_ADC_0_NAME, #endif #ifdef CONFIG_AUDIO_IN_I2SRX_SUPPORT .i2srx0_name = CONFIG_AUDIO_I2SRX_0_NAME, #endif #ifdef CONFIG_AUDIO_IN_SPDIFRX_SUPPORT .spdifrx0_name = CONFIG_AUDIO_SPDIFRX_0_NAME, #endif }; DEVICE_DEFINE(audio_in, CONFIG_AUDIO_IN_ACTS_DEV_NAME, audio_in_init, NULL, &audio_in_drv_data, &audio_in_config_data, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &ain_drv_api);