aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/kirkwood
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@arm.linux.org.uk>2012-11-20 07:18:52 -0500
committerMark Brown <broonie@opensource.wolfsonmicro.com>2012-11-20 20:38:39 -0500
commit982b604bc56a3da874e489051fc7adb49b1eba65 (patch)
tree4ca2ef9766708a6c5ae58d24f14d14a2b50c0737 /sound/soc/kirkwood
parent2424d458108e275ca736dabc792ee9b6733994c5 (diff)
ASoC: kirkwood-i2s: fix DMA underruns
Stress testing the driver with multiple start/stop events causes kirkwood-dma to report underrun errors (which used to cause the kernel to lock up solidly). This is because kirkwood-i2s is not respecting the restrictions imposed on clearing the 'pause' bit. Follow what the spec says; the busy bit must be read as being clear twice before the pause bit can be released. This solves the underruns. However, it has been noticed that the busy bit occasionally does not clear itself, hence the waiting is bounded to 5ms maximum to avoid a new reason for the kernel to lockup. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/kirkwood')
-rw-r--r--sound/soc/kirkwood/kirkwood-i2s.c67
1 files changed, 38 insertions, 29 deletions
diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c
index 485af80923de..826306dfb72b 100644
--- a/sound/soc/kirkwood/kirkwood-i2s.c
+++ b/sound/soc/kirkwood/kirkwood-i2s.c
@@ -180,67 +180,76 @@ static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream,
180 int cmd, struct snd_soc_dai *dai) 180 int cmd, struct snd_soc_dai *dai)
181{ 181{
182 struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai); 182 struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);
183 unsigned long value; 183 uint32_t ctl, value;
184 184
185 /* 185 ctl = readl(priv->io + KIRKWOOD_PLAYCTL);
186 * specs says KIRKWOOD_PLAYCTL must be read 2 times before 186 if (ctl & KIRKWOOD_PLAYCTL_PAUSE) {
187 * changing it. So read 1 time here and 1 later. 187 unsigned timeout = 5000;
188 */ 188 /*
189 value = readl(priv->io + KIRKWOOD_PLAYCTL); 189 * The Armada510 spec says that if we enter pause mode, the
190 * busy bit must be read back as clear _twice_. Make sure
191 * we respect that otherwise we get DMA underruns.
192 */
193 do {
194 value = ctl;
195 ctl = readl(priv->io + KIRKWOOD_PLAYCTL);
196 if (!((ctl | value) & KIRKWOOD_PLAYCTL_PLAY_BUSY))
197 break;
198 udelay(1);
199 } while (timeout--);
200
201 if ((ctl | value) & KIRKWOOD_PLAYCTL_PLAY_BUSY)
202 dev_notice(dai->dev, "timed out waiting for busy to deassert: %08x\n",
203 ctl);
204 }
190 205
191 switch (cmd) { 206 switch (cmd) {
192 case SNDRV_PCM_TRIGGER_START: 207 case SNDRV_PCM_TRIGGER_START:
193 /* stop audio, enable interrupts */ 208 /* stop audio, enable interrupts */
194 value = readl(priv->io + KIRKWOOD_PLAYCTL); 209 ctl |= KIRKWOOD_PLAYCTL_PAUSE;
195 value |= KIRKWOOD_PLAYCTL_PAUSE; 210 writel(ctl, priv->io + KIRKWOOD_PLAYCTL);
196 writel(value, priv->io + KIRKWOOD_PLAYCTL);
197 211
198 value = readl(priv->io + KIRKWOOD_INT_MASK); 212 value = readl(priv->io + KIRKWOOD_INT_MASK);
199 value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES; 213 value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES;
200 writel(value, priv->io + KIRKWOOD_INT_MASK); 214 writel(value, priv->io + KIRKWOOD_INT_MASK);
201 215
202 /* configure audio & enable i2s playback */ 216 /* configure audio & enable i2s playback */
203 value = readl(priv->io + KIRKWOOD_PLAYCTL); 217 ctl &= ~KIRKWOOD_PLAYCTL_BURST_MASK;
204 value &= ~KIRKWOOD_PLAYCTL_BURST_MASK; 218 ctl &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE
205 value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE
206 | KIRKWOOD_PLAYCTL_SPDIF_EN); 219 | KIRKWOOD_PLAYCTL_SPDIF_EN);
207 220
208 if (priv->burst == 32) 221 if (priv->burst == 32)
209 value |= KIRKWOOD_PLAYCTL_BURST_32; 222 ctl |= KIRKWOOD_PLAYCTL_BURST_32;
210 else 223 else
211 value |= KIRKWOOD_PLAYCTL_BURST_128; 224 ctl |= KIRKWOOD_PLAYCTL_BURST_128;
212 value |= KIRKWOOD_PLAYCTL_I2S_EN; 225 ctl |= KIRKWOOD_PLAYCTL_I2S_EN;
213 writel(value, priv->io + KIRKWOOD_PLAYCTL); 226 writel(ctl, priv->io + KIRKWOOD_PLAYCTL);
214 break; 227 break;
215 228
216 case SNDRV_PCM_TRIGGER_STOP: 229 case SNDRV_PCM_TRIGGER_STOP:
217 /* stop audio, disable interrupts */ 230 /* stop audio, disable interrupts */
218 value = readl(priv->io + KIRKWOOD_PLAYCTL); 231 ctl |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;
219 value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE; 232 writel(ctl, priv->io + KIRKWOOD_PLAYCTL);
220 writel(value, priv->io + KIRKWOOD_PLAYCTL);
221 233
222 value = readl(priv->io + KIRKWOOD_INT_MASK); 234 value = readl(priv->io + KIRKWOOD_INT_MASK);
223 value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES; 235 value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES;
224 writel(value, priv->io + KIRKWOOD_INT_MASK); 236 writel(value, priv->io + KIRKWOOD_INT_MASK);
225 237
226 /* disable all playbacks */ 238 /* disable all playbacks */
227 value = readl(priv->io + KIRKWOOD_PLAYCTL); 239 ctl &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN);
228 value &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN); 240 writel(ctl, priv->io + KIRKWOOD_PLAYCTL);
229 writel(value, priv->io + KIRKWOOD_PLAYCTL);
230 break; 241 break;
231 242
232 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 243 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
233 case SNDRV_PCM_TRIGGER_SUSPEND: 244 case SNDRV_PCM_TRIGGER_SUSPEND:
234 value = readl(priv->io + KIRKWOOD_PLAYCTL); 245 ctl |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;
235 value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE; 246 writel(ctl, priv->io + KIRKWOOD_PLAYCTL);
236 writel(value, priv->io + KIRKWOOD_PLAYCTL);
237 break; 247 break;
238 248
239 case SNDRV_PCM_TRIGGER_RESUME: 249 case SNDRV_PCM_TRIGGER_RESUME:
240 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 250 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
241 value = readl(priv->io + KIRKWOOD_PLAYCTL); 251 ctl &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE);
242 value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE); 252 writel(ctl, priv->io + KIRKWOOD_PLAYCTL);
243 writel(value, priv->io + KIRKWOOD_PLAYCTL);
244 break; 253 break;
245 254
246 default: 255 default: