Logo Search packages:      
Sourcecode: alsa-driver version File versions

snd_ps3.c

/*
 * Audio support for PS3
 * Copyright (C) 2007 Sony Computer Entertainment Inc.
 * All rights reserved.
 * Copyright 2006, 2007 Sony Corporation
 *
 * 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; version 2 of the Licence.
 *
 * 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/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/slab.h>

#include <sound/asound.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/memalloc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>

#include <asm/dma.h>
#include <asm/firmware.h>
#include <asm/lv1call.h>
#include <asm/ps3.h>
#include <asm/ps3av.h>

#include "snd_ps3.h"
#include "snd_ps3_reg.h"


/*
 * global
 */
static struct snd_ps3_card_info the_card;

static int snd_ps3_start_delay = CONFIG_SND_PS3_DEFAULT_START_DELAY;

module_param_named(start_delay, snd_ps3_start_delay, uint, 0644);
MODULE_PARM_DESC(start_delay, "time to insert silent data in milisec");

static int index = SNDRV_DEFAULT_IDX1;
static char *id = SNDRV_DEFAULT_STR1;

module_param(index, int, 0444);
MODULE_PARM_DESC(index, "Index value for PS3 soundchip.");
module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for PS3 soundchip.");


/*
 * PS3 audio register access
 */
static inline u32 read_reg(unsigned int reg)
{
      return in_be32(the_card.mapped_mmio_vaddr + reg);
}
static inline void write_reg(unsigned int reg, u32 val)
{
      out_be32(the_card.mapped_mmio_vaddr + reg, val);
}
static inline void update_reg(unsigned int reg, u32 or_val)
{
      u32 newval = read_reg(reg) | or_val;
      write_reg(reg, newval);
}
static inline void update_mask_reg(unsigned int reg, u32 mask, u32 or_val)
{
      u32 newval = (read_reg(reg) & mask) | or_val;
      write_reg(reg, newval);
}

/*
 * ALSA defs
 */
static const struct snd_pcm_hardware snd_ps3_pcm_hw = {
      .info = (SNDRV_PCM_INFO_MMAP |
             SNDRV_PCM_INFO_NONINTERLEAVED |
             SNDRV_PCM_INFO_MMAP_VALID),
      .formats = (SNDRV_PCM_FMTBIT_S16_BE |
                SNDRV_PCM_FMTBIT_S24_BE),
      .rates = (SNDRV_PCM_RATE_44100 |
              SNDRV_PCM_RATE_48000 |
              SNDRV_PCM_RATE_88200 |
              SNDRV_PCM_RATE_96000),
      .rate_min = 44100,
      .rate_max = 96000,

      .channels_min = 2, /* stereo only */
      .channels_max = 2,

      .buffer_bytes_max = PS3_AUDIO_FIFO_SIZE * 64,

      /* interrupt by four stages */
      .period_bytes_min = PS3_AUDIO_FIFO_STAGE_SIZE * 4,
      .period_bytes_max = PS3_AUDIO_FIFO_STAGE_SIZE * 4,

      .periods_min = 16,
      .periods_max = 32, /* buffer_size_max/ period_bytes_max */

      .fifo_size = PS3_AUDIO_FIFO_SIZE
};

static int snd_ps3_verify_dma_stop(struct snd_ps3_card_info *card,
                           int count, int force_stop)
{
      int dma_ch, done, retries, stop_forced = 0;
      uint32_t status;

      for (dma_ch = 0; dma_ch < 8; dma_ch++) {
            retries = count;
            do {
                  status = read_reg(PS3_AUDIO_KICK(dma_ch)) &
                        PS3_AUDIO_KICK_STATUS_MASK;
                  switch (status) {
                  case PS3_AUDIO_KICK_STATUS_DONE:
                  case PS3_AUDIO_KICK_STATUS_NOTIFY:
                  case PS3_AUDIO_KICK_STATUS_CLEAR:
                  case PS3_AUDIO_KICK_STATUS_ERROR:
                        done = 1;
                        break;
                  default:
                        done = 0;
                        udelay(10);
                  }
            } while (!done && --retries);
            if (!retries && force_stop) {
                  pr_info("%s: DMA ch %d is not stopped.",
                        __func__, dma_ch);
                  /* last resort. force to stop dma.
                   *  NOTE: this cause DMA done interrupts
                   */
                  update_reg(PS3_AUDIO_CONFIG, PS3_AUDIO_CONFIG_CLEAR);
                  stop_forced = 1;
            }
      }
      return stop_forced;
}

/*
 * wait for all dma is done.
 * NOTE: caller should reset card->running before call.
 *       If not, the interrupt handler will re-start DMA,
 *       then DMA is never stopped.
 */
static void snd_ps3_wait_for_dma_stop(struct snd_ps3_card_info *card)
{
      int stop_forced;
      /*
       * wait for the last dma is done
       */

      /*
       * expected maximum DMA done time is 5.7ms + something (DMA itself).
       * 5.7ms is from 16bit/sample 2ch 44.1Khz; the time next
       * DMA kick event would occur.
       */
      stop_forced = snd_ps3_verify_dma_stop(card, 700, 1);

      /*
       * clear outstanding interrupts.
       */
      update_reg(PS3_AUDIO_INTR_0, 0);
      update_reg(PS3_AUDIO_AX_IS, 0);

      /*
       *revert CLEAR bit since it will not reset automatically after DMA stop
       */
      if (stop_forced)
            update_mask_reg(PS3_AUDIO_CONFIG, ~PS3_AUDIO_CONFIG_CLEAR, 0);
      /* ensure the hardware sees changes */
      wmb();
}

static void snd_ps3_kick_dma(struct snd_ps3_card_info *card)
{

      update_reg(PS3_AUDIO_KICK(0), PS3_AUDIO_KICK_REQUEST);
      /* ensure the hardware sees the change */
      wmb();
}

/*
 * convert virtual addr to ioif bus addr.
 */
static dma_addr_t v_to_bus(struct snd_ps3_card_info *card, void *paddr, int ch)
{
      return card->dma_start_bus_addr[ch] +
            (paddr - card->dma_start_vaddr[ch]);
};


/*
 * increment ring buffer pointer.
 * NOTE: caller must hold write spinlock
 */
static void snd_ps3_bump_buffer(struct snd_ps3_card_info *card,
                        enum snd_ps3_ch ch, size_t byte_count,
                        int stage)
{
      if (!stage)
            card->dma_last_transfer_vaddr[ch] =
                  card->dma_next_transfer_vaddr[ch];
      card->dma_next_transfer_vaddr[ch] += byte_count;
      if ((card->dma_start_vaddr[ch] + (card->dma_buffer_size / 2)) <=
          card->dma_next_transfer_vaddr[ch]) {
            card->dma_next_transfer_vaddr[ch] = card->dma_start_vaddr[ch];
      }
}
/*
 * setup dmac to send data to audio and attenuate samples on the ring buffer
 */
static int snd_ps3_program_dma(struct snd_ps3_card_info *card,
                         enum snd_ps3_dma_filltype filltype)
{
      /* this dmac does not support over 4G */
      uint32_t dma_addr;
      int fill_stages, dma_ch, stage;
      enum snd_ps3_ch ch;
      uint32_t ch0_kick_event = 0; /* initialize to mute gcc */
      void *start_vaddr;
      unsigned long irqsave;
      int silent = 0;

      switch (filltype) {
      case SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL:
            silent = 1;
            /* intentionally fall thru */
      case SND_PS3_DMA_FILLTYPE_FIRSTFILL:
            ch0_kick_event = PS3_AUDIO_KICK_EVENT_ALWAYS;
            break;

      case SND_PS3_DMA_FILLTYPE_SILENT_RUNNING:
            silent = 1;
            /* intentionally fall thru */
      case SND_PS3_DMA_FILLTYPE_RUNNING:
            ch0_kick_event = PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY;
            break;
      }

      snd_ps3_verify_dma_stop(card, 700, 0);
      fill_stages = 4;
      spin_lock_irqsave(&card->dma_lock, irqsave);
      for (ch = 0; ch < 2; ch++) {
            start_vaddr = card->dma_next_transfer_vaddr[0];
            for (stage = 0; stage < fill_stages; stage++) {
                  dma_ch = stage * 2 + ch;
                  if (silent)
                        dma_addr = card->null_buffer_start_dma_addr;
                  else
                        dma_addr =
                        v_to_bus(card,
                               card->dma_next_transfer_vaddr[ch],
                               ch);

                  write_reg(PS3_AUDIO_SOURCE(dma_ch),
                          (PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY |
                           dma_addr));

                  /* dst: fixed to 3wire#0 */
                  if (ch == 0)
                        write_reg(PS3_AUDIO_DEST(dma_ch),
                                (PS3_AUDIO_DEST_TARGET_AUDIOFIFO |
                                 PS3_AUDIO_AO_3W_LDATA(0)));
                  else
                        write_reg(PS3_AUDIO_DEST(dma_ch),
                                (PS3_AUDIO_DEST_TARGET_AUDIOFIFO |
                                 PS3_AUDIO_AO_3W_RDATA(0)));

                  /* count always 1 DMA block (1/2 stage = 128 bytes) */
                  write_reg(PS3_AUDIO_DMASIZE(dma_ch), 0);
                  /* bump pointer if needed */
                  if (!silent)
                        snd_ps3_bump_buffer(card, ch,
                                        PS3_AUDIO_DMAC_BLOCK_SIZE,
                                        stage);

                  /* kick event  */
                  if (dma_ch == 0)
                        write_reg(PS3_AUDIO_KICK(dma_ch),
                                ch0_kick_event);
                  else
                        write_reg(PS3_AUDIO_KICK(dma_ch),
                                PS3_AUDIO_KICK_EVENT_AUDIO_DMA(dma_ch
                                                       - 1) |
                                PS3_AUDIO_KICK_REQUEST);
            }
      }
      /* ensure the hardware sees the change */
      wmb();
      spin_unlock_irqrestore(&card->dma_lock, irqsave);

      return 0;
}

/*
 * Interrupt handler
 */
static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id)
{

      uint32_t port_intr;
      int underflow_occured = 0;
      struct snd_ps3_card_info *card = dev_id;

      if (!card->running) {
            update_reg(PS3_AUDIO_AX_IS, 0);
            update_reg(PS3_AUDIO_INTR_0, 0);
            return IRQ_HANDLED;
      }

      port_intr = read_reg(PS3_AUDIO_AX_IS);
      /*
       *serial buffer empty detected (every 4 times),
       *program next dma and kick it
       */
      if (port_intr & PS3_AUDIO_AX_IE_ASOBEIE(0)) {
            write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBEIE(0));
            if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) {
                  write_reg(PS3_AUDIO_AX_IS, port_intr);
                  underflow_occured = 1;
            }
            if (card->silent) {
                  /* we are still in silent time */
                  snd_ps3_program_dma(card,
                        (underflow_occured) ?
                        SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL :
                        SND_PS3_DMA_FILLTYPE_SILENT_RUNNING);
                  snd_ps3_kick_dma(card);
                  card->silent--;
            } else {
                  snd_ps3_program_dma(card,
                        (underflow_occured) ?
                        SND_PS3_DMA_FILLTYPE_FIRSTFILL :
                        SND_PS3_DMA_FILLTYPE_RUNNING);
                  snd_ps3_kick_dma(card);
                  snd_pcm_period_elapsed(card->substream);
            }
      } else if (port_intr & PS3_AUDIO_AX_IE_ASOBUIE(0)) {
            write_reg(PS3_AUDIO_AX_IS, PS3_AUDIO_AX_IE_ASOBUIE(0));
            /*
             * serial out underflow, but buffer empty not detected.
             * in this case, fill fifo with 0 to recover.  After
             * filling dummy data, serial automatically start to
             * consume them and then will generate normal buffer
             * empty interrupts.
             * If both buffer underflow and buffer empty are occured,
             * it is better to do nomal data transfer than empty one
             */
            snd_ps3_program_dma(card,
                            SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL);
            snd_ps3_kick_dma(card);
            snd_ps3_program_dma(card,
                            SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL);
            snd_ps3_kick_dma(card);
      }
      /* clear interrupt cause */
      return IRQ_HANDLED;
};

/*
 * audio mute on/off
 * mute_on : 0 output enabled
 *           1 mute
 */
static int snd_ps3_mute(int mute_on)
{
      return ps3av_audio_mute(mute_on);
}

/*
 * av setting
 * NOTE: calling this function may generate audio interrupt.
 */
static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card)
{
      int ret, retries, i;
      pr_debug("%s: start\n", __func__);

      ret = ps3av_set_audio_mode(card->avs.avs_audio_ch,
                          card->avs.avs_audio_rate,
                          card->avs.avs_audio_width,
                          card->avs.avs_audio_format,
                          card->avs.avs_audio_source);
      /*
       * Reset the following unwanted settings:
       */

      /* disable all 3wire buffers */
      update_mask_reg(PS3_AUDIO_AO_3WMCTRL,
                  ~(PS3_AUDIO_AO_3WMCTRL_ASOEN(0) |
                    PS3_AUDIO_AO_3WMCTRL_ASOEN(1) |
                    PS3_AUDIO_AO_3WMCTRL_ASOEN(2) |
                    PS3_AUDIO_AO_3WMCTRL_ASOEN(3)),
                  0);
      wmb();      /* ensure the hardware sees the change */
      /* wait for actually stopped */
      retries = 1000;
      while ((read_reg(PS3_AUDIO_AO_3WMCTRL) &
            (PS3_AUDIO_AO_3WMCTRL_ASORUN(0) |
             PS3_AUDIO_AO_3WMCTRL_ASORUN(1) |
             PS3_AUDIO_AO_3WMCTRL_ASORUN(2) |
             PS3_AUDIO_AO_3WMCTRL_ASORUN(3))) &&
             --retries) {
            udelay(1);
      }

      /* reset buffer pointer */
      for (i = 0; i < 4; i++) {
            update_reg(PS3_AUDIO_AO_3WCTRL(i),
                     PS3_AUDIO_AO_3WCTRL_ASOBRST_RESET);
            udelay(10);
      }
      wmb(); /* ensure the hardware actually start resetting */

      /* enable 3wire#0 buffer */
      update_reg(PS3_AUDIO_AO_3WMCTRL, PS3_AUDIO_AO_3WMCTRL_ASOEN(0));


      /* In 24bit mode,ALSA inserts a zero byte at first byte of per sample */
      update_mask_reg(PS3_AUDIO_AO_3WCTRL(0),
                  ~PS3_AUDIO_AO_3WCTRL_ASODF,
                  PS3_AUDIO_AO_3WCTRL_ASODF_LSB);
      update_mask_reg(PS3_AUDIO_AO_SPDCTRL(0),
                  ~PS3_AUDIO_AO_SPDCTRL_SPODF,
                  PS3_AUDIO_AO_SPDCTRL_SPODF_LSB);
      /* ensure all the setting above is written back to register */
      wmb();
      /* avsetting driver altered AX_IE, caller must reset it if you want */
      pr_debug("%s: end\n", __func__);
      return ret;
}

/*
 *  set sampling rate according to the substream
 */
static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream)
{
      struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
      struct snd_ps3_avsetting_info avs;
      int ret;

      avs = card->avs;

      pr_debug("%s: called freq=%d width=%d\n", __func__,
             substream->runtime->rate,
             snd_pcm_format_width(substream->runtime->format));

      pr_debug("%s: before freq=%d width=%d\n", __func__,
             card->avs.avs_audio_rate, card->avs.avs_audio_width);

      /* sample rate */
      switch (substream->runtime->rate) {
      case 44100:
            avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_44K;
            break;
      case 48000:
            avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K;
            break;
      case 88200:
            avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_88K;
            break;
      case 96000:
            avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_96K;
            break;
      default:
            pr_info("%s: invalid rate %d\n", __func__,
                  substream->runtime->rate);
            return 1;
      }

      /* width */
      switch (snd_pcm_format_width(substream->runtime->format)) {
      case 16:
            avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16;
            break;
      case 24:
            avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_24;
            break;
      default:
            pr_info("%s: invalid width %d\n", __func__,
                  snd_pcm_format_width(substream->runtime->format));
            return 1;
      }

      memcpy(avs.avs_cs_info, ps3av_mode_cs_info, 8);

      if (memcmp(&card->avs, &avs, sizeof(avs))) {
            pr_debug("%s: after freq=%d width=%d\n", __func__,
                   card->avs.avs_audio_rate, card->avs.avs_audio_width);

            card->avs = avs;
            snd_ps3_change_avsetting(card);
            ret = 0;
      } else
            ret = 1;

      /* check CS non-audio bit and mute accordingly */
      if (avs.avs_cs_info[0] & 0x02)
            ps3av_audio_mute_analog(1); /* mute if non-audio */
      else
            ps3av_audio_mute_analog(0);

      return ret;
}

/*
 * PCM operators
 */
static int snd_ps3_pcm_open(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
      int pcm_index;

      pcm_index = substream->pcm->device;
      /* to retrieve substream/runtime in interrupt handler */
      card->substream = substream;

      runtime->hw = snd_ps3_pcm_hw;

      card->start_delay = snd_ps3_start_delay;

      /* mute off */
      snd_ps3_mute(0); /* this function sleep */

      snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
                           PS3_AUDIO_FIFO_STAGE_SIZE * 4 * 2);
      return 0;
};

static int snd_ps3_pcm_close(struct snd_pcm_substream *substream)
{
      /* mute on */
      snd_ps3_mute(1);
      return 0;
};

static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream,
                         struct snd_pcm_hw_params *hw_params)
{
      size_t size;

      /* alloc transport buffer */
      size = params_buffer_bytes(hw_params);
      snd_pcm_lib_malloc_pages(substream, size);
      return 0;
};

static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream)
{
      int ret;
      ret = snd_pcm_lib_free_pages(substream);
      return ret;
};

static int snd_ps3_delay_to_bytes(struct snd_pcm_substream *substream,
                          unsigned int delay_ms)
{
      int ret;
      int rate ;

      rate = substream->runtime->rate;
      ret = snd_pcm_format_size(substream->runtime->format,
                          rate * delay_ms / 1000)
            * substream->runtime->channels;

      pr_debug(KERN_ERR "%s: time=%d rate=%d bytes=%ld, frames=%d, ret=%d\n",
             __func__,
             delay_ms,
             rate,
             snd_pcm_format_size(substream->runtime->format, rate),
             rate * delay_ms / 1000,
             ret);

      return ret;
};

static int snd_ps3_pcm_prepare(struct snd_pcm_substream *substream)
{
      struct snd_pcm_runtime *runtime = substream->runtime;
      struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
      unsigned long irqsave;

      if (!snd_ps3_set_avsetting(substream)) {
            /* some parameter changed */
            write_reg(PS3_AUDIO_AX_IE,
                    PS3_AUDIO_AX_IE_ASOBEIE(0) |
                    PS3_AUDIO_AX_IE_ASOBUIE(0));
            /*
             * let SPDIF device re-lock with SPDIF signal,
             * start with some silence
             */
            card->silent = snd_ps3_delay_to_bytes(substream,
                                          card->start_delay) /
                  (PS3_AUDIO_FIFO_STAGE_SIZE * 4); /* every 4 times */
      }

      /* restart ring buffer pointer */
      spin_lock_irqsave(&card->dma_lock, irqsave);
      {
            card->dma_buffer_size = runtime->dma_bytes;

            card->dma_last_transfer_vaddr[SND_PS3_CH_L] =
                  card->dma_next_transfer_vaddr[SND_PS3_CH_L] =
                  card->dma_start_vaddr[SND_PS3_CH_L] =
                  runtime->dma_area;
            card->dma_start_bus_addr[SND_PS3_CH_L] = runtime->dma_addr;

            card->dma_last_transfer_vaddr[SND_PS3_CH_R] =
                  card->dma_next_transfer_vaddr[SND_PS3_CH_R] =
                  card->dma_start_vaddr[SND_PS3_CH_R] =
                  runtime->dma_area + (runtime->dma_bytes / 2);
            card->dma_start_bus_addr[SND_PS3_CH_R] =
                  runtime->dma_addr + (runtime->dma_bytes / 2);

            pr_debug("%s: vaddr=%p bus=%#llx\n", __func__,
                   card->dma_start_vaddr[SND_PS3_CH_L],
                   card->dma_start_bus_addr[SND_PS3_CH_L]);

      }
      spin_unlock_irqrestore(&card->dma_lock, irqsave);

      /* ensure the hardware sees the change */
      mb();

      return 0;
};

static int snd_ps3_pcm_trigger(struct snd_pcm_substream *substream,
                         int cmd)
{
      struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
      int ret = 0;

      switch (cmd) {
      case SNDRV_PCM_TRIGGER_START:
            /* clear outstanding interrupts  */
            update_reg(PS3_AUDIO_AX_IS, 0);

            spin_lock(&card->dma_lock);
            {
                  card->running = 1;
            }
            spin_unlock(&card->dma_lock);

            snd_ps3_program_dma(card,
                            SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL);
            snd_ps3_kick_dma(card);
            while (read_reg(PS3_AUDIO_KICK(7)) &
                   PS3_AUDIO_KICK_STATUS_MASK) {
                  udelay(1);
            }
            snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_RUNNING);
            snd_ps3_kick_dma(card);
            break;

      case SNDRV_PCM_TRIGGER_STOP:
            spin_lock(&card->dma_lock);
            {
                  card->running = 0;
            }
            spin_unlock(&card->dma_lock);
            snd_ps3_wait_for_dma_stop(card);
            break;
      default:
            break;

      }

      return ret;
};

/*
 * report current pointer
 */
static snd_pcm_uframes_t snd_ps3_pcm_pointer(
      struct snd_pcm_substream *substream)
{
      struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);
      size_t bytes;
      snd_pcm_uframes_t ret;

      spin_lock(&card->dma_lock);
      {
            bytes = (size_t)(card->dma_last_transfer_vaddr[SND_PS3_CH_L] -
                         card->dma_start_vaddr[SND_PS3_CH_L]);
      }
      spin_unlock(&card->dma_lock);

      ret = bytes_to_frames(substream->runtime, bytes * 2);

      return ret;
};

/*
 * SPDIF status bits controls
 */
static int snd_ps3_spdif_mask_info(struct snd_kcontrol *kcontrol,
                           struct snd_ctl_elem_info *uinfo)
{
      uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
      uinfo->count = 1;
      return 0;
}

/* FIXME: ps3av_set_audio_mode() assumes only consumer mode */
static int snd_ps3_spdif_cmask_get(struct snd_kcontrol *kcontrol,
                           struct snd_ctl_elem_value *ucontrol)
{
      memset(ucontrol->value.iec958.status, 0xff, 8);
      return 0;
}

static int snd_ps3_spdif_pmask_get(struct snd_kcontrol *kcontrol,
                           struct snd_ctl_elem_value *ucontrol)
{
      return 0;
}

static int snd_ps3_spdif_default_get(struct snd_kcontrol *kcontrol,
                             struct snd_ctl_elem_value *ucontrol)
{
      memcpy(ucontrol->value.iec958.status, ps3av_mode_cs_info, 8);
      return 0;
}

static int snd_ps3_spdif_default_put(struct snd_kcontrol *kcontrol,
                             struct snd_ctl_elem_value *ucontrol)
{
      if (memcmp(ps3av_mode_cs_info, ucontrol->value.iec958.status, 8)) {
            memcpy(ps3av_mode_cs_info, ucontrol->value.iec958.status, 8);
            return 1;
      }
      return 0;
}

static struct snd_kcontrol_new spdif_ctls[] = {
      {
            .access = SNDRV_CTL_ELEM_ACCESS_READ,
            .iface = SNDRV_CTL_ELEM_IFACE_PCM,
            .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
            .info = snd_ps3_spdif_mask_info,
            .get = snd_ps3_spdif_cmask_get,
      },
      {
            .access = SNDRV_CTL_ELEM_ACCESS_READ,
            .iface = SNDRV_CTL_ELEM_IFACE_PCM,
            .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK),
            .info = snd_ps3_spdif_mask_info,
            .get = snd_ps3_spdif_pmask_get,
      },
      {
            .iface = SNDRV_CTL_ELEM_IFACE_PCM,
            .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
            .info = snd_ps3_spdif_mask_info,
            .get = snd_ps3_spdif_default_get,
            .put = snd_ps3_spdif_default_put,
      },
};

static struct snd_pcm_ops snd_ps3_pcm_spdif_ops = {
      .open = snd_ps3_pcm_open,
      .close = snd_ps3_pcm_close,
      .ioctl = snd_pcm_lib_ioctl,
      .hw_params = snd_ps3_pcm_hw_params,
      .hw_free = snd_ps3_pcm_hw_free,
      .prepare = snd_ps3_pcm_prepare,
      .trigger = snd_ps3_pcm_trigger,
      .pointer = snd_ps3_pcm_pointer,
};


static int __devinit snd_ps3_map_mmio(void)
{
      the_card.mapped_mmio_vaddr =
            ioremap(the_card.ps3_dev->m_region->bus_addr,
                  the_card.ps3_dev->m_region->len);

      if (!the_card.mapped_mmio_vaddr) {
            pr_info("%s: ioremap 0 failed p=%#lx l=%#lx \n",
                   __func__, the_card.ps3_dev->m_region->lpar_addr,
                   the_card.ps3_dev->m_region->len);
            return -ENXIO;
      }

      return 0;
};

static void snd_ps3_unmap_mmio(void)
{
      iounmap(the_card.mapped_mmio_vaddr);
      the_card.mapped_mmio_vaddr = NULL;
}

static int __devinit snd_ps3_allocate_irq(void)
{
      int ret;
      u64 lpar_addr, lpar_size;
      u64 __iomem *mapped;

      /* FIXME: move this to device_init (H/W probe) */

      /* get irq outlet */
      ret = lv1_gpu_device_map(1, &lpar_addr, &lpar_size);
      if (ret) {
            pr_info("%s: device map 1 failed %d\n", __func__,
                  ret);
            return -ENXIO;
      }

      mapped = ioremap(lpar_addr, lpar_size);
      if (!mapped) {
            pr_info("%s: ioremap 1 failed \n", __func__);
            return -ENXIO;
      }

      the_card.audio_irq_outlet = in_be64(mapped);

      iounmap(mapped);
      ret = lv1_gpu_device_unmap(1);
      if (ret)
            pr_info("%s: unmap 1 failed\n", __func__);

      /* irq */
      ret = ps3_irq_plug_setup(PS3_BINDING_CPU_ANY,
                         the_card.audio_irq_outlet,
                         &the_card.irq_no);
      if (ret) {
            pr_info("%s:ps3_alloc_irq failed (%d)\n", __func__, ret);
            return ret;
      }

      ret = request_irq(the_card.irq_no, snd_ps3_interrupt, IRQF_DISABLED,
                    SND_PS3_DRIVER_NAME, &the_card);
      if (ret) {
            pr_info("%s: request_irq failed (%d)\n", __func__, ret);
            goto cleanup_irq;
      }

      return 0;

 cleanup_irq:
      ps3_irq_plug_destroy(the_card.irq_no);
      return ret;
};

static void snd_ps3_free_irq(void)
{
      free_irq(the_card.irq_no, &the_card);
      ps3_irq_plug_destroy(the_card.irq_no);
}

static void __devinit snd_ps3_audio_set_base_addr(uint64_t ioaddr_start)
{
      uint64_t val;
      int ret;

      val = (ioaddr_start & (0x0fUL << 32)) >> (32 - 20) |
            (0x03UL << 24) |
            (0x0fUL << 12) |
            (PS3_AUDIO_IOID);

      ret = lv1_gpu_attribute(0x100, 0x007, val, 0, 0);
      if (ret)
            pr_info("%s: gpu_attribute failed %d\n", __func__,
                  ret);
}

static void __devinit snd_ps3_audio_fixup(struct snd_ps3_card_info *card)
{
      /*
       * avsetting driver seems to never change the followings
       * so, init them here once
       */

      /* no dma interrupt needed */
      write_reg(PS3_AUDIO_INTR_EN_0, 0);

      /* use every 4 buffer empty interrupt */
      update_mask_reg(PS3_AUDIO_AX_IC,
                  PS3_AUDIO_AX_IC_AASOIMD_MASK,
                  PS3_AUDIO_AX_IC_AASOIMD_EVERY4);

      /* enable 3wire clocks */
      update_mask_reg(PS3_AUDIO_AO_3WMCTRL,
                  ~(PS3_AUDIO_AO_3WMCTRL_ASOBCLKD_DISABLED |
                    PS3_AUDIO_AO_3WMCTRL_ASOLRCKD_DISABLED),
                  0);
      update_reg(PS3_AUDIO_AO_3WMCTRL,
               PS3_AUDIO_AO_3WMCTRL_ASOPLRCK_DEFAULT);
}

static int __devinit snd_ps3_init_avsetting(struct snd_ps3_card_info *card)
{
      int ret;
      pr_debug("%s: start\n", __func__);
      card->avs.avs_audio_ch = PS3AV_CMD_AUDIO_NUM_OF_CH_2;
      card->avs.avs_audio_rate = PS3AV_CMD_AUDIO_FS_48K;
      card->avs.avs_audio_width = PS3AV_CMD_AUDIO_WORD_BITS_16;
      card->avs.avs_audio_format = PS3AV_CMD_AUDIO_FORMAT_PCM;
      card->avs.avs_audio_source = PS3AV_CMD_AUDIO_SOURCE_SERIAL;
      memcpy(card->avs.avs_cs_info, ps3av_mode_cs_info, 8);

      ret = snd_ps3_change_avsetting(card);

      snd_ps3_audio_fixup(card);

      /* to start to generate SPDIF signal, fill data */
      snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL);
      snd_ps3_kick_dma(card);
      pr_debug("%s: end\n", __func__);
      return ret;
}

static int __devinit snd_ps3_driver_probe(struct ps3_system_bus_device *dev)
{
      int i, ret;
      u64 lpar_addr, lpar_size;

      BUG_ON(!firmware_has_feature(FW_FEATURE_PS3_LV1));
      BUG_ON(dev->match_id != PS3_MATCH_ID_SOUND);

      the_card.ps3_dev = dev;

      ret = ps3_open_hv_device(dev);

      if (ret)
            return -ENXIO;

      /* setup MMIO */
      ret = lv1_gpu_device_map(2, &lpar_addr, &lpar_size);
      if (ret) {
            pr_info("%s: device map 2 failed %d\n", __func__, ret);
            goto clean_open;
      }
      ps3_mmio_region_init(dev, dev->m_region, lpar_addr, lpar_size,
            PAGE_SHIFT);

      ret = snd_ps3_map_mmio();
      if (ret)
            goto clean_dev_map;

      /* setup DMA area */
      ps3_dma_region_init(dev, dev->d_region,
                      PAGE_SHIFT, /* use system page size */
                      0, /* dma type; not used */
                      NULL,
                      _ALIGN_UP(SND_PS3_DMA_REGION_SIZE, PAGE_SIZE));
      dev->d_region->ioid = PS3_AUDIO_IOID;

      ret = ps3_dma_region_create(dev->d_region);
      if (ret) {
            pr_info("%s: region_create\n", __func__);
            goto clean_mmio;
      }

      snd_ps3_audio_set_base_addr(dev->d_region->bus_addr);

      /* CONFIG_SND_PS3_DEFAULT_START_DELAY */
      the_card.start_delay = snd_ps3_start_delay;

      /* irq */
      if (snd_ps3_allocate_irq()) {
            ret = -ENXIO;
            goto clean_dma_region;
      }

      /* create card instance */
      ret = snd_card_create(index, id, THIS_MODULE, 0, &the_card.card);
      if (ret < 0)
            goto clean_irq;

      strcpy(the_card.card->driver, "PS3");
      strcpy(the_card.card->shortname, "PS3");
      strcpy(the_card.card->longname, "PS3 sound");

      /* create control elements */
      for (i = 0; i < ARRAY_SIZE(spdif_ctls); i++) {
            ret = snd_ctl_add(the_card.card,
                          snd_ctl_new1(&spdif_ctls[i], &the_card));
            if (ret < 0)
                  goto clean_card;
      }

      /* create PCM devices instance */
      /* NOTE:this driver works assuming pcm:substream = 1:1 */
      ret = snd_pcm_new(the_card.card,
                    "SPDIF",
                    0, /* instance index, will be stored pcm.device*/
                    1, /* output substream */
                    0, /* input substream */
                    &(the_card.pcm));
      if (ret)
            goto clean_card;

      the_card.pcm->private_data = &the_card;
      strcpy(the_card.pcm->name, "SPDIF");

      /* set pcm ops */
      snd_pcm_set_ops(the_card.pcm, SNDRV_PCM_STREAM_PLAYBACK,
                  &snd_ps3_pcm_spdif_ops);

      the_card.pcm->info_flags = SNDRV_PCM_INFO_NONINTERLEAVED;
      /* pre-alloc PCM DMA buffer*/
      ret = snd_pcm_lib_preallocate_pages_for_all(the_card.pcm,
                              SNDRV_DMA_TYPE_DEV,
                              &dev->core,
                              SND_PS3_PCM_PREALLOC_SIZE,
                              SND_PS3_PCM_PREALLOC_SIZE);
      if (ret < 0) {
            pr_info("%s: prealloc failed\n", __func__);
            goto clean_card;
      }

      /*
       * allocate null buffer
       * its size should be lager than PS3_AUDIO_FIFO_STAGE_SIZE * 2
       * PAGE_SIZE is enogh
       */
      the_card.null_buffer_start_vaddr =
            dma_alloc_coherent(&the_card.ps3_dev->core,
                           PAGE_SIZE,
                           &the_card.null_buffer_start_dma_addr,
                           GFP_KERNEL);
      if (!the_card.null_buffer_start_vaddr) {
            pr_info("%s: nullbuffer alloc failed\n", __func__);
            goto clean_preallocate;
      }
      pr_debug("%s: null vaddr=%p dma=%#llx\n", __func__,
             the_card.null_buffer_start_vaddr,
             the_card.null_buffer_start_dma_addr);
      /* set default sample rate/word width */
      snd_ps3_init_avsetting(&the_card);

      /* register the card */
      snd_card_set_dev(the_card.card, &dev->core);
      ret = snd_card_register(the_card.card);
      if (ret < 0)
            goto clean_dma_map;

      pr_info("%s started. start_delay=%dms\n",
            the_card.card->longname, the_card.start_delay);
      return 0;

clean_dma_map:
      dma_free_coherent(&the_card.ps3_dev->core,
                    PAGE_SIZE,
                    the_card.null_buffer_start_vaddr,
                    the_card.null_buffer_start_dma_addr);
clean_preallocate:
      snd_pcm_lib_preallocate_free_for_all(the_card.pcm);
clean_card:
      snd_card_free(the_card.card);
clean_irq:
      snd_ps3_free_irq();
clean_dma_region:
      ps3_dma_region_free(dev->d_region);
clean_mmio:
      snd_ps3_unmap_mmio();
clean_dev_map:
      lv1_gpu_device_unmap(2);
clean_open:
      ps3_close_hv_device(dev);
      /*
       * there is no destructor function to pcm.
       * midlayer automatically releases if the card removed
       */
      return ret;
}; /* snd_ps3_probe */

/* called when module removal */
static int snd_ps3_driver_remove(struct ps3_system_bus_device *dev)
{
      int ret;
      pr_info("%s:start id=%d\n", __func__,  dev->match_id);
      if (dev->match_id != PS3_MATCH_ID_SOUND)
            return -ENXIO;

      /*
       * ctl and preallocate buffer will be freed in
       * snd_card_free
       */
      ret = snd_card_free(the_card.card);
      if (ret)
            pr_info("%s: ctl freecard=%d\n", __func__, ret);

      dma_free_coherent(&dev->core,
                    PAGE_SIZE,
                    the_card.null_buffer_start_vaddr,
                    the_card.null_buffer_start_dma_addr);

      ps3_dma_region_free(dev->d_region);

      snd_ps3_free_irq();
      snd_ps3_unmap_mmio();

      lv1_gpu_device_unmap(2);
      ps3_close_hv_device(dev);
      pr_info("%s:end id=%d\n", __func__, dev->match_id);
      return 0;
} /* snd_ps3_remove */

static struct ps3_system_bus_driver snd_ps3_bus_driver_info = {
      .match_id = PS3_MATCH_ID_SOUND,
      .probe = snd_ps3_driver_probe,
      .remove = snd_ps3_driver_remove,
      .shutdown = snd_ps3_driver_remove,
      .core = {
            .name = SND_PS3_DRIVER_NAME,
            .owner = THIS_MODULE,
      },
};


/*
 * module/subsystem initialize/terminate
 */
static int __init snd_ps3_init(void)
{
      int ret;

      if (!firmware_has_feature(FW_FEATURE_PS3_LV1))
            return -ENXIO;

      memset(&the_card, 0, sizeof(the_card));
      spin_lock_init(&the_card.dma_lock);

      /* register systembus DRIVER, this calls our probe() func */
      ret = ps3_system_bus_driver_register(&snd_ps3_bus_driver_info);

      return ret;
}
module_init(snd_ps3_init);

static void __exit snd_ps3_exit(void)
{
      ps3_system_bus_driver_unregister(&snd_ps3_bus_driver_info);
}
module_exit(snd_ps3_exit);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("PS3 sound driver");
MODULE_AUTHOR("Sony Computer Entertainment Inc.");
MODULE_ALIAS(PS3_MODULE_ALIAS_SOUND);

Generated by  Doxygen 1.6.0   Back to index