|  | /* | 
|  | * i2sbus driver -- pcm routines | 
|  | * | 
|  | * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | 
|  | * | 
|  | * GPL v2, can be found in COPYING. | 
|  | */ | 
|  |  | 
|  | #include <asm/io.h> | 
|  | #include <linux/delay.h> | 
|  | #include <sound/core.h> | 
|  | #include <asm/macio.h> | 
|  | #include <linux/pci.h> | 
|  | #include "../soundbus.h" | 
|  | #include "i2sbus.h" | 
|  |  | 
|  | static inline void get_pcm_info(struct i2sbus_dev *i2sdev, int in, | 
|  | struct pcm_info **pi, struct pcm_info **other) | 
|  | { | 
|  | if (in) { | 
|  | if (pi) | 
|  | *pi = &i2sdev->in; | 
|  | if (other) | 
|  | *other = &i2sdev->out; | 
|  | } else { | 
|  | if (pi) | 
|  | *pi = &i2sdev->out; | 
|  | if (other) | 
|  | *other = &i2sdev->in; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int clock_and_divisors(int mclk, int sclk, int rate, int *out) | 
|  | { | 
|  | /* sclk must be derived from mclk! */ | 
|  | if (mclk % sclk) | 
|  | return -1; | 
|  | /* derive sclk register value */ | 
|  | if (i2s_sf_sclkdiv(mclk / sclk, out)) | 
|  | return -1; | 
|  |  | 
|  | if (I2S_CLOCK_SPEED_18MHz % (rate * mclk) == 0) { | 
|  | if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_18MHz / (rate * mclk), out)) { | 
|  | *out |= I2S_SF_CLOCK_SOURCE_18MHz; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | if (I2S_CLOCK_SPEED_45MHz % (rate * mclk) == 0) { | 
|  | if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_45MHz / (rate * mclk), out)) { | 
|  | *out |= I2S_SF_CLOCK_SOURCE_45MHz; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | if (I2S_CLOCK_SPEED_49MHz % (rate * mclk) == 0) { | 
|  | if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_49MHz / (rate * mclk), out)) { | 
|  | *out |= I2S_SF_CLOCK_SOURCE_49MHz; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | #define CHECK_RATE(rate)						\ | 
|  | do { if (rates & SNDRV_PCM_RATE_ ##rate) {			\ | 
|  | int dummy;						\ | 
|  | if (clock_and_divisors(sysclock_factor,			\ | 
|  | bus_factor, rate, &dummy))	\ | 
|  | rates &= ~SNDRV_PCM_RATE_ ##rate;		\ | 
|  | } } while (0) | 
|  |  | 
|  | static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in) | 
|  | { | 
|  | struct pcm_info *pi, *other; | 
|  | struct soundbus_dev *sdev; | 
|  | int masks_inited = 0, err; | 
|  | struct codec_info_item *cii, *rev; | 
|  | struct snd_pcm_hardware *hw; | 
|  | u64 formats = 0; | 
|  | unsigned int rates = 0; | 
|  | struct transfer_info v; | 
|  | int result = 0; | 
|  | int bus_factor = 0, sysclock_factor = 0; | 
|  | int found_this; | 
|  |  | 
|  | mutex_lock(&i2sdev->lock); | 
|  |  | 
|  | get_pcm_info(i2sdev, in, &pi, &other); | 
|  |  | 
|  | hw = &pi->substream->runtime->hw; | 
|  | sdev = &i2sdev->sound; | 
|  |  | 
|  | if (pi->active) { | 
|  | /* alsa messed up */ | 
|  | result = -EBUSY; | 
|  | goto out_unlock; | 
|  | } | 
|  |  | 
|  | /* we now need to assign the hw */ | 
|  | list_for_each_entry(cii, &sdev->codec_list, list) { | 
|  | struct transfer_info *ti = cii->codec->transfers; | 
|  | bus_factor = cii->codec->bus_factor; | 
|  | sysclock_factor = cii->codec->sysclock_factor; | 
|  | while (ti->formats && ti->rates) { | 
|  | v = *ti; | 
|  | if (ti->transfer_in == in | 
|  | && cii->codec->usable(cii, ti, &v)) { | 
|  | if (masks_inited) { | 
|  | formats &= v.formats; | 
|  | rates &= v.rates; | 
|  | } else { | 
|  | formats = v.formats; | 
|  | rates = v.rates; | 
|  | masks_inited = 1; | 
|  | } | 
|  | } | 
|  | ti++; | 
|  | } | 
|  | } | 
|  | if (!masks_inited || !bus_factor || !sysclock_factor) { | 
|  | result = -ENODEV; | 
|  | goto out_unlock; | 
|  | } | 
|  | /* bus dependent stuff */ | 
|  | hw->info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | | 
|  | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | | 
|  | SNDRV_PCM_INFO_JOINT_DUPLEX; | 
|  |  | 
|  | CHECK_RATE(5512); | 
|  | CHECK_RATE(8000); | 
|  | CHECK_RATE(11025); | 
|  | CHECK_RATE(16000); | 
|  | CHECK_RATE(22050); | 
|  | CHECK_RATE(32000); | 
|  | CHECK_RATE(44100); | 
|  | CHECK_RATE(48000); | 
|  | CHECK_RATE(64000); | 
|  | CHECK_RATE(88200); | 
|  | CHECK_RATE(96000); | 
|  | CHECK_RATE(176400); | 
|  | CHECK_RATE(192000); | 
|  | hw->rates = rates; | 
|  |  | 
|  | /* well. the codec might want 24 bits only, and we'll | 
|  | * ever only transfer 24 bits, but they are top-aligned! | 
|  | * So for alsa, we claim that we're doing full 32 bit | 
|  | * while in reality we'll ignore the lower 8 bits of | 
|  | * that when doing playback (they're transferred as 0 | 
|  | * as far as I know, no codecs we have are 32-bit capable | 
|  | * so I can't really test) and when doing recording we'll | 
|  | * always have those lower 8 bits recorded as 0 */ | 
|  | if (formats & SNDRV_PCM_FMTBIT_S24_BE) | 
|  | formats |= SNDRV_PCM_FMTBIT_S32_BE; | 
|  | if (formats & SNDRV_PCM_FMTBIT_U24_BE) | 
|  | formats |= SNDRV_PCM_FMTBIT_U32_BE; | 
|  | /* now mask off what we can support. I suppose we could | 
|  | * also support S24_3LE and some similar formats, but I | 
|  | * doubt there's a codec that would be able to use that, | 
|  | * so we don't support it here. */ | 
|  | hw->formats = formats & (SNDRV_PCM_FMTBIT_S16_BE | | 
|  | SNDRV_PCM_FMTBIT_U16_BE | | 
|  | SNDRV_PCM_FMTBIT_S32_BE | | 
|  | SNDRV_PCM_FMTBIT_U32_BE); | 
|  |  | 
|  | /* we need to set the highest and lowest rate possible. | 
|  | * These are the highest and lowest rates alsa can | 
|  | * support properly in its bitfield. | 
|  | * Below, we'll use that to restrict to the rate | 
|  | * currently in use (if any). */ | 
|  | hw->rate_min = 5512; | 
|  | hw->rate_max = 192000; | 
|  | /* if the other stream is active, then we can only | 
|  | * support what it is currently using. | 
|  | * FIXME: I lied. This comment is wrong. We can support | 
|  | * anything that works with the same serial format, ie. | 
|  | * when recording 24 bit sound we can well play 16 bit | 
|  | * sound at the same time iff using the same transfer mode. | 
|  | */ | 
|  | if (other->active) { | 
|  | /* FIXME: is this guaranteed by the alsa api? */ | 
|  | hw->formats &= (1ULL << i2sdev->format); | 
|  | /* see above, restrict rates to the one we already have */ | 
|  | hw->rate_min = i2sdev->rate; | 
|  | hw->rate_max = i2sdev->rate; | 
|  | } | 
|  |  | 
|  | hw->channels_min = 2; | 
|  | hw->channels_max = 2; | 
|  | /* these are somewhat arbitrary */ | 
|  | hw->buffer_bytes_max = 131072; | 
|  | hw->period_bytes_min = 256; | 
|  | hw->period_bytes_max = 16384; | 
|  | hw->periods_min = 3; | 
|  | hw->periods_max = MAX_DBDMA_COMMANDS; | 
|  | err = snd_pcm_hw_constraint_integer(pi->substream->runtime, | 
|  | SNDRV_PCM_HW_PARAM_PERIODS); | 
|  | if (err < 0) { | 
|  | result = err; | 
|  | goto out_unlock; | 
|  | } | 
|  | list_for_each_entry(cii, &sdev->codec_list, list) { | 
|  | if (cii->codec->open) { | 
|  | err = cii->codec->open(cii, pi->substream); | 
|  | if (err) { | 
|  | result = err; | 
|  | /* unwind */ | 
|  | found_this = 0; | 
|  | list_for_each_entry_reverse(rev, | 
|  | &sdev->codec_list, list) { | 
|  | if (found_this && rev->codec->close) { | 
|  | rev->codec->close(rev, | 
|  | pi->substream); | 
|  | } | 
|  | if (rev == cii) | 
|  | found_this = 1; | 
|  | } | 
|  | goto out_unlock; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | out_unlock: | 
|  | mutex_unlock(&i2sdev->lock); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | #undef CHECK_RATE | 
|  |  | 
|  | static int i2sbus_pcm_close(struct i2sbus_dev *i2sdev, int in) | 
|  | { | 
|  | struct codec_info_item *cii; | 
|  | struct pcm_info *pi; | 
|  | int err = 0, tmp; | 
|  |  | 
|  | mutex_lock(&i2sdev->lock); | 
|  |  | 
|  | get_pcm_info(i2sdev, in, &pi, NULL); | 
|  |  | 
|  | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { | 
|  | if (cii->codec->close) { | 
|  | tmp = cii->codec->close(cii, pi->substream); | 
|  | if (tmp) | 
|  | err = tmp; | 
|  | } | 
|  | } | 
|  |  | 
|  | pi->substream = NULL; | 
|  | pi->active = 0; | 
|  | mutex_unlock(&i2sdev->lock); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static void i2sbus_wait_for_stop(struct i2sbus_dev *i2sdev, | 
|  | struct pcm_info *pi) | 
|  | { | 
|  | unsigned long flags; | 
|  | struct completion done; | 
|  | long timeout; | 
|  |  | 
|  | spin_lock_irqsave(&i2sdev->low_lock, flags); | 
|  | if (pi->dbdma_ring.stopping) { | 
|  | init_completion(&done); | 
|  | pi->stop_completion = &done; | 
|  | spin_unlock_irqrestore(&i2sdev->low_lock, flags); | 
|  | timeout = wait_for_completion_timeout(&done, HZ); | 
|  | spin_lock_irqsave(&i2sdev->low_lock, flags); | 
|  | pi->stop_completion = NULL; | 
|  | if (timeout == 0) { | 
|  | /* timeout expired, stop dbdma forcefully */ | 
|  | printk(KERN_ERR "i2sbus_wait_for_stop: timed out\n"); | 
|  | /* make sure RUN, PAUSE and S0 bits are cleared */ | 
|  | out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16); | 
|  | pi->dbdma_ring.stopping = 0; | 
|  | timeout = 10; | 
|  | while (in_le32(&pi->dbdma->status) & ACTIVE) { | 
|  | if (--timeout <= 0) | 
|  | break; | 
|  | udelay(1); | 
|  | } | 
|  | } | 
|  | } | 
|  | spin_unlock_irqrestore(&i2sdev->low_lock, flags); | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_PM | 
|  | void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev) | 
|  | { | 
|  | struct pcm_info *pi; | 
|  |  | 
|  | get_pcm_info(i2sdev, 0, &pi, NULL); | 
|  | i2sbus_wait_for_stop(i2sdev, pi); | 
|  | get_pcm_info(i2sdev, 1, &pi, NULL); | 
|  | i2sbus_wait_for_stop(i2sdev, pi); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static int i2sbus_hw_params(struct snd_pcm_substream *substream, | 
|  | struct snd_pcm_hw_params *params) | 
|  | { | 
|  | return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); | 
|  | } | 
|  |  | 
|  | static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  | struct pcm_info *pi; | 
|  |  | 
|  | get_pcm_info(i2sdev, in, &pi, NULL); | 
|  | if (pi->dbdma_ring.stopping) | 
|  | i2sbus_wait_for_stop(i2sdev, pi); | 
|  | snd_pcm_lib_free_pages(substream); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream) | 
|  | { | 
|  | return i2sbus_hw_free(substream, 0); | 
|  | } | 
|  |  | 
|  | static int i2sbus_record_hw_free(struct snd_pcm_substream *substream) | 
|  | { | 
|  | return i2sbus_hw_free(substream, 1); | 
|  | } | 
|  |  | 
|  | static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) | 
|  | { | 
|  | /* whee. Hard work now. The user has selected a bitrate | 
|  | * and bit format, so now we have to program our | 
|  | * I2S controller appropriately. */ | 
|  | struct snd_pcm_runtime *runtime; | 
|  | struct dbdma_cmd *command; | 
|  | int i, periodsize, nperiods; | 
|  | dma_addr_t offset; | 
|  | struct bus_info bi; | 
|  | struct codec_info_item *cii; | 
|  | int sfr = 0;		/* serial format register */ | 
|  | int dws = 0;		/* data word sizes reg */ | 
|  | int input_16bit; | 
|  | struct pcm_info *pi, *other; | 
|  | int cnt; | 
|  | int result = 0; | 
|  | unsigned int cmd, stopaddr; | 
|  |  | 
|  | mutex_lock(&i2sdev->lock); | 
|  |  | 
|  | get_pcm_info(i2sdev, in, &pi, &other); | 
|  |  | 
|  | if (pi->dbdma_ring.running) { | 
|  | result = -EBUSY; | 
|  | goto out_unlock; | 
|  | } | 
|  | if (pi->dbdma_ring.stopping) | 
|  | i2sbus_wait_for_stop(i2sdev, pi); | 
|  |  | 
|  | if (!pi->substream || !pi->substream->runtime) { | 
|  | result = -EINVAL; | 
|  | goto out_unlock; | 
|  | } | 
|  |  | 
|  | runtime = pi->substream->runtime; | 
|  | pi->active = 1; | 
|  | if (other->active && | 
|  | ((i2sdev->format != runtime->format) | 
|  | || (i2sdev->rate != runtime->rate))) { | 
|  | result = -EINVAL; | 
|  | goto out_unlock; | 
|  | } | 
|  |  | 
|  | i2sdev->format = runtime->format; | 
|  | i2sdev->rate = runtime->rate; | 
|  |  | 
|  | periodsize = snd_pcm_lib_period_bytes(pi->substream); | 
|  | nperiods = pi->substream->runtime->periods; | 
|  | pi->current_period = 0; | 
|  |  | 
|  | /* generate dbdma command ring first */ | 
|  | command = pi->dbdma_ring.cmds; | 
|  | memset(command, 0, (nperiods + 2) * sizeof(struct dbdma_cmd)); | 
|  |  | 
|  | /* commands to DMA to/from the ring */ | 
|  | /* | 
|  | * For input, we need to do a graceful stop; if we abort | 
|  | * the DMA, we end up with leftover bytes that corrupt | 
|  | * the next recording.  To do this we set the S0 status | 
|  | * bit and wait for the DMA controller to stop.  Each | 
|  | * command has a branch condition to | 
|  | * make it branch to a stop command if S0 is set. | 
|  | * On input we also need to wait for the S7 bit to be | 
|  | * set before turning off the DMA controller. | 
|  | * In fact we do the graceful stop for output as well. | 
|  | */ | 
|  | offset = runtime->dma_addr; | 
|  | cmd = (in? INPUT_MORE: OUTPUT_MORE) | BR_IFSET | INTR_ALWAYS; | 
|  | stopaddr = pi->dbdma_ring.bus_cmd_start + | 
|  | (nperiods + 1) * sizeof(struct dbdma_cmd); | 
|  | for (i = 0; i < nperiods; i++, command++, offset += periodsize) { | 
|  | command->command = cpu_to_le16(cmd); | 
|  | command->cmd_dep = cpu_to_le32(stopaddr); | 
|  | command->phy_addr = cpu_to_le32(offset); | 
|  | command->req_count = cpu_to_le16(periodsize); | 
|  | } | 
|  |  | 
|  | /* branch back to beginning of ring */ | 
|  | command->command = cpu_to_le16(DBDMA_NOP | BR_ALWAYS); | 
|  | command->cmd_dep = cpu_to_le32(pi->dbdma_ring.bus_cmd_start); | 
|  | command++; | 
|  |  | 
|  | /* set stop command */ | 
|  | command->command = cpu_to_le16(DBDMA_STOP); | 
|  |  | 
|  | /* ok, let's set the serial format and stuff */ | 
|  | switch (runtime->format) { | 
|  | /* 16 bit formats */ | 
|  | case SNDRV_PCM_FORMAT_S16_BE: | 
|  | case SNDRV_PCM_FORMAT_U16_BE: | 
|  | /* FIXME: if we add different bus factors we need to | 
|  | * do more here!! */ | 
|  | bi.bus_factor = 0; | 
|  | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { | 
|  | bi.bus_factor = cii->codec->bus_factor; | 
|  | break; | 
|  | } | 
|  | if (!bi.bus_factor) { | 
|  | result = -ENODEV; | 
|  | goto out_unlock; | 
|  | } | 
|  | input_16bit = 1; | 
|  | break; | 
|  | case SNDRV_PCM_FORMAT_S32_BE: | 
|  | case SNDRV_PCM_FORMAT_U32_BE: | 
|  | /* force 64x bus speed, otherwise the data cannot be | 
|  | * transferred quickly enough! */ | 
|  | bi.bus_factor = 64; | 
|  | input_16bit = 0; | 
|  | break; | 
|  | default: | 
|  | result = -EINVAL; | 
|  | goto out_unlock; | 
|  | } | 
|  | /* we assume all sysclocks are the same! */ | 
|  | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { | 
|  | bi.sysclock_factor = cii->codec->sysclock_factor; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (clock_and_divisors(bi.sysclock_factor, | 
|  | bi.bus_factor, | 
|  | runtime->rate, | 
|  | &sfr) < 0) { | 
|  | result = -EINVAL; | 
|  | goto out_unlock; | 
|  | } | 
|  | switch (bi.bus_factor) { | 
|  | case 32: | 
|  | sfr |= I2S_SF_SERIAL_FORMAT_I2S_32X; | 
|  | break; | 
|  | case 64: | 
|  | sfr |= I2S_SF_SERIAL_FORMAT_I2S_64X; | 
|  | break; | 
|  | } | 
|  | /* FIXME: THIS ASSUMES MASTER ALL THE TIME */ | 
|  | sfr |= I2S_SF_SCLK_MASTER; | 
|  |  | 
|  | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { | 
|  | int err = 0; | 
|  | if (cii->codec->prepare) | 
|  | err = cii->codec->prepare(cii, &bi, pi->substream); | 
|  | if (err) { | 
|  | result = err; | 
|  | goto out_unlock; | 
|  | } | 
|  | } | 
|  | /* codecs are fine with it, so set our clocks */ | 
|  | if (input_16bit) | 
|  | dws =	(2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) | | 
|  | (2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) | | 
|  | I2S_DWS_DATA_IN_16BIT | I2S_DWS_DATA_OUT_16BIT; | 
|  | else | 
|  | dws =	(2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) | | 
|  | (2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) | | 
|  | I2S_DWS_DATA_IN_24BIT | I2S_DWS_DATA_OUT_24BIT; | 
|  |  | 
|  | /* early exit if already programmed correctly */ | 
|  | /* not locking these is fine since we touch them only in this function */ | 
|  | if (in_le32(&i2sdev->intfregs->serial_format) == sfr | 
|  | && in_le32(&i2sdev->intfregs->data_word_sizes) == dws) | 
|  | goto out_unlock; | 
|  |  | 
|  | /* let's notify the codecs about clocks going away. | 
|  | * For now we only do mastering on the i2s cell... */ | 
|  | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) | 
|  | if (cii->codec->switch_clock) | 
|  | cii->codec->switch_clock(cii, CLOCK_SWITCH_PREPARE_SLAVE); | 
|  |  | 
|  | i2sbus_control_enable(i2sdev->control, i2sdev); | 
|  | i2sbus_control_cell(i2sdev->control, i2sdev, 1); | 
|  |  | 
|  | out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED); | 
|  |  | 
|  | i2sbus_control_clock(i2sdev->control, i2sdev, 0); | 
|  |  | 
|  | msleep(1); | 
|  |  | 
|  | /* wait for clock stopped. This can apparently take a while... */ | 
|  | cnt = 100; | 
|  | while (cnt-- && | 
|  | !(in_le32(&i2sdev->intfregs->intr_ctl) & I2S_PENDING_CLOCKS_STOPPED)) { | 
|  | msleep(5); | 
|  | } | 
|  | out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED); | 
|  |  | 
|  | /* not locking these is fine since we touch them only in this function */ | 
|  | out_le32(&i2sdev->intfregs->serial_format, sfr); | 
|  | out_le32(&i2sdev->intfregs->data_word_sizes, dws); | 
|  |  | 
|  | i2sbus_control_enable(i2sdev->control, i2sdev); | 
|  | i2sbus_control_cell(i2sdev->control, i2sdev, 1); | 
|  | i2sbus_control_clock(i2sdev->control, i2sdev, 1); | 
|  | msleep(1); | 
|  |  | 
|  | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) | 
|  | if (cii->codec->switch_clock) | 
|  | cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE); | 
|  |  | 
|  | out_unlock: | 
|  | mutex_unlock(&i2sdev->lock); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_PM | 
|  | void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev) | 
|  | { | 
|  | i2sbus_pcm_prepare(i2sdev, 0); | 
|  | i2sbus_pcm_prepare(i2sdev, 1); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static int i2sbus_pcm_trigger(struct i2sbus_dev *i2sdev, int in, int cmd) | 
|  | { | 
|  | struct codec_info_item *cii; | 
|  | struct pcm_info *pi; | 
|  | int result = 0; | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&i2sdev->low_lock, flags); | 
|  |  | 
|  | get_pcm_info(i2sdev, in, &pi, NULL); | 
|  |  | 
|  | switch (cmd) { | 
|  | case SNDRV_PCM_TRIGGER_START: | 
|  | case SNDRV_PCM_TRIGGER_RESUME: | 
|  | if (pi->dbdma_ring.running) { | 
|  | result = -EALREADY; | 
|  | goto out_unlock; | 
|  | } | 
|  | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) | 
|  | if (cii->codec->start) | 
|  | cii->codec->start(cii, pi->substream); | 
|  | pi->dbdma_ring.running = 1; | 
|  |  | 
|  | if (pi->dbdma_ring.stopping) { | 
|  | /* Clear the S0 bit, then see if we stopped yet */ | 
|  | out_le32(&pi->dbdma->control, 1 << 16); | 
|  | if (in_le32(&pi->dbdma->status) & ACTIVE) { | 
|  | /* possible race here? */ | 
|  | udelay(10); | 
|  | if (in_le32(&pi->dbdma->status) & ACTIVE) { | 
|  | pi->dbdma_ring.stopping = 0; | 
|  | goto out_unlock; /* keep running */ | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* make sure RUN, PAUSE and S0 bits are cleared */ | 
|  | out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16); | 
|  |  | 
|  | /* set branch condition select register */ | 
|  | out_le32(&pi->dbdma->br_sel, (1 << 16) | 1); | 
|  |  | 
|  | /* write dma command buffer address to the dbdma chip */ | 
|  | out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start); | 
|  |  | 
|  | /* initialize the frame count and current period */ | 
|  | pi->current_period = 0; | 
|  | pi->frame_count = in_le32(&i2sdev->intfregs->frame_count); | 
|  |  | 
|  | /* set the DMA controller running */ | 
|  | out_le32(&pi->dbdma->control, (RUN << 16) | RUN); | 
|  |  | 
|  | /* off you go! */ | 
|  | break; | 
|  |  | 
|  | case SNDRV_PCM_TRIGGER_STOP: | 
|  | case SNDRV_PCM_TRIGGER_SUSPEND: | 
|  | if (!pi->dbdma_ring.running) { | 
|  | result = -EALREADY; | 
|  | goto out_unlock; | 
|  | } | 
|  | pi->dbdma_ring.running = 0; | 
|  |  | 
|  | /* Set the S0 bit to make the DMA branch to the stop cmd */ | 
|  | out_le32(&pi->dbdma->control, (1 << 16) | 1); | 
|  | pi->dbdma_ring.stopping = 1; | 
|  |  | 
|  | list_for_each_entry(cii, &i2sdev->sound.codec_list, list) | 
|  | if (cii->codec->stop) | 
|  | cii->codec->stop(cii, pi->substream); | 
|  | break; | 
|  | default: | 
|  | result = -EINVAL; | 
|  | goto out_unlock; | 
|  | } | 
|  |  | 
|  | out_unlock: | 
|  | spin_unlock_irqrestore(&i2sdev->low_lock, flags); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | static snd_pcm_uframes_t i2sbus_pcm_pointer(struct i2sbus_dev *i2sdev, int in) | 
|  | { | 
|  | struct pcm_info *pi; | 
|  | u32 fc; | 
|  |  | 
|  | get_pcm_info(i2sdev, in, &pi, NULL); | 
|  |  | 
|  | fc = in_le32(&i2sdev->intfregs->frame_count); | 
|  | fc = fc - pi->frame_count; | 
|  |  | 
|  | if (fc >= pi->substream->runtime->buffer_size) | 
|  | fc %= pi->substream->runtime->buffer_size; | 
|  | return fc; | 
|  | } | 
|  |  | 
|  | static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in) | 
|  | { | 
|  | struct pcm_info *pi; | 
|  | u32 fc, nframes; | 
|  | u32 status; | 
|  | int timeout, i; | 
|  | int dma_stopped = 0; | 
|  | struct snd_pcm_runtime *runtime; | 
|  |  | 
|  | spin_lock(&i2sdev->low_lock); | 
|  | get_pcm_info(i2sdev, in, &pi, NULL); | 
|  | if (!pi->dbdma_ring.running && !pi->dbdma_ring.stopping) | 
|  | goto out_unlock; | 
|  |  | 
|  | i = pi->current_period; | 
|  | runtime = pi->substream->runtime; | 
|  | while (pi->dbdma_ring.cmds[i].xfer_status) { | 
|  | if (le16_to_cpu(pi->dbdma_ring.cmds[i].xfer_status) & BT) | 
|  | /* | 
|  | * BT is the branch taken bit.  If it took a branch | 
|  | * it is because we set the S0 bit to make it | 
|  | * branch to the stop command. | 
|  | */ | 
|  | dma_stopped = 1; | 
|  | pi->dbdma_ring.cmds[i].xfer_status = 0; | 
|  |  | 
|  | if (++i >= runtime->periods) { | 
|  | i = 0; | 
|  | pi->frame_count += runtime->buffer_size; | 
|  | } | 
|  | pi->current_period = i; | 
|  |  | 
|  | /* | 
|  | * Check the frame count.  The DMA tends to get a bit | 
|  | * ahead of the frame counter, which confuses the core. | 
|  | */ | 
|  | fc = in_le32(&i2sdev->intfregs->frame_count); | 
|  | nframes = i * runtime->period_size; | 
|  | if (fc < pi->frame_count + nframes) | 
|  | pi->frame_count = fc - nframes; | 
|  | } | 
|  |  | 
|  | if (dma_stopped) { | 
|  | timeout = 1000; | 
|  | for (;;) { | 
|  | status = in_le32(&pi->dbdma->status); | 
|  | if (!(status & ACTIVE) && (!in || (status & 0x80))) | 
|  | break; | 
|  | if (--timeout <= 0) { | 
|  | printk(KERN_ERR "i2sbus: timed out " | 
|  | "waiting for DMA to stop!\n"); | 
|  | break; | 
|  | } | 
|  | udelay(1); | 
|  | } | 
|  |  | 
|  | /* Turn off DMA controller, clear S0 bit */ | 
|  | out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16); | 
|  |  | 
|  | pi->dbdma_ring.stopping = 0; | 
|  | if (pi->stop_completion) | 
|  | complete(pi->stop_completion); | 
|  | } | 
|  |  | 
|  | if (!pi->dbdma_ring.running) | 
|  | goto out_unlock; | 
|  | spin_unlock(&i2sdev->low_lock); | 
|  | /* may call _trigger again, hence needs to be unlocked */ | 
|  | snd_pcm_period_elapsed(pi->substream); | 
|  | return; | 
|  |  | 
|  | out_unlock: | 
|  | spin_unlock(&i2sdev->low_lock); | 
|  | } | 
|  |  | 
|  | irqreturn_t i2sbus_tx_intr(int irq, void *devid) | 
|  | { | 
|  | handle_interrupt((struct i2sbus_dev *)devid, 0); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | irqreturn_t i2sbus_rx_intr(int irq, void *devid) | 
|  | { | 
|  | handle_interrupt((struct i2sbus_dev *)devid, 1); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static int i2sbus_playback_open(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | i2sdev->out.substream = substream; | 
|  | return i2sbus_pcm_open(i2sdev, 0); | 
|  | } | 
|  |  | 
|  | static int i2sbus_playback_close(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  | int err; | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | if (i2sdev->out.substream != substream) | 
|  | return -EINVAL; | 
|  | err = i2sbus_pcm_close(i2sdev, 0); | 
|  | if (!err) | 
|  | i2sdev->out.substream = NULL; | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int i2sbus_playback_prepare(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | if (i2sdev->out.substream != substream) | 
|  | return -EINVAL; | 
|  | return i2sbus_pcm_prepare(i2sdev, 0); | 
|  | } | 
|  |  | 
|  | static int i2sbus_playback_trigger(struct snd_pcm_substream *substream, int cmd) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | if (i2sdev->out.substream != substream) | 
|  | return -EINVAL; | 
|  | return i2sbus_pcm_trigger(i2sdev, 0, cmd); | 
|  | } | 
|  |  | 
|  | static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream | 
|  | *substream) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | if (i2sdev->out.substream != substream) | 
|  | return 0; | 
|  | return i2sbus_pcm_pointer(i2sdev, 0); | 
|  | } | 
|  |  | 
|  | static struct snd_pcm_ops i2sbus_playback_ops = { | 
|  | .open =		i2sbus_playback_open, | 
|  | .close =	i2sbus_playback_close, | 
|  | .ioctl =	snd_pcm_lib_ioctl, | 
|  | .hw_params =	i2sbus_hw_params, | 
|  | .hw_free =	i2sbus_playback_hw_free, | 
|  | .prepare =	i2sbus_playback_prepare, | 
|  | .trigger =	i2sbus_playback_trigger, | 
|  | .pointer =	i2sbus_playback_pointer, | 
|  | }; | 
|  |  | 
|  | static int i2sbus_record_open(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | i2sdev->in.substream = substream; | 
|  | return i2sbus_pcm_open(i2sdev, 1); | 
|  | } | 
|  |  | 
|  | static int i2sbus_record_close(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  | int err; | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | if (i2sdev->in.substream != substream) | 
|  | return -EINVAL; | 
|  | err = i2sbus_pcm_close(i2sdev, 1); | 
|  | if (!err) | 
|  | i2sdev->in.substream = NULL; | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int i2sbus_record_prepare(struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | if (i2sdev->in.substream != substream) | 
|  | return -EINVAL; | 
|  | return i2sbus_pcm_prepare(i2sdev, 1); | 
|  | } | 
|  |  | 
|  | static int i2sbus_record_trigger(struct snd_pcm_substream *substream, int cmd) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | if (i2sdev->in.substream != substream) | 
|  | return -EINVAL; | 
|  | return i2sbus_pcm_trigger(i2sdev, 1, cmd); | 
|  | } | 
|  |  | 
|  | static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream | 
|  | *substream) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); | 
|  |  | 
|  | if (!i2sdev) | 
|  | return -EINVAL; | 
|  | if (i2sdev->in.substream != substream) | 
|  | return 0; | 
|  | return i2sbus_pcm_pointer(i2sdev, 1); | 
|  | } | 
|  |  | 
|  | static struct snd_pcm_ops i2sbus_record_ops = { | 
|  | .open =		i2sbus_record_open, | 
|  | .close =	i2sbus_record_close, | 
|  | .ioctl =	snd_pcm_lib_ioctl, | 
|  | .hw_params =	i2sbus_hw_params, | 
|  | .hw_free =	i2sbus_record_hw_free, | 
|  | .prepare =	i2sbus_record_prepare, | 
|  | .trigger =	i2sbus_record_trigger, | 
|  | .pointer =	i2sbus_record_pointer, | 
|  | }; | 
|  |  | 
|  | static void i2sbus_private_free(struct snd_pcm *pcm) | 
|  | { | 
|  | struct i2sbus_dev *i2sdev = snd_pcm_chip(pcm); | 
|  | struct codec_info_item *p, *tmp; | 
|  |  | 
|  | i2sdev->sound.pcm = NULL; | 
|  | i2sdev->out.created = 0; | 
|  | i2sdev->in.created = 0; | 
|  | list_for_each_entry_safe(p, tmp, &i2sdev->sound.codec_list, list) { | 
|  | printk(KERN_ERR "i2sbus: a codec didn't unregister!\n"); | 
|  | list_del(&p->list); | 
|  | module_put(p->codec->owner); | 
|  | kfree(p); | 
|  | } | 
|  | soundbus_dev_put(&i2sdev->sound); | 
|  | module_put(THIS_MODULE); | 
|  | } | 
|  |  | 
|  | int | 
|  | i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card, | 
|  | struct codec_info *ci, void *data) | 
|  | { | 
|  | int err, in = 0, out = 0; | 
|  | struct transfer_info *tmp; | 
|  | struct i2sbus_dev *i2sdev = soundbus_dev_to_i2sbus_dev(dev); | 
|  | struct codec_info_item *cii; | 
|  |  | 
|  | if (!dev->pcmname || dev->pcmid == -1) { | 
|  | printk(KERN_ERR "i2sbus: pcm name and id must be set!\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | list_for_each_entry(cii, &dev->codec_list, list) { | 
|  | if (cii->codec_data == data) | 
|  | return -EALREADY; | 
|  | } | 
|  |  | 
|  | if (!ci->transfers || !ci->transfers->formats | 
|  | || !ci->transfers->rates || !ci->usable) | 
|  | return -EINVAL; | 
|  |  | 
|  | /* we currently code the i2s transfer on the clock, and support only | 
|  | * 32 and 64 */ | 
|  | if (ci->bus_factor != 32 && ci->bus_factor != 64) | 
|  | return -EINVAL; | 
|  |  | 
|  | /* If you want to fix this, you need to keep track of what transport infos | 
|  | * are to be used, which codecs they belong to, and then fix all the | 
|  | * sysclock/busclock stuff above to depend on which is usable */ | 
|  | list_for_each_entry(cii, &dev->codec_list, list) { | 
|  | if (cii->codec->sysclock_factor != ci->sysclock_factor) { | 
|  | printk(KERN_DEBUG | 
|  | "cannot yet handle multiple different sysclocks!\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | if (cii->codec->bus_factor != ci->bus_factor) { | 
|  | printk(KERN_DEBUG | 
|  | "cannot yet handle multiple different bus clocks!\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | tmp = ci->transfers; | 
|  | while (tmp->formats && tmp->rates) { | 
|  | if (tmp->transfer_in) | 
|  | in = 1; | 
|  | else | 
|  | out = 1; | 
|  | tmp++; | 
|  | } | 
|  |  | 
|  | cii = kzalloc(sizeof(struct codec_info_item), GFP_KERNEL); | 
|  | if (!cii) { | 
|  | printk(KERN_DEBUG "i2sbus: failed to allocate cii\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | /* use the private data to point to the codec info */ | 
|  | cii->sdev = soundbus_dev_get(dev); | 
|  | cii->codec = ci; | 
|  | cii->codec_data = data; | 
|  |  | 
|  | if (!cii->sdev) { | 
|  | printk(KERN_DEBUG | 
|  | "i2sbus: failed to get soundbus dev reference\n"); | 
|  | err = -ENODEV; | 
|  | goto out_free_cii; | 
|  | } | 
|  |  | 
|  | if (!try_module_get(THIS_MODULE)) { | 
|  | printk(KERN_DEBUG "i2sbus: failed to get module reference!\n"); | 
|  | err = -EBUSY; | 
|  | goto out_put_sdev; | 
|  | } | 
|  |  | 
|  | if (!try_module_get(ci->owner)) { | 
|  | printk(KERN_DEBUG | 
|  | "i2sbus: failed to get module reference to codec owner!\n"); | 
|  | err = -EBUSY; | 
|  | goto out_put_this_module; | 
|  | } | 
|  |  | 
|  | if (!dev->pcm) { | 
|  | err = snd_pcm_new(card, dev->pcmname, dev->pcmid, 0, 0, | 
|  | &dev->pcm); | 
|  | if (err) { | 
|  | printk(KERN_DEBUG "i2sbus: failed to create pcm\n"); | 
|  | goto out_put_ci_module; | 
|  | } | 
|  | dev->pcm->dev = &dev->ofdev.dev; | 
|  | } | 
|  |  | 
|  | /* ALSA yet again sucks. | 
|  | * If it is ever fixed, remove this line. See below. */ | 
|  | out = in = 1; | 
|  |  | 
|  | if (!i2sdev->out.created && out) { | 
|  | if (dev->pcm->card != card) { | 
|  | /* eh? */ | 
|  | printk(KERN_ERR | 
|  | "Can't attach same bus to different cards!\n"); | 
|  | err = -EINVAL; | 
|  | goto out_put_ci_module; | 
|  | } | 
|  | err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); | 
|  | if (err) | 
|  | goto out_put_ci_module; | 
|  | snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, | 
|  | &i2sbus_playback_ops); | 
|  | i2sdev->out.created = 1; | 
|  | } | 
|  |  | 
|  | if (!i2sdev->in.created && in) { | 
|  | if (dev->pcm->card != card) { | 
|  | printk(KERN_ERR | 
|  | "Can't attach same bus to different cards!\n"); | 
|  | err = -EINVAL; | 
|  | goto out_put_ci_module; | 
|  | } | 
|  | err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 1); | 
|  | if (err) | 
|  | goto out_put_ci_module; | 
|  | snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, | 
|  | &i2sbus_record_ops); | 
|  | i2sdev->in.created = 1; | 
|  | } | 
|  |  | 
|  | /* so we have to register the pcm after adding any substream | 
|  | * to it because alsa doesn't create the devices for the | 
|  | * substreams when we add them later. | 
|  | * Therefore, force in and out on both busses (above) and | 
|  | * register the pcm now instead of just after creating it. | 
|  | */ | 
|  | err = snd_device_register(card, dev->pcm); | 
|  | if (err) { | 
|  | printk(KERN_ERR "i2sbus: error registering new pcm\n"); | 
|  | goto out_put_ci_module; | 
|  | } | 
|  | /* no errors any more, so let's add this to our list */ | 
|  | list_add(&cii->list, &dev->codec_list); | 
|  |  | 
|  | dev->pcm->private_data = i2sdev; | 
|  | dev->pcm->private_free = i2sbus_private_free; | 
|  |  | 
|  | /* well, we really should support scatter/gather DMA */ | 
|  | snd_pcm_lib_preallocate_pages_for_all( | 
|  | dev->pcm, SNDRV_DMA_TYPE_DEV, | 
|  | snd_dma_pci_data(macio_get_pci_dev(i2sdev->macio)), | 
|  | 64 * 1024, 64 * 1024); | 
|  |  | 
|  | return 0; | 
|  | out_put_ci_module: | 
|  | module_put(ci->owner); | 
|  | out_put_this_module: | 
|  | module_put(THIS_MODULE); | 
|  | out_put_sdev: | 
|  | soundbus_dev_put(dev); | 
|  | out_free_cii: | 
|  | kfree(cii); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | void i2sbus_detach_codec(struct soundbus_dev *dev, void *data) | 
|  | { | 
|  | struct codec_info_item *cii = NULL, *i; | 
|  |  | 
|  | list_for_each_entry(i, &dev->codec_list, list) { | 
|  | if (i->codec_data == data) { | 
|  | cii = i; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (cii) { | 
|  | list_del(&cii->list); | 
|  | module_put(cii->codec->owner); | 
|  | kfree(cii); | 
|  | } | 
|  | /* no more codecs, but still a pcm? */ | 
|  | if (list_empty(&dev->codec_list) && dev->pcm) { | 
|  | /* the actual cleanup is done by the callback above! */ | 
|  | snd_device_free(dev->pcm->card, dev->pcm); | 
|  | } | 
|  | } |