blob: d4dc0a040953017e147a4d5dc4a19da0591e9ab3 [file] [log] [blame]
/****************************************************************************
* 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 */