ASoC: Check progress when reporting periods from i.MX FIQ handler

Currently the i.MX FIQ handler is reporting periods as elapsed based
purely on a timer running in the CPU. This means that any clock
mismatch between the CPU and the audio subsystem can result in the
status reported to applications drifting away from the actual status
of the hardware. This is particularly likely at present since the
SSI driver is only capable of operating in slave mode so it's very
likely that the interface will be clocked from a different source.

Instead check the offset reported by the FIQ and only notify when we
have transferred at least one period, re-firing the timer if we didn't
do so. Also factor out the calculation of the timer expiry time for
make it a bit easier to experiment with.

Note that this only improves the situation, problems can still be
triggered.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Acked-by: Sascha Hauer <s.hauer@pengutronix.de>
Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>

+30 -6
+30 -6
sound/soc/imx/imx-pcm-fiq.c
··· 36 int period; 37 int periods; 38 unsigned long offset; 39 unsigned long size; 40 struct timer_list timer; 41 - int period_time; 42 }; 43 44 static void imx_ssi_timer_callback(unsigned long data) 45 { ··· 53 struct snd_pcm_runtime *runtime = substream->runtime; 54 struct imx_pcm_runtime_data *iprtd = runtime->private_data; 55 struct pt_regs regs; 56 57 get_fiq_regs(&regs); 58 ··· 62 else 63 iprtd->offset = regs.ARM_r9 & 0xffff; 64 65 - iprtd->timer.expires = jiffies + iprtd->period_time; 66 add_timer(&iprtd->timer); 67 - snd_pcm_period_elapsed(substream); 68 } 69 70 static struct fiq_handler fh = { ··· 95 96 iprtd->size = params_buffer_bytes(params); 97 iprtd->periods = params_periods(params); 98 - iprtd->period = params_period_bytes(params); 99 iprtd->offset = 0; 100 - iprtd->period_time = HZ / (params_rate(params) / params_period_size(params)); 101 102 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 103 ··· 134 case SNDRV_PCM_TRIGGER_START: 135 case SNDRV_PCM_TRIGGER_RESUME: 136 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 137 - iprtd->timer.expires = jiffies + iprtd->period_time; 138 add_timer(&iprtd->timer); 139 if (++fiq_enable == 1) 140 enable_fiq(imx_pcm_fiq);
··· 36 int period; 37 int periods; 38 unsigned long offset; 39 + unsigned long last_offset; 40 unsigned long size; 41 struct timer_list timer; 42 + int poll_time; 43 }; 44 + 45 + static inline void imx_ssi_set_next_poll(struct imx_pcm_runtime_data *iprtd) 46 + { 47 + iprtd->timer.expires = jiffies + iprtd->poll_time; 48 + } 49 50 static void imx_ssi_timer_callback(unsigned long data) 51 { ··· 47 struct snd_pcm_runtime *runtime = substream->runtime; 48 struct imx_pcm_runtime_data *iprtd = runtime->private_data; 49 struct pt_regs regs; 50 + unsigned long delta; 51 52 get_fiq_regs(&regs); 53 ··· 55 else 56 iprtd->offset = regs.ARM_r9 & 0xffff; 57 58 + /* How much data have we transferred since the last period report? */ 59 + if (iprtd->offset >= iprtd->last_offset) 60 + delta = iprtd->offset - iprtd->last_offset; 61 + else 62 + delta = runtime->buffer_size + iprtd->offset 63 + - iprtd->last_offset; 64 + 65 + /* If we've transferred at least a period then report it and 66 + * reset our poll time */ 67 + if (delta >= runtime->period_size) { 68 + snd_pcm_period_elapsed(substream); 69 + iprtd->last_offset = iprtd->offset; 70 + 71 + imx_ssi_set_next_poll(iprtd); 72 + } 73 + 74 + /* Restart the timer; if we didn't report we'll run on the next tick */ 75 add_timer(&iprtd->timer); 76 + 77 } 78 79 static struct fiq_handler fh = { ··· 72 73 iprtd->size = params_buffer_bytes(params); 74 iprtd->periods = params_periods(params); 75 + iprtd->period = params_period_bytes(params) ; 76 iprtd->offset = 0; 77 + iprtd->last_offset = 0; 78 + iprtd->poll_time = HZ / (params_rate(params) / params_period_size(params)); 79 80 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 81 ··· 110 case SNDRV_PCM_TRIGGER_START: 111 case SNDRV_PCM_TRIGGER_RESUME: 112 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 113 + imx_ssi_set_next_poll(iprtd); 114 add_timer(&iprtd->timer); 115 if (++fiq_enable == 1) 116 enable_fiq(imx_pcm_fiq);