| /**************************************************************************** |
| * arch/arm/src/sama5/sam_classd.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include "nuttx/arch.h" |
| |
| #include <nuttx/config.h> |
| #include <sys/types.h> |
| #include <sys/ioctl.h> |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <pthread.h> |
| #include <mqueue.h> |
| #include <nuttx/mutex.h> |
| #include <debug.h> |
| #include <assert.h> |
| #include <arch/board/board.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/mqueue.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/fs/fs.h> |
| #include <nuttx/fs/ioctl.h> |
| #include <nuttx/audio/audio.h> |
| #include <hardware/sam_classd.h> |
| |
| #include "sam_dmac.h" |
| #include "sam_memories.h" |
| #include "sam_periphclks.h" |
| #include "sam_clockconfig.h" |
| |
| #ifdef CONFIG_SAMA5D2_CLASSD |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_SCHED_WORKQUEUE |
| # error Work queue support is required (CONFIG_SCHED_WORKQUEUE) |
| #endif |
| |
| #ifndef CONFIG_AUDIO |
| # error CONFIG_AUDIO required by this driver |
| #endif |
| |
| #ifndef CONFIG_SAMA5D2_CLASSD_INFLIGHT |
| # define CONFIG_SAMA5D2_CLASSD_INFLIGHT 2 |
| #endif |
| |
| /* DMA is required */ |
| |
| #ifdef CONFIG_SAMA5_HAVE_XDMA |
| # if !defined(CONFIG_SAMA5_XDMAC0) && !defined(CONFIG_SAMA5_XDMAC1) |
| # error XDMAC required by CLASSD |
| #endif |
| |
| /* DMA timeout. The value is not critical; we just don't want the system to |
| * hang in the event that a DMA does not finish. |
| */ |
| |
| #define DMA_TIMEOUT_MS (800) |
| #define DMA_TIMEOUT_TICKS MSEC2TICK(DMA_TIMEOUT_MS) |
| |
| /* DMA Bus configuration - only the SAMA5D2 has Class D */ |
| |
| #define DMA8_FLAGS \ |
| (DMACH_FLAG_PERIPHAHB_AHB_IF1 | \ |
| DMACH_FLAG_FIFOCFG_LARGEST | \ |
| DMACH_FLAG_PERIPHH2SEL | \ |
| DMACH_FLAG_PERIPHISPERIPH | \ |
| DMACH_FLAG_PERIPHWIDTH_8BITS | \ |
| DMACH_FLAG_PERIPHCHUNKSIZE_1 | \ |
| DMACH_FLAG_MEMPID_MAX | \ |
| DMACH_FLAG_MEMAHB_AHB_IF0 | \ |
| DMACH_FLAG_MEMWIDTH_8BITS | \ |
| DMACH_FLAG_MEMINCREMENT | \ |
| DMACH_FLAG_MEMCHUNKSIZE_1 | \ |
| DMACH_FLAG_MEMBURST_4 | \ |
| DMACH_FLAG_PERIPHPID(SAM_PID_CLASSD)) |
| #define DMA16_FLAGS \ |
| (DMACH_FLAG_PERIPHAHB_AHB_IF1 | \ |
| DMACH_FLAG_FIFOCFG_LARGEST | \ |
| DMACH_FLAG_PERIPHH2SEL | \ |
| DMACH_FLAG_PERIPHISPERIPH | \ |
| DMACH_FLAG_PERIPHWIDTH_16BITS | \ |
| DMACH_FLAG_PERIPHCHUNKSIZE_1 | \ |
| DMACH_FLAG_MEMPID_MAX | \ |
| DMACH_FLAG_MEMAHB_AHB_IF0 | \ |
| DMACH_FLAG_MEMWIDTH_8BITS | \ |
| DMACH_FLAG_MEMINCREMENT | \ |
| DMACH_FLAG_MEMCHUNKSIZE_1 | \ |
| DMACH_FLAG_MEMBURST_4 | \ |
| DMACH_FLAG_PERIPHPID(SAM_PID_CLASSD)) |
| #define DMA32_FLAGS \ |
| (DMACH_FLAG_PERIPHAHB_AHB_IF1 | \ |
| DMACH_FLAG_FIFOCFG_LARGEST | \ |
| DMACH_FLAG_PERIPHH2SEL | \ |
| DMACH_FLAG_PERIPHISPERIPH | \ |
| DMACH_FLAG_PERIPHWIDTH_32BITS | \ |
| DMACH_FLAG_PERIPHCHUNKSIZE_1 | \ |
| DMACH_FLAG_MEMPID_MAX | \ |
| DMACH_FLAG_MEMAHB_AHB_IF0 | \ |
| DMACH_FLAG_MEMWIDTH_8BITS | \ |
| DMACH_FLAG_MEMINCREMENT | \ |
| DMACH_FLAG_MEMCHUNKSIZE_1 | \ |
| DMACH_FLAG_MEMBURST_4 | \ |
| DMACH_FLAG_PERIPHPID(SAM_PID_CLASSD)) |
| |
| #define DMA_INITIAL 0 |
| #define DMA_AFTER_SETUP 1 |
| #define DMA_AFTER_START 2 |
| #define DMA_CALLBACK 3 |
| #define DMA_TIMEOUT 3 |
| #define DMA_END_TRANSFER 4 |
| #define DMA_NSAMPLES 5 |
| |
| /* DSP clock settings as per Datasheet |
| * |
| * PCM Clock = (Crystal * (ND + 1 + FRACR/2^22) / (QDPMC + 1)) / 8 |
| */ |
| |
| #if BOARD_MAINOSC_FREQUENCY == (12000000) |
| # define DSP_CK_ND_12M288 56 |
| # define DSP_CK_FRACR_12M288 1442841 |
| # define DSP_CK_QDPMC_12M288 6 |
| # define DSP_CK_DIV_12M288 0 |
| # define DSP_QDAUDIO_12M288 0 |
| # define DSP_CK_ND_11M1896 59 |
| # define DSP_CK_FRACR_11M1896 885837 |
| # define DSP_CK_QDPMC_11M1896 7 |
| # define DSP_CK_DIV_11M1896 0 |
| # define DSP_QDAUDIO_11M1896 0 |
| # define DSP_CK_ND_5M6498 50 |
| # define DSP_CK_FRACR_5M6498 7066563 |
| # define DSP_CK_QDPMC_5M6498 13 |
| # define DSP_CK_DIV_5M6498 0 |
| # define DSP_QDAUDIO_5M6498 0 |
| |
| #elif BOARD_MAINOSC_FREQUENCY == (24000000) |
| # define DSP_CK_ND_12M288 27 |
| # define DSP_CK_FRACR_12M288 2818572 |
| # define DSP_CK_QDPMC_12M288 6 |
| # define DSP_CK_DIV_12M288 0 |
| # define DSP_QDAUDIO_12M288 0 |
| # define DSP_CK_ND_11M1896 29 |
| # define DSP_CK_FRACR_11M1896 442918 |
| # define DSP_CK_QDPMC_11M1896 7 |
| # define DSP_CK_DIV_11M1896 0 |
| # define DSP_QDAUDIO_11M1896 0 |
| # define DSP_CK_ND_5M6498 25 |
| # define DSP_CK_FRACR_5M6498 1436129 |
| # define DSP_CK_QDPMC_5M6498 13 |
| # define DSP_CK_DIV_5M6498 0 |
| # define DSP_QDAUDIO_5M6498 0 |
| #else |
| # error Unsupported board frequency for Class D clock configuration |
| #endif |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* CLASSD driver state */ |
| |
| enum classd_state |
| { |
| CLASSD_STATE_UNINIT = 0, /* Not yet initialized */ |
| CLASSD_STATE_RESET, /* Initialized, reset state */ |
| CLASSD_STATE_CONFIGURED, /* classd_configure() has been called */ |
| }; |
| |
| enum sam_classd_pwm_type |
| { |
| CLASSD_PWM_TYPE_TRAILING = 0, |
| CLASSD_PWM_TYPE_UNIFORM, |
| }; |
| |
| enum sam_classd_overlap_value |
| { |
| CLASSD_OVERLAP_5NS = 0, |
| CLASSD_OVERLAP_10NS, |
| CLASSD_OVERLAP_15NS, |
| CLASSD_OVERLAP_20NS, |
| }; |
| |
| enum sam_classd_dsp_frequency |
| { |
| CLASSD_DSP_FREQ_12M288 = 0, |
| CLASSD_DSP_FREQ_11M2896, |
| CLASSD_DSP_FREQ_5M6448, |
| }; |
| |
| /* NB: 11.025kHz is an unsupported frequency */ |
| |
| enum sam_classd_frame_rate |
| { |
| CLASSD_DATA_SAMPLE_FREQ_8K = 0, |
| CLASSD_DATA_SAMPLE_FREQ_16K, |
| CLASSD_DATA_SAMPLE_FREQ_32K, |
| CLASSD_DATA_SAMPLE_FREQ_48K, |
| CLASSD_DATA_SAMPLE_FREQ_96K, |
| CLASSD_DATA_SAMPLE_FREQ_22K05, |
| CLASSD_DATA_SAMPLE_FREQ_44K1, |
| CLASSD_DATA_SAMPLE_FREQ_88K2, |
| CLASSD_DATA_SAMPLE_FREQ_11K025 = CLASSD_DATA_SAMPLE_FREQ_22K05, |
| }; |
| |
| enum sam_classd_eq_mode |
| { |
| CLASSD_EQ_FLAT = 0, |
| CLASSD_EQ_BASS_BOOST_12DB, |
| CLASSD_EQ_BASS_BOOST_6DB, |
| CLASSD_EQ_BASS_CUT_12DB, |
| CLASSD_EQ_BASS_CUT_6DB, |
| CLASSD_EQ_MEDIUM_BOOST_3DB, |
| CLASSD_EQ_MEDIUM_BOOST_8DB, |
| CLASSD_EQ_MEDIUM_CUT_3DB, |
| CLASSD_EQ_MEDIUM_CUT_8DB, |
| CLASSD_EQ_TREBLE_BOOST_12DB, |
| CLASSD_EQ_TREBLE_BOOST_6DB, |
| CLASSD_EQ_TREBLE_CUT_12DB, |
| CLASSD_EQ_TREBLE_CUT_6DB, |
| }; |
| |
| enum sam_classd_mono_mode |
| { |
| CLASSD_MONO_MODE_MIX = 0, /* (left+right)/2 on both channels */ |
| CLASSD_MONO_MODE_SAT, /* (left+roght)/2 on both channels |
| * if sum is too high the result is saturated |
| */ |
| CLASSD_MONO_MODE_LEFT, /* left channel sent to both */ |
| CLASSD_MONO_MODE_RIGHT, /* left channel sent to both */ |
| }; |
| |
| /* This structure provides the config. data for the CLASSD peripheral */ |
| |
| struct sam_classd_config_s |
| { |
| bool left_channel_enabled; |
| bool right_channel_enabled; |
| bool left_channel_muted; |
| bool right_channel_muted; |
| enum sam_classd_pwm_type pwm_type; |
| bool non_overlap_mode; |
| enum sam_classd_overlap_value non_overlap_value; |
| enum sam_classd_dsp_frequency dsp_clock_frequency; |
| bool de_emphasis_enabled; |
| bool left_and_right_swapped; |
| uint32_t sample_rate; /* Configured sample rate */ |
| enum sam_classd_frame_rate frame; /* Calculated frame rate */ |
| enum sam_classd_eq_mode eq_mode; |
| bool mono_mode_selected; |
| enum sam_classd_mono_mode mono_mode; |
| uint16_t volume; |
| uint16_t balance; |
| uint8_t nchanns; /* Number of channels (1 or 2) */ |
| uint8_t nbits; /* Number of bits/sample 8 or 16) */ |
| }; |
| |
| struct classd_dev_s |
| { |
| /* We are an audio lower half driver, also the upper "half" of |
| * the driver with respect to the lower half driver. |
| * Terminology: |
| * Our "lower" half audio instances will be called dev for the publicly |
| * visible version and "priv" for the version that only this driver knows. |
| * From the point of view of this driver, it is the board lower "half" |
| * that is referred to as "lower" |
| */ |
| |
| struct audio_lowerhalf_s dev; /* Audio lower half (this device) */ |
| struct dq_queue_s pendq; /* Queue of pending buffers */ |
| struct dq_queue_s doneq; /* Queue of sent buffers */ |
| struct file mq; /* Message queue for messages */ |
| char mqname[16]; /* Our message queue name */ |
| pthread_t threadid; /* ID of our thread */ |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| volatile bool terminate; /* True: request to terminate */ |
| #endif |
| bool running; /* True: Worker thread is running */ |
| bool paused; /* True: Playing is paused */ |
| bool mute; /* True: Output is muted */ |
| #ifdef CONFIG_SAMA5D2_CLASSD_REGDEBUG |
| uintptr_t regaddr; /* Last register address read */ |
| uint32_t regval; /* Last value read from the register */ |
| #endif |
| uint8_t state; /* See classd_state */ |
| mutex_t pendlock; /* Protect pendq */ |
| volatile uint8_t inflight; /* Number of audio buffers in-flight */ |
| bool reserved; /* True: Device is reserved */ |
| struct sam_classd_config_s *config; /* Configuration */ |
| sem_t dmawait; /* Used to wait for DMA completion */ |
| struct wdog_s dmadog; /* Watchdog that for DMA timeouts */ |
| int dmares; /* DMA result */ |
| DMA_HANDLE txdma; /* SPI TX DMA handle */ |
| #ifdef CONFIG_SAMA5D2_CLASSD_DMADEBUG |
| struct sam_dmaregs_s txdmaregs[DMA_NSAMPLES]; |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| void classd_dsp_clock_config(struct classd_dev_s *priv); |
| void classd_enable_audio(struct classd_dev_s *priv, bool pmc_clock_enable); |
| void classd_configure_pmc(struct classd_dev_s *priv); |
| void classd_disable_audio(struct classd_dev_s *priv); |
| void classd_set_mode(struct classd_dev_s *priv); |
| void classd_set_interpolator(struct classd_dev_s *priv); |
| static int classd_setup(struct classd_dev_s *priv); |
| static uint32_t classd_getreg(struct classd_dev_s *priv, uint32_t regaddr); |
| static void classd_putreg(uint32_t regaddr, uint32_t regval); |
| #ifdef CONFIG_SAMA5D2_CLASSD_REGDEBUG |
| static void classd_dump_registers(const char *msg); |
| #else |
| # define classd_dump_registers(msg); |
| #endif |
| |
| /* Audio lower half functions */ |
| |
| static int classd_getcaps(struct audio_lowerhalf_s *dev, int type, |
| struct audio_caps_s *caps); |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_configure(struct audio_lowerhalf_s *dev, void *session, |
| const struct audio_caps_s *caps); |
| #else |
| static int classd_configure(struct audio_lowerhalf_s *dev, |
| const struct audio_caps_s *caps); |
| #endif |
| static int classd_shutdown(struct audio_lowerhalf_s *dev); |
| static void *classd_workerthread(pthread_addr_t pvarg); |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_start(struct audio_lowerhalf_s *dev, void *session); |
| #else |
| static int classd_start(struct audio_lowerhalf_s *dev); |
| #endif |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_stop(struct audio_lowerhalf_s *dev, void *session); |
| #else |
| static int classd_stop(struct audio_lowerhalf_s *dev); |
| #endif |
| #endif |
| #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_pause(struct audio_lowerhalf_s *dev, void *session); |
| static int classd_resume(struct audio_lowerhalf_s *dev, void *session); |
| #else |
| static int classd_pause(struct audio_lowerhalf_s *dev); |
| static int classd_resume(struct audio_lowerhalf_s *dev); |
| #endif |
| #endif |
| static int classd_enqueuebuffer(struct audio_lowerhalf_s *dev, |
| struct ap_buffer_s *apb); |
| static int classd_cancelbuffer(struct audio_lowerhalf_s *dev, |
| struct ap_buffer_s *apb); |
| static int classd_ioctl(struct audio_lowerhalf_s *dev, int cmd, |
| unsigned long arg); |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_reserve(struct audio_lowerhalf_s *dev, |
| void **session); |
| #else |
| static int classd_reserve(struct audio_lowerhalf_s *dev); |
| #endif |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_release(struct audio_lowerhalf_s *dev, void *session); |
| #else |
| static int classd_release(struct audio_lowerhalf_s *dev); |
| #endif |
| static void classd_reset(struct classd_dev_s *priv); |
| inline static uint8_t classd_get_atten(uint16_t volume, uint16_t balance); |
| #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME |
| static int classd_setvolume(struct classd_dev_s *priv, uint16_t volume); |
| #endif |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE |
| static int classd_setbalance(struct classd_dev_s *priv, uint16_t balance); |
| #endif |
| static int classd_sendbuffer(struct classd_dev_s *priv); |
| static void classd_returnbuffers(struct classd_dev_s *priv); |
| |
| /* DMA support */ |
| |
| static void classd_txcallback(DMA_HANDLE handle, void *arg, int result); |
| |
| #ifdef CONFIG_SAMA5D2_CLASSD_DMADEBUG |
| struct sam_dmaregs_s txdmaregs[DMA_NSAMPLES]; |
| # define classd_txdma_sample(s,i) sam_dmasample((s)->txdma, &(s)->txdmaregs[i]) |
| static void classd_dma_sampleinit(struct classd_dev_s *priv); |
| static void classd_dma_sampledone(struct classd_dev_s *priv); |
| #else |
| # define classd_txdma_sample(s,i) |
| # define classd_dma_sampleinit(s) |
| # define classd_dma_sampledone(s) |
| #endif |
| |
| static void classd_txcallback(DMA_HANDLE handle, void *arg, int result); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* default/constant configuration */ |
| |
| static struct sam_classd_config_s sam_classd_const = |
| { |
| #ifdef CONFIG_SAMA5D2_CLASSD_LEN |
| .left_channel_enabled = true, |
| #else |
| .left_channel_enabled = false, |
| #endif |
| #ifdef CONFIG_SAMA5D2_CLASSD_REN |
| .right_channel_enabled = true, |
| #else |
| .right_channel_enabled = false, |
| #endif |
| .left_channel_muted = false, |
| .right_channel_muted = false, |
| #ifdef CONFIG_SAMA5D2_CLASSD_PWM_UNIFORM |
| .pwm_type = CLASSD_PWM_TYPE_UNIFORM, |
| #else |
| .pwm_type = CLASSD_PWM_TYPE_TRAILING, |
| #endif |
| #ifdef CONFIG_SAMA5D2_CLASSD_NON_OVERLAP_MODE |
| .non_overlap_mode = true, |
| # if defined(CONFIG_SAMA5D2_CLASSD_NON_OVERLAP_5NS) |
| .non_overlap_value = CLASSD_OVERLAP_5NS, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_NON_OVERLAP_10NS) |
| .non_overlap_value = CLASSD_OVERLAP_10NS, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_NON_OVERLAP_15NS) |
| .non_overlap_value = CLASSD_OVERLAP_15NS, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_NON_OVERLAP_20NS) |
| .non_overlap_value = CLASSD_OVERLAP_20NS, |
| # else |
| # error Invalid SAMA5D2 Overlap value |
| #endif |
| #else |
| .non_overlap_mode = false, |
| .non_overlap_value = CLASSD_OVERLAP_5NS, |
| #endif |
| .dsp_clock_frequency = CLASSD_DSP_FREQ_12M288, |
| #ifdef CONFIG_SAMA5D2_CLASSD_DEEMP |
| .de_emphasis_enabled = 1, |
| #else |
| .de_emphasis_enabled = 0, |
| #endif |
| #ifdef CONFIG_SAMA5D2_CLASSD_SWAP |
| .left_and_right_swapped = 1, |
| #else |
| .left_and_right_swapped = 0, |
| #endif |
| |
| #if defined(CONFIG_SAMA5D2_CLASSD_FRAME_8K) |
| .sample_rate = 8000, |
| .frame = CLASSD_DATA_SAMPLE_FREQ_8K, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_FRAME_16K) |
| .sample_rate = 16000, |
| .frame = CLASSD_DATA_SAMPLE_FREQ_16K, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_FRAME_32K) |
| .sample_rate = 32000, |
| .frame = CLASSD_DATA_SAMPLE_FREQ_32K, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_FRAME_48K) |
| .sample_rate = 48000, |
| .frame = CLASSD_DATA_SAMPLE_FREQ_48K, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_FRAME_96K) |
| .sample_rate = 96000, |
| .frame = CLASSD_DATA_SAMPLE_FREQ_96K, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_FRAME_22K05) |
| .sample_rate = 22050, |
| .frame = CLASSD_DATA_SAMPLE_FREQ_22K05, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_FRAME_44K1) |
| .sample_rate = 44100, |
| .frame = CLASSD_DATA_SAMPLE_FREQ_44K1, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_FRAME_88K2) |
| .sample_rate = 88200, |
| .frame = CLASSD_DATA_SAMPLE_FREQ_88K2, |
| # else |
| #error Invalid SAMA5D2 ClassD frame rate |
| #endif |
| |
| #if defined(CONFIG_SAMA5D2_CLASSD_EQ_FLAT) |
| .eq_mode = CLASSD_EQ_FLAT, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_BB12) |
| .eq_mode = CLASSD_EQ_BASS_BOOST_12DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_BB6) |
| .eq_mode = CLASSD_EQ_BASS_BOOST_6DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_BC12) |
| .eq_mode = CLASSD_EQ_BASS_CUT_12DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_BC6) |
| .eq_mode = CLASSD_EQ_BASS_CUT_12DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_MB3) |
| .eq_mode = CLASSD_EQ_MEDIUM_BOOST_3DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_MB8) |
| .eq_mode = CLASSD_EQ_MEDIUM_BOOST_8DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_MC3) |
| .eq_mode = CLASSD_EQ_MEDIUM_CUT_3DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_MC8) |
| .eq_mode = CLASSD_EQ_MEDIUM_CUT_8DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_TB12) |
| .eq_mode = CLASSD_EQ_TREBLE_BOOST_12DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_TB6) |
| .eq_mode = CLASSD_EQ_TREBLE_BOOST_6DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_TC12) |
| .eq_mode = CLASSD_EQ_TREBLE_CUT_12DB, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_EQ_TC6) |
| .eq_mode = CLASSD_EQ_TREBLE_CUT_6DB, |
| # else |
| #error Invalid SAMA5D2 ClassD equaliser setting |
| #endif |
| |
| #ifdef CONFIG_SAMA5D2_CLASSD_MONO |
| .mono_mode_selected = true, |
| # if defined(CONFIG_SAMA5D2_CLASSD_MONOMODE_MIX) |
| .mono_mode = CLASSD_MONO_MODE_MIX, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_MONOMODE_SAT) |
| .mono_mode = CLASSD_MONO_MODE_SAT, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_MONO_MODE_LEFT) |
| .mono_mode = CLASSD_MONO_MODE_LEFT, |
| # elif defined(CONFIG_SAMA5D2_CLASSD_MONO_MODE_RIGHT) |
| .mono_mode = CLASSD_MONO_MODE_RIGHT, |
| # else |
| #error Invalids SAMA5D2 ClassD Mono mode |
| #endif |
| #else |
| .mono_mode_selected = false, |
| .mono_mode = CLASSD_MONO_MODE_MIX, |
| #endif |
| .balance = 500, |
| .volume = 0, |
| .nbits = 16, |
| .nchanns = 2, |
| }; |
| |
| static const struct audio_ops_s g_audioops = |
| { |
| classd_getcaps, /* getcaps */ |
| classd_configure, /* configure */ |
| classd_shutdown, /* shutdown */ |
| classd_start, /* start */ |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| classd_stop, /* stop */ |
| #endif |
| #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME |
| classd_pause, /* pause */ |
| classd_resume, /* resume */ |
| #endif |
| NULL, /* allocbuffer */ |
| NULL, /* freebuffer */ |
| classd_enqueuebuffer, /* enqueue_buffer */ |
| classd_cancelbuffer, /* cancel_buffer */ |
| classd_ioctl, /* ioctl */ |
| NULL, /* read */ |
| NULL, /* write */ |
| classd_reserve, /* reserve */ |
| classd_release /* release */ |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: classd_configure_pmc |
| * |
| * Description: |
| * Enable ClassD audio PMC |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void classd_configure_pmc(struct classd_dev_s *priv) |
| { |
| uint32_t regval; |
| |
| regval = PMC_PCR_GCKEN | PMC_PCR_CMD | PMC_PCR_EN | |
| PMC_GCK_DIV(0) | PMC_PCR_GCKCSS_AUDIO; |
| regval |= PMC_PCR_PID(SAM_PID_CLASSD); |
| putreg32(regval, SAM_PMC_PCR); |
| } |
| |
| /**************************************************************************** |
| * Name: classd_disable_audio |
| * |
| * Description: |
| * Disable ClassD audio |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state. |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| void classd_disable_audio(struct classd_dev_s *priv) |
| { |
| #ifdef PMC_AUDIO_PLL0_PLLEN |
| uint32_t regval; |
| |
| regval = classd_getreg(priv, SAM_PMC_AUDIO_PLL0); |
| regval &= ~(PMC_AUDIO_PLL0_PLLEN | PMC_AUDIO_PLL0_PADEN |
| | PMC_AUDIO_PLL0_PMCEN); |
| classd_putreg(SAM_PMC_AUDIO_PLL0, regval); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: classd_enable_audio |
| * |
| * Description: |
| * Enable ClassD audio |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state. |
| * pmc_clock_enable: |
| * true: the output clock of the audio PLL is sent to the PMC |
| * false: the output clock of the audio PLL is NOT sent to the PMC |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void classd_enable_audio(struct classd_dev_s *priv, bool pmc_clock_enable) |
| { |
| #ifdef PMC_AUDIO_PLL0_PLLEN |
| uint32_t regval; |
| |
| regval = classd_getreg(priv, SAM_PMC_AUDIO_PLL0); |
| regval &= ~(PMC_AUDIO_PLL0_PADEN | PMC_AUDIO_PLL0_PMCEN); |
| regval |= PMC_AUDIO_PLL0_PLLEN; |
| #ifdef CONFIG_SAMA5D2_CLASSD_PAD_CLK |
| regval |= PMC_AUDIO_PLL0_PADEN; |
| #endif |
| if (pmc_clock_enable) |
| { |
| regval |= PMC_AUDIO_PLL0_PMCEN; |
| } |
| |
| classd_putreg(SAM_PMC_AUDIO_PLL0, regval); |
| |
| /* wait for Audio PLL startup time */ |
| |
| nxsched_usleep(100); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: classd_set_mode |
| * |
| * Description: |
| * Set overall operation mode |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void classd_set_mode(struct classd_dev_s *priv) |
| { |
| uint32_t regval; |
| struct sam_classd_config_s *config = priv->config; |
| |
| regval = classd_getreg(priv, SAM_CLASSD_MR); |
| |
| if (config->left_channel_enabled) |
| { |
| regval |= CLASSD_MR_LEN_BIT; |
| } |
| else |
| { |
| regval &= ~CLASSD_MR_LEN_BIT; |
| } |
| |
| if (config->right_channel_enabled) |
| { |
| regval |= CLASSD_MR_REN_BIT; |
| } |
| else |
| { |
| regval &= ~CLASSD_MR_REN_BIT; |
| } |
| |
| if (config->left_channel_muted) |
| { |
| regval |= CLASSD_MR_LMUTE_BIT; |
| } |
| else |
| { |
| regval &= ~CLASSD_MR_LMUTE_BIT; |
| } |
| |
| if (config->right_channel_muted) |
| { |
| regval |= CLASSD_MR_RMUTE_BIT; |
| } |
| else |
| { |
| regval &= ~CLASSD_MR_RMUTE_BIT; |
| } |
| |
| if (config->pwm_type == CLASSD_PWM_TYPE_UNIFORM) |
| { |
| regval |= CLASSD_MR_PWMTYP_BIT; |
| } |
| else |
| { |
| regval &= ~CLASSD_MR_PWMTYP_BIT; |
| } |
| |
| if (config->non_overlap_mode) |
| { |
| regval |= CLASSD_MR_NOVR_BIT; |
| regval &= ~CLASSD_MR_NOVRVAL_MASK; |
| regval |= CLASSD_MR_NOVR(config->non_overlap_value); |
| } |
| else |
| { |
| regval &= ~CLASSD_MR_NOVR_BIT; |
| } |
| |
| classd_putreg(SAM_CLASSD_MR, regval); |
| |
| classd_dump_registers("After mode set"); |
| } |
| |
| /**************************************************************************** |
| * Name: classd_set_interpolator |
| * |
| * Description: |
| * Set sample rate and other interpolator configuration |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void classd_set_interpolator(struct classd_dev_s *priv) |
| { |
| uint32_t regval; |
| struct sam_classd_config_s *config = priv->config; |
| uint32_t samp_freq = config->sample_rate; |
| |
| switch (samp_freq) |
| { |
| case 8000: |
| { |
| config->frame = CLASSD_DATA_SAMPLE_FREQ_8K; |
| config->dsp_clock_frequency = CLASSD_DSP_FREQ_12M288; |
| } |
| break; |
| case 11025: |
| { |
| /* NB: 11.025kHz is an unsupported frequency, but can be made to |
| * work, albeit with some audio distortion. |
| */ |
| |
| config->frame = CLASSD_DATA_SAMPLE_FREQ_11K025; |
| config->dsp_clock_frequency = CLASSD_DSP_FREQ_5M6448; |
| } |
| break; |
| case 16000: |
| { |
| config->frame = CLASSD_DATA_SAMPLE_FREQ_16K; |
| config->dsp_clock_frequency = CLASSD_DSP_FREQ_12M288; |
| } |
| break; |
| case 32000: |
| { |
| config->frame = CLASSD_DATA_SAMPLE_FREQ_32K; |
| config->dsp_clock_frequency = CLASSD_DSP_FREQ_12M288; |
| } |
| break; |
| case 48000: |
| { |
| config->frame = CLASSD_DATA_SAMPLE_FREQ_48K; |
| config->dsp_clock_frequency = CLASSD_DSP_FREQ_12M288; |
| } |
| break; |
| case 96000: |
| { |
| config->frame = CLASSD_DATA_SAMPLE_FREQ_96K; |
| config->dsp_clock_frequency = CLASSD_DSP_FREQ_12M288; |
| } |
| break; |
| case 22050: |
| { |
| config->frame = CLASSD_DATA_SAMPLE_FREQ_22K05; |
| config->dsp_clock_frequency = CLASSD_DSP_FREQ_11M2896; |
| } |
| break; |
| case 44100: |
| { |
| config->frame = CLASSD_DATA_SAMPLE_FREQ_44K1; |
| config->dsp_clock_frequency = CLASSD_DSP_FREQ_11M2896; |
| } |
| break; |
| case 88200: |
| { |
| config->frame = CLASSD_DATA_SAMPLE_FREQ_88K2; |
| config->dsp_clock_frequency = CLASSD_DSP_FREQ_11M2896; |
| } |
| break; |
| default: |
| auderr("ERROR: Invalid interpolator settings\n"); |
| break; |
| } |
| |
| regval = classd_getreg(priv, SAM_CLASSD_INTPMR); |
| |
| regval &= ~CLASSD_INTPMR_FRAME_MASK; |
| regval |= CLASSD_INTRMR_FRAME(config->frame); |
| |
| if (config->dsp_clock_frequency == CLASSD_DSP_FREQ_12M288) |
| { |
| regval &= ~CLASSD_INTPMR_DSPCLKF_BIT; |
| } |
| else |
| { |
| /* NB. This is also used for non-supported 11.025kHz sample f */ |
| |
| regval |= CLASSD_INTPMR_DSPCLKF_BIT; |
| } |
| |
| if (config->de_emphasis_enabled) |
| { |
| regval |= CLASSD_INTPMR_DEEMP_BIT; |
| } |
| else |
| { |
| regval &= ~CLASSD_INTPMR_DEEMP_BIT; |
| } |
| |
| if (config->left_and_right_swapped) |
| { |
| regval |= CLASSD_INTPMR_SWAP_BIT; |
| } |
| else |
| { |
| regval &= ~CLASSD_INTPMR_SWAP_BIT; |
| } |
| |
| regval &= ~CLASSD_INTPMR_EQCFG_MASK; |
| regval |= CLASSD_INTRMR_EQCFG(config->eq_mode); |
| |
| if (config->mono_mode_selected) |
| { |
| regval |= CLASSD_INTPMR_MONO_BIT; |
| regval &= ~CLASSD_INTPMR_MONOMODE_MASK; |
| regval |= CLASSD_INTRMR_MONOMODE(config->mono_mode); |
| } |
| else |
| { |
| regval &= ~CLASSD_INTPMR_MONO_BIT; |
| } |
| |
| classd_putreg(SAM_CLASSD_INTPMR, regval); |
| classd_dump_registers("After interpolator setup"); |
| } |
| |
| /**************************************************************************** |
| * Name: classd_dsp_clock_config |
| * |
| * Description: |
| * Configure the ClassD PLL clocks. |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state. |
| * |
| * Returned Value: |
| * |
| ****************************************************************************/ |
| |
| void classd_dsp_clock_config(struct classd_dev_s *priv) |
| { |
| uint32_t regval; |
| struct sam_classd_config_s *config; |
| #ifdef CONFIG_CLASSD_REGDEBUG |
| /* double check clock settings */ |
| |
| uint32_t divider; |
| uint32_t fracr; |
| uint32_t qdpmc; |
| uint64_t clk = BOARD_MAINOSC_FREQUENCY; |
| uint32_t pll0 = classd_getreg(priv, SAM_PMC_AUDIO_PLL0); |
| uint32_t pll1 = classd_getreg(priv, SAM_PMC_AUDIO_PLL1); |
| #endif |
| |
| config = priv->config; |
| |
| /* reset audio clock */ |
| |
| regval = classd_getreg(priv, SAM_PMC_AUDIO_PLL0); |
| regval &= ~(PMC_AUDIO_PLL0_RESETN | PMC_AUDIO_PLL0_PLLEN); |
| classd_putreg(SAM_PMC_AUDIO_PLL0, regval); |
| |
| regval |= PMC_AUDIO_PLL0_RESETN; |
| classd_putreg(SAM_PMC_AUDIO_PLL0, regval); |
| |
| /* Configure values */ |
| |
| switch (config->dsp_clock_frequency) |
| { |
| case CLASSD_DSP_FREQ_5M6448: |
| { |
| /* This is a non-standard, unsupported, mode. |
| * We set up the PLL to use 22.05Khz filtering but clock it |
| * at half the speed (5.6498MHz). |
| */ |
| |
| regval = PMC_AUDIO_PLL0_ND(DSP_CK_ND_5M6498) | |
| PMC_AUDIO_PLL0_QDPMC(DSP_CK_QDPMC_5M6498) | |
| PMC_AUDIO_PLL0_FLT(PMC_AUDIO_PLL0_PLLFLT_STD) | |
| PMC_AUDIO_PLL0_RESETN; |
| classd_putreg(SAM_PMC_AUDIO_PLL0, regval); |
| |
| regval = PMC_AUDIO_PLL1_FRACR(DSP_CK_FRACR_5M6498) | |
| PMC_AUDIO_PLL1_DIV(DSP_CK_DIV_5M6498) | |
| PMC_AUDIO_PLL1_QDAUDIO(DSP_QDAUDIO_5M6498) | |
| PMC_AUDIO_PLL1_DIV(2); |
| classd_putreg(SAM_PMC_AUDIO_PLL1, regval); |
| } |
| break; |
| case CLASSD_DSP_FREQ_11M2896: |
| { |
| regval = PMC_AUDIO_PLL0_ND(DSP_CK_ND_11M1896) | |
| PMC_AUDIO_PLL0_QDPMC(DSP_CK_QDPMC_11M1896) | |
| PMC_AUDIO_PLL0_FLT(PMC_AUDIO_PLL0_PLLFLT_STD) | |
| PMC_AUDIO_PLL0_RESETN; |
| classd_putreg(SAM_PMC_AUDIO_PLL0, regval); |
| |
| regval = PMC_AUDIO_PLL1_FRACR(DSP_CK_FRACR_11M1896) | |
| PMC_AUDIO_PLL1_DIV(DSP_CK_DIV_11M1896) | |
| PMC_AUDIO_PLL1_QDAUDIO(DSP_QDAUDIO_11M1896) | |
| PMC_AUDIO_PLL1_DIV(2); |
| classd_putreg(SAM_PMC_AUDIO_PLL1, regval); |
| } |
| break; |
| default: /* CLASSD_DSP_FREQ_12M288 */ |
| { |
| regval = PMC_AUDIO_PLL0_ND(DSP_CK_ND_12M288) | |
| PMC_AUDIO_PLL0_QDPMC(DSP_CK_QDPMC_12M288) | |
| PMC_AUDIO_PLL0_FLT(PMC_AUDIO_PLL0_PLLFLT_STD) | |
| PMC_AUDIO_PLL0_RESETN; |
| classd_putreg(SAM_PMC_AUDIO_PLL0, regval); |
| |
| regval = PMC_AUDIO_PLL1_FRACR(DSP_CK_FRACR_12M288) | |
| PMC_AUDIO_PLL1_DIV(DSP_CK_DIV_12M288) | |
| PMC_AUDIO_PLL1_QDAUDIO(DSP_QDAUDIO_12M288) | |
| PMC_AUDIO_PLL1_DIV(2); |
| classd_putreg(SAM_PMC_AUDIO_PLL1, regval); |
| } |
| break; |
| } |
| #endif |
| |
| classd_dump_registers("After dsp clock setup"); |
| |
| #ifdef CONFIG_CLASSD_REGDEBUG |
| divider = (pll0 & PMC_AUDIO_PLL0_ND_MASK) >> PMC_AUDIO_PLL0_ND_SHIFT; |
| fracr = (pll1 & PMC_AUDIO_PLL1_FRACR_MASK) >> PMC_AUDIO_PLL1_FRACR_SHIFT; |
| qdpmc = (pll0 & PMC_AUDIO_PLL0_QDPMC_MASK) >> PMC_AUDIO_PLL0_QDPMC_SHIFT; |
| |
| clk *= ((divider + 1) << 22) + fracr; |
| clk /= 1 << 22; |
| clk /= (qdpmc + 1); |
| clk /= 8; |
| |
| audinfo("INFO: audio pll clk = %" PRId64 "\n", clk); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: classd_setup |
| * |
| * Description: |
| * Configure the ClassD peripheral. |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state. |
| * |
| * Returned Value: |
| * OK (or not) |
| * |
| ****************************************************************************/ |
| |
| static int classd_setup(struct classd_dev_s *priv) |
| { |
| uint32_t regval; |
| |
| classd_set_mode(priv); |
| classd_set_interpolator(priv); |
| classd_dsp_clock_config(priv); |
| |
| regval = classd_getreg(priv, SAM_CLASSD_INTSR); |
| if (regval != 0) |
| { |
| return -EINVAL; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_getreg |
| * |
| * Description: |
| * Read the value of a CLASSD register. |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state |
| * offset - The offset to the register to read |
| * |
| * Returned Value: |
| * Value of the register requested |
| * |
| ****************************************************************************/ |
| |
| static uint32_t classd_getreg(struct classd_dev_s *priv, uint32_t regaddr) |
| { |
| return getreg32(regaddr); |
| } |
| |
| /**************************************************************************** |
| * Name: classd_putreg |
| * |
| * Description: |
| * Set the value of a CLASSD register. |
| * |
| * Input Parameters: |
| * regaddr - The register to write |
| * regval - The value to write to the register |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void classd_putreg(uint32_t regaddr, uint32_t regval) |
| { |
| putreg32(regval, regaddr); |
| } |
| |
| /**************************************************************************** |
| * Name: classd_dump_registers |
| * |
| * Description: |
| * Dump the contents of all CLASSD control registers |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5D2_CLASSD_REGDEBUG |
| static void classd_dump_registers(const char *msg) |
| { |
| audinfo("CLASSD Registers: %s\n", msg); |
| putreg32(PMC_PCR_PID(SAM_PID_CLASSD), SAM_PMC_PCR); |
| audinfo("\tPMC_PCR %" PRIx32 "\n", getreg32(SAM_PMC_PCR)); |
| audinfo(" PLL0 %" PRIx32 "\t\tPLL1 %" PRIx32 "\n", |
| getreg32(SAM_PMC_AUDIO_PLL0), getreg32(SAM_PMC_AUDIO_PLL1)); |
| audinfo("\tCR: %" PRIx32 "\tMR: %" PRIx32 "\tintMR %" PRIx32 "\tintSR %"\ |
| PRIx32 "\t\tTHR %" PRIx32 "\n", |
| getreg32(SAM_CLASSD_CR), |
| getreg32(SAM_CLASSD_MR), |
| getreg32(SAM_CLASSD_INTPMR), |
| getreg32(SAM_CLASSD_INTSR), |
| getreg32(SAM_CLASSD_THR)); |
| audinfo("\tIDR: %" PRIx32 "\tIMR %" PRIx32 "\t\tISR %"\ |
| PRIx32 "\t\tWPMR %" PRIx32 "\n", |
| getreg32(SAM_CLASSD_IDR), |
| getreg32(SAM_CLASSD_IMR), |
| getreg32(SAM_CLASSD_ISR), |
| getreg32(SAM_CLASSD_WPMR)); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: classd_dma_sampleinit |
| * |
| * Description: |
| * Initialize sampling of DMA registers |
| * (if CONFIG_SAMA5D2_CLASSD_DMADEBUG) |
| * |
| * Input Parameters: |
| * *priv - Pointer to device structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5D2_CLASSD_DMADEBUG |
| static void classd_dma_sampleinit(struct classd_dev_s *priv) |
| { |
| /* Put contents of register samples into a known state */ |
| |
| memset(priv->txdmaregs, 0xff, |
| DMA_NSAMPLES * sizeof(struct sam_dmaregs_s)); |
| |
| /* Then get the initial samples */ |
| |
| sam_dmasample(priv->txdma, &priv->txdmaregs[DMA_INITIAL]); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: classd_dma_sampledone |
| * |
| * Description: |
| * Dump sampled DMA registers |
| * |
| * Input Parameters: |
| * priv - Pointer to device configuration |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5D2_CLASSD_DMADEBUG |
| static void classd_dma_sampledone(struct classd_dev_s *priv) |
| { |
| /* Sample the final registers */ |
| |
| sam_dmasample(priv->txdma, &priv->txdmaregs[DMA_END_TRANSFER]); |
| |
| /* Then dump the sampled DMA registers */ |
| |
| /* Initial register values */ |
| |
| sam_dmadump(priv->txdma, &priv->txdmaregs[DMA_INITIAL], |
| "TX: Initial Registers"); |
| |
| /* Register values after DMA setup */ |
| |
| sam_dmadump(priv->txdma, &priv->txdmaregs[DMA_AFTER_SETUP], |
| "TX: After DMA Setup"); |
| |
| /* Register values after DMA start */ |
| |
| sam_dmadump(priv->txdma, &priv->txdmaregs[DMA_AFTER_START], |
| "TX: After DMA Start"); |
| |
| /* Register values at the time of the TX and RX DMA callbacks |
| * -OR- DMA timeout. |
| * |
| * If the DMA timedout, then there will not be any RX DMA |
| * callback samples. There is probably no TX DMA callback |
| * samples either, but we don't know for sure. |
| */ |
| |
| sam_dmadump(priv->txdma, &priv->txdmaregs[DMA_CALLBACK], |
| "TX: At DMA callback"); |
| |
| /* Register values at the end of the DMA */ |
| |
| sam_dmadump(priv->txdma, &priv->txdmaregs[DMA_END_TRANSFER], |
| "TX: At End-of-Transfer"); |
| } |
| #endif /* CONFIG_SAMA5D2_CLASSD_DMADEBUG */ |
| |
| /**************************************************************************** |
| * Name: classd_dmatimeout |
| * |
| * Description: |
| * The watchdog timeout setup when a has expired without completion of a |
| * DMA. |
| * |
| * Input Parameters: |
| * arg - The argument |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Always called from the interrupt level with interrupts disabled. |
| * |
| ****************************************************************************/ |
| |
| static void classd_dmatimeout(wdparm_t arg) |
| { |
| struct classd_dev_s *priv = (struct classd_dev_s *)arg; |
| DEBUGASSERT(priv != NULL); |
| |
| /* Sample DMA registers at the time of the timeout */ |
| |
| classd_txdma_sample(priv, DMA_CALLBACK); |
| |
| sam_dmastop(priv->txdma); |
| |
| /* Report timeout result, perhaps overwriting any failure reports from |
| * the TX callback. |
| */ |
| |
| priv->dmares = -ETIMEDOUT; |
| |
| /* Then wake up the waiting thread */ |
| |
| nxsem_post(&priv->dmawait); |
| } |
| |
| /**************************************************************************** |
| * Name: classd_txcallback |
| * |
| * Description: |
| * This callback function is invoked at the completion of the CLASSD DMA. |
| * |
| * Input Parameters: |
| * handle - The DMA handler |
| * arg - A pointer to the chip select struction |
| * result - The result of the DMA transfer |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void classd_txcallback(DMA_HANDLE handle, void *arg, int result) |
| { |
| struct classd_dev_s *priv = (struct classd_dev_s *)arg; |
| DEBUGASSERT(priv != NULL && priv->running); |
| |
| /* Cancel the watchdog timeout */ |
| |
| wd_cancel(&priv->dmadog); |
| |
| classd_txdma_sample(priv, DMA_CALLBACK); |
| |
| priv->dmares = result; |
| |
| nxsem_post(&priv->dmawait); |
| } |
| |
| /**************************************************************************** |
| * Name: classd_reset |
| * |
| * Description: |
| * Reset peripheral to default state |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void classd_reset(struct classd_dev_s *priv) |
| { |
| DEBUGASSERT(priv); |
| |
| audinfo("CLASSD reset\n"); |
| |
| /* get exclusive access to the CLASSD peripheral */ |
| |
| nxmutex_lock(&priv->pendlock); |
| |
| /* disable all interrupts and HW reset */ |
| |
| classd_putreg(SAM_CLASSD_IER, 0); |
| classd_putreg(SAM_CLASSD_CR, CLASSD_CR_SWRST_BIT); |
| |
| /* Release anything pending - needed? How? */ |
| |
| priv->state = CLASSD_STATE_RESET; |
| |
| nxmutex_unlock(&priv->pendlock); |
| } |
| |
| /**************************************************************************** |
| * Name: classd_get_atten |
| * |
| * Description |
| * Calculate the right and left attenuation values based on the |
| * volume and balance settings. |
| * |
| * The range is limited to 0..78 since any value <77 will be treated as |
| * mutes. |
| * |
| * Input Parameters: |
| * volume - The required volume, 0-1000 |
| * balnce - The required balance, 0-500 (500 is centre) |
| * |
| * Returned Value: |
| * Scaled and range limited attenuation value |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME |
| inline static uint8_t classd_get_atten(uint16_t volume, uint16_t balance) |
| { |
| int scaled_vol; |
| scaled_vol = 78 - ((78 * volume * balance) / (AUDIO_VOLUME_MAX * |
| AUDIO_BALANCE_CENTER)); |
| if (scaled_vol < 0) |
| { |
| return 0; |
| } |
| else |
| |
| return (uint8_t)scaled_vol & 0x7f; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: classd_setvolume |
| * |
| * Description: |
| * Set the required volume level, taking balance into account |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state |
| * vol - the requirede volume, range 0-1000 |
| * |
| * Returned Value: |
| * OK or -EDOM if requested volume is out of range |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME |
| static int classd_setvolume(struct classd_dev_s *priv, uint16_t vol) |
| { |
| struct sam_classd_config_s *config; |
| uint32_t left; |
| uint32_t right; |
| uint32_t bal; |
| uint32_t regval; |
| |
| if (vol < AUDIO_VOLUME_MIN || vol > AUDIO_VOLUME_MAX) |
| { |
| return -EDOM; |
| } |
| |
| config = priv->config; |
| |
| config->volume = vol; |
| bal = config->balance; |
| |
| /* Calculate the attenuation value to send to the peripheral */ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE |
| left = classd_get_atten(vol, AUDIO_BALANCE_RIGHT - bal); |
| right = classd_get_atten(vol, bal); |
| #else |
| left = classd_get_atten(volume, AUDIO_BALANCE_CENTER); |
| right = classd_get_atten(volume, AUDIO_BALANCE_CENTER); |
| #endif |
| |
| /* Set the volume */ |
| |
| regval = classd_getreg(priv, SAM_CLASSD_INTPMR); |
| regval &= ~CLASSD_INTPMR_ATTL_MASK; |
| regval &= ~CLASSD_INTPMR_ATTR_MASK; |
| regval |= CLASSD_VOL_LEFT(left); |
| regval |= CLASSD_VOL_RIGHT(right); |
| |
| classd_putreg(SAM_CLASSD_INTPMR, regval); |
| |
| return OK; |
| } |
| #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ |
| |
| /**************************************************************************** |
| * Name: classd_balance |
| * |
| * Description: |
| * Set the balance for the CLASSD device. |
| * There is no built in balance feature so we save the value, which will |
| * be used during volume setting to offset each channel's volume. |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state |
| * bal - the requirede volume, range 0-1000 |
| * |
| * Returned Value: |
| * OK or -EDOM if value is out of range |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE |
| static int classd_setbalance(struct classd_dev_s *priv, uint16_t bal) |
| { |
| struct sam_classd_config_s *config; |
| |
| if (bal < AUDIO_BALANCE_LEFT || bal > AUDIO_BALANCE_RIGHT) |
| { |
| return -EDOM; |
| } |
| |
| config = priv->config; |
| config->balance = bal; |
| |
| classd_setvolume(priv, config->volume); |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: classd_getcaps |
| * |
| * Description: Get the audio device capabilities |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral state |
| * type - The type of capability being requested |
| * caps - struct of capabilities to be returned |
| * |
| * Returned Value: |
| * OK or -EDOM if value is out of range |
| * |
| ****************************************************************************/ |
| |
| static int classd_getcaps(struct audio_lowerhalf_s *dev, int type, |
| struct audio_caps_s *caps) |
| { |
| audinfo("type=%d\n", type); |
| |
| /* Validate the structure */ |
| |
| DEBUGASSERT(caps->ac_len >= sizeof(struct audio_caps_s)); |
| |
| /* Fill in the caller's structure based on requested info */ |
| |
| caps->ac_format.hw = 0; |
| caps->ac_controls.w = 0; |
| caps->ac_controls.b[0] = 0; |
| caps->ac_controls.b[1] = 0; |
| |
| switch (caps->ac_type) |
| { |
| /* Caller is querying for the types of units we support */ |
| |
| case AUDIO_TYPE_QUERY: |
| |
| /* Provide our overall capabilities. The interfacing software |
| * must then call us back for specific info for each capability. |
| */ |
| |
| caps->ac_channels = 2; /* Stereo output */ |
| |
| switch (caps->ac_subtype) |
| { |
| case AUDIO_TYPE_QUERY: |
| /* We don't decode any formats! Only something above us in |
| * the audio stream can perform decoding on our behalf. |
| */ |
| |
| /* The types of audio units we implement */ |
| |
| caps->ac_controls.b[0] = AUDIO_TYPE_OUTPUT | |
| AUDIO_TYPE_FEATURE ; |
| |
| break; |
| |
| case AUDIO_FMT_PCM: |
| caps->ac_controls.b[0] = AUDIO_SUBFMT_PCM_S16_LE; |
| break; |
| |
| default: |
| caps->ac_controls.b[0] = AUDIO_SUBFMT_PCM_S16_LE; |
| break; |
| } |
| |
| break; |
| |
| /* Provide capabilities of our OUTPUT unit */ |
| |
| case AUDIO_TYPE_OUTPUT: |
| |
| caps->ac_channels = 2; /* stereo output */ |
| |
| switch (caps->ac_subtype) |
| { |
| case AUDIO_TYPE_QUERY: |
| |
| /* Report the Sample rates we support */ |
| |
| caps->ac_controls.b[0] = (uint8_t)(AUDIO_SAMP_RATE_8K | |
| AUDIO_SAMP_RATE_11K | |
| AUDIO_SAMP_RATE_16K | |
| AUDIO_SAMP_RATE_22K | |
| AUDIO_SAMP_RATE_32K | |
| AUDIO_SAMP_RATE_44K | |
| AUDIO_SAMP_RATE_48K | |
| AUDIO_SAMP_RATE_88K | |
| AUDIO_SAMP_RATE_96K) ; |
| break; |
| |
| default: |
| break; |
| } |
| |
| break; |
| |
| /* Provide capabilities of our FEATURE units */ |
| |
| case AUDIO_TYPE_FEATURE: |
| /* If the sub-type is UNDEF, |
| * then report the Feature Units we support |
| */ |
| |
| if (caps->ac_subtype == AUDIO_FU_UNDEF) |
| { |
| /* Fill in the ac_controls section with |
| * the Feature Units we have |
| */ |
| |
| caps->ac_controls.b[0] = AUDIO_FU_MUTE | |
| AUDIO_FU_VOLUME; |
| caps->ac_controls.b[1] = AUDIO_FU_BALANCE >> 8; |
| } |
| else |
| { |
| /* TODO: Do we need to provide specific info for the |
| * Feature Units, such as volume setting ranges, etc.? |
| */ |
| } |
| |
| break; |
| |
| /* All others we don't support */ |
| |
| default: |
| |
| /* Zero out the fields to indicate no support */ |
| |
| caps->ac_subtype = 0; |
| caps->ac_channels = 0; |
| |
| break; |
| } |
| |
| /* Return the length of the audio_caps_s struct for validation of |
| * proper Audio device type. |
| */ |
| |
| audinfo("Return %d\n", caps->ac_len); |
| return caps->ac_len; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_configure |
| * |
| * Description: |
| * Configure the audio device for the specified mode of operation. |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral |
| * session - The audio session |
| * caps - struct of capabilities to be configured |
| * |
| * Returned Value: |
| * OK or negated error value |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_configure(struct audio_lowerhalf_s *dev, |
| void *session, const struct audio_caps_s *caps) |
| #else |
| static int classd_configure(struct audio_lowerhalf_s *dev, |
| const struct audio_caps_s *caps) |
| #endif |
| { |
| int ret = OK; |
| |
| struct classd_dev_s *priv = (struct classd_dev_s *)dev; |
| DEBUGASSERT(priv != NULL); |
| |
| struct sam_classd_config_s *config = priv->config; |
| |
| DEBUGASSERT(caps != NULL); |
| audinfo("ac_type: %d\n", caps->ac_type); |
| |
| /* Process the configure operation */ |
| |
| switch (caps->ac_type) |
| { |
| case AUDIO_TYPE_FEATURE: |
| { |
| audinfo(" AUDIO_TYPE_FEATURE\n"); |
| |
| /* Process based on Feature Unit */ |
| |
| switch (caps->ac_format.hw) |
| { |
| #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME |
| case AUDIO_FU_VOLUME: |
| { |
| /* Set the volume */ |
| |
| uint16_t volume = caps->ac_controls.hw[0]; |
| audinfo(" Volume: %d\n", volume); |
| classd_setvolume(priv, volume); |
| } |
| break; |
| #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE |
| case AUDIO_FU_BALANCE: |
| { |
| uint16_t balance = caps->ac_controls.hw[0]; |
| audinfo(" Balance: %d\n", balance); |
| classd_setbalance(priv, balance); |
| } |
| break; |
| #endif /* CONFIG_AUDIO_EXCLUDE_BALANCE */ |
| |
| default: |
| { |
| auderr(" ERROR: Unrecognized hw feature: %d\n", |
| caps->ac_format.hw); |
| ret = -ENOTTY; |
| } |
| break; |
| } |
| } |
| break; |
| |
| case AUDIO_TYPE_OUTPUT: |
| { |
| audinfo(" AUDIO_TYPE_OUTPUT:\n"); |
| audinfo(" Number of channels: %u\n", caps->ac_channels); |
| audinfo(" Sample rate: %u\n", caps->ac_controls.hw[0]); |
| audinfo(" Bits per sample: %d\n", caps->ac_controls.b[2]); |
| |
| /* Verify that all of the requested values are supported */ |
| |
| ret = -EINVAL; |
| |
| if ((caps->ac_controls.hw[0] != 8000) && |
| (caps->ac_controls.hw[0] != 11025) && |
| (caps->ac_controls.hw[0] != 16000) && |
| (caps->ac_controls.hw[0] != 22050) && |
| (caps->ac_controls.hw[0] != 32000) && |
| (caps->ac_controls.hw[0] != 44100) && |
| (caps->ac_controls.hw[0] != 48000) && |
| (caps->ac_controls.hw[0] != 88200) && |
| (caps->ac_controls.hw[0] != 96000)) |
| { |
| auderr("ERROR: Unsupported sample rate: %d\n", |
| caps->ac_controls.hw[0]); |
| break; |
| } |
| |
| if (caps->ac_channels != 1 && caps->ac_channels != 2) |
| { |
| auderr("ERROR: Unsupported sample rate: %d\n", |
| caps->ac_controls.hw[0]); |
| break; |
| } |
| |
| /* Note: strictly, this peripheral needs 16 bit values but |
| * by allowing for 8 bits it at least allows the file to be played |
| * even if it is then rather quiet. |
| */ |
| |
| if (caps->ac_controls.b[2] != 16 && caps->ac_controls.b[2] != 8) |
| { |
| auderr("ERROR: Unsupported bits per sample: %d\n", |
| caps->ac_controls.b[2]); |
| break; |
| } |
| |
| /* Save the current stream configuration */ |
| |
| config->sample_rate = caps->ac_controls.hw[0]; |
| config->nchanns = caps->ac_channels; |
| config->nbits = caps->ac_controls.b[2]; |
| |
| classd_disable_audio(priv); |
| classd_reset(priv); |
| ret = classd_setup(priv); |
| classd_enable_audio(priv, true); |
| classd_dump_registers("After playback setup"); |
| ret = OK; |
| } |
| break; |
| #if 0 |
| case AUDIO_TYPE_PROCESSING: |
| { |
| audinfo(" INFO: Configure Processing\n"); |
| } |
| break; |
| #endif |
| default: |
| { |
| auderr(" ERROR: Unsupported configure command\n"); |
| ret = -ENOTTY; |
| } |
| break; |
| } |
| |
| classd_dump_registers("After system configuration"); |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_shutdown |
| * |
| * Description: |
| * Shutdown the driver . |
| * |
| * Description: |
| * Configure the audio device for the specified mode of operation. |
| * |
| * Returned Value: |
| * OK |
| * |
| ****************************************************************************/ |
| |
| static int classd_shutdown(struct audio_lowerhalf_s *dev) |
| { |
| struct classd_dev_s *priv = (struct classd_dev_s *)dev; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| classd_disable_audio(priv); |
| audinfo("Shutdown OK\n"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_returnbuffers |
| * |
| * Description: |
| * This function is called after the complete of one or more data |
| * transfers. This function will empty the done queue and release our |
| * reference to each buffer. |
| * |
| ****************************************************************************/ |
| |
| static void classd_returnbuffers(struct classd_dev_s *priv) |
| { |
| struct ap_buffer_s *apb; |
| irqstate_t flags; |
| |
| /* The doneq and in-flight values might be accessed from the interrupt |
| * level in some implementations. Not the best design. But we will |
| * use interrupt controls to protect against that possibility. |
| */ |
| |
| flags = enter_critical_section(); |
| while (dq_peek(&priv->doneq) != NULL) |
| { |
| /* Take the next buffer from the queue of completed transfers */ |
| |
| apb = (struct ap_buffer_s *)dq_remfirst(&priv->doneq); |
| leave_critical_section(flags); |
| |
| audinfo("Returning: apb=%p curbyte=%d nbytes=%d flags=%04x\n", |
| apb, apb->curbyte, apb->nbytes, apb->flags); |
| |
| /* Are we returning the final buffer in the stream? */ |
| |
| if ((apb->flags & AUDIO_APB_FINAL) != 0) |
| { |
| /* Both the pending and the done queues should be empty and there |
| * should be no buffers in-flight. |
| */ |
| |
| DEBUGASSERT(dq_empty(&priv->doneq) && dq_empty(&priv->pendq) && |
| priv->inflight == 0); |
| |
| /* Set the terminating flag. This will, eventually, cause the |
| * worker thread to exit (if it is not already terminating). |
| */ |
| |
| audinfo("Terminating\n"); |
| priv->terminate = true; |
| } |
| |
| /* Release our reference to the audio buffer */ |
| |
| apb_free(apb); |
| |
| /* Send the buffer back up to the previous level. */ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK, NULL); |
| #else |
| priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK); |
| #endif |
| flags = enter_critical_section(); |
| } |
| |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Name: classd_sendbuffer |
| * |
| * Description: |
| * Start the transfer of an audio buffer to the peripheral. This |
| * will not wait for the transfer to complete but will return immediately. |
| * The classd_senddone callback will be invoked when the transfer |
| * completes, stimulating the worker thread to call this function again. |
| * |
| * Input Parameters: |
| * priv - A reference to the CLASSD peripheral. |
| * |
| * Returned Value: |
| * OK or negated error value |
| ****************************************************************************/ |
| |
| static int classd_sendbuffer(struct classd_dev_s *priv) |
| { |
| struct sam_classd_config_s *config; |
| struct ap_buffer_s *apb; |
| struct audio_msg_s msg; |
| irqstate_t flags; |
| uintptr_t samp; |
| uintptr_t paddr; |
| uintptr_t maddr; |
| apb_samp_t nbytes; |
| int ret; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| config = priv->config; |
| |
| /* Loop while there are audio buffers to be sent and we have few than |
| * CONFIG_SAMA5D2_INFLIGHT then "in-flight" |
| * |
| * The 'inflight' value might be modified from the interrupt level in some |
| * implementations. We will use interrupt controls to protect against |
| * that possibility. |
| * |
| * The 'pendq', on the other hand, is protected via a semaphore. Let's |
| * hold the semaphore while we are busy here and disable the interrupts |
| * only while accessing 'inflight'. |
| */ |
| |
| if (config->nchanns == 2) |
| { |
| if (config->nbits == 16) |
| { |
| sam_dmaconfig(priv->txdma, DMA32_FLAGS); |
| } |
| else |
| { |
| sam_dmaconfig(priv->txdma, DMA16_FLAGS); |
| } |
| } |
| else |
| { |
| if (config->nbits == 16) |
| { |
| sam_dmaconfig(priv->txdma, DMA16_FLAGS); |
| } |
| else |
| { |
| sam_dmaconfig(priv->txdma, DMA8_FLAGS); |
| } |
| } |
| |
| classd_dma_sampleinit(priv); |
| |
| while (priv->inflight < CONFIG_SAMA5D2_CLASSD_INFLIGHT && |
| dq_peek(&priv->pendq) != NULL && !priv->paused) |
| { |
| /* Take next buffer from the queue of pending transfers */ |
| |
| apb = (struct ap_buffer_s *)dq_remfirst(&priv->pendq); |
| audinfo("Sending apb=%p, size=%d inflight=%d\n", |
| apb, apb->nbytes, priv->inflight); |
| |
| /* Increment the number of buffers in-flight before sending in order |
| * to avoid a possible race condition. |
| */ |
| |
| flags = enter_critical_section(); |
| priv->inflight++; |
| leave_critical_section(flags); |
| |
| /* Send the entire audio buffer. */ |
| |
| samp = (uintptr_t)&apb->samp[apb->curbyte]; |
| paddr = SAM_CLASSD_THR; |
| maddr = sam_physramaddr(samp); |
| nbytes = apb->nbytes - apb->curbyte; |
| |
| ret = sam_dmatxsetup(priv->txdma, paddr, maddr, nbytes); |
| if (ret < 0) |
| { |
| dmaerr("ERROR: sam_dmatxsetup failed: %d\n", ret); |
| break; |
| } |
| |
| classd_txdma_sample(priv, DMA_AFTER_SETUP); |
| |
| /* Start the DMA transfer. |
| * The callback will clear up the DMA once completed. |
| */ |
| |
| priv->dmares = -EBUSY; |
| ret = sam_dmastart(priv->txdma, classd_txcallback, (void *)priv); |
| |
| if (ret < 0) |
| { |
| dmaerr("ERROR: TX sam_dmastart failed: %d\n", ret); |
| return ret; |
| } |
| |
| classd_txdma_sample(priv, DMA_AFTER_START); |
| |
| ret = wd_start(&priv->dmadog, DMA_TIMEOUT_TICKS, |
| classd_dmatimeout, (wdparm_t)priv); |
| if (ret < 0) |
| { |
| auderr("ERROR: wd_start failed: %d\n", ret); |
| break; |
| } |
| |
| /* Wait for DMA completion. This is done in a loop because there my be |
| * false alarm semaphore counts that cause sam_wait() not fail to wait |
| * or to wake-up prematurely (for example due to the receipt of a |
| * signal). We know that the DMA has completed when the result is |
| * anything other than -EBUSY. |
| */ |
| |
| do |
| { |
| /* Start (or re-start) the watchdog timeout */ |
| |
| ret = wd_start(&priv->dmadog, DMA_TIMEOUT_TICKS, |
| classd_dmatimeout, (wdparm_t)priv); |
| if (ret < 0) |
| { |
| auderr("ERROR: wd_start failed: %d\n", ret); |
| break; |
| } |
| |
| /* Wait for the DMA complete */ |
| |
| ret = nxsem_wait_uninterruptible(&priv->dmawait); |
| |
| /* Cancel the watchdog timeout */ |
| |
| wd_cancel(&priv->dmadog); |
| |
| /* Check if we were awakened by an error of some kind. */ |
| |
| if (ret < 0) |
| { |
| DEBUGPANIC(); |
| break; |
| } |
| |
| /* Note that we might be awakened before the wait is over due to |
| * residual counts on the semaphore. So, to handle, that case, |
| * we loop until something changes the DMA result to any value |
| * other than -EBUSY. |
| */ |
| } |
| while (priv->dmares == -EBUSY); |
| |
| /* Dump the sampled DMA registers */ |
| |
| classd_dma_sampledone(priv); |
| |
| /* Make sure that the DMA is stopped (it will be stopped automatically |
| * on normal transfers, but not necessarily when the transfer |
| * terminateson an error condition). |
| */ |
| |
| sam_dmastop(priv->txdma); |
| |
| /* All we can do is complain if the DMA fails */ |
| |
| if (priv->dmares < 0) |
| { |
| auderr("ERROR: DMA failed with result: %d\n", priv->dmares); |
| break; |
| } |
| |
| DEBUGASSERT(priv->inflight > 0); |
| flags = enter_critical_section(); |
| dq_addlast((dq_entry_t *)apb, &priv->doneq); |
| priv->inflight--; |
| leave_critical_section(flags); |
| |
| /* Now send a message to the worker thread, informing it that there are |
| * buffers in the done queue that need to be cleaned up. |
| */ |
| |
| msg.msg_id = AUDIO_MSG_COMPLETE; |
| ret = file_mq_send(&priv->mq, (const char *)&msg, sizeof(msg), |
| CONFIG_AUDIO_SAMA5_CLASSD_MSG_PRIO); |
| if (ret < 0) |
| { |
| auderr("ERROR: file_mq_send failed: %d\n", ret); |
| } |
| |
| audinfo("apb=%p inflight=%d result=%d\n", apb, priv->inflight, |
| priv->dmares); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_workerthread |
| * |
| * This is the thread that feeds data to the classd peripheral and keeps |
| * the audio stream going. |
| * |
| ****************************************************************************/ |
| |
| static void *classd_workerthread(pthread_addr_t pvarg) |
| { |
| struct classd_dev_s *priv = (struct classd_dev_s *)pvarg; |
| struct audio_msg_s msg; |
| struct ap_buffer_s *apb; |
| int msglen; |
| unsigned int prio; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| audinfo("Entry\n"); |
| |
| /* Loop as long as we are supposed to be running */ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| priv->terminate = false; |
| #endif |
| |
| priv->running = true; |
| |
| while (priv->running || priv->inflight > 0) |
| { |
| /* Check if we have been asked to terminate. We have to check if we |
| * still have buffers in-flight: if so, we can't stop yet. |
| */ |
| |
| if (priv->terminate && priv->inflight <= 0) |
| { |
| /* We are IDLE. Break out of the loop and exit. */ |
| |
| break; |
| } |
| |
| else |
| { |
| /* Send the audio buffer */ |
| |
| classd_sendbuffer(priv); |
| } |
| |
| /* Wait for messages from our message queue */ |
| |
| msglen = file_mq_receive(&priv->mq, (char *)&msg, |
| sizeof(msg), &prio); |
| |
| /* Handle the case when we return with no message */ |
| |
| if (msglen < sizeof(struct audio_msg_s)) |
| { |
| auderr("ERROR: Message too small: %d\n", msglen); |
| continue; |
| } |
| |
| /* Process the message */ |
| |
| switch (msg.msg_id) |
| { |
| case AUDIO_MSG_DATA_REQUEST: |
| break; |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| case AUDIO_MSG_STOP: |
| audinfo("AUDIO_MSG_STOP\n"); |
| priv->terminate = true; |
| break; |
| #endif |
| |
| case AUDIO_MSG_ENQUEUE: |
| audinfo("AUDIO_MSG_ENQUEUE\n"); |
| break; |
| |
| case AUDIO_MSG_COMPLETE: |
| audinfo("AUDIO_MSG_COMPLETE\n"); |
| classd_returnbuffers(priv); |
| break; |
| |
| default: |
| auderr("ERROR: Ignoring message ID %d\n", msg.msg_id); |
| break; |
| } |
| } |
| |
| /* reset the classD peripheral */ |
| |
| classd_reset(priv); |
| |
| /* Return any pending buffers in our pending queue */ |
| |
| nxmutex_lock(&priv->pendlock); |
| while ((apb = (struct ap_buffer_s *)dq_remfirst(&priv->pendq)) != NULL) |
| { |
| /* Release our reference to the buffer */ |
| |
| apb_free(apb); |
| |
| /* Send the buffer back up to the previous level. */ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK, NULL); |
| #else |
| priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK); |
| #endif |
| } |
| |
| nxmutex_unlock(&priv->pendlock); |
| |
| classd_returnbuffers(priv); |
| |
| /* Close the message queue */ |
| |
| file_mq_close(&priv->mq); |
| file_mq_unlink(priv->mqname); |
| |
| /* Send an AUDIO_MSG_COMPLETE message to the client */ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK, NULL); |
| #else |
| priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK); |
| #endif |
| |
| audinfo("Exit\n"); |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_start |
| * |
| * Description: |
| * Start the configured operation (audio streaming, volume enabled, etc.). |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_start(struct audio_lowerhalf_s *dev, void *session) |
| #else |
| static int classd_start(struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| struct classd_dev_s *priv = (struct classd_dev_s *)dev; |
| struct sched_param sparam; |
| struct mq_attr attr; |
| pthread_attr_t tattr; |
| void *value; |
| int ret; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| audinfo("Entry\n"); |
| |
| /* Create a message queue for the worker thread */ |
| |
| snprintf(priv->mqname, sizeof(priv->mqname), "/tmp/%" PRIXPTR, |
| (uintptr_t)priv); |
| |
| attr.mq_maxmsg = 16; |
| attr.mq_msgsize = sizeof(struct audio_msg_s); |
| attr.mq_curmsgs = 0; |
| attr.mq_flags = 0; |
| |
| ret = file_mq_open(&priv->mq, priv->mqname, |
| O_RDWR | O_CREAT, 0644, &attr); |
| if (ret < 0) |
| { |
| /* Error creating message queue! */ |
| |
| auderr("ERROR: Couldn't allocate message queue\n"); |
| return ret; |
| } |
| |
| /* Join any old worker thread we had created to prevent a memory leak */ |
| |
| if (priv->threadid != 0) |
| { |
| audinfo("Joining old thread\n"); |
| pthread_join(priv->threadid, &value); |
| } |
| |
| /* Start our thread for sending data to the device */ |
| |
| pthread_attr_init(&tattr); |
| sparam.sched_priority = sched_get_priority_max(SCHED_FIFO) - 3; |
| pthread_attr_setschedparam(&tattr, &sparam); |
| pthread_attr_setstacksize(&tattr, |
| CONFIG_AUDIO_SAMA5_CLASSD_WORKER_STACKSIZE); |
| |
| audinfo("Starting worker thread\n"); |
| ret = pthread_create(&priv->threadid, &tattr, classd_workerthread, |
| (pthread_addr_t)priv); |
| if (ret != OK) |
| { |
| auderr("ERROR: pthread_create failed: %d\n", ret); |
| } |
| else |
| { |
| pthread_setname_np(priv->threadid, "SAMA5D2 classD audio"); |
| audinfo("Created worker thread\n"); |
| } |
| |
| audinfo("Return %d\n", ret); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_stop |
| * |
| * Description: Stop the configured operation (audio streaming, volume |
| * disabled, etc.). |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_stop(struct audio_lowerhalf_s *dev, void *session) |
| #else |
| static int classd_stop(struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| struct classd_dev_s *priv = (struct classd_dev_s *)dev; |
| struct audio_msg_s term_msg; |
| void *value; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| /* Send a message to stop all audio streaming */ |
| |
| term_msg.msg_id = AUDIO_MSG_STOP; |
| term_msg.u.data = 0; |
| file_mq_send(&priv->mq, (const char *)&term_msg, sizeof(term_msg), |
| CONFIG_AUDIO_SAMA5_CLASSD_MSG_PRIO); |
| |
| /* Join the worker thread */ |
| |
| pthread_join(priv->threadid, &value); |
| priv->threadid = 0; |
| |
| audinfo("Return OK\n"); |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: classd_pause |
| * |
| * Description: Pauses the playback. |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_pause(struct audio_lowerhalf_s *dev, void *session) |
| #else |
| static int classd_pause(struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| audinfo("Return OK\n"); |
| return OK; |
| } |
| #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ |
| |
| /**************************************************************************** |
| * Name: classd_resume |
| * |
| * Description: Resumes the playback. |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_resume(struct audio_lowerhalf_s *dev, void *session) |
| #else |
| static int classd_resume(struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| audinfo("Return OK\n"); |
| return OK; |
| } |
| #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ |
| |
| /**************************************************************************** |
| * Name: classd_enqueuebuffer |
| * |
| * Description: Enqueue an Audio Pipeline Buffer for playback/ processing. |
| * |
| ****************************************************************************/ |
| |
| static int classd_enqueuebuffer(struct audio_lowerhalf_s *dev, |
| struct ap_buffer_s *apb) |
| { |
| struct classd_dev_s *priv = (struct classd_dev_s *)dev; |
| struct audio_msg_s msg; |
| int ret; |
| |
| ret = OK; |
| |
| DEBUGASSERT(priv && apb && priv->dev.upper); |
| |
| audinfo("Enqueuing: apb=%p curbyte=%d nbytes=%d\n", |
| apb, apb->curbyte, apb->nbytes); |
| |
| /* Take a reference on the new audio buffer */ |
| |
| apb_reference(apb); |
| |
| ret = nxmutex_lock(&priv->pendlock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Add the new buffer to the tail of pending audio buffers */ |
| |
| apb->flags |= AUDIO_APB_OUTPUT_ENQUEUED; |
| dq_addlast(&apb->dq_entry, &priv->pendq); |
| nxmutex_unlock(&priv->pendlock); |
| |
| ret = OK; |
| |
| if (priv->mq.f_inode != NULL) |
| { |
| msg.msg_id = AUDIO_MSG_ENQUEUE; |
| msg.u.data = 0; |
| |
| ret = file_mq_send(&priv->mq, (const char *)&msg, |
| sizeof(msg), CONFIG_AUDIO_SAMA5_CLASSD_MSG_PRIO); |
| if (ret < 0) |
| { |
| auderr("ERROR: file_mq_send failed: %d\n", ret); |
| } |
| } |
| |
| audinfo("Return OK\n"); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_cancelbuffer |
| * |
| * Description: Called when an enqueued buffer is being cancelled. |
| * |
| ****************************************************************************/ |
| |
| static int classd_cancelbuffer(struct audio_lowerhalf_s *dev, |
| struct ap_buffer_s *apb) |
| { |
| audinfo("apb=%p curbyte=%d nbytes=%d, return OK\n", |
| apb, apb->curbyte, apb->nbytes); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_ioctl |
| * |
| * Description: Perform a device ioctl |
| * |
| ****************************************************************************/ |
| |
| static int classd_ioctl(struct audio_lowerhalf_s *dev, int cmd, |
| unsigned long arg) |
| { |
| int ret = OK; |
| #ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS |
| struct ap_buffer_info_s *bufinfo; |
| #endif |
| |
| audinfo("cmd=%d arg=%ld\n", cmd, arg); |
| |
| /* Deal with ioctls passed from the upper-half driver */ |
| |
| switch (cmd) |
| { |
| /* Check for AUDIOIOC_HWRESET ioctl. This ioctl is passed straight |
| * through from the upper-half audio driver. |
| */ |
| |
| case AUDIOIOC_HWRESET: |
| { |
| audinfo("AUDIOIOC_HWRESET:\n"); |
| } |
| break; |
| |
| /* Report our preferred buffer size and quantity */ |
| |
| #ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS |
| case AUDIOIOC_GETBUFFERINFO: |
| { |
| audinfo("AUDIOIOC_GETBUFFERINFO:\n"); |
| bufinfo = (struct ap_buffer_info_s *)arg; |
| bufinfo->buffer_size = CONFIG_AUDIO_SAMA5_CLASSD_BUFFER_SIZE; |
| bufinfo->nbuffers = CONFIG_AUDIO_SAMA5_CLASSD_NUM_BUFFERS; |
| } |
| break; |
| #endif |
| |
| default: |
| ret = -ENOTTY; |
| audinfo("Ignored\n"); |
| break; |
| } |
| |
| audinfo("Return OK\n"); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_reserve |
| * |
| * Description: Reserves a session (the only one we have). |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_reserve(struct audio_lowerhalf_s *dev, |
| void **session) |
| #else |
| static int classd_reserve(struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| struct classd_dev_s *priv = (struct classd_dev_s *)dev; |
| int ret; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| ret = nxmutex_lock(&priv->pendlock); |
| |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| if (priv->reserved) |
| { |
| ret = -EBUSY; |
| } |
| else |
| { |
| /* Initialize the session context */ |
| |
| priv->inflight = 0; |
| priv->running = false; |
| #ifndef CONFIG_AUDIO_EXCLUDE_STOP |
| priv->terminate = false; |
| #endif |
| priv->reserved = true; |
| } |
| |
| nxmutex_unlock(&priv->pendlock); |
| |
| audinfo("Return OK\n"); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: classd_release |
| * |
| * Description: Releases the session (the only one we have). |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_AUDIO_MULTI_SESSION |
| static int classd_release(struct audio_lowerhalf_s *dev, void *session) |
| #else |
| static int classd_release(struct audio_lowerhalf_s *dev) |
| #endif |
| { |
| struct classd_dev_s *priv = (struct classd_dev_s *)dev; |
| void *value; |
| int ret; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| /* Join any old worker thread we had created to prevent a memory leak */ |
| |
| if (priv->threadid != 0) |
| { |
| pthread_join(priv->threadid, &value); |
| priv->threadid = 0; |
| } |
| |
| ret = nxmutex_lock(&priv->pendlock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Really we should free any queued buffers here */ |
| |
| priv->reserved = false; |
| nxmutex_unlock(&priv->pendlock); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: sama5_classd_initialize |
| * |
| * Description: |
| * Initialize the CLASSD device. |
| * |
| * Returned Value: |
| * A new lower half audio interface for the CLASSD device is returned on |
| * success; NULL is returned on failure. |
| * |
| ****************************************************************************/ |
| |
| struct audio_lowerhalf_s *sama5_classd_initialize(void) |
| { |
| static struct classd_dev_s priv; |
| |
| /* Allocate a CLASSD device structure */ |
| |
| if (priv.state != CLASSD_STATE_CONFIGURED) |
| { |
| /* one-time data initialization */ |
| |
| memset(&priv, 0, sizeof(struct classd_dev_s)); |
| |
| priv.config = &sam_classd_const; |
| |
| /* Initialize the ClassD device structure. Since we used kmm_zalloc, |
| * only the non-zero elements of :the structure need to be initialized. |
| */ |
| |
| priv.dev.ops = &g_audioops; |
| nxmutex_init(&priv.pendlock); |
| nxsem_init(&priv.dmawait, 0, 0); |
| dq_init(&priv.pendq); |
| dq_init(&priv.doneq); |
| |
| /* Pre-allocate DMA channels. */ |
| |
| priv.txdma = sam_dmachannel(CONFIG_SAMA5D2_CLASSD_DMA_DMAC_NUMBER, 0); |
| |
| if (!priv.txdma) |
| { |
| auderr("ERROR: Failed to allocate the TX DMA channel\n"); |
| } |
| |
| /* Reset and set up the HW. */ |
| |
| classd_dump_registers("Before reset"); |
| |
| classd_reset(&priv); |
| if (classd_setup(&priv) < 0) |
| { |
| auderr("ERROR: Failed to setup ClassD Peripheral\n"); |
| } |
| |
| classd_configure_pmc(&priv); |
| sam_classd_enableclk(); |
| classd_enable_audio(&priv, true); |
| classd_configure_pmc(&priv); |
| |
| classd_setvolume(&priv, 1000); |
| |
| classd_dump_registers("After init."); |
| |
| priv.state = CLASSD_STATE_CONFIGURED; |
| return &priv.dev; |
| } |
| |
| return NULL; |
| } |
| |
| #endif /* CONFIG_SAMA5D2_CLASSD */ |