drivers: i2s_cavs: fix for I2S start after stop
Fixed an issue I2S wouldn't resume when started after a stop Added code to empty the TX/RX FIFOs upon stop TX stop is achieved by letting FIFO underrun, then changing state in ISR RX FIFO is read until empty when RX is stopped Signed-off-by: Sathish Kuttan <sathish.k.kuttan@intel.com>
This commit is contained in:
parent
d835714de3
commit
b72f5fd033
1 changed files with 89 additions and 76 deletions
|
@ -185,6 +185,19 @@ static void i2s_tx_stream_disable(struct i2s_cavs_dev_data *,
|
||||||
static void i2s_rx_stream_disable(struct i2s_cavs_dev_data *,
|
static void i2s_rx_stream_disable(struct i2s_cavs_dev_data *,
|
||||||
volatile struct i2s_cavs_ssp *const, struct device *);
|
volatile struct i2s_cavs_ssp *const, struct device *);
|
||||||
|
|
||||||
|
static inline void i2s_purge_stream_buffers(struct stream *strm,
|
||||||
|
struct k_mem_slab *mem_slab)
|
||||||
|
{
|
||||||
|
void *buffer;
|
||||||
|
|
||||||
|
while (k_msgq_get(&strm->in_queue, &buffer, K_NO_WAIT) == 0) {
|
||||||
|
k_mem_slab_free(mem_slab, &buffer);
|
||||||
|
}
|
||||||
|
while (k_msgq_get(&strm->out_queue, &buffer, K_NO_WAIT) == 0) {
|
||||||
|
k_mem_slab_free(mem_slab, &buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* This function is executed in the interrupt context */
|
/* This function is executed in the interrupt context */
|
||||||
static void i2s_dma_tx_callback(void *arg, u32_t channel,
|
static void i2s_dma_tx_callback(void *arg, u32_t channel,
|
||||||
int status)
|
int status)
|
||||||
|
@ -221,21 +234,23 @@ static void i2s_dma_tx_callback(void *arg, u32_t channel,
|
||||||
ssp->ssc1 |= SSCR1_TSRE;
|
ssp->ssc1 |= SSCR1_TSRE;
|
||||||
k_msgq_put(&strm->out_queue, &buffer, K_NO_WAIT);
|
k_msgq_put(&strm->out_queue, &buffer, K_NO_WAIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ret || status) {
|
||||||
|
/*
|
||||||
|
* DMA encountered an error (status != 0)
|
||||||
|
* or
|
||||||
|
* No bufers in input queue
|
||||||
|
*/
|
||||||
|
LOG_ERR("DMA status %08x channel %u k_msgq_get ret %d",
|
||||||
|
status, channel, ret);
|
||||||
|
strm->state = I2S_STATE_STOPPING;
|
||||||
|
i2s_tx_stream_disable(dev_data, ssp, dev_data->dev_dma);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case I2S_STATE_STOPPING:
|
case I2S_STATE_STOPPING:
|
||||||
strm->state = I2S_STATE_READY;
|
|
||||||
/* fall through */
|
|
||||||
|
|
||||||
case I2S_STATE_ERROR:
|
|
||||||
i2s_tx_stream_disable(dev_data, ssp, dev_data->dev_dma);
|
i2s_tx_stream_disable(dev_data, ssp, dev_data->dev_dma);
|
||||||
/* purge buffer queue */
|
|
||||||
while (k_msgq_get(&strm->in_queue, &buffer, K_NO_WAIT) == 0) {
|
|
||||||
k_mem_slab_free(dev_data->cfg.mem_slab, &buffer);
|
|
||||||
}
|
|
||||||
while (k_msgq_get(&strm->out_queue, &buffer, K_NO_WAIT) == 0) {
|
|
||||||
k_mem_slab_free(dev_data->cfg.mem_slab, &buffer);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,8 +286,8 @@ static void i2s_dma_rx_callback(void *arg, u32_t channel, int status)
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
LOG_ERR("buffer alloc from slab %p err %d",
|
LOG_ERR("buffer alloc from slab %p err %d",
|
||||||
dev_data->cfg.mem_slab, ret);
|
dev_data->cfg.mem_slab, ret);
|
||||||
strm->state = I2S_STATE_ERROR;
|
|
||||||
i2s_rx_stream_disable(dev_data, ssp, dev_data->dev_dma);
|
i2s_rx_stream_disable(dev_data, ssp, dev_data->dev_dma);
|
||||||
|
strm->state = I2S_STATE_READY;
|
||||||
} else {
|
} else {
|
||||||
/* put buffer in input queue */
|
/* put buffer in input queue */
|
||||||
ret = k_msgq_put(&strm->in_queue, &buffer, K_NO_WAIT);
|
ret = k_msgq_put(&strm->in_queue, &buffer, K_NO_WAIT);
|
||||||
|
@ -292,20 +307,8 @@ static void i2s_dma_rx_callback(void *arg, u32_t channel, int status)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case I2S_STATE_STOPPING:
|
case I2S_STATE_STOPPING:
|
||||||
strm->state = I2S_STATE_READY;
|
|
||||||
/* fall-through */
|
|
||||||
|
|
||||||
case I2S_STATE_ERROR:
|
|
||||||
i2s_rx_stream_disable(dev_data, ssp, dev_data->dev_dma);
|
i2s_rx_stream_disable(dev_data, ssp, dev_data->dev_dma);
|
||||||
/*
|
strm->state = I2S_STATE_READY;
|
||||||
* retrieve all buffers from input & output queues and free them
|
|
||||||
*/
|
|
||||||
while (k_msgq_get(&strm->in_queue, &buffer, K_NO_WAIT) == 0) {
|
|
||||||
k_mem_slab_free(dev_data->cfg.mem_slab, &buffer);
|
|
||||||
}
|
|
||||||
while (k_msgq_get(&strm->out_queue, &buffer, K_NO_WAIT) == 0) {
|
|
||||||
k_mem_slab_free(dev_data->cfg.mem_slab, &buffer);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,10 +379,10 @@ static int i2s_cavs_configure(struct device *dev, enum i2s_dir dir,
|
||||||
|
|
||||||
/* reset SSP settings */
|
/* reset SSP settings */
|
||||||
/* sscr0 dynamic settings are DSS, EDSS, SCR, FRDC, ECS */
|
/* sscr0 dynamic settings are DSS, EDSS, SCR, FRDC, ECS */
|
||||||
ssc0 = SSCR0_MOD | SSCR0_PSP | SSCR0_RIM | SSCR0_TIM;
|
ssc0 = SSCR0_MOD | SSCR0_PSP | SSCR0_RIM;
|
||||||
|
|
||||||
/* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR */
|
/* sscr1 dynamic settings are SFRMDIR, SCLKDIR, SCFR */
|
||||||
ssc1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL | SSCR1_TSRE | SSCR1_RSRE;
|
ssc1 = SSCR1_TTE | SSCR1_TTELP | SSCR1_TRAIL;
|
||||||
|
|
||||||
/* sscr2 dynamic setting is LJDFD */
|
/* sscr2 dynamic setting is LJDFD */
|
||||||
ssc2 = 0U;
|
ssc2 = 0U;
|
||||||
|
@ -564,8 +567,12 @@ static int i2s_cavs_configure(struct device *dev, enum i2s_dir dir,
|
||||||
/* enable port */
|
/* enable port */
|
||||||
ssp->ssc0 |= SSCR0_SSE;
|
ssp->ssc0 |= SSCR0_SSE;
|
||||||
|
|
||||||
|
/* enable interrupt */
|
||||||
|
irq_enable(dev_cfg->irq_id);
|
||||||
|
|
||||||
dev_data->tx.state = I2S_STATE_READY;
|
dev_data->tx.state = I2S_STATE_READY;
|
||||||
dev_data->rx.state = I2S_STATE_READY;
|
dev_data->rx.state = I2S_STATE_READY;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,6 +615,7 @@ static int i2s_tx_stream_start(struct i2s_cavs_dev_data *dev_data,
|
||||||
|
|
||||||
/* Enable transmit operation */
|
/* Enable transmit operation */
|
||||||
key = irq_lock();
|
key = irq_lock();
|
||||||
|
ssp->ssc1 |= SSCR1_TSRE;
|
||||||
ssp->sstsa |= SSTSA_TXEN;
|
ssp->sstsa |= SSTSA_TXEN;
|
||||||
irq_unlock(key);
|
irq_unlock(key);
|
||||||
|
|
||||||
|
@ -655,6 +663,7 @@ static int i2s_rx_stream_start(struct i2s_cavs_dev_data *dev_data,
|
||||||
|
|
||||||
/* Enable Receive operation */
|
/* Enable Receive operation */
|
||||||
key = irq_lock();
|
key = irq_lock();
|
||||||
|
ssp->ssc1 |= SSCR1_RSRE;
|
||||||
ssp->ssrsa |= SSRSA_RXEN;
|
ssp->ssrsa |= SSRSA_RXEN;
|
||||||
irq_unlock(key);
|
irq_unlock(key);
|
||||||
|
|
||||||
|
@ -666,14 +675,22 @@ static void i2s_tx_stream_disable(struct i2s_cavs_dev_data *dev_data,
|
||||||
struct device *dev_dma)
|
struct device *dev_dma)
|
||||||
{
|
{
|
||||||
struct stream *strm = &dev_data->tx;
|
struct stream *strm = &dev_data->tx;
|
||||||
|
unsigned int key;
|
||||||
|
|
||||||
/* Disable DMA service request handshake logic. Handshake is
|
/*
|
||||||
* not required now since DMA is not in operation.
|
* Enable transmit undderrun interrupt to allow notification
|
||||||
|
* upon transmit FIFO being emptied.
|
||||||
|
* Defer disabling of TX to the underrun processing in ISR
|
||||||
*/
|
*/
|
||||||
ssp->sstsa &= ~SSTSA_TXEN;
|
key = irq_lock();
|
||||||
|
ssp->ssc0 &= ~SSCR0_TIM;
|
||||||
|
irq_unlock(key);
|
||||||
|
|
||||||
LOG_INF("Stopping TX stream & DMA channel %u", strm->dma_channel);
|
LOG_INF("Stopping DMA channel %u for TX stream", strm->dma_channel);
|
||||||
dma_stop(dev_dma, strm->dma_channel);
|
dma_stop(dev_dma, strm->dma_channel);
|
||||||
|
|
||||||
|
/* purge buffers queued in the stream */
|
||||||
|
i2s_purge_stream_buffers(strm, dev_data->cfg.mem_slab);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void i2s_rx_stream_disable(struct i2s_cavs_dev_data *dev_data,
|
static void i2s_rx_stream_disable(struct i2s_cavs_dev_data *dev_data,
|
||||||
|
@ -681,6 +698,7 @@ static void i2s_rx_stream_disable(struct i2s_cavs_dev_data *dev_data,
|
||||||
struct device *dev_dma)
|
struct device *dev_dma)
|
||||||
{
|
{
|
||||||
struct stream *strm = &dev_data->rx;
|
struct stream *strm = &dev_data->rx;
|
||||||
|
u32_t data;
|
||||||
|
|
||||||
/* Disable DMA service request handshake logic. Handshake is
|
/* Disable DMA service request handshake logic. Handshake is
|
||||||
* not required now since DMA is not in operation.
|
* not required now since DMA is not in operation.
|
||||||
|
@ -689,6 +707,15 @@ static void i2s_rx_stream_disable(struct i2s_cavs_dev_data *dev_data,
|
||||||
|
|
||||||
LOG_INF("Stopping RX stream & DMA channel %u", strm->dma_channel);
|
LOG_INF("Stopping RX stream & DMA channel %u", strm->dma_channel);
|
||||||
dma_stop(dev_dma, strm->dma_channel);
|
dma_stop(dev_dma, strm->dma_channel);
|
||||||
|
|
||||||
|
/* Empty the FIFO */
|
||||||
|
while (ssp->sss & SSSR_RNE) {
|
||||||
|
/* read the RX FIFO */
|
||||||
|
data = ssp->ssd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* purge buffers queued in the stream */
|
||||||
|
i2s_purge_stream_buffers(strm, dev_data->cfg.mem_slab);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int i2s_cavs_trigger(struct device *dev, enum i2s_dir dir,
|
static int i2s_cavs_trigger(struct device *dev, enum i2s_dir dir,
|
||||||
|
@ -699,7 +726,7 @@ static int i2s_cavs_trigger(struct device *dev, enum i2s_dir dir,
|
||||||
volatile struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
|
volatile struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
|
||||||
struct stream *strm;
|
struct stream *strm;
|
||||||
unsigned int key;
|
unsigned int key;
|
||||||
int ret;
|
int ret = 0;
|
||||||
|
|
||||||
strm = (dir == I2S_DIR_TX) ? &dev_data->tx : &dev_data->rx;
|
strm = (dir == I2S_DIR_TX) ? &dev_data->tx : &dev_data->rx;
|
||||||
|
|
||||||
|
@ -707,9 +734,9 @@ static int i2s_cavs_trigger(struct device *dev, enum i2s_dir dir,
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case I2S_TRIGGER_START:
|
case I2S_TRIGGER_START:
|
||||||
if (strm->state != I2S_STATE_READY) {
|
if (strm->state != I2S_STATE_READY) {
|
||||||
irq_unlock(key);
|
|
||||||
LOG_ERR("START trigger: invalid state %u", strm->state);
|
LOG_ERR("START trigger: invalid state %u", strm->state);
|
||||||
return -EIO;
|
ret = -EIO;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
__ASSERT_NO_MSG(strm->mem_block == NULL);
|
__ASSERT_NO_MSG(strm->mem_block == NULL);
|
||||||
|
@ -723,63 +750,34 @@ static int i2s_cavs_trigger(struct device *dev, enum i2s_dir dir,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
irq_unlock(key);
|
|
||||||
LOG_DBG("START trigger failed %d", ret);
|
LOG_DBG("START trigger failed %d", ret);
|
||||||
return ret;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
strm->state = I2S_STATE_RUNNING;
|
strm->state = I2S_STATE_RUNNING;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case I2S_TRIGGER_STOP:
|
case I2S_TRIGGER_STOP:
|
||||||
if (strm->state != I2S_STATE_RUNNING) {
|
|
||||||
irq_unlock(key);
|
|
||||||
LOG_DBG("STOP trigger: invalid state");
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
strm->state = I2S_STATE_STOPPING;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case I2S_TRIGGER_DRAIN:
|
case I2S_TRIGGER_DRAIN:
|
||||||
|
case I2S_TRIGGER_DROP:
|
||||||
if (strm->state != I2S_STATE_RUNNING) {
|
if (strm->state != I2S_STATE_RUNNING) {
|
||||||
irq_unlock(key);
|
LOG_DBG("STOP/DRAIN/DROP trigger: invalid state");
|
||||||
LOG_DBG("DRAIN trigger: invalid state");
|
ret = -EIO;
|
||||||
return -EIO;
|
break;
|
||||||
}
|
}
|
||||||
strm->state = I2S_STATE_STOPPING;
|
strm->state = I2S_STATE_STOPPING;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case I2S_TRIGGER_DROP:
|
|
||||||
if (strm->state == I2S_STATE_NOT_READY) {
|
|
||||||
irq_unlock(key);
|
|
||||||
LOG_DBG("DROP trigger: invalid state");
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
if (dir == I2S_DIR_TX) {
|
|
||||||
i2s_tx_stream_disable(dev_data, ssp, dev_data->dev_dma);
|
|
||||||
} else {
|
|
||||||
i2s_rx_stream_disable(dev_data, ssp, dev_data->dev_dma);
|
|
||||||
}
|
|
||||||
strm->state = I2S_STATE_READY;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case I2S_TRIGGER_PREPARE:
|
case I2S_TRIGGER_PREPARE:
|
||||||
if (strm->state != I2S_STATE_ERROR) {
|
|
||||||
irq_unlock(key);
|
|
||||||
LOG_DBG("PREPARE trigger: invalid state");
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
strm->state = I2S_STATE_READY;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
irq_unlock(key);
|
|
||||||
LOG_ERR("Unsupported trigger command");
|
LOG_ERR("Unsupported trigger command");
|
||||||
return -EINVAL;
|
ret = -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
irq_unlock(key);
|
irq_unlock(key);
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int i2s_cavs_read(struct device *dev, void **mem_block, size_t *size)
|
static int i2s_cavs_read(struct device *dev, void **mem_block, size_t *size)
|
||||||
|
@ -789,8 +787,7 @@ static int i2s_cavs_read(struct device *dev, void **mem_block, size_t *size)
|
||||||
void *buffer;
|
void *buffer;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
if ((strm->state == I2S_STATE_NOT_READY) ||
|
if (strm->state == I2S_STATE_NOT_READY) {
|
||||||
(strm->state == I2S_STATE_ERROR)) {
|
|
||||||
LOG_ERR("invalid state %d", strm->state);
|
LOG_ERR("invalid state %d", strm->state);
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
@ -834,15 +831,28 @@ static void i2s_cavs_isr(void *arg)
|
||||||
struct device *dev = (struct device *)arg;
|
struct device *dev = (struct device *)arg;
|
||||||
const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
|
const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
|
||||||
volatile struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
|
volatile struct i2s_cavs_ssp *const ssp = dev_cfg->regs;
|
||||||
u32_t temp;
|
struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
|
||||||
|
u32_t status;
|
||||||
|
|
||||||
/* clear IRQ */
|
/* clear interrupts */
|
||||||
temp = ssp->sss;
|
status = ssp->sss;
|
||||||
ssp->sss = temp;
|
ssp->sss = status;
|
||||||
|
|
||||||
|
if (status & SSSR_TUR) {
|
||||||
|
/*
|
||||||
|
* transmit underrun occurred.
|
||||||
|
* 1. disable transmission
|
||||||
|
* 2. disable underrun interrupt
|
||||||
|
*/
|
||||||
|
ssp->sstsa &= ~SSTSA_TXEN;
|
||||||
|
ssp->ssc0 |= SSCR0_TIM;
|
||||||
|
dev_data->tx.state = I2S_STATE_READY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int i2s_cavs_initialize(struct device *dev)
|
static int i2s_cavs_initialize(struct device *dev)
|
||||||
{
|
{
|
||||||
|
const struct i2s_cavs_config *const dev_cfg = DEV_CFG(dev);
|
||||||
struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
|
struct i2s_cavs_dev_data *const dev_data = DEV_DATA(dev);
|
||||||
|
|
||||||
dev_data->dev_dma = device_get_binding(CONFIG_I2S_CAVS_DMA_NAME);
|
dev_data->dev_dma = device_get_binding(CONFIG_I2S_CAVS_DMA_NAME);
|
||||||
|
@ -861,6 +871,9 @@ static int i2s_cavs_initialize(struct device *dev)
|
||||||
k_msgq_init(&dev_data->rx.out_queue, (char *)dev_data->rx.out_msgs,
|
k_msgq_init(&dev_data->rx.out_queue, (char *)dev_data->rx.out_msgs,
|
||||||
sizeof(void *), I2S_CAVS_BUF_Q_LEN);
|
sizeof(void *), I2S_CAVS_BUF_Q_LEN);
|
||||||
|
|
||||||
|
/* register ISR */
|
||||||
|
dev_cfg->irq_connect();
|
||||||
|
|
||||||
dev_data->tx.state = I2S_STATE_NOT_READY;
|
dev_data->tx.state = I2S_STATE_NOT_READY;
|
||||||
dev_data->rx.state = I2S_STATE_NOT_READY;
|
dev_data->rx.state = I2S_STATE_NOT_READY;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue