/************************************************************************************ * configs/sama5d4-ek/src/sam_wm8904.c * * Copyright (C) 2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS 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. * ************************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "up_arch.h" #include "sam_pio.h" #include "sam_twi.h" #include "sam_ssc.h" #include "sam_pck.h" #include "sama5d4-ek.h" #ifdef HAVE_WM8904 /**************************************************************************** * Pre-Processor Definitions ****************************************************************************/ /**************************************************************************** * Private Types ****************************************************************************/ struct sama5d4ek_mwinfo_s { /* Standard MW8904 interface */ struct wm8904_lower_s lower; /* Extensions for the sama5d4ek board */ wm8904_handler_t handler; FAR void *arg; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* IRQ/PIO access callbacks. These operations all hidden behind * callbacks to isolate the WM8904 driver from differences in PIO * interrupt handling by varying boards and MCUs. If possible, * interrupts should be configured on both rising and falling edges * so that contact and loss-of-contact events can be detected. * * attach - Attach the WM8904 interrupt handler to the PIO interrupt * enable - Enable or disable the PIO interrupt */ static int wm8904_attach(FAR const struct wm8904_lower_s *lower, wm8904_handler_t isr, FAR void *arg); static bool wm8904_enable(FAR const struct wm8904_lower_s *lower, bool enable); /**************************************************************************** * Private Data ****************************************************************************/ /* A reference to a structure of this type must be passed to the WM8904 * driver. This structure provides information about the configuration * of the WM8904 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 sama5d4ek_mwinfo_s g_wm8904info = { .lower = { .address = WM8904_I2C_ADDRESS, .frequency = CONFIG_SAMA5D4EK_WM8904_I2CFREQUENCY, #ifdef CONFIG_SAMA5D4EK_WM8904_SRCSCK .mclk = BOARD_SLOWCLK_FREQUENCY, #else .mclk = BOARD_MAINCK_FREQUENCY, #endif .attach = wm8904_attach, .enable = wm8904_enable, }, }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * IRQ/PIO access callbacks. These operations all hidden behind * callbacks to isolate the WM8904 driver from differences in PIO * interrupt handling by varying boards and MCUs. If possible, * interrupts should be configured on both rising and falling edges * so that contact and loss-of-contact events can be detected. * * attach - Attach the WM8904 interrupt handler to the PIO interrupt * enable - Enable or disable the PIO interrupt * clear - Acknowledge/clear any pending PIO interrupt * ****************************************************************************/ static int wm8904_attach(FAR const struct wm8904_lower_s *lower, wm8904_handler_t isr, FAR void *arg) { if (isr) { /* Just save the address of the handler and its argument for now. The * new handler will called via wm8904_interrupt() when the interrupt occurs. */ audvdbg("Attaching %p\n", isr); g_wm8904info.handler = isr; g_wm8904info.arg = arg; } else { audvdbg("Detaching %p\n", g_wm8904info.handler); (void)wm8904_enable(lower, false); g_wm8904info.handler = NULL; g_wm8904info.arg = NULL; } return OK; } static bool wm8904_enable(FAR const struct wm8904_lower_s *lower, bool enable) { static bool enabled; irqstate_t flags; bool ret; /* Has the interrupt state changed */ flags = irqsave(); if (enable != enabled) { /* Enable or disable interrupts */ if (enable && g_wm8904info.handler) { audvdbg("Enabling\n"); sam_pioirqenable(IRQ_INT_WM8904); enabled = true; } else { audvdbg("Disabling\n"); sam_pioirqdisable(IRQ_INT_WM8904); enabled = false; } } ret = enabled; irqrestore(flags); return ret; } static int wm8904_interrupt(int irq, FAR void *context) { /* Just forward the interrupt to the WM8904 driver */ audvdbg("handler %p\n", g_wm8904info.handler); if (g_wm8904info.handler) { return g_wm8904info.handler(&g_wm8904info.lower, g_wm8904info.arg); } /* We got an interrupt with no handler. This should not * happen. */ sam_pioirqdisable(IRQ_INT_WM8904); return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: sam_wm8904_initialize * * Description: * This function is called by platform-specific, setup logic to configure * and register the WM8904 device. This function will register the driver * as /dev/wm8904[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 sam_wm8904_initialize(int minor) { FAR struct audio_lowerhalf_s *wm8904; FAR struct audio_lowerhalf_s *pcm; FAR struct i2c_dev_s *i2c; FAR struct i2s_dev_s *i2s; static bool initialized = false; char devname[12]; int ret; auddbg("minor %d\n", minor); DEBUGASSERT(minor >= 0 && minor <= 25); /* Have we already initialized? Since we never uninitialize we must prevent * multiple initializations. This is necessary, for example, when the * touchscreen example is used as a built-in application in NSH and can be * called numerous time. It will attempt to initialize each time. */ if (!initialized) { /* Configure the WM8904 interrupt pin */ (void)sam_configpio(PIO_INT_WM8904); /* Get an instance of the I2C interface for the WM8904 chip select */ i2c = up_i2cinitialize(WM8904_TWI_BUS); if (!i2c) { auddbg("Failed to initialize TWI%d\n", WM8904_TWI_BUS); ret = -ENODEV; goto errout; } /* Get an instance of the I2S interface for the WM8904 data channel */ i2s = sam_ssc_initialize(WM8904_SSC_BUS); if (!i2s) { auddbg("Failed to initialize SSC%d\n", WM8904_SSC_BUS); ret = -ENODEV; goto errout_with_i2c; } /* Configure the DAC master clock. This clock is provided by PCK2 (PB10) * that is connected to the WM8904 MCLK. */ #ifdef CONFIG_SAMA5D4EK_WM8904_SRCSCK /* Drive the DAC with the slow clock (32.768 KHz). The slow clock was * enabled in sam_boot.c if needed. */ (void)sam_pck_configure(PCK2, PCKSRC_SCK, BOARD_SLOWCLK_FREQUENCY); #else /* Drive the DAC with the main clock (12 MHz) */ (void)sam_pck_configure(PCK2, PCKSRC_MAINCK, BOARD_MAINCK_FREQUENCY); #endif /* Enable the DAC master clock */ sam_pck_enable(PCK2, true); /* Configure WM8904 interrupts */ sam_pioirq(PIO_INT_WM8904); ret = irq_attach(IRQ_INT_WM8904, wm8904_interrupt); if (ret < 0) { auddbg("ERROR: Failed to attach WM8904 interrupt: %d\n", ret); goto errout_with_i2s; } /* Now we can use these I2C and I2S interfaces to initialize the * MW8904 which will return an audio interface. */ wm8904 = wm8904_initialize(i2c, i2s, &g_wm8904info.lower); if (!wm8904) { auddbg("Failed to initialize the WM8904\n"); ret = -ENODEV; goto errout_with_irq; } /* No we can embed the WM8904/I2C/I2S conglomerate into a PCM decoder * instance so that we will have a PCM front end for the the WM8904 * driver. */ pcm = pcm_decode_initialize(wm8904); if (!pcm) { auddbg("ERROR: Failed create the PCM decoder\n"); ret = -ENODEV; goto errout_with_wm8904; } /* Create a device name */ snprintf(devname, 12, "pcm%d", minor); /* Finally, we can register the PCM/WM8904/I2C/I2S audio device. * * Is anyone young enough to remember Rube Goldberg? */ ret = audio_register(devname, pcm); if (ret < 0) { auddbg("ERROR: Failed to register /dev/%s device: %d\n", devname, ret); goto errout_with_pcm; } /* 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. */ errout_with_pcm: errout_with_wm8904: errout_with_irq: irq_detach(IRQ_INT_WM8904); errout_with_i2s: errout_with_i2c: errout: return ret; } #endif /* HAVE_WM8904 */