diff --git a/drivers/audio/Kconfig b/drivers/audio/Kconfig index 81c247a6420..20664dbe2b3 100644 --- a/drivers/audio/Kconfig +++ b/drivers/audio/Kconfig @@ -33,7 +33,18 @@ config AUDIO_I2SCHAR_TXTIMEOUT transfers. This is in units of system clock ticks (configurable). The special value of zero disables RX timeouts. Default: 0 -endif #AUDIO_I2SCHAR +endif # AUDIO_I2SCHAR + +config AUDIO_TONE + bool "Audio Tone Generator using PWM" + default n + depends on PWM && AUDIO_DEVICES + ---help--- + This driver enables the Audio Tone Generator for NuttX. + +if AUDIO_TONE + +endif # AUDIO_TONE config VS1053 bool "VS1053 codec chip" diff --git a/drivers/audio/Make.defs b/drivers/audio/Make.defs index f645deade79..5932714f6d6 100644 --- a/drivers/audio/Make.defs +++ b/drivers/audio/Make.defs @@ -62,6 +62,10 @@ ifeq ($(CONFIG_AUDIO_I2SCHAR),y) CSRCS += i2schar.c endif +ifeq ($(CONFIG_AUDIO_TONE),y) +CSRCS += tone.c +endif + # Include Audio driver support DEPPATH += --dep-path audio diff --git a/drivers/audio/tone.c b/drivers/audio/tone.c new file mode 100644 index 00000000000..8e401ab098d --- /dev/null +++ b/drivers/audio/tone.c @@ -0,0 +1,946 @@ +/**************************************************************************** + * drivers/audio/tone.c + * + * Copyright (C) 2016 Gregory Nutt. All rights reserved. + * Author: Alan Carvalho de Assis + * + * This driver is based on Tone Alarm driver from PX4 project. It was + * modified to become a NuttX driver and to use the Oneshot Timer API. + * + * The PX4 driver is here: + * https://github.com/PX4/Firmware/blob/master/src/drivers/stm32/tone_alarm/tone_alarm.cpp + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef CONFIG_AUDIO_TONE + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Define tone modes */ + +#define MODE_NORMAL 1 +#define MODE_LEGATO 2 +#define MODE_STACCATO 3 + +/* Max tune string length*/ + +#define MAX_TUNE_LEN (1 * 256) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure describes the state of the upper half driver */ + +struct tone_upperhalf_s +{ + uint8_t crefs; /* The number of times the device has been + * opened */ + volatile bool started; /* True: pulsed output is being generated */ + sem_t exclsem; /* Supports mutual exclusion */ + struct pwm_info_s tone; /* Pulsed output for Audio Tone */ + struct pwm_lowerhalf_s *devtone; + struct oneshot_lowerhalf_s *oneshot; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Buffer to store the tune */ + +static char tune_buf[MAX_TUNE_LEN]; + +/* Semitone offsets from C for the characters 'A'-'G' */ + +static const uint8_t g_note_tab[] = { 9, 11, 0, 2, 4, 5, 7 }; + +/* Global variable used by the tone generator */ + +static const char *g_tune; +static const char *g_next; +static uint8_t g_tempo; +static uint8_t g_note_mode; +static uint32_t g_note_length; +static uint32_t g_silence_length; +static uint8_t g_octave; +static bool g_repeat; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int tone_open(FAR struct file *filep); +static int tone_close(FAR struct file *filep); +static ssize_t tone_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t tone_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); + +static int next_char(void); +static uint8_t next_number(void); +static uint8_t next_dots(void); +static void next_note(FAR struct tone_upperhalf_s *upper); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_toneops = +{ + tone_open, /* open */ + tone_close, /* close */ + tone_read, /* read */ + tone_write, /* write */ + 0, /* seek */ + 0 /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + , 0 /* poll */ +#endif +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , 0 /* unlink */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: oneshot_callback + ****************************************************************************/ + +static void oneshot_callback(FAR struct oneshot_lowerhalf_s *lower, + FAR void *arg) +{ + FAR struct tone_upperhalf_s *upper = (FAR struct tone_upperhalf_s *)arg; + + audinfo("Oneshot timer expired!\n"); + + /* Play the next note */ + + next_note(upper); +} + +/**************************************************************************** + * Name: note_to_freq + * + * Description: + * This function converts a note value in the range C1 to B7 to frequency. + * + ****************************************************************************/ + +static uint16_t note_to_freq(uint8_t note) +{ + /* Compute the frequency in Hz */ + + uint16_t freq = 880.0f * expf(logf(2.0f) * ((int)note - 46) / 12.0f); + + return freq; +} + +/**************************************************************************** + * Name: note_duration + * + * Description: + * This function calculates the duration in microseconds of play and + * silence for a note given the current tempo, length and mode and the + * number of dots following in the play string. + * + ****************************************************************************/ + +static uint32_t note_duration(FAR uint32_t *silence, uint32_t note_length, + uint32_t dots) +{ + uint32_t whole_note_period = (60 * 1000000 * 4) / g_tempo; + uint32_t note_period; + uint32_t dot_extension; + + if (note_length == 0) + { + note_length = 1; + } + + note_period = whole_note_period / note_length; + + switch (g_note_mode) + { + case MODE_NORMAL: + *silence = note_period / 8; + break; + + case MODE_STACCATO: + *silence = note_period / 4; + break; + + case MODE_LEGATO: + *silence = 0; + break; + + default: + auderr("Mode undefined!\n"); + break; + } + + note_period -= *silence; + dot_extension = note_period / 2; + + while (dots--) + { + note_period += dot_extension; + dot_extension /= 2; + } + + return note_period; +} + +/**************************************************************************** + * Name: rest_duration + * + * Description: + * This function calculates the duration in microseconds of a rest + * corresponding to a given note length. + * + ****************************************************************************/ + +static uint32_t rest_duration(uint32_t rest_length, uint32_t dots) +{ + uint32_t whole_note_period = (60 * 1000000 * 4) / g_tempo; + uint32_t rest_period; + uint32_t dot_extension; + + if (rest_length == 0) + { + rest_length = 1; + } + + rest_period = whole_note_period / rest_length; + + dot_extension = rest_period / 2; + + while (dots--) + { + rest_period += dot_extension; + dot_extension /= 2; + } + + return rest_period; +} + +/**************************************************************************** + * Name: start_note + ****************************************************************************/ + +static void start_note(FAR struct tone_upperhalf_s *upper, uint8_t note) +{ + FAR struct pwm_lowerhalf_s *tone = upper->devtone; + + upper->tone.frequency = note_to_freq(note); + upper->tone.duty = 50; + + tone->ops->start(tone, &upper->tone); + + return; +} + +/**************************************************************************** + * Name: stop_note + ****************************************************************************/ + +static void stop_note(FAR struct tone_upperhalf_s *upper) +{ + FAR struct pwm_lowerhalf_s *tone = upper->devtone; + + tone->ops->stop(tone); + + return; +} + +/**************************************************************************** + * Name: start_tune + * + * Description: + * This function starts playing the note. + * + ****************************************************************************/ + +static void start_tune(FAR struct tone_upperhalf_s *upper, const char *tune) +{ + FAR struct timespec ts; + + /* Kill any current playback */ + + ONESHOT_CANCEL(upper->oneshot, &ts); + + /* Record the tune */ + + g_tune = tune; + g_next = tune; + + /* Initialise player state */ + + g_tempo = 120; + g_note_length = 4; + g_note_mode = MODE_NORMAL; + g_octave = 4; + g_silence_length = 0; + g_repeat = false; + + /* Schedule a callback to start playing */ + + ts.tv_sec = 1; + ts.tv_nsec = 0; + + ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); +} + +/**************************************************************************** + * Name: next_note + * + * Description: + * This function parses the next note out of the string and play it. + * + ****************************************************************************/ + +static void next_note(FAR struct tone_upperhalf_s *upper) +{ + uint32_t note; + uint32_t note_length; + uint32_t duration; + uint32_t sec; + uint32_t nsec; + FAR struct timespec ts; + + /* Do we have an inter-note gap to wait for? */ + + if (g_silence_length > 0) + { + stop_note(upper); + + duration = g_silence_length; + + /* Setup the time duration */ + + sec = duration / USEC_PER_SEC; + nsec = ((duration) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; + + ts.tv_sec = (time_t) sec; + ts.tv_nsec = (unsigned long)nsec; + + ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); + + g_silence_length = 0; + return; + } + + /* Make sure we still have a tune - may be removed by the write / ioctl + * handler */ + + if ((g_next == NULL) || (g_tune == NULL)) + { + stop_note(upper); + return; + } + + /* Parse characters out of the string until we have resolved a note */ + + note = 0; + note_length = g_note_length; + + while (note == 0) + { + /* We always need at least one character from the string */ + + int c = next_char(); + + if (c == 0) + { + goto tune_end; + } + + g_next++; + + switch (c) + { + uint8_t nt; + + /* Select note length */ + + case 'L': + g_note_length = next_number(); + if (g_note_length < 1) + { + auderr("note length too short!\n"); + goto tune_error; + } + break; + + /* Select octave */ + + case 'O': + g_octave = next_number(); + if (g_octave > 6) + { + g_octave = 6; + } + break; + + /* Decrease octave */ + + case '<': + if (g_octave > 0) + { + g_octave--; + } + break; + + /* Increase octave */ + + case '>': + if (g_octave < 6) + { + g_octave++; + } + break; + + /* Select inter-note gap */ + + case 'M': + c = next_char(); + + if (c == 0) + { + auderr("no character after M!\n"); + goto tune_error; + } + + g_next++; + + switch (c) + { + case 'N': + g_note_mode = MODE_NORMAL; + break; + + case 'L': + g_note_mode = MODE_LEGATO; + break; + + case 'S': + g_note_mode = MODE_STACCATO; + break; + + case 'F': + g_repeat = false; + break; + + case 'B': + g_repeat = true; + + default: + auderr("unknown symbol: %c!\n", c); + goto tune_error; + break; + } + + /* Pause for a note length */ + + case 'P': + + stop_note(upper); + + duration = rest_duration(next_number(), next_dots()); + + /* Setup the time duration */ + + sec = duration / USEC_PER_SEC; + nsec = ((duration) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; + + ts.tv_sec = (time_t) sec; + ts.tv_nsec = (unsigned long)nsec; + + ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); + + return; + + /* Change tempo */ + + case 'T': + nt = next_number(); + + if ((nt >= 32) && (nt <= 255)) + { + g_tempo = nt; + } + else + { + auderr("T is out of range 32-255!\n"); + goto tune_error; + } + break; + + /* Play an arbitrary note */ + + case 'N': + note = next_number(); + if (note > 84) + { + auderr("Note higher than 84!\n"); + goto tune_error; + } + + /* This is a rest - pause for the current note length */ + + if (note == 0) + { + duration = rest_duration(g_note_length, next_dots()); + + /* Setup the time duration */ + + sec = duration / USEC_PER_SEC; + nsec = ((duration) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; + + ts.tv_sec = (time_t) sec; + ts.tv_nsec = (unsigned long)nsec; + + ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); + + return; + } + break; + + /* Play a note in the current octave */ + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + note = g_note_tab[c - 'A'] + (g_octave * 12) + 1; + + c = next_char(); + + switch (c) + { + /* Up a semitone */ + + case '#': + case '+': + if (note < 84) + { + note++; + } + + g_next++; + break; + + /* Down a semitone */ + + case '-': + if (note > 1) + { + note--; + } + + g_next++; + break; + + /* No next char here is OK */ + + default: + break; + } + + /* Shorthand length notation */ + + note_length = next_number(); + + if (note_length == 0) + { + note_length = g_note_length; + } + + break; + + default: + goto tune_error; + } + } + + /* Compute the duration of the note and the following silence (if any) */ + + duration = note_duration(&g_silence_length, note_length, next_dots()); + + /* Start playing the note */ + + start_note(upper, note); + + /* Setup time duration */ + + sec = duration / USEC_PER_SEC; + nsec = ((duration) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC; + + ts.tv_sec = (time_t) sec; + ts.tv_nsec = (unsigned long)nsec; + + /* And arrange a callback when the note should stop */ + + ONESHOT_START(upper->oneshot, oneshot_callback, upper, &ts); + + return; + + /* Tune looks bad (unexpected EOF, bad character, etc.) */ + +tune_error: + auderr("tune error\n"); + + /* Don't loop on error */ + + g_repeat = false; + + /* Stop (and potentially restart) the tune */ + +tune_end: + stop_note(upper); + + if (g_repeat) + { + start_tune(upper, g_tune); + } + else + { + g_tune = NULL; + } +} + +/**************************************************************************** + * Name: next_char + * + * Description: + * This function find the next character in the string, discard any + * whitespace and return the canonical (uppercase) version. + * + ****************************************************************************/ + +static int next_char(void) +{ + while (isspace(*g_next)) + { + g_next++; + } + + return toupper(*g_next); +} + +/**************************************************************************** + * Name: next_number + * + * Description: + * This function extract a number from the string, consuming all the digit + * characters. + * + ****************************************************************************/ + +static uint8_t next_number(void) +{ + uint8_t number = 0; + int c; + + for (;;) + { + c = next_char(); + + if (!isdigit(c)) + { + return number; + } + + g_next++; + number = (number * 10) + (c - '0'); + } + + return number; +} + +/**************************************************************************** + * Name: next_dots + * + * Description: + * This function consumes dot characters from the string, returning the + * number consumed. + * + ****************************************************************************/ + +static uint8_t next_dots(void) +{ + uint8_t dots = 0; + + while (next_char() == '.') + { + g_next++; + dots++; + } + + return dots; +} + +/**************************************************************************** + * Name: tone_open + * + * Description: + * This function is called whenever the PWM device is opened. + * + ****************************************************************************/ + +static int tone_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct tone_upperhalf_s *upper = inode->i_private; + uint8_t tmp; + int ret; + + audinfo("crefs: %d\n", upper->crefs); + + /* Get exclusive access to the device structures */ + + ret = sem_wait(&upper->exclsem); + if (ret < 0) + { + ret = -get_errno(); + goto errout; + } + + /* Increment the count of references to the device. If this the first time + * that the driver has been opened for this device, then initialize the + * device. */ + + tmp = upper->crefs + 1; + if (tmp == 0) + { + /* More than 255 opens; uint8_t overflows to zero */ + + ret = -EMFILE; + goto errout_with_sem; + } + + /* Save the new open count on success */ + + upper->crefs = tmp; + ret = OK; + +errout_with_sem: + sem_post(&upper->exclsem); + +errout: + return ret; +} + +/**************************************************************************** + * Name: tone_close + * + * Description: + * This function is called when the PWM device is closed. + * + ****************************************************************************/ + +static int tone_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct tone_upperhalf_s *upper = inode->i_private; + int ret; + + audinfo("crefs: %d\n", upper->crefs); + + /* Get exclusive access to the device structures */ + + ret = sem_wait(&upper->exclsem); + if (ret < 0) + { + ret = -get_errno(); + goto errout; + } + + /* Decrement the references to the driver. If the reference count will + * decrement to 0, then uninitialize the driver. */ + + if (upper->crefs > 1) + { + upper->crefs--; + } + + sem_post(&upper->exclsem); + ret = OK; + +errout: + return ret; +} + +/**************************************************************************** + * Name: tone_read + * + * Description: + * A dummy read method. This is provided only to satisfy the VFS layer. + * + ****************************************************************************/ + +static ssize_t tone_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + /* Return zero -- usually meaning end-of-file */ + + return 0; +} + +/**************************************************************************** + * Name: tone_write + * + * Description: + * A dummy write method. This is provided only to satisfy the VFS layer. + * + ****************************************************************************/ + +static ssize_t tone_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + + FAR struct inode *inode = filep->f_inode; + FAR struct tone_upperhalf_s *upper = inode->i_private; + + /* We need to receive a string #RRGGBB = 7 bytes */ + + if (buffer == NULL) + { + /* Well... nothing to do */ + + return -EINVAL; + } + + /* Copy music to internal buffer */ + + memcpy(tune_buf, buffer, buflen); + + /* Let the music play */ + + start_tune(upper, tune_buf); + + return buflen; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tone_register + * + * Description: + * This function binds an instance of a "lower half" PWM driver with + * the "upper half" Audio Tone device and registers that device so that can + * be used by application code. + * + * + * Input parameters: + * path - The full path to the driver to be registers in the NuttX pseudo- + * filesystem. The recommended convention is to name of PWM driver + * as "/dev/tone0". + * tone - A pointer to an instance of lower half PWM + * drivers for the tone device. This instance will be bound to the Audio + * tone driver and must persists as long as that driver persists. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int tone_register(FAR const char *path, FAR struct pwm_lowerhalf_s *tone, + FAR struct oneshot_lowerhalf_s *oneshot) +{ + FAR struct tone_upperhalf_s *upper; + + /* Allocate the upper-half data structure */ + + upper = + (FAR struct tone_upperhalf_s *)kmm_zalloc(sizeof(struct tone_upperhalf_s)); + + if (!upper) + { + auderr("ERROR: Allocation failed\n"); + return -ENOMEM; + } + + /* Initialize the PWM device structure (it was already zeroed by + * kmm_zalloc()). + */ + + sem_init(&upper->exclsem, 0, 1); + upper->devtone = tone; + upper->oneshot = oneshot; + + /* Register the PWM device */ + + audinfo("Registering %s\n", path); + return register_driver(path, &g_toneops, 0666, upper); +} + +#endif /* CONFIG_AUDIO_TONE */ diff --git a/include/nuttx/audio/tone.h b/include/nuttx/audio/tone.h new file mode 100644 index 00000000000..7f15dc69548 --- /dev/null +++ b/include/nuttx/audio/tone.h @@ -0,0 +1,97 @@ +/**************************************************************************** + * include/nuttx/audio/tone.h + * + * Copyright (C) 2016 Gregory Nutt. All rights reserved. + * Author: Alan Carvalho de Assis + * + * 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_AUDIO_TONE_H +#define __INCLUDE_NUTTX_AUDIO_TONE_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#include +#include + +#ifdef CONFIG_AUDIO_TONE + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: tone_register + * + * Description: + * This function binds an instance of a "lower half" PWM driver with + * the "upper half" Audio Tone device and registers that device so that can + * be used by application code. + * + * + * Input parameters: + * path - The full path to the driver to be registers in the NuttX pseudo- + * filesystem. The recommended convention is to name all PWM drivers + * as "/dev/tone0", "/dev/tone1", etc. where the driver path + * differs only in the "minor" number at the end of the device name. + * tone - A pointer to an instance of lower half PWM driver tone. This + * instance will be bound to the Audio Tone driver and must persists as + * long as that driver persists. + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int tone_register(FAR const char *path, FAR struct pwm_lowerhalf_s *tone, + FAR struct oneshot_lowerhalf_s *oneshot); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_AUDIO_TONE */ +#endif /* __INCLUDE_NUTTX_AUDIO_TONE_H */