diff options
Diffstat (limited to 'sound/pci/mixart/mixart_core.c')
-rw-r--r-- | sound/pci/mixart/mixart_core.c | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/sound/pci/mixart/mixart_core.c b/sound/pci/mixart/mixart_core.c new file mode 100644 index 0000000..b9a06c2 --- /dev/null +++ b/sound/pci/mixart/mixart_core.c @@ -0,0 +1,598 @@ +/* + * Driver for Digigram miXart soundcards + * + * low level interface with interrupt handling and mail box implementation + * + * Copyright (c) 2003 by Digigram <alsa@digigram.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/interrupt.h> +#include <linux/mutex.h> + +#include <asm/io.h> +#include <sound/core.h> +#include "mixart.h" +#include "mixart_hwdep.h" +#include "mixart_core.h" + + +#define MSG_TIMEOUT_JIFFIES (400 * HZ) / 1000 /* 400 ms */ + +#define MSG_DESCRIPTOR_SIZE 0x24 +#define MSG_HEADER_SIZE (MSG_DESCRIPTOR_SIZE + 4) + +#define MSG_DEFAULT_SIZE 512 + +#define MSG_TYPE_MASK 0x00000003 /* mask for following types */ +#define MSG_TYPE_NOTIFY 0 /* embedded -> driver (only notification, do not get_msg() !) */ +#define MSG_TYPE_COMMAND 1 /* driver <-> embedded (a command has no answer) */ +#define MSG_TYPE_REQUEST 2 /* driver -> embedded (request will get an answer back) */ +#define MSG_TYPE_ANSWER 3 /* embedded -> driver */ +#define MSG_CANCEL_NOTIFY_MASK 0x80000000 /* this bit is set for a notification that has been canceled */ + + +static int retrieve_msg_frame(struct mixart_mgr *mgr, u32 *msg_frame) +{ + /* read the message frame fifo */ + u32 headptr, tailptr; + + tailptr = readl_be(MIXART_MEM(mgr, MSG_OUTBOUND_POST_TAIL)); + headptr = readl_be(MIXART_MEM(mgr, MSG_OUTBOUND_POST_HEAD)); + + if (tailptr == headptr) + return 0; /* no message posted */ + + if (tailptr < MSG_OUTBOUND_POST_STACK) + return 0; /* error */ + if (tailptr >= MSG_OUTBOUND_POST_STACK + MSG_BOUND_STACK_SIZE) + return 0; /* error */ + + *msg_frame = readl_be(MIXART_MEM(mgr, tailptr)); + + /* increment the tail index */ + tailptr += 4; + if( tailptr >= (MSG_OUTBOUND_POST_STACK+MSG_BOUND_STACK_SIZE) ) + tailptr = MSG_OUTBOUND_POST_STACK; + writel_be(tailptr, MIXART_MEM(mgr, MSG_OUTBOUND_POST_TAIL)); + + return 1; +} + +static int get_msg(struct mixart_mgr *mgr, struct mixart_msg *resp, + u32 msg_frame_address ) +{ + unsigned long flags; + u32 headptr; + u32 size; + int err; +#ifndef __BIG_ENDIAN + unsigned int i; +#endif + + spin_lock_irqsave(&mgr->msg_lock, flags); + err = 0; + + /* copy message descriptor from miXart to driver */ + size = readl_be(MIXART_MEM(mgr, msg_frame_address)); /* size of descriptor + response */ + resp->message_id = readl_be(MIXART_MEM(mgr, msg_frame_address + 4)); /* dwMessageID */ + resp->uid.object_id = readl_be(MIXART_MEM(mgr, msg_frame_address + 8)); /* uidDest */ + resp->uid.desc = readl_be(MIXART_MEM(mgr, msg_frame_address + 12)); /* */ + + if( (size < MSG_DESCRIPTOR_SIZE) || (resp->size < (size - MSG_DESCRIPTOR_SIZE))) { + err = -EINVAL; + snd_printk(KERN_ERR "problem with response size = %d\n", size); + goto _clean_exit; + } + size -= MSG_DESCRIPTOR_SIZE; + + memcpy_fromio(resp->data, MIXART_MEM(mgr, msg_frame_address + MSG_HEADER_SIZE ), size); + resp->size = size; + + /* swap if necessary */ +#ifndef __BIG_ENDIAN + size /= 4; /* u32 size */ + for(i=0; i < size; i++) { + ((u32*)resp->data)[i] = be32_to_cpu(((u32*)resp->data)[i]); + } +#endif + + /* + * free message frame address + */ + headptr = readl_be(MIXART_MEM(mgr, MSG_OUTBOUND_FREE_HEAD)); + + if( (headptr < MSG_OUTBOUND_FREE_STACK) || ( headptr >= (MSG_OUTBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE))) { + err = -EINVAL; + goto _clean_exit; + } + + /* give address back to outbound fifo */ + writel_be(msg_frame_address, MIXART_MEM(mgr, headptr)); + + /* increment the outbound free head */ + headptr += 4; + if( headptr >= (MSG_OUTBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE) ) + headptr = MSG_OUTBOUND_FREE_STACK; + + writel_be(headptr, MIXART_MEM(mgr, MSG_OUTBOUND_FREE_HEAD)); + + _clean_exit: + spin_unlock_irqrestore(&mgr->msg_lock, flags); + + return err; +} + + +/* + * send a message to miXart. return: the msg_frame used for this message + */ +/* call with mgr->msg_lock held! */ +static int send_msg( struct mixart_mgr *mgr, + struct mixart_msg *msg, + int max_answersize, + int mark_pending, + u32 *msg_event) +{ + u32 headptr, tailptr; + u32 msg_frame_address; + int err, i; + + if (snd_BUG_ON(msg->size % 4)) + return -EINVAL; + + err = 0; + + /* get message frame address */ + tailptr = readl_be(MIXART_MEM(mgr, MSG_INBOUND_FREE_TAIL)); + headptr = readl_be(MIXART_MEM(mgr, MSG_INBOUND_FREE_HEAD)); + + if (tailptr == headptr) { + snd_printk(KERN_ERR "error: no message frame available\n"); + return -EBUSY; + } + + if( (tailptr < MSG_INBOUND_FREE_STACK) || (tailptr >= (MSG_INBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE))) { + return -EINVAL; + } + + msg_frame_address = readl_be(MIXART_MEM(mgr, tailptr)); + writel(0, MIXART_MEM(mgr, tailptr)); /* set address to zero on this fifo position */ + + /* increment the inbound free tail */ + tailptr += 4; + if( tailptr >= (MSG_INBOUND_FREE_STACK+MSG_BOUND_STACK_SIZE) ) + tailptr = MSG_INBOUND_FREE_STACK; + + writel_be(tailptr, MIXART_MEM(mgr, MSG_INBOUND_FREE_TAIL)); + + /* TODO : use memcpy_toio() with intermediate buffer to copy the message */ + + /* copy message descriptor to card memory */ + writel_be( msg->size + MSG_DESCRIPTOR_SIZE, MIXART_MEM(mgr, msg_frame_address) ); /* size of descriptor + request */ + writel_be( msg->message_id , MIXART_MEM(mgr, msg_frame_address + 4) ); /* dwMessageID */ + writel_be( msg->uid.object_id, MIXART_MEM(mgr, msg_frame_address + 8) ); /* uidDest */ + writel_be( msg->uid.desc, MIXART_MEM(mgr, msg_frame_address + 12) ); /* */ + writel_be( MSG_DESCRIPTOR_SIZE, MIXART_MEM(mgr, msg_frame_address + 16) ); /* SizeHeader */ + writel_be( MSG_DESCRIPTOR_SIZE, MIXART_MEM(mgr, msg_frame_address + 20) ); /* OffsetDLL_T16 */ + writel_be( msg->size, MIXART_MEM(mgr, msg_frame_address + 24) ); /* SizeDLL_T16 */ + writel_be( MSG_DESCRIPTOR_SIZE, MIXART_MEM(mgr, msg_frame_address + 28) ); /* OffsetDLL_DRV */ + writel_be( 0, MIXART_MEM(mgr, msg_frame_address + 32) ); /* SizeDLL_DRV */ + writel_be( MSG_DESCRIPTOR_SIZE + max_answersize, MIXART_MEM(mgr, msg_frame_address + 36) ); /* dwExpectedAnswerSize */ + + /* copy message data to card memory */ + for( i=0; i < msg->size; i+=4 ) { + writel_be( *(u32*)(msg->data + i), MIXART_MEM(mgr, MSG_HEADER_SIZE + msg_frame_address + i) ); + } + + if( mark_pending ) { + if( *msg_event ) { + /* the pending event is the notification we wait for ! */ + mgr->pending_event = *msg_event; + } + else { + /* the pending event is the answer we wait for (same address than the request)! */ + mgr->pending_event = msg_frame_address; + + /* copy address back to caller */ + *msg_event = msg_frame_address; + } + } + + /* mark the frame as a request (will have an answer) */ + msg_frame_address |= MSG_TYPE_REQUEST; + + /* post the frame */ + headptr = readl_be(MIXART_MEM(mgr, MSG_INBOUND_POST_HEAD)); + + if( (headptr < MSG_INBOUND_POST_STACK) || (headptr >= (MSG_INBOUND_POST_STACK+MSG_BOUND_STACK_SIZE))) { + return -EINVAL; + } + + writel_be(msg_frame_address, MIXART_MEM(mgr, headptr)); + + /* increment the inbound post head */ + headptr += 4; + if( headptr >= (MSG_INBOUND_POST_STACK+MSG_BOUND_STACK_SIZE) ) + headptr = MSG_INBOUND_POST_STACK; + + writel_be(headptr, MIXART_MEM(mgr, MSG_INBOUND_POST_HEAD)); + + return 0; +} + + +int snd_mixart_send_msg(struct mixart_mgr *mgr, struct mixart_msg *request, int max_resp_size, void *resp_data) +{ + struct mixart_msg resp; + u32 msg_frame = 0; /* set to 0, so it's no notification to wait for, but the answer */ + int err; + wait_queue_t wait; + long timeout; + + mutex_lock(&mgr->msg_mutex); + + init_waitqueue_entry(&wait, current); + + spin_lock_irq(&mgr->msg_lock); + /* send the message */ + err = send_msg(mgr, request, max_resp_size, 1, &msg_frame); /* send and mark the answer pending */ + if (err) { + spin_unlock_irq(&mgr->msg_lock); + mutex_unlock(&mgr->msg_mutex); + return err; + } + + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&mgr->msg_sleep, &wait); + spin_unlock_irq(&mgr->msg_lock); + timeout = schedule_timeout(MSG_TIMEOUT_JIFFIES); + remove_wait_queue(&mgr->msg_sleep, &wait); + + if (! timeout) { + /* error - no ack */ + mutex_unlock(&mgr->msg_mutex); + snd_printk(KERN_ERR "error: no reponse on msg %x\n", msg_frame); + return -EIO; + } + + /* retrieve the answer into the same struct mixart_msg */ + resp.message_id = 0; + resp.uid = (struct mixart_uid){0,0}; + resp.data = resp_data; + resp.size = max_resp_size; + + err = get_msg(mgr, &resp, msg_frame); + + if( request->message_id != resp.message_id ) + snd_printk(KERN_ERR "REPONSE ERROR!\n"); + + mutex_unlock(&mgr->msg_mutex); + return err; +} + + +int snd_mixart_send_msg_wait_notif(struct mixart_mgr *mgr, + struct mixart_msg *request, u32 notif_event) +{ + int err; + wait_queue_t wait; + long timeout; + + if (snd_BUG_ON(!notif_event)) + return -EINVAL; + if (snd_BUG_ON((notif_event & MSG_TYPE_MASK) != MSG_TYPE_NOTIFY)) + return -EINVAL; + if (snd_BUG_ON(notif_event & MSG_CANCEL_NOTIFY_MASK)) + return -EINVAL; + + mutex_lock(&mgr->msg_mutex); + + init_waitqueue_entry(&wait, current); + + spin_lock_irq(&mgr->msg_lock); + /* send the message */ + err = send_msg(mgr, request, MSG_DEFAULT_SIZE, 1, ¬if_event); /* send and mark the notification event pending */ + if(err) { + spin_unlock_irq(&mgr->msg_lock); + mutex_unlock(&mgr->msg_mutex); + return err; + } + + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&mgr->msg_sleep, &wait); + spin_unlock_irq(&mgr->msg_lock); + timeout = schedule_timeout(MSG_TIMEOUT_JIFFIES); + remove_wait_queue(&mgr->msg_sleep, &wait); + + if (! timeout) { + /* error - no ack */ + mutex_unlock(&mgr->msg_mutex); + snd_printk(KERN_ERR "error: notification %x not received\n", notif_event); + return -EIO; + } + + mutex_unlock(&mgr->msg_mutex); + return 0; +} + + +int snd_mixart_send_msg_nonblock(struct mixart_mgr *mgr, struct mixart_msg *request) +{ + u32 message_frame; + unsigned long flags; + int err; + + /* just send the message (do not mark it as a pending one) */ + spin_lock_irqsave(&mgr->msg_lock, flags); + err = send_msg(mgr, request, MSG_DEFAULT_SIZE, 0, &message_frame); + spin_unlock_irqrestore(&mgr->msg_lock, flags); + + /* the answer will be handled by snd_struct mixart_msgasklet() */ + atomic_inc(&mgr->msg_processed); + + return err; +} + + +/* common buffer of tasklet and interrupt to send/receive messages */ +static u32 mixart_msg_data[MSG_DEFAULT_SIZE / 4]; + + +void snd_mixart_msg_tasklet(unsigned long arg) +{ + struct mixart_mgr *mgr = ( struct mixart_mgr*)(arg); + struct mixart_msg resp; + u32 msg, addr, type; + int err; + + spin_lock(&mgr->lock); + + while (mgr->msg_fifo_readptr != mgr->msg_fifo_writeptr) { + msg = mgr->msg_fifo[mgr->msg_fifo_readptr]; + mgr->msg_fifo_readptr++; + mgr->msg_fifo_readptr %= MSG_FIFO_SIZE; + + /* process the message ... */ + addr = msg & ~MSG_TYPE_MASK; + type = msg & MSG_TYPE_MASK; + + switch (type) { + case MSG_TYPE_ANSWER: + /* answer to a message on that we did not wait for (send_msg_nonblock) */ + resp.message_id = 0; + resp.data = mixart_msg_data; + resp.size = sizeof(mixart_msg_data); + err = get_msg(mgr, &resp, addr); + if( err < 0 ) { + snd_printk(KERN_ERR "tasklet: error(%d) reading mf %x\n", err, msg); + break; + } + + switch(resp.message_id) { + case MSG_STREAM_START_INPUT_STAGE_PACKET: + case MSG_STREAM_START_OUTPUT_STAGE_PACKET: + case MSG_STREAM_STOP_INPUT_STAGE_PACKET: + case MSG_STREAM_STOP_OUTPUT_STAGE_PACKET: + if(mixart_msg_data[0]) + snd_printk(KERN_ERR "tasklet : error MSG_STREAM_ST***_***PUT_STAGE_PACKET status=%x\n", mixart_msg_data[0]); + break; + default: + snd_printdd("tasklet received mf(%x) : msg_id(%x) uid(%x, %x) size(%zd)\n", + msg, resp.message_id, resp.uid.object_id, resp.uid.desc, resp.size); + break; + } + break; + case MSG_TYPE_NOTIFY: + /* msg contains no address ! do not get_msg() ! */ + case MSG_TYPE_COMMAND: + /* get_msg() necessary */ + default: + snd_printk(KERN_ERR "tasklet doesn't know what to do with message %x\n", msg); + } /* switch type */ + + /* decrement counter */ + atomic_dec(&mgr->msg_processed); + + } /* while there is a msg in fifo */ + + spin_unlock(&mgr->lock); +} + + +irqreturn_t snd_mixart_interrupt(int irq, void *dev_id) +{ + struct mixart_mgr *mgr = dev_id; + int err; + struct mixart_msg resp; + + u32 msg; + u32 it_reg; + + spin_lock(&mgr->lock); + + it_reg = readl_le(MIXART_REG(mgr, MIXART_PCI_OMISR_OFFSET)); + if( !(it_reg & MIXART_OIDI) ) { + /* this device did not cause the interrupt */ + spin_unlock(&mgr->lock); + return IRQ_NONE; + } + + /* mask all interrupts */ + writel_le(MIXART_HOST_ALL_INTERRUPT_MASKED, MIXART_REG(mgr, MIXART_PCI_OMIMR_OFFSET)); + + /* outdoorbell register clear */ + it_reg = readl(MIXART_REG(mgr, MIXART_PCI_ODBR_OFFSET)); + writel(it_reg, MIXART_REG(mgr, MIXART_PCI_ODBR_OFFSET)); + + /* clear interrupt */ + writel_le( MIXART_OIDI, MIXART_REG(mgr, MIXART_PCI_OMISR_OFFSET) ); + + /* process interrupt */ + while (retrieve_msg_frame(mgr, &msg)) { + + switch (msg & MSG_TYPE_MASK) { + case MSG_TYPE_COMMAND: + resp.message_id = 0; + resp.data = mixart_msg_data; + resp.size = sizeof(mixart_msg_data); + err = get_msg(mgr, &resp, msg & ~MSG_TYPE_MASK); + if( err < 0 ) { + snd_printk(KERN_ERR "interrupt: error(%d) reading mf %x\n", err, msg); + break; + } + + if(resp.message_id == MSG_SERVICES_TIMER_NOTIFY) { + int i; + struct mixart_timer_notify *notify; + notify = (struct mixart_timer_notify *)mixart_msg_data; + + for(i=0; i<notify->stream_count; i++) { + + u32 buffer_id = notify->streams[i].buffer_id; + unsigned int chip_number = (buffer_id & MIXART_NOTIFY_CARD_MASK) >> MIXART_NOTIFY_CARD_OFFSET; /* card0 to 3 */ + unsigned int pcm_number = (buffer_id & MIXART_NOTIFY_PCM_MASK ) >> MIXART_NOTIFY_PCM_OFFSET; /* pcm0 to 3 */ + unsigned int sub_number = buffer_id & MIXART_NOTIFY_SUBS_MASK; /* 0 to MIXART_PLAYBACK_STREAMS */ + unsigned int is_capture = ((buffer_id & MIXART_NOTIFY_CAPT_MASK) != 0); /* playback == 0 / capture == 1 */ + + struct snd_mixart *chip = mgr->chip[chip_number]; + struct mixart_stream *stream; + + if ((chip_number >= mgr->num_cards) || (pcm_number >= MIXART_PCM_TOTAL) || (sub_number >= MIXART_PLAYBACK_STREAMS)) { + snd_printk(KERN_ERR "error MSG_SERVICES_TIMER_NOTIFY buffer_id (%x) pos(%d)\n", + buffer_id, notify->streams[i].sample_pos_low_part); + break; + } + + if (is_capture) + stream = &chip->capture_stream[pcm_number]; + else + stream = &chip->playback_stream[pcm_number][sub_number]; + + if (stream->substream && (stream->status == MIXART_STREAM_STATUS_RUNNING)) { + struct snd_pcm_runtime *runtime = stream->substream->runtime; + int elapsed = 0; + u64 sample_count = ((u64)notify->streams[i].sample_pos_high_part) << 32; + sample_count |= notify->streams[i].sample_pos_low_part; + + while (1) { + u64 new_elapse_pos = stream->abs_period_elapsed + runtime->period_size; + + if (new_elapse_pos > sample_count) { + break; /* while */ + } + else { + elapsed = 1; + stream->buf_periods++; + if (stream->buf_periods >= runtime->periods) + stream->buf_periods = 0; + + stream->abs_period_elapsed = new_elapse_pos; + } + } + stream->buf_period_frag = (u32)( sample_count - stream->abs_period_elapsed ); + + if(elapsed) { + spin_unlock(&mgr->lock); + snd_pcm_period_elapsed(stream->substream); + spin_lock(&mgr->lock); + } + } + } + break; + } + if(resp.message_id == MSG_SERVICES_REPORT_TRACES) { + if(resp.size > 1) { +#ifndef __BIG_ENDIAN + /* Traces are text: the swapped msg_data has to be swapped back ! */ + int i; + for(i=0; i<(resp.size/4); i++) { + (mixart_msg_data)[i] = cpu_to_be32((mixart_msg_data)[i]); + } +#endif + ((char*)mixart_msg_data)[resp.size - 1] = 0; + snd_printdd("MIXART TRACE : %s\n", (char*)mixart_msg_data); + } + break; + } + + snd_printdd("command %x not handled\n", resp.message_id); + break; + + case MSG_TYPE_NOTIFY: + if(msg & MSG_CANCEL_NOTIFY_MASK) { + msg &= ~MSG_CANCEL_NOTIFY_MASK; + snd_printk(KERN_ERR "canceled notification %x !\n", msg); + } + /* no break, continue ! */ + case MSG_TYPE_ANSWER: + /* answer or notification to a message we are waiting for*/ + spin_lock(&mgr->msg_lock); + if( (msg & ~MSG_TYPE_MASK) == mgr->pending_event ) { + wake_up(&mgr->msg_sleep); + mgr->pending_event = 0; + } + /* answer to a message we did't want to wait for */ + else { + mgr->msg_fifo[mgr->msg_fifo_writeptr] = msg; + mgr->msg_fifo_writeptr++; + mgr->msg_fifo_writeptr %= MSG_FIFO_SIZE; + tasklet_hi_schedule(&mgr->msg_taskq); + } + spin_unlock(&mgr->msg_lock); + break; + case MSG_TYPE_REQUEST: + default: + snd_printdd("interrupt received request %x\n", msg); + /* TODO : are there things to do here ? */ + break; + } /* switch on msg type */ + } /* while there are msgs */ + + /* allow interrupt again */ + writel_le( MIXART_ALLOW_OUTBOUND_DOORBELL, MIXART_REG( mgr, MIXART_PCI_OMIMR_OFFSET)); + + spin_unlock(&mgr->lock); + + return IRQ_HANDLED; +} + + +void snd_mixart_init_mailbox(struct mixart_mgr *mgr) +{ + writel( 0, MIXART_MEM( mgr, MSG_HOST_RSC_PROTECTION ) ); + writel( 0, MIXART_MEM( mgr, MSG_AGENT_RSC_PROTECTION ) ); + + /* allow outbound messagebox to generate interrupts */ + if(mgr->irq >= 0) { + writel_le( MIXART_ALLOW_OUTBOUND_DOORBELL, MIXART_REG( mgr, MIXART_PCI_OMIMR_OFFSET)); + } + return; +} + +void snd_mixart_exit_mailbox(struct mixart_mgr *mgr) +{ + /* no more interrupts on outbound messagebox */ + writel_le( MIXART_HOST_ALL_INTERRUPT_MASKED, MIXART_REG( mgr, MIXART_PCI_OMIMR_OFFSET)); + return; +} + +void snd_mixart_reset_board(struct mixart_mgr *mgr) +{ + /* reset miXart */ + writel_be( 1, MIXART_REG(mgr, MIXART_BA1_BRUTAL_RESET_OFFSET) ); + return; +} |