blob: cbc96730e4f113d6003212a48a1e09043ce449ce [file] [log] [blame]
/****************************************************************************
* boards/arm/stm32f7/stm32f746g-disco/src/stm32_wm8994.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/config.h>
#include <stdbool.h>
#include <stdio.h>
#include <debug.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/irq.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/audio/i2s.h>
#include <nuttx/audio/pcm.h>
#include <nuttx/audio/wm8994.h>
#include <arch/board/board.h>
#include "chip.h"
#include "stm32f746g-disco.h"
#include "stm32_i2c.h"
#include "stm32_sai.h"
#include "stm32_pwr.h"
#include "stm32_rcc.h"
#define HAVE_WM8994
#define WM8994_I2C_ADDRESS (0x34 >> 1)
#define WM8994_I2C_BUS 3
#define WM8994_SAI_BUS 2
#ifdef HAVE_WM8994
/****************************************************************************
* Private Types
****************************************************************************/
struct stm32_mwinfo_s
{
/* Standard ADAU1961 interface */
struct wm8994_lower_s lower;
};
/****************************************************************************
* Private Functions
****************************************************************************/
static int wm8994_attach(const struct wm8994_lower_s *lower,
wm8994_handler_t isr, void *arg)
{
audinfo("TODO\n");
return 0;
}
static bool wm8994_enable(const struct wm8994_lower_s *lower,
bool enable)
{
audinfo("TODO\n");
return 0;
}
#if 0
static void wm8994_hw_reset(const struct wm8994_lower_s *lower)
{
audinfo("TODO\n");
}
#endif
/****************************************************************************
* Private Data
****************************************************************************/
/* A reference to a structure of this type must be passed to the ADAU1961
* driver. This structure provides information about the configuration
* of the ADAU1961 and provides some board-specific hooks.
*
* Memory for this structure is provided by the caller. It is not copied
* by the driver and is presumed to persist while the driver is active.
*/
static struct stm32_mwinfo_s g_wm8994 =
{
.lower =
{
.address = WM8994_I2C_ADDRESS,
.frequency = I2C_SPEED_FAST, /* 100 kHz */
.mclk = 12000000, /* see MCO1 configuration */
.attach = wm8994_attach,
.enable = wm8994_enable,
}
};
/****************************************************************************
* Public Functions
****************************************************************************/
static uint32_t stm32_wm8994_sampleratecb(struct i2s_dev_s *dev,
uint32_t rate)
{
uint32_t frequency = 0;
uint32_t regval = 0;
uint32_t mask_divq = 0;
uint32_t mask_i2s = 0;
/* Table 210 in "STM32F75xxx and STM32F74xxx advanced Arm(r)-based 32-bit
* MCUs - Reference manual" suggests the use of specific sai_x_ker_ck
* frequencies for common audio sample frequencies:
* For 44.1, 22.05, 11.025 KHz, 44.1 kHz * 256 = 11289600 Hz
* For 192, 96, 48, 32, 16, 8 KHz, 192 kHz * 256 = 49152000 Hz
*
* The below configurations use 429000000 / 19 / 2 = 1128473.x
* as approximation for the first group of sample frequencies and
* 344000000 / 1 / 7 = 49142857.x as approximation for the second
* group of sample frequencies
*/
if ((rate == 44100) || (rate == 22050) || (rate == 11025))
{
/* Division factor has 1 offset! (i.e. 0 = /1, 1 = /2, etc. */
mask_divq = RCC_DCKCFGR1_PLLI2SDIVQ(18);
mask_i2s = RCC_PLLI2SCFGR_PLLI2SN(429) |
RCC_PLLI2SCFGR_PLLI2SQ(2);
frequency = 11289473;
}
else
{
/* Division factor has 1 offset! (i.e. 0 = /1, 1 = /2, etc. */
mask_divq = RCC_DCKCFGR1_PLLI2SDIVQ(0);
mask_i2s = RCC_PLLI2SCFGR_PLLI2SN(344) |
RCC_PLLI2SCFGR_PLLI2SQ(7);
frequency = 49142857;
}
/* Check if i2s PLL is already in correct configuration */
if ((getreg32(STM32_RCC_DCKCFGR1) &
(RCC_DCKCFGR1_PLLI2SDIVQ_MASK)) != mask_divq)
{
/* Disable the PLLI2S */
regval = getreg32(STM32_RCC_CR);
regval &= ~(RCC_CR_PLLI2SON);
putreg32 (regval, STM32_RCC_CR);
while ((getreg32(STM32_RCC_CR) & RCC_CR_PLLI2SON))
{
}
/* Set PLLI2S Configuration */
regval = getreg32(STM32_RCC_DCKCFGR1);
regval &= ~(RCC_DCKCFGR1_PLLI2SDIVQ_MASK);
regval |= mask_divq;
putreg32(regval, STM32_RCC_DCKCFGR1);
regval = getreg32(STM32_RCC_PLLI2SCFGR);
regval &= ~(RCC_PLLI2SCFGR_PLLI2SN_MASK
| RCC_PLLI2SCFGR_PLLI2SQ_MASK);
regval |= mask_i2s;
putreg32(regval, STM32_RCC_PLLI2SCFGR);
/* Enable the PLLI2S */
regval = getreg32(STM32_RCC_CR);
regval |= (RCC_CR_PLLI2SON);
putreg32 (regval, STM32_RCC_CR);
while (!(getreg32(STM32_RCC_CR) & RCC_CR_PLLI2SRDY))
{
}
}
audinfo("i2s PLL configured for samplerate %" PRIu32 "\n", rate);
return frequency;
}
/****************************************************************************
* Name: stm32_wm8994_initialize
*
* Description:
* This function is called by platform-specific, setup logic to configure
* and register the ADAU1961 device. This function will register the driver
* as /dev/audio/pcm[x] where x is determined by the minor device number.
*
* Input Parameters:
* minor - The input device minor number
*
* Returned Value:
* Zero is returned on success. Otherwise, a negated errno value is
* returned to indicate the nature of the failure.
*
****************************************************************************/
int stm32_wm8994_initialize(int minor)
{
struct audio_lowerhalf_s *wm8994;
struct audio_lowerhalf_s *pcm;
struct i2c_master_s *i2c;
struct i2s_dev_s *i2s;
static bool initialized = false;
char devname[12];
int ret;
audinfo("minor %d\n", minor);
DEBUGASSERT(minor >= 0 && minor <= 25);
/* Initialize the CODEC if we have not already done so */
if (!initialized)
{
/* Configure MC01 to drive the master clock of the CODEC at 8MHz */
/* stm32_configgpio(GPIO_MCO1);
* stm32_mco1config(RCC_CFGR_MCO1_HSE, RCC_CFGR_MCO1PRE_NONE);
*/
/* Get an instance of the I2C interface for the CODEC */
i2c = stm32_i2cbus_initialize(WM8994_I2C_BUS);
if (!i2c)
{
auderr("stm32_i2cbus_initialize failed\n");
ret = -ENODEV;
goto error;
}
/* Get an instance of the I2S interface for the CODEC data streams */
i2s = stm32_sai_initialize(WM8994_SAI_BUS, stm32_wm8994_sampleratecb);
if (!i2s)
{
auderr("stm32_sai_initialize failed\n");
ret = -ENODEV;
goto error;
}
/* Now we can use these I2C and I2S interfaces to initialize the
* CODEC which will return an audio interface.
*/
wm8994 = wm8994_initialize(i2c, i2s, &g_wm8994.lower);
if (!wm8994)
{
auderr("wm8994_initialize failed\n");
ret = -ENODEV;
goto error;
}
/* No we can embed the WM8994/I2C/I2S conglomerate into a PCM decoder
* instance so that we will have a PCM front end for the the WM8994
* driver.
*/
pcm = pcm_decode_initialize(wm8994);
if (pcm == NULL)
{
auderr("ERROR: Failed create the PCM decoder\n");
ret = -ENODEV;
goto error;
}
/* Create a device name */
snprintf(devname, sizeof(devname), "pcm%d", minor);
/* Finally, we can register the ADAU1961/I2C/I2S audio device. */
ret = audio_register(devname, pcm);
if (ret < 0)
{
auderr("failed to register /dev/%s device: %d\n", devname, ret);
goto error;
}
/* Now we are initialized */
initialized = true;
}
return OK;
/* Error exits. Unfortunately there is no mechanism in place now to
* recover resources from most errors on initialization failures.
*/
error:
return ret;
}
#endif /* HAVE_WM8994 */