summaryrefslogtreecommitdiffstats
path: root/c/src/lib/libbsp/lm32/shared/milkymist_ac97
diff options
context:
space:
mode:
authorJoel Sherrill <joel.sherrill@OARcorp.com>2011-08-01 13:48:40 +0000
committerJoel Sherrill <joel.sherrill@OARcorp.com>2011-08-01 13:48:40 +0000
commitdce1032b6cdc2cd3c4e6b0ca3695aca6558c56c3 (patch)
treee485d78b238db7255395470e037305af8b8a642c /c/src/lib/libbsp/lm32/shared/milkymist_ac97
parent2011-08-01 Jennifer Averett <Jennifer.Averett@OARcorp.com> (diff)
downloadrtems-dce1032b6cdc2cd3c4e6b0ca3695aca6558c56c3.tar.bz2
2011-08-01 Sebastien Bourdeauducq <sebastien.bourdeauducq@gmail.com>
PR 1869/bsps * startup/bspclean.c: New file. * include/tm27.h: Removed. * ChangeLog, Makefile.am, README, preinstall.am, include/bsp.h, include/system_conf.h, make/custom/milkymist.cfg, startup/linkcmds: Complete BSP for Milkymist One supporting Milkymist SOC 1.0.x. Includes new or updated drivers for: - Multi-standard video input (PAL/SECAM/NTSC) - Two DMX512 (RS485) ports - MIDI IN and MIDI OUT ports - VGA output - AC'97 audio - NOR flash - 10/100 Ethernet - Memory card (experimental and incomplete) - USB host connectors (input devices only) - RC5 infrared receiver - RS232 debug port
Diffstat (limited to 'c/src/lib/libbsp/lm32/shared/milkymist_ac97')
-rw-r--r--c/src/lib/libbsp/lm32/shared/milkymist_ac97/ac97.c443
-rw-r--r--c/src/lib/libbsp/lm32/shared/milkymist_ac97/milkymist_ac97.h63
2 files changed, 438 insertions, 68 deletions
diff --git a/c/src/lib/libbsp/lm32/shared/milkymist_ac97/ac97.c b/c/src/lib/libbsp/lm32/shared/milkymist_ac97/ac97.c
index 671d4f165b..b467538122 100644
--- a/c/src/lib/libbsp/lm32/shared/milkymist_ac97/ac97.c
+++ b/c/src/lib/libbsp/lm32/shared/milkymist_ac97/ac97.c
@@ -1,7 +1,6 @@
/* ac97.c
*
- * This file is the sound driver for the Milkymist One board
- * It does an OSS style character device in /dev/snd
+ * Sound driver for Milkymist SoC
*
* The license and distribution terms for this file may be
* found in the file LICENSE in this distribution or at
@@ -9,111 +8,419 @@
*
* $Id$
*
- * COPYRIGHT (c) Yann Sionneau <yann.sionneau@telecom-sudparis.eu> (GSoC 2010)
- * Telecom SudParis
+ * COPYRIGHT (c) 2010, 2011 Sebastien Bourdeauducq
*/
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <pthread.h>
+#define RTEMS_STATUS_CHECKS_USE_PRINTK
+
#include <rtems.h>
#include <bsp.h>
-#include "../include/system_conf.h"
+#include <bsp/irq-generic.h>
#include <rtems/libio.h>
+#include <rtems/status-checks.h>
+#include "../include/system_conf.h"
+#include "milkymist_ac97.h"
-#define GPIO_DRIVER_TABLE_ENTRY { ac97_initialize, \
-ac97_open, ac97_close, ac97_read, ac97_write, ac97_control}
+#define SND_DEVICE_NAME "/dev/snd"
+#define MIXER_DEVICE_NAME "/dev/mixer"
-#define SND_DEVICE_NAME "/dev/snd"
+static rtems_id cr_write_sem;
+static rtems_id cr_read_sem;
-static struct milkymist_ac97 {
- rtems_device_minor_number minor;
- pthread_mutex_t mutex;
-} ac97_driver;
+static rtems_isr crrequest_handler(rtems_vector_number n)
+{
+ rtems_semaphore_release(cr_write_sem);
+ lm32_interrupt_ack(1 << MM_IRQ_AC97CRREQUEST);
+}
-rtems_device_driver gpio_initialize(
-rtems_device_major_number major,
-rtems_device_minor_number minor,
-void *arg)
+static rtems_isr crreply_handler(rtems_vector_number n)
{
- rtems_status_code status;
+ rtems_semaphore_release(cr_read_sem);
+ lm32_interrupt_ack(1 << MM_IRQ_AC97CRREPLY);
+}
- printk( "ac97 driver initializing..\n" );
+/* queued playback buffers */
+#define PLAY_Q_SIZE 8
+#define PLAY_Q_MASK (PLAY_Q_SIZE-1)
- status = rtems_io_register_name(SND_DEVICE_NAME, major, 0);
- if (status != RTEMS_SUCCESSFUL)
- {
- printk("Error registering /dev/snd ac97 device \n");
- rtems_fatal_error_occurred( status );
- }
- gpio[0].minor = 0;
- gpio[0].mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct snd_buffer *play_q[PLAY_Q_SIZE];
+static int play_produce;
+static int play_consume;
+static int play_level;
- return RTEMS_SUCCESSFUL;
+/* buffers played, for application to collect */
+static rtems_id play_q_done;
+
+static void play_start(struct snd_buffer *buf)
+{
+ if (buf->nsamples > (AC97_MAX_DMASIZE/4))
+ buf->nsamples = AC97_MAX_DMASIZE/4;
+
+ MM_WRITE(MM_AC97_DADDRESS, (unsigned int)buf->samples);
+ MM_WRITE(MM_AC97_DREMAINING, buf->nsamples*4);
+ MM_WRITE(MM_AC97_DCTL, AC97_SCTL_EN);
+}
+
+static rtems_isr pcmplay_handler(rtems_vector_number n)
+{
+ lm32_interrupt_ack(1 << MM_IRQ_AC97DMAR);
+
+ rtems_message_queue_send(play_q_done, &play_q[play_consume],
+ sizeof(void *));
+
+ play_consume = (play_consume + 1) & PLAY_Q_MASK;
+ play_level--;
+
+ if(play_level > 0)
+ play_start(play_q[play_consume]);
+ else
+ MM_WRITE(MM_AC97_DCTL, 0);
+}
+
+/* queued record buffers */
+#define RECORD_Q_SIZE 8
+#define RECORD_Q_MASK (RECORD_Q_SIZE-1)
+
+static struct snd_buffer *record_q[RECORD_Q_SIZE];
+static int record_produce;
+static int record_consume;
+static int record_level;
+
+/* buffers recorded, for application to collect */
+static rtems_id record_q_done;
+
+static void record_start(struct snd_buffer *buf)
+{
+ if (buf->nsamples > (AC97_MAX_DMASIZE/4))
+ buf->nsamples = AC97_MAX_DMASIZE/4;
+
+ MM_WRITE(MM_AC97_UADDRESS, (unsigned int)buf->samples);
+ MM_WRITE(MM_AC97_UREMAINING, buf->nsamples*4);
+ MM_WRITE(MM_AC97_UCTL, AC97_SCTL_EN);
+}
+
+static rtems_isr pcmrecord_handler(rtems_vector_number n)
+{
+ lm32_interrupt_ack(1 << MM_IRQ_AC97DMAW);
+
+ __asm__ volatile( /* Invalidate Level-1 data cache */
+ "wcsr DCC, r0\n"
+ "nop\n"
+ );
+
+ rtems_message_queue_send(record_q_done, &record_q[record_consume],
+ sizeof(void *));
+
+ record_consume = (record_consume + 1) & RECORD_Q_MASK;
+ record_level--;
+
+ if(record_level > 0)
+ record_start(record_q[record_consume]);
+ else
+ MM_WRITE(MM_AC97_UCTL, 0);
}
-rtems_device_driver gpio_close(
- rtems_device_major_number major,
- rtems_device_minor_number minor,
+rtems_device_driver ac97_initialize(
+ rtems_device_major_number major,
+ rtems_device_minor_number minor,
void *arg
)
{
- if (pthread_mutex_unlock(&ac97_driver.mutex) == 0){
- return RTEMS_SUCCESSFUL;
+ rtems_status_code sc;
+ rtems_isr_entry dummy;
+
+ sc = rtems_io_register_name(SND_DEVICE_NAME, major, 0);
+ RTEMS_CHECK_SC(sc, "create snd device");
+
+ sc = rtems_io_register_name(MIXER_DEVICE_NAME, major, 1);
+ RTEMS_CHECK_SC(sc, "create mixer device");
+
+ sc = rtems_semaphore_create(
+ rtems_build_name('C', 'R', 'W', 'S'),
+ 0,
+ RTEMS_SIMPLE_BINARY_SEMAPHORE,
+ 0,
+ &cr_write_sem
+ );
+ RTEMS_CHECK_SC(sc, "create AC97 register write semaphore");
+
+ sc = rtems_semaphore_create(
+ rtems_build_name('C', 'R', 'R', 'S'),
+ 0,
+ RTEMS_SIMPLE_BINARY_SEMAPHORE,
+ 0,
+ &cr_read_sem
+ );
+ RTEMS_CHECK_SC(sc, "create AC97 register read semaphore");
+
+ sc = rtems_message_queue_create(
+ rtems_build_name('P', 'L', 'Y', 'Q'),
+ PLAY_Q_SIZE*2,
+ sizeof(void *),
+ 0,
+ &play_q_done
+ );
+ RTEMS_CHECK_SC(sc, "create playback done queue");
+
+ sc = rtems_message_queue_create(
+ rtems_build_name('R', 'E', 'C', 'Q'),
+ RECORD_Q_SIZE*2,
+ sizeof(void *),
+ 0,
+ &record_q_done
+ );
+ RTEMS_CHECK_SC(sc, "create record done queue");
+
+ rtems_interrupt_catch(crrequest_handler, MM_IRQ_AC97CRREQUEST, &dummy);
+ rtems_interrupt_catch(crreply_handler, MM_IRQ_AC97CRREPLY, &dummy);
+ rtems_interrupt_catch(pcmplay_handler, MM_IRQ_AC97DMAR, &dummy);
+ rtems_interrupt_catch(pcmrecord_handler, MM_IRQ_AC97DMAW, &dummy);
+ bsp_interrupt_vector_enable(MM_IRQ_AC97CRREQUEST);
+ bsp_interrupt_vector_enable(MM_IRQ_AC97CRREPLY);
+ bsp_interrupt_vector_enable(MM_IRQ_AC97DMAR);
+ bsp_interrupt_vector_enable(MM_IRQ_AC97DMAW);
+
+ play_produce = 0;
+ play_consume = 0;
+ play_level = 0;
+
+ record_produce = 0;
+ record_consume = 0;
+ record_level = 0;
+
+ return RTEMS_SUCCESSFUL;
+}
+
+static rtems_status_code submit_play(struct snd_buffer *buf)
+{
+ bsp_interrupt_vector_disable(MM_IRQ_AC97DMAR);
+ if (play_level == PLAY_Q_SIZE) {
+ bsp_interrupt_vector_enable(MM_IRQ_AC97DMAR);
+ return RTEMS_UNSATISFIED;
}
- return RTEMS_UNSATISFIED;
+ play_q[play_produce] = buf;
+ play_produce = (play_produce + 1) & PLAY_Q_MASK;
+ play_level++;
+
+ if (play_level == 1)
+ play_start(buf);
+
+ bsp_interrupt_vector_enable(MM_IRQ_AC97DMAR);
+ return RTEMS_SUCCESSFUL;
}
-rtems_device_driver gpio_open(
- rtems_device_major_number major,
- rtems_device_minor_number minor,
- void *arg
-)
+static rtems_status_code collect_play(struct snd_buffer **buf)
{
- if (pthread_mutex_trylock(&ac97_driver.mutex) == 0){
- return RTEMS_SUCCESSFUL;
+ size_t s;
+
+ return rtems_message_queue_receive(
+ play_q_done,
+ buf,
+ &s,
+ RTEMS_WAIT,
+ RTEMS_NO_TIMEOUT
+ );
+}
+
+static rtems_status_code submit_record(struct snd_buffer *buf)
+{
+ bsp_interrupt_vector_disable(MM_IRQ_AC97DMAW);
+ if (record_level == RECORD_Q_SIZE) {
+ bsp_interrupt_vector_enable(MM_IRQ_AC97DMAW);
+ return RTEMS_UNSATISFIED;
}
- return RTEMS_UNSATISFIED;
+ record_q[record_produce] = buf;
+ record_produce = (record_produce + 1) & RECORD_Q_MASK;
+ record_level++;
+
+ if (record_level == 1)
+ record_start(buf);
+
+ bsp_interrupt_vector_enable(MM_IRQ_AC97DMAW);
+ return RTEMS_SUCCESSFUL;
+}
+
+static rtems_status_code collect_record(struct snd_buffer **buf)
+{
+ size_t s;
+
+ return rtems_message_queue_receive(
+ record_q_done,
+ buf,
+ &s,
+ RTEMS_WAIT,
+ RTEMS_NO_TIMEOUT
+ );
}
+#define CR_TIMEOUT 10
+
+static int read_cr(unsigned int adr)
+{
+ rtems_status_code sc;
+
+ MM_WRITE(MM_AC97_CRADDR, adr);
+ MM_WRITE(MM_AC97_CRCTL, AC97_CRCTL_RQEN);
+ sc = rtems_semaphore_obtain(cr_write_sem, RTEMS_WAIT, CR_TIMEOUT);
+ if (sc != RTEMS_SUCCESSFUL)
+ return -1;
+ sc = rtems_semaphore_obtain(cr_read_sem, RTEMS_WAIT, CR_TIMEOUT);
+ if (sc != RTEMS_SUCCESSFUL)
+ return -1;
+ return MM_READ(MM_AC97_CRDATAIN);
+}
+
+static int write_cr(unsigned int adr, unsigned int val)
+{
+ rtems_status_code sc;
+
+ MM_WRITE(MM_AC97_CRADDR, adr);
+ MM_WRITE(MM_AC97_CRDATAOUT, val);
+ MM_WRITE(MM_AC97_CRCTL, AC97_CRCTL_RQEN|AC97_CRCTL_WRITE);
+ sc = rtems_semaphore_obtain(cr_write_sem, RTEMS_WAIT, CR_TIMEOUT);
+ if (sc != RTEMS_SUCCESSFUL)
+ return 0;
+ return 1;
+}
-rtems_device_driver gpio_read(
- rtems_device_major_number major,
- rtems_device_minor_number minor,
- void *arg
+rtems_device_driver ac97_open(
+ rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg
)
{
- rtems_libio_rw_args_t *rw_args = (rtems_libio_rw_args_t *)arg;
+ int codec_id;
- return RTEMS_SUCCESSFUL;
+ if (minor == 0) {
+ /* snd */
+ return RTEMS_SUCCESSFUL;
+ } else {
+ /* mixer */
+ codec_id = read_cr(0x00);
+ if ((codec_id != 0x0d50) && (codec_id != 0x6150)) {
+ printk("AC97 codec detection failed\n");
+ return RTEMS_UNSATISFIED;
+ }
+ write_cr(0x02, 0x0000); /* master volume */
+ write_cr(0x04, 0x0f0f); /* headphones volume */
+ write_cr(0x18, 0x0000); /* PCM out volume */
+ write_cr(0x1c, 0x0f0f); /* record gain */
+
+ write_cr(0x1a, 0x0505); /* record select: stereo mix */
+
+ return RTEMS_SUCCESSFUL;
+ }
}
-rtems_device_driver gpio_write(
- rtems_device_major_number major,
- rtems_device_minor_number minor,
- void *arg
-)
+static rtems_status_code ioctl_read_channel(void *buf,
+ unsigned int chan, int mono)
{
- rtems_libio_rw_args_t *rw_args = (rtems_libio_rw_args_t *)arg;
+ unsigned int *val = (unsigned int *)buf;
+ int codec;
+ int left, right;
+ codec = read_cr(chan);
+ if (codec < 0)
+ return RTEMS_UNSATISFIED;
+ if (codec & 0x8000) {
+ /* muted */
+ *val = 0;
+ return RTEMS_SUCCESSFUL;
+ }
+ if (mono) {
+ right = left = 100-(((codec & 0x1f) + 1)*100)/32;
+ } else {
+ right = 100-(((codec & 0x1f) + 1)*100)/32;
+ left = 100-((((codec & 0x1f00) >> 8) + 1)*100)/32;
+ }
+ *val = left | (right << 8);
return RTEMS_SUCCESSFUL;
}
-rtems_device_driver gpio_control(
- rtems_device_major_number major,
- rtems_device_minor_number minor,
- void *arg
+static rtems_status_code ioctl_write_channel(void *buf,
+ unsigned int chan, int mono)
+{
+ unsigned int *val = (unsigned int *)buf;
+ int left, right;
+ int codec;
+ rtems_status_code sc;
+
+ left = *val & 0xff;
+ left = (left*32)/100 - 1;
+ if(left < 0)
+ left = 0;
+
+ if (mono)
+ right = 31;
+ else {
+ right = (*val >> 8) & 0xff;
+ right = (right*32)/100 - 1;
+ if(right < 0)
+ right = 0;
+ }
+
+ if ((left == 0) && (right == 0))
+ /* mute */
+ codec = 0x8000;
+ else
+ codec = (31-left) | ((31-right) << 8);
+
+ if (!write_cr(chan, codec))
+ sc = RTEMS_UNSATISFIED;
+ else
+ sc = RTEMS_SUCCESSFUL;
+ return sc;
+}
+
+rtems_device_driver ac97_control(
+ rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg
)
{
rtems_libio_ioctl_args_t *args = arg;
+ rtems_status_code sc;
- switch( args->command ) {
- default:
- args->ioctl_return = 0;
- break;
+ args->ioctl_return = -1;
+ if(minor == 0) {
+ /* dsp */
+ switch (args->command) {
+ case SOUND_SND_SUBMIT_PLAY:
+ return submit_play((struct snd_buffer *)args->buffer);
+ case SOUND_SND_COLLECT_PLAY:
+ return collect_play((struct snd_buffer **)args->buffer);
+ case SOUND_SND_SUBMIT_RECORD:
+ return submit_record((struct snd_buffer *)args->buffer);
+ case SOUND_SND_COLLECT_RECORD:
+ return collect_record((struct snd_buffer **)args->buffer);
+ default:
+ return RTEMS_UNSATISFIED;
+ }
+ } else {
+ /* mixer */
+ switch (args->command) {
+ case SOUND_MIXER_READ(SOUND_MIXER_MIC):
+ sc = ioctl_read_channel(args->buffer, 0x0e, 1);
+ if(sc == RTEMS_SUCCESSFUL)
+ args->ioctl_return = 0;
+ return sc;
+ case SOUND_MIXER_READ(SOUND_MIXER_LINE):
+ sc = ioctl_read_channel(args->buffer, 0x10, 0);
+ if(sc == RTEMS_SUCCESSFUL)
+ args->ioctl_return = 0;
+ return sc;
+ case SOUND_MIXER_WRITE(SOUND_MIXER_MIC):
+ sc = ioctl_write_channel(args->buffer, 0x0e, 1);
+ if(sc == RTEMS_SUCCESSFUL)
+ args->ioctl_return = 0;
+ return sc;
+ case SOUND_MIXER_WRITE(SOUND_MIXER_LINE):
+ sc = ioctl_write_channel(args->buffer, 0x10, 0);
+ if(sc == RTEMS_SUCCESSFUL)
+ args->ioctl_return = 0;
+ return sc;
+ default:
+ return RTEMS_UNSATISFIED;
+ }
}
- return RTEMS_SUCCESSFUL;
}
-
diff --git a/c/src/lib/libbsp/lm32/shared/milkymist_ac97/milkymist_ac97.h b/c/src/lib/libbsp/lm32/shared/milkymist_ac97/milkymist_ac97.h
new file mode 100644
index 0000000000..a13a0481ee
--- /dev/null
+++ b/c/src/lib/libbsp/lm32/shared/milkymist_ac97/milkymist_ac97.h
@@ -0,0 +1,63 @@
+/* milkymist_ac97.h
+ *
+ * Milkymist AC97 driver for RTEMS
+ *
+ * The license and distribution terms for this file may be
+ * found in the file LICENSE in this distribution or at
+ * http://www.rtems.com/license/LICENSE.
+ *
+ * $Id$
+ *
+ * COPYRIGHT (c) 2010 Sebastien Bourdeauducq
+ */
+
+#ifndef __MILKYMIST_AC97_H_
+#define __MILKYMIST_AC97_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Ioctls. 0x41 is 'A' */
+#define SOUND_MIXER_MIC 0x0
+#define SOUND_MIXER_LINE 0x1
+#define SOUND_MIXER_READ(x) (0x4100+x)
+#define SOUND_MIXER_WRITE(x) (0x4110+x)
+
+#define SOUND_SND_SUBMIT_PLAY 0x4120
+#define SOUND_SND_COLLECT_PLAY 0x4121
+#define SOUND_SND_SUBMIT_RECORD 0x4122
+#define SOUND_SND_COLLECT_RECORD 0x4123
+
+struct snd_buffer {
+ unsigned int nsamples;
+ void *user;
+ unsigned int samples[];
+};
+
+rtems_device_driver ac97_initialize(
+ rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg
+);
+
+rtems_device_driver ac97_open(
+ rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg
+);
+
+rtems_device_driver ac97_control(
+ rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg
+);
+
+#define AC97_DRIVER_TABLE_ENTRY {ac97_initialize, \
+ac97_open, NULL, NULL, NULL, ac97_control}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MILKYMIST_AC97_H_ */