blob: 276c731be3dcde3759b49ec5e9f7b9920b4770a4 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* SAM Software Package License
* ----------------------------------------------------------------------------
* Copyright (c) 2015, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the disclaimer below.
*
* Atmel's name may not be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ----------------------------------------------------------------------------
*/
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "chip.h"
#include "board.h"
#include "trace.h"
#include "peripherals/classd.h"
#include "peripherals/pmc.h"
#include <stdio.h>
#include <string.h>
/*----------------------------------------------------------------------------
* Local constants
*----------------------------------------------------------------------------*/
static const struct {
uint32_t rate;
uint32_t sample_rate;
uint32_t dsp_clk;
} audio_info[] = {
{ 8000, CLASSD_INTPMR_FRAME_FRAME_8K, CLASSD_INTPMR_DSPCLKFREQ_12M288 },
{ 16000, CLASSD_INTPMR_FRAME_FRAME_16K, CLASSD_INTPMR_DSPCLKFREQ_12M288 },
{ 32000, CLASSD_INTPMR_FRAME_FRAME_32K, CLASSD_INTPMR_DSPCLKFREQ_12M288 },
{ 48000, CLASSD_INTPMR_FRAME_FRAME_48K, CLASSD_INTPMR_DSPCLKFREQ_12M288 },
{ 96000, CLASSD_INTPMR_FRAME_FRAME_96K, CLASSD_INTPMR_DSPCLKFREQ_12M288 },
{ 22050, CLASSD_INTPMR_FRAME_FRAME_22K, CLASSD_INTPMR_DSPCLKFREQ_11M2896 },
{ 44100, CLASSD_INTPMR_FRAME_FRAME_44K, CLASSD_INTPMR_DSPCLKFREQ_11M2896 },
{ 88200, CLASSD_INTPMR_FRAME_FRAME_88K, CLASSD_INTPMR_DSPCLKFREQ_11M2896 },
};
/*----------------------------------------------------------------------------
* Local functions
*----------------------------------------------------------------------------*/
static bool _dspclk_configure(uint32_t dsp_clk)
{
struct _pmc_audio_cfg cfg;
/* Pad Clock: not used */
cfg.div = 0;
cfg.qdaudio = 0;
/* PMC Clock: */
/* 12Mhz * (ND + 1 + FRACR/2^22) / (QDPMC + 1) = 8 * DSPCLK */
switch (dsp_clk) {
case CLASSD_INTPMR_DSPCLKFREQ_12M288:
/* 12Mhz * (56 + 1 + 1442841/2^22) / (6 + 1) = 8 * 12.288Mhz */
cfg.nd = 56;
cfg.fracr = 1442841;
cfg.qdpmc = 6;
break;
case CLASSD_INTPMR_DSPCLKFREQ_11M2896:
/* 12Mhz * (59 + 1 + 885837/2^22) / (7 + 1) = 8 * 11.2896Mhz */
cfg.nd = 59;
cfg.fracr = 885837;
cfg.qdpmc = 7;
break;
default:
return false;
}
pmc_configure_audio(&cfg);
pmc_enable_audio(true, false);
#ifndef NDEBUG
{
uint32_t clk;
clk = pmc_get_audio_pmc_clock();
trace_debug("Configured Audio PLL PMC Clock: %u (= 8 * %u)\r\n",
(unsigned)clk, (unsigned)(clk >> 3));
}
#endif
return true;
}
static bool _set_eqcfg_bits(enum _classd_eqcfg eqcfg, volatile uint32_t *intpmr)
{
uint32_t mask = CLASSD_INTPMR_EQCFG_Msk;
uint32_t bits = 0;
switch (eqcfg) {
case CLASSD_EQCFG_FLAT:
bits = CLASSD_INTPMR_EQCFG_FLAT;
break;
case CLASSD_EQCFG_BBOOST12:
bits = CLASSD_INTPMR_EQCFG_BBOOST12;
break;
case CLASSD_EQCFG_BBOOST6:
bits = CLASSD_INTPMR_EQCFG_BBOOST6;
break;
case CLASSD_EQCFG_BCUT12:
bits = CLASSD_INTPMR_EQCFG_BCUT12;
break;
case CLASSD_EQCFG_BCUT6:
bits = CLASSD_INTPMR_EQCFG_BCUT6;
break;
case CLASSD_EQCFG_MBOOST3:
bits = CLASSD_INTPMR_EQCFG_MBOOST3;
break;
case CLASSD_EQCFG_MBOOST8:
bits = CLASSD_INTPMR_EQCFG_MBOOST8;
break;
case CLASSD_EQCFG_MCUT3:
bits = CLASSD_INTPMR_EQCFG_MCUT3;
break;
case CLASSD_EQCFG_MCUT8:
bits = CLASSD_INTPMR_EQCFG_MCUT8;
break;
case CLASSD_EQCFG_TBOOST12:
bits = CLASSD_INTPMR_EQCFG_TBOOST12;
break;
case CLASSD_EQCFG_TBOOST6:
bits = CLASSD_INTPMR_EQCFG_TBOOST6;
break;
case CLASSD_EQCFG_TCUT12:
bits = CLASSD_INTPMR_EQCFG_TCUT12;
break;
case CLASSD_EQCFG_TCUT6:
bits = CLASSD_INTPMR_EQCFG_TCUT6;
break;
default:
trace_warning("classd: invalid equalizer config %u\r\n",
(unsigned)eqcfg);
return false;
};
*intpmr = (*intpmr & ~mask) | bits;
return true;
}
static bool _set_mono_bits(bool mono, enum _classd_mono mono_mode, volatile uint32_t *intpmr)
{
uint32_t mask = CLASSD_INTPMR_MONO_ENABLED | CLASSD_INTPMR_MONOMODE_Msk;
uint32_t bits = 0;
if (mono) {
bits = CLASSD_INTPMR_MONO_ENABLED;
switch (mono_mode) {
case CLASSD_MONO_MIXED:
bits |= CLASSD_INTPMR_MONOMODE_MONOMIX;
break;
case CLASSD_MONO_SAT:
bits |= CLASSD_INTPMR_MONOMODE_MONOSAT;
break;
case CLASSD_MONO_LEFT:
bits |= CLASSD_INTPMR_MONOMODE_MONOLEFT;
break;
case CLASSD_MONO_RIGHT:
bits |= CLASSD_INTPMR_MONOMODE_MONORIGHT;
break;
default:
trace_warning("classd: invalid mono mode %u\r\n",
(unsigned)mono_mode);
return false;
}
}
*intpmr = (*intpmr & ~mask) | bits;
return true;
}
/*----------------------------------------------------------------------------
* Exported functions
*----------------------------------------------------------------------------*/
bool classd_configure(struct _classd_desc *desc)
{
uint8_t i;
uint32_t mr, intpmr, dsp_clk_set, frame_set;
for (i = 0; i < ARRAY_SIZE(audio_info); i++) {
if (audio_info[i].rate == desc->sample_rate) {
dsp_clk_set = audio_info[i].dsp_clk;
frame_set = audio_info[i].sample_rate;
break;
}
}
if(i == ARRAY_SIZE(audio_info))
return false;
if (!_dspclk_configure(dsp_clk_set))
return false;
/* enable peripheral clock, disable audio clock for now */
pmc_enable_peripheral(ID_CLASSD);
pmc_disable_gck(ID_CLASSD);
pmc_configure_gck(ID_CLASSD, PMC_PCR_GCKCSS_AUDIO_CLK, 0);
/* perform soft reset */
CLASSD->CLASSD_CR = CLASSD_CR_SWRST;
CLASSD->CLASSD_IDR = CLASSD_IDR_DATRDY;
/* initial MR/INTPMR values */
mr = 0;
intpmr = dsp_clk_set | frame_set;
/* configure output mode */
switch (desc->mode) {
case CLASSD_OUTPUT_SINGLE_ENDED:
break;
case CLASSD_OUTPUT_DIFFERENTIAL:
mr |= CLASSD_MR_PWMTYP;
break;
case CLASSD_OUTPUT_HALF_BRIDGE:
mr |= CLASSD_MR_NON_OVERLAP;
break;
case CLASSD_OUTPUT_FULL_BRIDGE:
mr |= CLASSD_MR_PWMTYP | CLASSD_MR_NON_OVERLAP;
break;
default:
trace_warning("classd: invalid mode %u\n", (unsigned)desc->mode);
return false;
}
/* configure non-overlapping time */
if (mr & CLASSD_MR_NON_OVERLAP) {
switch (desc->non_ovr) {
case CLASSD_NONOVR_5NS:
mr |= CLASSD_MR_NOVRVAL_5NS;
break;
case CLASSD_NONOVR_10NS:
mr |= CLASSD_MR_NOVRVAL_10NS;
break;
case CLASSD_NONOVR_15NS:
mr |= CLASSD_MR_NOVRVAL_15NS;
break;
case CLASSD_NONOVR_20NS:
mr |= CLASSD_MR_NOVRVAL_20NS;
break;
default:
trace_warning("classd: invalid non overlap value %u\r\n",
(unsigned)desc->non_ovr);
return false;
}
}
/* configure mono/stereo */
if (desc->swap_channels)
intpmr |= CLASSD_INTPMR_SWAP;
if (!_set_mono_bits(desc->mono, desc->mono_mode, &intpmr))
return false;
/* configure left channel (muted, max attn) */
if (desc->left_enable)
mr |= CLASSD_MR_LEN;
mr |= CLASSD_MR_LMUTE;
intpmr |= CLASSD_INTPMR_ATTL(CLASSD_INTPMR_ATTL_Msk);
/* configure right channel (muted, max attn) */
if (desc->right_enable)
mr |= CLASSD_MR_REN;
mr |= CLASSD_MR_RMUTE;
intpmr |= CLASSD_INTPMR_ATTR(CLASSD_INTPMR_ATTL_Msk);
/* write configuration */
CLASSD->CLASSD_MR = mr;
CLASSD->CLASSD_INTPMR = intpmr;
/* enable audio clock */
pmc_enable_gck(ID_CLASSD);
return (CLASSD->CLASSD_INTSR & CLASSD_INTSR_CFGERR) == 0;
}
void classd_disable(void)
{
pmc_disable_audio();
pmc_disable_gck(ID_CLASSD);
pmc_disable_peripheral(ID_CLASSD);
}
void classd_swap_channels(bool swap)
{
if (swap) {
CLASSD->CLASSD_INTPMR |= CLASSD_INTPMR_SWAP;
} else {
CLASSD->CLASSD_INTPMR &= ~CLASSD_INTPMR_SWAP;
}
}
void classd_enable_mono(enum _classd_mono mono_mode)
{
_set_mono_bits(true, mono_mode, &CLASSD->CLASSD_INTPMR);
}
void classd_disable_mono(void)
{
_set_mono_bits(false, CLASSD_MONO_MIXED, &CLASSD->CLASSD_INTPMR);
}
void classd_set_equalizer(enum _classd_eqcfg eqcfg)
{
_set_eqcfg_bits(eqcfg, &CLASSD->CLASSD_INTPMR);
}
void classd_enable_channels(bool left, bool right)
{
uint32_t bits = 0;
if (left)
bits |= CLASSD_MR_LEN;
if (right)
bits |= CLASSD_MR_REN;
CLASSD->CLASSD_MR |= bits;
}
void classd_disable_channels(bool left, bool right)
{
uint32_t bits = 0;
if (left)
bits |= CLASSD_MR_LEN;
if (right)
bits |= CLASSD_MR_REN;
CLASSD->CLASSD_MR &= ~bits;
}
void classd_set_left_attenuation(uint8_t attn)
{
if (attn < 1 || attn > 0x3f)
return;
uint32_t intpmr = CLASSD->CLASSD_INTPMR & ~CLASSD_INTPMR_ATTL_Msk;
CLASSD->CLASSD_INTPMR = intpmr | CLASSD_INTPMR_ATTL(attn);
}
void classd_set_right_attenuation(uint8_t attn)
{
if (attn < 1 || attn > 0x3f)
return;
uint32_t intpmr = CLASSD->CLASSD_INTPMR & ~CLASSD_INTPMR_ATTR_Msk;
CLASSD->CLASSD_INTPMR = intpmr | CLASSD_INTPMR_ATTR(attn);
}
void classd_volume_mute(bool left, bool right)
{
uint32_t bits = 0;
if (left)
bits |= CLASSD_MR_LMUTE;
if (right)
bits |= CLASSD_MR_RMUTE;
CLASSD->CLASSD_MR |= bits;
}
void classd_volume_unmute(bool left, bool right)
{
uint32_t bits = 0;
if (left)
bits |= CLASSD_MR_LMUTE;
if (right)
bits |= CLASSD_MR_RMUTE;
CLASSD->CLASSD_MR &= ~bits;
}