(I2S_FIFO_ATN_LVL_EIGHT_SLOTS << I2S_FIFO1_ATN_LVL_SHIFT)
#define I2S_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS \
(I2S_FIFO_ATN_LVL_TWELVE_SLOTS << I2S_FIFO1_ATN_LVL_SHIFT)
+/*
+ * I2S_I2S_PCM_CTRL_0
+ */
+#define I2S_PCM_TRM_EDGE_POS_EDGE_NO_HIGHZ 0
+#define I2S_PCM_TRM_EDGE_POS_EDGE_HIGHZ 1
+#define I2S_PCM_TRM_EDGE_NEG_EDGE_NO_HIGHZ 2
+#define I2S_PCM_TRM_EDGE_NEG_EDGE_HIGHZ 3
+#define I2S_PCM_TRM_EDGE_CTRL_SHIFT 9
+
+#define I2S_I2S_PCM_TRM_EDGE_CTRL_MASK \
+ (3 << I2S_I2S_PCM_TRM_EDGE_CTRL_SHIFT)
+#define I2S_I2S_PCM_TRM_EDGE_POS_EDGE_NO_HIGHZ \
+ (I2S_PCM_TRM_EDGE_POS_EDGE_HIGHZ \
+ << I2S_PCM_TRM_EDGE_CTRL_SHIFT)
+#define I2S_I2S_PCM_TRM_EDGE_POS_EDGE_HIGHZ \
+ (I2S_PCM_TRM_EDGE_POS_EDGE_NO_HIGHZ \
+ << I2S_PCM_TRM_EDGE_CTRL_SHIFT)
+#define I2S_I2S_PCM_TRM_EDGE_NEG_EDGE_NO_HIGHZ \
+ (I2S_PCM_TRM_EDGE_NEG_EDGE_NO_HIGHZ \
+ << I2S_PCM_TRM_EDGE_CTRL_SHIFT)
+#define I2S_I2S_PCM_TRM_EDGE_NEG_EDGE_HIGHZ \
+ (I2S_PCM_TRM_EDGE_NEG_EDGE_HIGHZ \
+ << I2S_PCM_TRM_EDGE_CTRL_SHIFT)
+
+#define I2S_PCM_TRM_MASK_BITS_ZERO 0
+#define I2S_PCM_TRM_MASK_BITS_ONE 1
+#define I2S_PCM_TRM_MASK_BITS_TWO 2
+#define I2S_PCM_TRM_MASK_BITS_THREE 3
+#define I2S_PCM_TRM_MASK_BITS_FOUR 4
+#define I2S_PCM_TRM_MASK_BITS_FIVE 5
+#define I2S_PCM_TRM_MASK_BITS_SIX 6
+#define I2S_PCM_TRM_MASK_BITS_SEVEN 7
+#define I2S_PCM_TRM_MASK_BITS_SHIFT 6
+
+#define I2S_I2S_PCM_TRM_MASK_BITS_MASK \
+ (7 << I2S_PCM_TRM_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_TRM_MASK_BITS_ZERO \
+ (I2S_PCM_TRM_MASK_BITS_ZERO \
+ << I2S_PCM_TRM_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_TRM_MASK_BITS_ONE \
+ (I2S_PCM_TRM_MASK_BITS_ONE \
+ << I2S_PCM_TRM_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_TRM_MASK_BITS_TWO \
+ (I2S_PCM_TRM_MASK_BITS_TWO \
+ << I2S_PCM_TRM_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_TRM_MASK_BITS_THREE \
+ (I2S_PCM_TRM_MASK_BITS_THREE \
+ << I2S_PCM_TRM_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_TRM_MASK_BITS_FOUR \
+ (I2S_PCM_TRM_MASK_BITS_FOUR \
+ << I2S_PCM_TRM_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_TRM_MASK_BITS_FIVE \
+ (I2S_PCM_TRM_MASK_BITS_FIVE \
+ << I2S_PCM_TRM_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_TRM_MASK_BITS_SIX \
+ (I2S_PCM_TRM_MASK_BITS_SIX \
+ << I2S_PCM_TRM_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_TRM_MASK_BITS_SEVEN \
+ (I2S_PCM_TRM_MASK_BITS_SEVEN \
+ << I2S_PCM_TRM_MASK_BITS_SHIFT)
+
+#define I2S_I2S_PCM_CTRL_FSYNC_PCM_CTRL (1<<5)
+#define I2S_I2S_PCM_CTRL_TRM_MODE (1<<4)
+
+#define I2S_PCM_RCV_MASK_BITS_ZERO 0
+#define I2S_PCM_RCV_MASK_BITS_ONE 1
+#define I2S_PCM_RCV_MASK_BITS_TWO 2
+#define I2S_PCM_RCV_MASK_BITS_THREE 3
+#define I2S_PCM_RCV_MASK_BITS_FOUR 4
+#define I2S_PCM_RCV_MASK_BITS_FIVE 5
+#define I2S_PCM_RCV_MASK_BITS_SIX 6
+#define I2S_PCM_RCV_MASK_BITS_SEVEN 7
+#define I2S_PCM_RCV_MASK_BITS_SHIFT 1
+
+#define I2S_I2S_PCM_RCV_MASK_BITS_MASK \
+ (7 << I2S_PCM_RCV_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_RCV_MASK_BITS_ZERO \
+ (I2S_PCM_RCV_MASK_BITS_ZERO \
+ << I2S_PCM_RCV_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_RCV_MASK_BITS_ONE \
+ (I2S_PCM_RCV_MASK_BITS_ONE \
+ << I2S_PCM_RCV_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_RCV_MASK_BITS_TWO \
+ (I2S_PCM_RCV_MASK_BITS_TWO \
+ << I2S_PCM_RCV_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_RCV_MASK_BITS_THREE \
+ (I2S_PCM_RCV_MASK_BITS_THREE \
+ << I2S_PCM_RCV_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_RCV_MASK_BITS_FOUR \
+ (I2S_PCM_RCV_MASK_BITS_FOUR \
+ << I2S_PCM_RCV_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_RCV_MASK_BITS_FIVE \
+ (I2S_PCM_RCV_MASK_BITS_FIVE \
+ << I2S_PCM_RCV_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_RCV_MASK_BITS_SIX \
+ (I2S_PCM_RCV_MASK_BITS_SIX \
+ << I2S_PCM_RCV_MASK_BITS_SHIFT)
+#define I2S_I2S_PCM_RCV_MASK_BITS_SEVEN \
+ (I2S_PCM_RCV_MASK_BITS_SEVEN \
+ << I2S_PCM_RCV_MASK_BITS_SHIFT)
+
+#define I2S_I2S_PCM_CTRL_RCV_MODE (1<<0)
+
#endif /* __ARCH_ARM_MACH_TEGRA_I2S_H */
#include <linux/sysfs.h>
#include <linux/pm_qos_params.h>
#include <linux/wakelock.h>
-
+#include <linux/delay.h>
#include <linux/tegra_audio.h>
#include <mach/dma.h>
ktime_t last_dma_ts;
struct tegra_dma_channel *dma_chan;
+ bool stop;
struct completion stop_completion;
spinlock_t dma_req_lock; /* guards dma_has_it */
int dma_has_it;
struct pm_qos_request_list pm_qos;
struct wake_lock wake_lock;
+ char wake_lock_name[100];
};
struct i2s_pio_stats {
int irq; /* for pio mode */
struct i2s_pio_stats pio_stats;
- bool recording_cancelled;
struct tegra_audio_in_config in_config;
const int *in_divs;
int in_divs_len;
return container_of(ais, struct audio_driver_state, in);
}
+static inline void prevent_suspend(struct audio_stream *as)
+{
+ pr_debug("%s\n", __func__);
+ wake_lock(&as->wake_lock);
+ pm_qos_update_request(&as->pm_qos, 0);
+}
+
+static inline void allow_suspend(struct audio_stream *as)
+{
+ pr_debug("%s\n", __func__);
+ pm_qos_update_request(&as->pm_qos, PM_QOS_DEFAULT_VALUE);
+ wake_unlock(&as->wake_lock);
+}
+
#define I2S_I2S_FIFO_TX_BUSY I2S_I2S_STATUS_FIFO1_BSY
#define I2S_I2S_FIFO_TX_QS I2S_I2S_STATUS_QS_FIFO1
#define I2S_I2S_FIFO_TX_ERR I2S_I2S_STATUS_FIFO1_ERR
val = i2s_readl(base, I2S_I2S_CTRL_0);
val &= ~I2S_I2S_CTRL_BIT_FORMAT_MASK;
val |= fmt << I2S_BIT_FORMAT_SHIFT;
-
i2s_writel(base, val, I2S_I2S_CTRL_0);
+
+ if (fmt == I2S_BIT_FORMAT_DSP) {
+ val = i2s_readl(base, I2S_I2S_PCM_CTRL_0);
+ val |= I2S_I2S_PCM_CTRL_TRM_MODE|I2S_I2S_PCM_CTRL_RCV_MODE;
+ i2s_writel(base, val, I2S_I2S_PCM_CTRL_0);
+ }
+
return 0;
}
pr_debug("%s: starting playback\n", __func__);
rc = sound_ops->start_playback(aos);
spin_unlock_irqrestore(&aos->dma_req_lock, flags);
- if (!rc) {
- pm_qos_update_request(&aos->pm_qos, 0);
- wake_lock(&aos->wake_lock);
- }
+ if (!rc)
+ prevent_suspend(aos);
return rc;
}
static int start_recording_if_necessary(struct audio_stream *ais)
{
int rc = 0;
+ bool started = false;
unsigned long flags;
- struct audio_driver_state *ads = ads_from_in(ais);
spin_lock_irqsave(&ais->dma_req_lock, flags);
- if (!ads->recording_cancelled && !kfifo_is_full(&ais->fifo)) {
+ if (!ais->stop && !kfifo_is_full(&ais->fifo)) {
pr_debug("%s: starting recording\n", __func__);
rc = sound_ops->start_recording(ais);
+ started = !rc;
}
spin_unlock_irqrestore(&ais->dma_req_lock, flags);
- if (!rc) {
- pm_qos_update_request(&ais->pm_qos, 0);
- wake_lock(&ais->wake_lock);
- }
+ if (started)
+ prevent_suspend(ais);
return rc;
}
spin_lock_irqsave(&aos->dma_req_lock, flags);
if (kfifo_is_empty(&aos->fifo)) {
sound_ops->stop_playback(aos);
- if (aos->active)
- aos->errors.full_empty++; /* underflow */
spin_unlock_irqrestore(&aos->dma_req_lock, flags);
- pm_qos_update_request(&aos->pm_qos, PM_QOS_DEFAULT_VALUE);
- wake_unlock(&aos->wake_lock);
+ allow_suspend(aos);
return true;
}
spin_unlock_irqrestore(&aos->dma_req_lock, flags);
static bool stop_recording_if_necessary_nosync(struct audio_stream *ais)
{
- struct audio_driver_state *ads = ads_from_in(ais);
-
- if (ads->recording_cancelled || kfifo_is_full(&ais->fifo)) {
+ if (ais->stop || kfifo_is_full(&ais->fifo)) {
if (kfifo_is_full(&ais->fifo))
ais->errors.full_empty++; /* overflow */
sound_ops->stop_recording(ais);
return false;
}
-static bool stop_recording(struct audio_stream *ais)
+/* playback and recording */
+static bool wait_till_stopped(struct audio_stream *as)
{
int rc;
pr_debug("%s: wait for completion\n", __func__);
rc = wait_for_completion_interruptible(
- &ais->stop_completion);
+ &as->stop_completion);
+ allow_suspend(as);
pr_debug("%s: done: %d\n", __func__, rc);
- pm_qos_update_request(&ais->pm_qos, PM_QOS_DEFAULT_VALUE);
- wake_unlock(&ais->wake_lock);
return true;
}
+/* Ask for playback and recording to stop. The _nosync means that
+ * as->lock has to be locked by the caller.
+ */
+static void request_stop_nosync(struct audio_stream *as)
+{
+ pr_debug("%s\n", __func__);
+ if (!as->stop) {
+ as->stop = true;
+ wait_till_stopped(as);
+ if (!completion_done(&as->fifo_completion)) {
+ pr_debug("%s: complete\n", __func__);
+ complete(&as->fifo_completion);
+ }
+ }
+ kfifo_reset(&as->fifo);
+ as->active = false; /* applies to recording only */
+ pr_debug("%s: done\n", __func__);
+}
+
static void toggle_dma(struct audio_driver_state *ads)
{
pr_info("%s: %s\n", __func__, ads->using_dma ? "pio" : "dma");
kfifo_dma_out_finish(&aos->fifo, count);
dma_unmap_sg(NULL, &aos->sg, 1, DMA_TO_DEVICE);
- if (kfifo_avail(&aos->fifo) >= threshold_size(aos) &&
- !completion_done(&aos->fifo_completion)) {
+ if (!completion_done(&aos->fifo_completion)) {
pr_debug("%s: complete (%d avail)\n", __func__,
kfifo_avail(&aos->fifo));
complete(&aos->fifo_completion);
}
- if (stop_playback_if_necessary(aos))
+ if (stop_playback_if_necessary(aos)) {
+ pr_debug("%s: done (stopped)\n", __func__);
+ if (!completion_done(&aos->stop_completion)) {
+ pr_debug("%s: signalling stop completion\n", __func__);
+ complete(&aos->stop_completion);
+ }
return;
+ }
+
spin_lock_irqsave(&aos->dma_req_lock, flags);
resume_dma_playback(aos);
spin_unlock_irqrestore(&aos->dma_req_lock, flags);
static void dma_rx_complete_threshold(struct tegra_dma_req *req)
{
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
}
static void dma_rx_complete_callback(struct tegra_dma_req *req)
kfifo_dma_in_finish(&ais->fifo, count);
dma_unmap_sg(NULL, &ais->sg, 1, DMA_FROM_DEVICE);
- if (kfifo_avail(&ais->fifo) <= threshold_size(ais) &&
- !completion_done(&ais->fifo_completion)) {
+ if (!completion_done(&ais->fifo_completion)) {
pr_debug("%s: signalling fifo completion\n", __func__);
complete(&ais->fifo_completion);
}
pr_debug("%s\n", __func__);
i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 0);
while ((i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_TX_BUSY) &&
- spin < 100)
+ spin < 100) {
+ udelay(10);
if (spin++ > 50)
pr_info("%s: spin %d\n", __func__, spin);
+ }
if (spin == 100)
pr_warn("%s: spinny\n", __func__);
}
i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 0);
i2s_fifo_clear(ads->i2s_base, I2S_FIFO_RX);
while ((i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_RX_BUSY) &&
- spin < 100)
+ spin < 100) {
+ udelay(10);
if (spin++ > 50)
pr_info("%s: spin %d\n", __func__, spin);
+ }
if (spin == 100)
pr_warn("%s: spinny\n", __func__);
}
i2s_fifo_clear(ads->i2s_base, I2S_FIFO_TX);
#endif
+ i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 1);
+
i2s_set_fifo_irq_on_err(ads->i2s_base, I2S_FIFO_TX, 1);
i2s_set_fifo_irq_on_qe(ads->i2s_base, I2S_FIFO_TX, 1);
- i2s_fifo_enable(ads->i2s_base, I2S_FIFO_TX, 1);
return 0;
}
while (i2s_get_status(ads->i2s_base) & I2S_I2S_FIFO_TX_BUSY)
/* spin */;
- pr_info("%s: interrupts %d\n", __func__,
+ pr_debug("%s: interrupts %d\n", __func__,
ads->pio_stats.i2s_interrupt_count);
pr_info("%s: sent %d\n", __func__,
ads->pio_stats.tx_fifo_written);
i2s_fifo_clear(ads->i2s_base, I2S_FIFO_RX);
#endif
+ i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 1);
+
i2s_set_fifo_irq_on_err(ads->i2s_base, I2S_FIFO_RX, 1);
i2s_set_fifo_irq_on_qe(ads->i2s_base, I2S_FIFO_RX, 1);
- i2s_fifo_enable(ads->i2s_base, I2S_FIFO_RX, 1);
-
return 0;
}
pr_debug("%s tx fifo is ready\n", __func__);
- if (kfifo_avail(&out->fifo) > threshold_size(out) &&
- !completion_done(&out->fifo_completion)) {
+ if (!completion_done(&out->fifo_completion)) {
pr_debug("%s: tx complete (%d avail)\n", __func__,
kfifo_avail(&out->fifo));
complete(&out->fifo_completion);
}
- if (stop_playback_if_necessary(out))
+ if (stop_playback_if_necessary(out)) {
+ pr_debug("%s: done (stopped)\n", __func__);
+ if (!completion_done(&out->stop_completion)) {
+ pr_debug("%s: signalling stop completion\n",
+ __func__);
+ complete(&out->stop_completion);
+ }
goto check_rx;
+ }
empty = i2s_get_fifo_full_empty_count(ads->i2s_base,
I2S_FIFO_TX);
ads->pio_stats.rx_fifo_read += full * sizeof(u16);
- if (kfifo_avail(&in->fifo) < threshold_size(in) &&
- !completion_done(&in->fifo_completion)) {
+ if (!completion_done(&in->fifo_completion)) {
pr_debug("%s: rx complete (%d avail)\n", __func__,
kfifo_avail(&in->fifo));
complete(&in->fifo_completion);
static ssize_t tegra_audio_write(struct file *file,
const char __user *buf, size_t size, loff_t *off)
{
- ssize_t rc, total = 0;
+ ssize_t rc = 0, total = 0;
unsigned nw = 0;
struct audio_driver_state *ads = ads_from_misc_out(file);
mutex_lock(&ads->out.lock);
- ads->out.active = true;
-
if (!IS_ALIGNED(size, 4)) {
pr_err("%s: user size request %d not aligned to 4\n",
__func__, size);
size, kfifo_avail(&ads->out.fifo));
again:
+ if (ads->out.stop) {
+ pr_info("%s: playback has been cancelled (%d/%d bytes)\n",
+ __func__, total, size);
+ goto done;
+ }
+
rc = kfifo_from_user(&ads->out.fifo, buf + total, size - total, &nw);
if (rc < 0) {
pr_err("%s: error copying from user\n", __func__);
*off += total;
done:
- ads->out.active = false;
mutex_unlock(&ads->out.lock);
return rc;
}
rc = -EFAULT;
break;
}
- if (aos->active) {
+ if (kfifo_len(&aos->fifo)) {
pr_err("%s: playback in progress\n", __func__);
rc = -EBUSY;
break;
if (!rc)
memset(&aos->errors, 0, sizeof(aos->errors));
break;
- case TEGRA_AUDIO_OUT_PRELOAD_FIFO: {
- struct tegra_audio_out_preload preload;
- if (copy_from_user(&preload, (void __user *)arg,
- sizeof(preload))) {
- rc = -EFAULT;
- break;
+ case TEGRA_AUDIO_OUT_FLUSH:
+ if (kfifo_len(&aos->fifo)) {
+ pr_debug("%s: flushing\n", __func__);
+ request_stop_nosync(aos);
+ pr_debug("%s: flushed\n", __func__);
}
- rc = kfifo_from_user(&ads->out.fifo,
- (void __user *)preload.data, preload.len,
- &preload.len_written);
- if (rc < 0) {
- pr_err("%s: error copying from user\n", __func__);
- break;
- }
- if (copy_to_user((void __user *)arg, &preload, sizeof(preload)))
- rc = -EFAULT;
- pr_info("%s: preloaded output fifo with %d bytes\n", __func__,
- preload.len_written);
- }
+ aos->stop = false;
break;
default:
rc = -EINVAL;
case TEGRA_AUDIO_BIT_FORMAT_DEFAULT:
i2s_set_bit_format(ads->i2s_base, ads->pdata->mode);
ads->bit_format = mode;
+ break;
case TEGRA_AUDIO_BIT_FORMAT_DSP:
i2s_set_bit_format(ads->i2s_base, I2S_BIT_FORMAT_DSP);
ads->bit_format = mode;
switch (cmd) {
case TEGRA_AUDIO_IN_START:
- pr_info("%s: start recording\n", __func__);
- ads->recording_cancelled = false;
- start_recording_if_necessary(ais);
+ pr_debug("%s: start recording\n", __func__);
+ ais->stop = false;
+ rc = start_recording_if_necessary(ais);
+ ais->active = !rc || rc == -EALREADY;
break;
case TEGRA_AUDIO_IN_STOP:
- pr_info("%s: stop recording\n", __func__);
- if (ais->active && !ads->recording_cancelled) {
- ads->recording_cancelled = true;
- stop_recording(ais);
- if (!completion_done(&ais->fifo_completion)) {
- pr_info("%s: complete\n", __func__);
- complete(&ais->fifo_completion);
- }
- }
+ pr_debug("%s: start recording\n", __func__);
+ if (ais->active)
+ request_stop_nosync(ais);
break;
case TEGRA_AUDIO_IN_SET_CONFIG: {
struct tegra_audio_in_config cfg;
mutex_lock(&ads->in.lock);
- ads->in.active = true;
-
if (!IS_ALIGNED(size, 4)) {
pr_err("%s: user size request %d not aligned to 4\n",
__func__, size);
smp_processor_id(),
size, kfifo_len(&ads->in.fifo));
- rc = start_recording_if_necessary(&ads->in);
- if (rc < 0 && rc != -EALREADY) {
- pr_err("%s: could not start recording\n", __func__);
- goto done_err;
- }
-
again:
/* If we want recording to stop immediately after it gets cancelled,
* then we do not want to wait for the fifo to get drained.
*/
- if (ads->recording_cancelled /* && kfifo_is_empty(&ads->in.fifo) */) {
- pr_debug("%s: recording has been cancelled (read %d bytes)\n",
- __func__, total);
+ if (ads->in.stop /* && kfifo_is_empty(&ads->in.fifo) */) {
+ pr_debug("%s: recording has been cancelled (%d/%d bytes)\n",
+ __func__, total, size);
goto done_ok;
}
+ rc = start_recording_if_necessary(&ads->in);
+ if (rc < 0 && rc != -EALREADY) {
+ pr_err("%s: could not start recording\n", __func__);
+ goto done_err;
+ }
+
+ ads->in.active = true;
+
nr = 0;
do {
nr = downsample_to_user(ads, buf + total, size - total);
__func__, nr, total, size);
if (total < size) {
- /* If we lost data, recording was stopped, so we need to resume
- * it here.
- */
- rc = start_recording_if_necessary(&ads->in);
- if (rc < 0 && rc != -EALREADY) {
- pr_err("%s: could not resume recording\n", __func__);
- goto done_err;
- }
-
mutex_unlock(&ads->in.lock);
pr_debug("%s: sleep (user %d total %d nr %d)\n", __func__,
size, total, nr);
*off += total;
done_err:
- ads->in.active = false;
mutex_unlock(&ads->in.lock);
return rc;
}
{
struct audio_driver_state *ads = ads_from_misc_out(file);
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
mutex_lock(&ads->out.lock);
if (!ads->out.opened++) {
- pr_info("%s: resetting fifo and error count\n", __func__);
+ pr_debug("%s: resetting fifo and error count\n", __func__);
+ ads->out.stop = false;
memset(&ads->out.errors, 0, sizeof(ads->out.errors));
kfifo_reset(&ads->out.fifo);
}
{
struct audio_driver_state *ads = ads_from_misc_out(file);
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
mutex_lock(&ads->out.lock);
if (ads->out.opened)
ads->out.opened--;
- if (!ads->out.opened)
+ if (!ads->out.opened) {
stop_playback_if_necessary(&ads->out);
+ if (wake_lock_active(&ads->out.wake_lock))
+ pr_err("%s: wake lock is still held!\n", __func__);
+ if (kfifo_len(&ads->out.fifo))
+ pr_err("%s: output fifo is not empty (%d bytes left)\n",
+ __func__, kfifo_len(&ads->out.fifo));
+ allow_suspend(&ads->out);
+ }
mutex_unlock(&ads->out.lock);
return 0;
{
struct audio_driver_state *ads = ads_from_misc_in(file);
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
mutex_lock(&ads->in.lock);
if (!ads->in.opened++) {
- pr_info("%s: resetting fifo\n", __func__);
+ pr_debug("%s: resetting fifo\n", __func__);
/* By default, do not start recording when someone reads from
* input device.
*/
- ads->recording_cancelled = false;
+ ads->in.stop = false;
memset(&ads->in.errors, 0, sizeof(ads->in.errors));
kfifo_reset(&ads->in.fifo);
}
mutex_unlock(&ads->in.lock);
- pr_info("%s: done\n", __func__);
+ pr_debug("%s: done\n", __func__);
return 0;
}
{
struct audio_driver_state *ads = ads_from_misc_in(file);
- pr_info("%s\n", __func__);
+ pr_debug("%s\n", __func__);
+
mutex_lock(&ads->in.lock);
if (ads->in.opened)
ads->in.opened--;
+
+ if (!ads->in.opened) {
+ if (ads->in.active)
+ request_stop_nosync(&ads->in);
+ if (wake_lock_active(&ads->in.wake_lock))
+ pr_err("%s: wake lock is still held!\n", __func__);
+ if (kfifo_len(&ads->in.fifo))
+ pr_err("%s: input fifo is not empty (%d bytes left)\n",
+ __func__, kfifo_len(&ads->in.fifo));
+ allow_suspend(&ads->in);
+ }
+
mutex_unlock(&ads->in.lock);
- pr_info("%s: done\n", __func__);
+ pr_debug("%s: done\n", __func__);
return 0;
}
struct tegra_audio_buf_config *cfg,
unsigned padding)
{
- pr_info("%s (size %d threshold %d chunk %d)\n", __func__,
+ pr_debug("%s (size %d threshold %d chunk %d)\n", __func__,
cfg->size, cfg->threshold, cfg->chunk);
if (cfg->chunk < PCM_DMA_CHUNK_MIN_SIZE_ORDER) {
mutex_lock(&ads->out.lock);
mutex_lock(&ads->in.lock);
- if (ads->out.active || ads->in.active) {
+ if (kfifo_len(&ads->out.fifo) || ads->in.active) {
dev_err(dev, "%s: playback or recording in progress.\n",
__func__);
mutex_unlock(&ads->in.lock);
return -EINVAL;
}
- mutex_lock(&as->lock);
- if (as->active) {
- pr_err("%s: in progress.\n", __func__);
- mutex_unlock(&as->lock);
- return -EBUSY;
- }
*fifo_lvl = lvl;
pr_info("%s: fifo level %d\n", __func__, *fifo_lvl);
- mutex_unlock(&as->lock);
return size;
}
struct device_attribute *attr,
const char *buf, size_t count)
{
+ ssize_t rc;
struct tegra_audio_platform_data *pdata = dev->platform_data;
struct audio_driver_state *ads = pdata->driver_data;
- return __attr_fifo_atn_write(ads, &ads->out,
- &ads->out.i2s_fifo_atn_level, buf, count);
+ mutex_lock(&ads->out.lock);
+ if (kfifo_len(&ads->out.fifo)) {
+ pr_err("%s: playback in progress.\n", __func__);
+ rc = -EBUSY;
+ goto done;
+ }
+ rc = __attr_fifo_atn_write(ads, &ads->out,
+ &ads->out.i2s_fifo_atn_level,
+ buf, count);
+done:
+ mutex_unlock(&ads->out.lock);
+ return rc;
}
static DEVICE_ATTR(tx_fifo_atn, 0644, tx_fifo_atn_show, tx_fifo_atn_store);
struct device_attribute *attr,
const char *buf, size_t count)
{
+ ssize_t rc;
struct tegra_audio_platform_data *pdata = dev->platform_data;
struct audio_driver_state *ads = pdata->driver_data;
- return __attr_fifo_atn_write(ads, &ads->in,
- &ads->in.i2s_fifo_atn_level, buf, count);
+ mutex_lock(&ads->in.lock);
+ if (ads->in.active) {
+ pr_err("%s: recording in progress.\n", __func__);
+ rc = -EBUSY;
+ goto done;
+ }
+ rc = __attr_fifo_atn_write(ads, &ads->in,
+ &ads->in.i2s_fifo_atn_level,
+ buf, count);
+done:
+ mutex_unlock(&ads->in.lock);
+ return rc;
}
static DEVICE_ATTR(rx_fifo_atn, 0644, rx_fifo_atn_show, rx_fifo_atn_store);
pm_qos_add_request(&state->out.pm_qos, PM_QOS_CPU_DMA_LATENCY,
PM_QOS_DEFAULT_VALUE);
+ snprintf(state->in.wake_lock_name, sizeof(state->in.wake_lock_name),
+ "i2s.%d-audio-in", state->pdev->id);
wake_lock_init(&state->in.wake_lock, WAKE_LOCK_SUSPEND,
- "tegra-audio-in");
+ state->in.wake_lock_name);
+ snprintf(state->out.wake_lock_name, sizeof(state->out.wake_lock_name),
+ "i2s.%d-audio-out", state->pdev->id);
wake_lock_init(&state->out.wake_lock, WAKE_LOCK_SUSPEND,
- "tegra-audio-out");
+ state->out.wake_lock_name);
if (request_irq(state->irq, i2s_interrupt,
IRQF_DISABLED, state->pdev->name, state) < 0) {
#define fsl_writel(val32, addr) writel(val32, addr)
#endif
+/*
+ * High speed test mode packet(53 bytes).
+ * See USB 2.0 spec, section 7.1.20.
+ */
+static const u8 fsl_udc_test_packet[53] = {
+ /* JKJKJKJK x9 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* JJKKJJKK x8 */
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ /* JJJJKKKK x8 */
+ 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
+ /* JJJJJJJKKKKKKK x8 */
+ 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ /* JJJJJJJK x8 */
+ 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd,
+ /* JKKKKKKK x10, JK */
+ 0xfc, 0x7e, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0x7e
+};
+
/********************************************************************
* Internal Used Function
********************************************************************/
ep0stall(udc);
}
+static void udc_test_mode(struct fsl_udc *udc, u32 test_mode)
+{
+ struct fsl_req *req;
+ struct fsl_ep *ep;
+ u32 portsc, bitmask;
+ unsigned long timeout;
+
+ /* Ack the ep0 IN */
+ if (ep0_prime_status(udc, EP_DIR_IN))
+ ep0stall(udc);
+
+ /* get the ep0 */
+ ep = &udc->eps[0];
+ bitmask = ep_is_in(ep)
+ ? (1 << (ep_index(ep) + 16))
+ : (1 << (ep_index(ep)));
+
+ timeout = jiffies + HZ;
+ /* Wait until ep0 IN endpoint txfr is complete */
+ while (!(fsl_readl(&dr_regs->endptcomplete) & bitmask)) {
+ if (time_after(jiffies, timeout)) {
+ pr_err("Timeout for Ep0 IN Ack\n");
+ break;
+ }
+ cpu_relax();
+ }
+
+ switch (test_mode << PORTSCX_PTC_BIT_POS) {
+ case PORTSCX_PTC_JSTATE:
+ VDBG("TEST_J\n");
+ break;
+ case PORTSCX_PTC_KSTATE:
+ VDBG("TEST_K\n");
+ break;
+ case PORTSCX_PTC_SEQNAK:
+ VDBG("TEST_SE0_NAK\n");
+ break;
+ case PORTSCX_PTC_PACKET:
+ VDBG("TEST_PACKET\n");
+
+ /* get the ep and configure for IN direction */
+ ep = &udc->eps[0];
+ udc->ep0_dir = USB_DIR_IN;
+
+ /* Initialize ep0 status request structure */
+ req = container_of(fsl_alloc_request(NULL, GFP_ATOMIC),
+ struct fsl_req, req);
+ /* allocate a small amount of memory to get valid address */
+ req->req.buf = kmalloc(sizeof(fsl_udc_test_packet), GFP_ATOMIC);
+ req->req.dma = virt_to_phys(req->req.buf);
+
+ /* Fill in the reqest structure */
+ memcpy(req->req.buf, fsl_udc_test_packet, sizeof(fsl_udc_test_packet));
+ req->ep = ep;
+ req->req.length = sizeof(fsl_udc_test_packet);
+ req->req.status = -EINPROGRESS;
+ req->req.actual = 0;
+ req->req.complete = NULL;
+ req->dtd_count = 0;
+ req->mapped = 0;
+
+ dma_sync_single_for_device(ep->udc->gadget.dev.parent,
+ req->req.dma, req->req.length,
+ ep_is_in(ep)
+ ? DMA_TO_DEVICE
+ : DMA_FROM_DEVICE);
+
+ /* prime the data phase */
+ if ((fsl_req_to_dtd(req, GFP_ATOMIC) == 0))
+ fsl_queue_td(ep, req);
+ else /* no mem */
+ goto stall;
+
+ list_add_tail(&req->queue, &ep->queue);
+ udc->ep0_state = DATA_STATE_XMIT;
+ break;
+ case PORTSCX_PTC_FORCE_EN:
+ VDBG("TEST_FORCE_EN\n");
+ break;
+ default:
+ ERR("udc unknown test mode[%d]!\n", test_mode);
+ goto stall;
+ }
+
+ /* read the portsc register */
+ portsc = fsl_readl(&dr_regs->portsc1);
+ /* set the test mode selector */
+ portsc |= test_mode << PORTSCX_PTC_BIT_POS;
+ fsl_writel(portsc, &dr_regs->portsc1);
+
+ /*
+ * The device must have its power cycled to exit test mode.
+ * See USB 2.0 spec, section 9.4.9 for test modes operation in "Set Feature"
+ * See USB 2.0 spec, section 7.1.20 for test modes.
+ */
+ pr_info("udc entering the test mode, power cycle to exit test mode\n");
+ return;
+stall:
+ ep0stall(udc);
+}
+
static void setup_received_irq(struct fsl_udc *udc,
struct usb_ctrlrequest *setup)
{
{
int rc = -EOPNOTSUPP;
- if ((setup->bRequestType & (USB_RECIP_MASK | USB_TYPE_MASK))
+ if (setup->bRequestType == USB_RECIP_DEVICE &&
+ wValue == USB_DEVICE_TEST_MODE) {
+ /*
+ * If the feature selector is TEST_MODE, then the most
+ * significant byte of wIndex is used to specify the specific
+ * test mode and the lower byte of wIndex must be zero.
+ */
+ udc_test_mode(udc, wIndex >> 8);
+ return;
+
+ } else if ((setup->bRequestType & (USB_RECIP_MASK | USB_TYPE_MASK))
== (USB_RECIP_ENDPOINT | USB_TYPE_STANDARD)) {
int pipe = get_pipe_by_windex(wIndex);
struct fsl_ep *ep;