kernel-ark/sound/soc/blackfin/bf5xx-sport.c
Lars-Peter Clausen 6344260484 ASoC: blackfin: bf5xx-sport: Allow setting rx and tx mask independently
Since the hardware supports it there is no need to artificially limit this to
just being able to set the same mask for both tx and rx.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
2013-05-30 12:33:40 +01:00

1095 lines
27 KiB
C

/*
* File: bf5xx_sport.c
* Based on:
* Author: Roy Huang <roy.huang@analog.com>
*
* Created: Tue Sep 21 10:52:42 CEST 2004
* Description:
* Blackfin SPORT Driver
*
* Copyright 2004-2007 Analog Devices Inc.
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/gpio.h>
#include <linux/bug.h>
#include <linux/module.h>
#include <asm/portmux.h>
#include <asm/dma.h>
#include <asm/blackfin.h>
#include <asm/cacheflush.h>
#include "bf5xx-sport.h"
/* delay between frame sync pulse and first data bit in multichannel mode */
#define FRAME_DELAY (1<<12)
/* note: multichannel is in units of 8 channels,
* tdm_count is # channels NOT / 8 ! */
int sport_set_multichannel(struct sport_device *sport,
int tdm_count, u32 tx_mask, u32 rx_mask, int packed)
{
pr_debug("%s tdm_count=%d tx_mask:0x%08x rx_mask:0x%08x packed=%d\n",
__func__, tdm_count, tx_mask, rx_mask, packed);
if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
return -EBUSY;
if (tdm_count & 0x7)
return -EINVAL;
if (tdm_count > 32)
return -EINVAL; /* Only support less than 32 channels now */
if (tdm_count) {
sport->regs->mcmc1 = ((tdm_count>>3)-1) << 12;
sport->regs->mcmc2 = FRAME_DELAY | MCMEN | \
(packed ? (MCDTXPE|MCDRXPE) : 0);
sport->regs->mtcs0 = tx_mask;
sport->regs->mrcs0 = rx_mask;
sport->regs->mtcs1 = 0;
sport->regs->mrcs1 = 0;
sport->regs->mtcs2 = 0;
sport->regs->mrcs2 = 0;
sport->regs->mtcs3 = 0;
sport->regs->mrcs3 = 0;
} else {
sport->regs->mcmc1 = 0;
sport->regs->mcmc2 = 0;
sport->regs->mtcs0 = 0;
sport->regs->mrcs0 = 0;
}
sport->regs->mtcs1 = 0; sport->regs->mtcs2 = 0; sport->regs->mtcs3 = 0;
sport->regs->mrcs1 = 0; sport->regs->mrcs2 = 0; sport->regs->mrcs3 = 0;
SSYNC();
return 0;
}
EXPORT_SYMBOL(sport_set_multichannel);
int sport_config_rx(struct sport_device *sport, unsigned int rcr1,
unsigned int rcr2, unsigned int clkdiv, unsigned int fsdiv)
{
if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
return -EBUSY;
sport->regs->rcr1 = rcr1;
sport->regs->rcr2 = rcr2;
sport->regs->rclkdiv = clkdiv;
sport->regs->rfsdiv = fsdiv;
SSYNC();
return 0;
}
EXPORT_SYMBOL(sport_config_rx);
int sport_config_tx(struct sport_device *sport, unsigned int tcr1,
unsigned int tcr2, unsigned int clkdiv, unsigned int fsdiv)
{
if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN))
return -EBUSY;
sport->regs->tcr1 = tcr1;
sport->regs->tcr2 = tcr2;
sport->regs->tclkdiv = clkdiv;
sport->regs->tfsdiv = fsdiv;
SSYNC();
return 0;
}
EXPORT_SYMBOL(sport_config_tx);
static void setup_desc(struct dmasg *desc, void *buf, int fragcount,
size_t fragsize, unsigned int cfg,
unsigned int x_count, unsigned int ycount, size_t wdsize)
{
int i;
for (i = 0; i < fragcount; ++i) {
desc[i].next_desc_addr = &(desc[i + 1]);
desc[i].start_addr = (unsigned long)buf + i*fragsize;
desc[i].cfg = cfg;
desc[i].x_count = x_count;
desc[i].x_modify = wdsize;
desc[i].y_count = ycount;
desc[i].y_modify = wdsize;
}
/* make circular */
desc[fragcount-1].next_desc_addr = desc;
pr_debug("setup desc: desc0=%p, next0=%p, desc1=%p,"
"next1=%p\nx_count=%x,y_count=%x,addr=0x%lx,cfs=0x%x\n",
desc, desc[0].next_desc_addr,
desc+1, desc[1].next_desc_addr,
desc[0].x_count, desc[0].y_count,
desc[0].start_addr, desc[0].cfg);
}
static int sport_start(struct sport_device *sport)
{
enable_dma(sport->dma_rx_chan);
enable_dma(sport->dma_tx_chan);
sport->regs->rcr1 |= RSPEN;
sport->regs->tcr1 |= TSPEN;
SSYNC();
return 0;
}
static int sport_stop(struct sport_device *sport)
{
sport->regs->tcr1 &= ~TSPEN;
sport->regs->rcr1 &= ~RSPEN;
SSYNC();
disable_dma(sport->dma_rx_chan);
disable_dma(sport->dma_tx_chan);
return 0;
}
static inline int sport_hook_rx_dummy(struct sport_device *sport)
{
struct dmasg *desc, temp_desc;
unsigned long flags;
BUG_ON(sport->dummy_rx_desc == NULL);
BUG_ON(sport->curr_rx_desc == sport->dummy_rx_desc);
/* Maybe the dummy buffer descriptor ring is damaged */
sport->dummy_rx_desc->next_desc_addr = sport->dummy_rx_desc + 1;
local_irq_save(flags);
desc = get_dma_next_desc_ptr(sport->dma_rx_chan);
/* Copy the descriptor which will be damaged to backup */
temp_desc = *desc;
desc->x_count = sport->dummy_count / 2;
desc->y_count = 0;
desc->next_desc_addr = sport->dummy_rx_desc;
local_irq_restore(flags);
/* Waiting for dummy buffer descriptor is already hooked*/
while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
sizeof(struct dmasg)) != sport->dummy_rx_desc)
continue;
sport->curr_rx_desc = sport->dummy_rx_desc;
/* Restore the damaged descriptor */
*desc = temp_desc;
return 0;
}
static inline int sport_rx_dma_start(struct sport_device *sport, int dummy)
{
if (dummy) {
sport->dummy_rx_desc->next_desc_addr = sport->dummy_rx_desc;
sport->curr_rx_desc = sport->dummy_rx_desc;
} else
sport->curr_rx_desc = sport->dma_rx_desc;
set_dma_next_desc_addr(sport->dma_rx_chan, sport->curr_rx_desc);
set_dma_x_count(sport->dma_rx_chan, 0);
set_dma_x_modify(sport->dma_rx_chan, 0);
set_dma_config(sport->dma_rx_chan, (DMAFLOW_LARGE | NDSIZE_9 | \
WDSIZE_32 | WNR));
set_dma_curr_addr(sport->dma_rx_chan, sport->curr_rx_desc->start_addr);
SSYNC();
return 0;
}
static inline int sport_tx_dma_start(struct sport_device *sport, int dummy)
{
if (dummy) {
sport->dummy_tx_desc->next_desc_addr = sport->dummy_tx_desc;
sport->curr_tx_desc = sport->dummy_tx_desc;
} else
sport->curr_tx_desc = sport->dma_tx_desc;
set_dma_next_desc_addr(sport->dma_tx_chan, sport->curr_tx_desc);
set_dma_x_count(sport->dma_tx_chan, 0);
set_dma_x_modify(sport->dma_tx_chan, 0);
set_dma_config(sport->dma_tx_chan,
(DMAFLOW_LARGE | NDSIZE_9 | WDSIZE_32));
set_dma_curr_addr(sport->dma_tx_chan, sport->curr_tx_desc->start_addr);
SSYNC();
return 0;
}
int sport_rx_start(struct sport_device *sport)
{
unsigned long flags;
pr_debug("%s enter\n", __func__);
if (sport->rx_run)
return -EBUSY;
if (sport->tx_run) {
/* tx is running, rx is not running */
BUG_ON(sport->dma_rx_desc == NULL);
BUG_ON(sport->curr_rx_desc != sport->dummy_rx_desc);
local_irq_save(flags);
while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
sizeof(struct dmasg)) != sport->dummy_rx_desc)
continue;
sport->dummy_rx_desc->next_desc_addr = sport->dma_rx_desc;
local_irq_restore(flags);
sport->curr_rx_desc = sport->dma_rx_desc;
} else {
sport_tx_dma_start(sport, 1);
sport_rx_dma_start(sport, 0);
sport_start(sport);
}
sport->rx_run = 1;
return 0;
}
EXPORT_SYMBOL(sport_rx_start);
int sport_rx_stop(struct sport_device *sport)
{
pr_debug("%s enter\n", __func__);
if (!sport->rx_run)
return 0;
if (sport->tx_run) {
/* TX dma is still running, hook the dummy buffer */
sport_hook_rx_dummy(sport);
} else {
/* Both rx and tx dma will be stopped */
sport_stop(sport);
sport->curr_rx_desc = NULL;
sport->curr_tx_desc = NULL;
}
sport->rx_run = 0;
return 0;
}
EXPORT_SYMBOL(sport_rx_stop);
static inline int sport_hook_tx_dummy(struct sport_device *sport)
{
struct dmasg *desc, temp_desc;
unsigned long flags;
BUG_ON(sport->dummy_tx_desc == NULL);
BUG_ON(sport->curr_tx_desc == sport->dummy_tx_desc);
sport->dummy_tx_desc->next_desc_addr = sport->dummy_tx_desc + 1;
/* Shorten the time on last normal descriptor */
local_irq_save(flags);
desc = get_dma_next_desc_ptr(sport->dma_tx_chan);
/* Store the descriptor which will be damaged */
temp_desc = *desc;
desc->x_count = sport->dummy_count / 2;
desc->y_count = 0;
desc->next_desc_addr = sport->dummy_tx_desc;
local_irq_restore(flags);
/* Waiting for dummy buffer descriptor is already hooked*/
while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) - \
sizeof(struct dmasg)) != sport->dummy_tx_desc)
continue;
sport->curr_tx_desc = sport->dummy_tx_desc;
/* Restore the damaged descriptor */
*desc = temp_desc;
return 0;
}
int sport_tx_start(struct sport_device *sport)
{
unsigned long flags;
pr_debug("%s: tx_run:%d, rx_run:%d\n", __func__,
sport->tx_run, sport->rx_run);
if (sport->tx_run)
return -EBUSY;
if (sport->rx_run) {
BUG_ON(sport->dma_tx_desc == NULL);
BUG_ON(sport->curr_tx_desc != sport->dummy_tx_desc);
/* Hook the normal buffer descriptor */
local_irq_save(flags);
while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) -
sizeof(struct dmasg)) != sport->dummy_tx_desc)
continue;
sport->dummy_tx_desc->next_desc_addr = sport->dma_tx_desc;
local_irq_restore(flags);
sport->curr_tx_desc = sport->dma_tx_desc;
} else {
sport_tx_dma_start(sport, 0);
/* Let rx dma run the dummy buffer */
sport_rx_dma_start(sport, 1);
sport_start(sport);
}
sport->tx_run = 1;
return 0;
}
EXPORT_SYMBOL(sport_tx_start);
int sport_tx_stop(struct sport_device *sport)
{
if (!sport->tx_run)
return 0;
if (sport->rx_run) {
/* RX is still running, hook the dummy buffer */
sport_hook_tx_dummy(sport);
} else {
/* Both rx and tx dma stopped */
sport_stop(sport);
sport->curr_rx_desc = NULL;
sport->curr_tx_desc = NULL;
}
sport->tx_run = 0;
return 0;
}
EXPORT_SYMBOL(sport_tx_stop);
static inline int compute_wdsize(size_t wdsize)
{
switch (wdsize) {
case 1:
return WDSIZE_8;
case 2:
return WDSIZE_16;
case 4:
default:
return WDSIZE_32;
}
}
int sport_config_rx_dma(struct sport_device *sport, void *buf,
int fragcount, size_t fragsize)
{
unsigned int x_count;
unsigned int y_count;
unsigned int cfg;
dma_addr_t addr;
pr_debug("%s buf:%p, frag:%d, fragsize:0x%lx\n", __func__, \
buf, fragcount, fragsize);
x_count = fragsize / sport->wdsize;
y_count = 0;
/* for fragments larger than 64k words we use 2d dma,
* denote fragecount as two numbers' mutliply and both of them
* are less than 64k.*/
if (x_count >= 0x10000) {
int i, count = x_count;
for (i = 16; i > 0; i--) {
x_count = 1 << i;
if ((count & (x_count - 1)) == 0) {
y_count = count >> i;
if (y_count < 0x10000)
break;
}
}
if (i == 0)
return -EINVAL;
}
pr_debug("%s(x_count:0x%x, y_count:0x%x)\n", __func__,
x_count, y_count);
if (sport->dma_rx_desc)
dma_free_coherent(NULL, sport->rx_desc_bytes,
sport->dma_rx_desc, 0);
/* Allocate a new descritor ring as current one. */
sport->dma_rx_desc = dma_alloc_coherent(NULL, \
fragcount * sizeof(struct dmasg), &addr, 0);
sport->rx_desc_bytes = fragcount * sizeof(struct dmasg);
if (!sport->dma_rx_desc) {
pr_err("Failed to allocate memory for rx desc\n");
return -ENOMEM;
}
sport->rx_buf = buf;
sport->rx_fragsize = fragsize;
sport->rx_frags = fragcount;
cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | WNR | \
(DESC_ELEMENT_COUNT << 8); /* large descriptor mode */
if (y_count != 0)
cfg |= DMA2D;
setup_desc(sport->dma_rx_desc, buf, fragcount, fragsize,
cfg|DMAEN, x_count, y_count, sport->wdsize);
return 0;
}
EXPORT_SYMBOL(sport_config_rx_dma);
int sport_config_tx_dma(struct sport_device *sport, void *buf, \
int fragcount, size_t fragsize)
{
unsigned int x_count;
unsigned int y_count;
unsigned int cfg;
dma_addr_t addr;
pr_debug("%s buf:%p, fragcount:%d, fragsize:0x%lx\n",
__func__, buf, fragcount, fragsize);
x_count = fragsize/sport->wdsize;
y_count = 0;
/* for fragments larger than 64k words we use 2d dma,
* denote fragecount as two numbers' mutliply and both of them
* are less than 64k.*/
if (x_count >= 0x10000) {
int i, count = x_count;
for (i = 16; i > 0; i--) {
x_count = 1 << i;
if ((count & (x_count - 1)) == 0) {
y_count = count >> i;
if (y_count < 0x10000)
break;
}
}
if (i == 0)
return -EINVAL;
}
pr_debug("%s x_count:0x%x, y_count:0x%x\n", __func__,
x_count, y_count);
if (sport->dma_tx_desc) {
dma_free_coherent(NULL, sport->tx_desc_bytes, \
sport->dma_tx_desc, 0);
}
sport->dma_tx_desc = dma_alloc_coherent(NULL, \
fragcount * sizeof(struct dmasg), &addr, 0);
sport->tx_desc_bytes = fragcount * sizeof(struct dmasg);
if (!sport->dma_tx_desc) {
pr_err("Failed to allocate memory for tx desc\n");
return -ENOMEM;
}
sport->tx_buf = buf;
sport->tx_fragsize = fragsize;
sport->tx_frags = fragcount;
cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | \
(DESC_ELEMENT_COUNT << 8); /* large descriptor mode */
if (y_count != 0)
cfg |= DMA2D;
setup_desc(sport->dma_tx_desc, buf, fragcount, fragsize,
cfg|DMAEN, x_count, y_count, sport->wdsize);
return 0;
}
EXPORT_SYMBOL(sport_config_tx_dma);
/* setup dummy dma descriptor ring, which don't generate interrupts,
* the x_modify is set to 0 */
static int sport_config_rx_dummy(struct sport_device *sport)
{
struct dmasg *desc;
unsigned config;
pr_debug("%s entered\n", __func__);
if (L1_DATA_A_LENGTH)
desc = l1_data_sram_zalloc(2 * sizeof(*desc));
else {
dma_addr_t addr;
desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
memset(desc, 0, 2 * sizeof(*desc));
}
if (desc == NULL) {
pr_err("Failed to allocate memory for dummy rx desc\n");
return -ENOMEM;
}
sport->dummy_rx_desc = desc;
desc->start_addr = (unsigned long)sport->dummy_buf;
config = DMAFLOW_LARGE | NDSIZE_9 | compute_wdsize(sport->wdsize)
| WNR | DMAEN;
desc->cfg = config;
desc->x_count = sport->dummy_count/sport->wdsize;
desc->x_modify = sport->wdsize;
desc->y_count = 0;
desc->y_modify = 0;
memcpy(desc+1, desc, sizeof(*desc));
desc->next_desc_addr = desc + 1;
desc[1].next_desc_addr = desc;
return 0;
}
static int sport_config_tx_dummy(struct sport_device *sport)
{
struct dmasg *desc;
unsigned int config;
pr_debug("%s entered\n", __func__);
if (L1_DATA_A_LENGTH)
desc = l1_data_sram_zalloc(2 * sizeof(*desc));
else {
dma_addr_t addr;
desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
memset(desc, 0, 2 * sizeof(*desc));
}
if (!desc) {
pr_err("Failed to allocate memory for dummy tx desc\n");
return -ENOMEM;
}
sport->dummy_tx_desc = desc;
desc->start_addr = (unsigned long)sport->dummy_buf + \
sport->dummy_count;
config = DMAFLOW_LARGE | NDSIZE_9 |
compute_wdsize(sport->wdsize) | DMAEN;
desc->cfg = config;
desc->x_count = sport->dummy_count/sport->wdsize;
desc->x_modify = sport->wdsize;
desc->y_count = 0;
desc->y_modify = 0;
memcpy(desc+1, desc, sizeof(*desc));
desc->next_desc_addr = desc + 1;
desc[1].next_desc_addr = desc;
return 0;
}
unsigned long sport_curr_offset_rx(struct sport_device *sport)
{
unsigned long curr = get_dma_curr_addr(sport->dma_rx_chan);
return (unsigned char *)curr - sport->rx_buf;
}
EXPORT_SYMBOL(sport_curr_offset_rx);
unsigned long sport_curr_offset_tx(struct sport_device *sport)
{
unsigned long curr = get_dma_curr_addr(sport->dma_tx_chan);
return (unsigned char *)curr - sport->tx_buf;
}
EXPORT_SYMBOL(sport_curr_offset_tx);
void sport_incfrag(struct sport_device *sport, int *frag, int tx)
{
++(*frag);
if (tx == 1 && *frag == sport->tx_frags)
*frag = 0;
if (tx == 0 && *frag == sport->rx_frags)
*frag = 0;
}
EXPORT_SYMBOL(sport_incfrag);
void sport_decfrag(struct sport_device *sport, int *frag, int tx)
{
--(*frag);
if (tx == 1 && *frag == 0)
*frag = sport->tx_frags;
if (tx == 0 && *frag == 0)
*frag = sport->rx_frags;
}
EXPORT_SYMBOL(sport_decfrag);
static int sport_check_status(struct sport_device *sport,
unsigned int *sport_stat,
unsigned int *rx_stat,
unsigned int *tx_stat)
{
int status = 0;
if (sport_stat) {
SSYNC();
status = sport->regs->stat;
if (status & (TOVF|TUVF|ROVF|RUVF))
sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF));
SSYNC();
*sport_stat = status;
}
if (rx_stat) {
SSYNC();
status = get_dma_curr_irqstat(sport->dma_rx_chan);
if (status & (DMA_DONE|DMA_ERR))
clear_dma_irqstat(sport->dma_rx_chan);
SSYNC();
*rx_stat = status;
}
if (tx_stat) {
SSYNC();
status = get_dma_curr_irqstat(sport->dma_tx_chan);
if (status & (DMA_DONE|DMA_ERR))
clear_dma_irqstat(sport->dma_tx_chan);
SSYNC();
*tx_stat = status;
}
return 0;
}
int sport_dump_stat(struct sport_device *sport, char *buf, size_t len)
{
int ret;
ret = snprintf(buf, len,
"sts: 0x%04x\n"
"rx dma %d sts: 0x%04x tx dma %d sts: 0x%04x\n",
sport->regs->stat,
sport->dma_rx_chan,
get_dma_curr_irqstat(sport->dma_rx_chan),
sport->dma_tx_chan,
get_dma_curr_irqstat(sport->dma_tx_chan));
buf += ret;
len -= ret;
ret += snprintf(buf, len,
"curr_rx_desc:0x%p, curr_tx_desc:0x%p\n"
"dma_rx_desc:0x%p, dma_tx_desc:0x%p\n"
"dummy_rx_desc:0x%p, dummy_tx_desc:0x%p\n",
sport->curr_rx_desc, sport->curr_tx_desc,
sport->dma_rx_desc, sport->dma_tx_desc,
sport->dummy_rx_desc, sport->dummy_tx_desc);
return ret;
}
static irqreturn_t rx_handler(int irq, void *dev_id)
{
unsigned int rx_stat;
struct sport_device *sport = dev_id;
pr_debug("%s enter\n", __func__);
sport_check_status(sport, NULL, &rx_stat, NULL);
if (!(rx_stat & DMA_DONE))
pr_err("rx dma is already stopped\n");
if (sport->rx_callback) {
sport->rx_callback(sport->rx_data);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static irqreturn_t tx_handler(int irq, void *dev_id)
{
unsigned int tx_stat;
struct sport_device *sport = dev_id;
pr_debug("%s enter\n", __func__);
sport_check_status(sport, NULL, NULL, &tx_stat);
if (!(tx_stat & DMA_DONE)) {
pr_err("tx dma is already stopped\n");
return IRQ_HANDLED;
}
if (sport->tx_callback) {
sport->tx_callback(sport->tx_data);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static irqreturn_t err_handler(int irq, void *dev_id)
{
unsigned int status = 0;
struct sport_device *sport = dev_id;
pr_debug("%s\n", __func__);
if (sport_check_status(sport, &status, NULL, NULL)) {
pr_err("error checking status ??");
return IRQ_NONE;
}
if (status & (TOVF|TUVF|ROVF|RUVF)) {
pr_info("sport status error:%s%s%s%s\n",
status & TOVF ? " TOVF" : "",
status & TUVF ? " TUVF" : "",
status & ROVF ? " ROVF" : "",
status & RUVF ? " RUVF" : "");
if (status & TOVF || status & TUVF) {
disable_dma(sport->dma_tx_chan);
if (sport->tx_run)
sport_tx_dma_start(sport, 0);
else
sport_tx_dma_start(sport, 1);
enable_dma(sport->dma_tx_chan);
} else {
disable_dma(sport->dma_rx_chan);
if (sport->rx_run)
sport_rx_dma_start(sport, 0);
else
sport_rx_dma_start(sport, 1);
enable_dma(sport->dma_rx_chan);
}
}
status = sport->regs->stat;
if (status & (TOVF|TUVF|ROVF|RUVF))
sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF));
SSYNC();
if (sport->err_callback)
sport->err_callback(sport->err_data);
return IRQ_HANDLED;
}
int sport_set_rx_callback(struct sport_device *sport,
void (*rx_callback)(void *), void *rx_data)
{
BUG_ON(rx_callback == NULL);
sport->rx_callback = rx_callback;
sport->rx_data = rx_data;
return 0;
}
EXPORT_SYMBOL(sport_set_rx_callback);
int sport_set_tx_callback(struct sport_device *sport,
void (*tx_callback)(void *), void *tx_data)
{
BUG_ON(tx_callback == NULL);
sport->tx_callback = tx_callback;
sport->tx_data = tx_data;
return 0;
}
EXPORT_SYMBOL(sport_set_tx_callback);
int sport_set_err_callback(struct sport_device *sport,
void (*err_callback)(void *), void *err_data)
{
BUG_ON(err_callback == NULL);
sport->err_callback = err_callback;
sport->err_data = err_data;
return 0;
}
EXPORT_SYMBOL(sport_set_err_callback);
static int sport_config_pdev(struct platform_device *pdev, struct sport_param *param)
{
/* Extract settings from platform data */
struct device *dev = &pdev->dev;
struct bfin_snd_platform_data *pdata = dev->platform_data;
struct resource *res;
param->num = pdev->id;
if (!pdata) {
dev_err(dev, "no platform_data\n");
return -ENODEV;
}
param->pin_req = pdata->pin_req;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "no MEM resource\n");
return -ENODEV;
}
param->regs = (struct sport_register *)res->start;
/* first RX, then TX */
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!res) {
dev_err(dev, "no rx DMA resource\n");
return -ENODEV;
}
param->dma_rx_chan = res->start;
res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!res) {
dev_err(dev, "no tx DMA resource\n");
return -ENODEV;
}
param->dma_tx_chan = res->start;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(dev, "no irq resource\n");
return -ENODEV;
}
param->err_irq = res->start;
return 0;
}
struct sport_device *sport_init(struct platform_device *pdev,
unsigned int wdsize, unsigned int dummy_count, size_t priv_size)
{
struct device *dev = &pdev->dev;
struct sport_param param;
struct sport_device *sport;
int ret;
dev_dbg(dev, "%s enter\n", __func__);
param.wdsize = wdsize;
param.dummy_count = dummy_count;
BUG_ON(param.wdsize == 0 || param.dummy_count == 0);
ret = sport_config_pdev(pdev, &param);
if (ret)
return NULL;
if (peripheral_request_list(param.pin_req, "soc-audio")) {
dev_err(dev, "requesting Peripherals failed\n");
return NULL;
}
sport = kzalloc(sizeof(*sport), GFP_KERNEL);
if (!sport) {
dev_err(dev, "failed to allocate for sport device\n");
goto __init_err0;
}
sport->num = param.num;
sport->dma_rx_chan = param.dma_rx_chan;
sport->dma_tx_chan = param.dma_tx_chan;
sport->err_irq = param.err_irq;
sport->regs = param.regs;
sport->pin_req = param.pin_req;
if (request_dma(sport->dma_rx_chan, "SPORT RX Data") == -EBUSY) {
dev_err(dev, "failed to request RX dma %d\n", sport->dma_rx_chan);
goto __init_err1;
}
if (set_dma_callback(sport->dma_rx_chan, rx_handler, sport) != 0) {
dev_err(dev, "failed to request RX irq %d\n", sport->dma_rx_chan);
goto __init_err2;
}
if (request_dma(sport->dma_tx_chan, "SPORT TX Data") == -EBUSY) {
dev_err(dev, "failed to request TX dma %d\n", sport->dma_tx_chan);
goto __init_err2;
}
if (set_dma_callback(sport->dma_tx_chan, tx_handler, sport) != 0) {
dev_err(dev, "failed to request TX irq %d\n", sport->dma_tx_chan);
goto __init_err3;
}
if (request_irq(sport->err_irq, err_handler, IRQF_SHARED, "SPORT err",
sport) < 0) {
dev_err(dev, "failed to request err irq %d\n", sport->err_irq);
goto __init_err3;
}
dev_info(dev, "dma rx:%d tx:%d, err irq:%d, regs:%p\n",
sport->dma_rx_chan, sport->dma_tx_chan,
sport->err_irq, sport->regs);
sport->wdsize = param.wdsize;
sport->dummy_count = param.dummy_count;
sport->private_data = kzalloc(priv_size, GFP_KERNEL);
if (!sport->private_data) {
dev_err(dev, "could not alloc priv data %zu bytes\n", priv_size);
goto __init_err4;
}
if (L1_DATA_A_LENGTH)
sport->dummy_buf = l1_data_sram_zalloc(param.dummy_count * 2);
else
sport->dummy_buf = kzalloc(param.dummy_count * 2, GFP_KERNEL);
if (sport->dummy_buf == NULL) {
dev_err(dev, "failed to allocate dummy buffer\n");
goto __error1;
}
ret = sport_config_rx_dummy(sport);
if (ret) {
dev_err(dev, "failed to config rx dummy ring\n");
goto __error2;
}
ret = sport_config_tx_dummy(sport);
if (ret) {
dev_err(dev, "failed to config tx dummy ring\n");
goto __error3;
}
platform_set_drvdata(pdev, sport);
return sport;
__error3:
if (L1_DATA_A_LENGTH)
l1_data_sram_free(sport->dummy_rx_desc);
else
dma_free_coherent(NULL, 2*sizeof(struct dmasg),
sport->dummy_rx_desc, 0);
__error2:
if (L1_DATA_A_LENGTH)
l1_data_sram_free(sport->dummy_buf);
else
kfree(sport->dummy_buf);
__error1:
kfree(sport->private_data);
__init_err4:
free_irq(sport->err_irq, sport);
__init_err3:
free_dma(sport->dma_tx_chan);
__init_err2:
free_dma(sport->dma_rx_chan);
__init_err1:
kfree(sport);
__init_err0:
peripheral_free_list(param.pin_req);
return NULL;
}
EXPORT_SYMBOL(sport_init);
void sport_done(struct sport_device *sport)
{
if (sport == NULL)
return;
sport_stop(sport);
if (sport->dma_rx_desc)
dma_free_coherent(NULL, sport->rx_desc_bytes,
sport->dma_rx_desc, 0);
if (sport->dma_tx_desc)
dma_free_coherent(NULL, sport->tx_desc_bytes,
sport->dma_tx_desc, 0);
#if L1_DATA_A_LENGTH != 0
l1_data_sram_free(sport->dummy_rx_desc);
l1_data_sram_free(sport->dummy_tx_desc);
l1_data_sram_free(sport->dummy_buf);
#else
dma_free_coherent(NULL, 2*sizeof(struct dmasg),
sport->dummy_rx_desc, 0);
dma_free_coherent(NULL, 2*sizeof(struct dmasg),
sport->dummy_tx_desc, 0);
kfree(sport->dummy_buf);
#endif
free_dma(sport->dma_rx_chan);
free_dma(sport->dma_tx_chan);
free_irq(sport->err_irq, sport);
kfree(sport->private_data);
peripheral_free_list(sport->pin_req);
kfree(sport);
}
EXPORT_SYMBOL(sport_done);
/*
* It is only used to send several bytes when dma is not enabled
* sport controller is configured but not enabled.
* Multichannel cannot works with pio mode */
/* Used by ac97 to write and read codec register */
int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \
u8 *in_data, int len)
{
unsigned short dma_config;
unsigned short status;
unsigned long flags;
unsigned long wait = 0;
pr_debug("%s enter, out_data:%p, in_data:%p len:%d\n", \
__func__, out_data, in_data, len);
pr_debug("tcr1:0x%04x, tcr2:0x%04x, tclkdiv:0x%04x, tfsdiv:0x%04x\n"
"mcmc1:0x%04x, mcmc2:0x%04x\n",
sport->regs->tcr1, sport->regs->tcr2,
sport->regs->tclkdiv, sport->regs->tfsdiv,
sport->regs->mcmc1, sport->regs->mcmc2);
flush_dcache_range((unsigned)out_data, (unsigned)(out_data + len));
/* Enable tx dma */
dma_config = (RESTART | WDSIZE_16 | DI_EN);
set_dma_start_addr(sport->dma_tx_chan, (unsigned long)out_data);
set_dma_x_count(sport->dma_tx_chan, len/2);
set_dma_x_modify(sport->dma_tx_chan, 2);
set_dma_config(sport->dma_tx_chan, dma_config);
enable_dma(sport->dma_tx_chan);
if (in_data != NULL) {
invalidate_dcache_range((unsigned)in_data, \
(unsigned)(in_data + len));
/* Enable rx dma */
dma_config = (RESTART | WDSIZE_16 | WNR | DI_EN);
set_dma_start_addr(sport->dma_rx_chan, (unsigned long)in_data);
set_dma_x_count(sport->dma_rx_chan, len/2);
set_dma_x_modify(sport->dma_rx_chan, 2);
set_dma_config(sport->dma_rx_chan, dma_config);
enable_dma(sport->dma_rx_chan);
}
local_irq_save(flags);
sport->regs->tcr1 |= TSPEN;
sport->regs->rcr1 |= RSPEN;
SSYNC();
status = get_dma_curr_irqstat(sport->dma_tx_chan);
while (status & DMA_RUN) {
udelay(1);
status = get_dma_curr_irqstat(sport->dma_tx_chan);
pr_debug("DMA status:0x%04x\n", status);
if (wait++ > 100)
goto __over;
}
status = sport->regs->stat;
wait = 0;
while (!(status & TXHRE)) {
pr_debug("sport status:0x%04x\n", status);
udelay(1);
status = *(unsigned short *)&sport->regs->stat;
if (wait++ > 1000)
goto __over;
}
/* Wait for the last byte sent out */
udelay(20);
pr_debug("sport status:0x%04x\n", status);
__over:
sport->regs->tcr1 &= ~TSPEN;
sport->regs->rcr1 &= ~RSPEN;
SSYNC();
disable_dma(sport->dma_tx_chan);
/* Clear the status */
clear_dma_irqstat(sport->dma_tx_chan);
if (in_data != NULL) {
disable_dma(sport->dma_rx_chan);
clear_dma_irqstat(sport->dma_rx_chan);
}
SSYNC();
local_irq_restore(flags);
return 0;
}
EXPORT_SYMBOL(sport_send_and_recv);
MODULE_AUTHOR("Roy Huang");
MODULE_DESCRIPTION("SPORT driver for ADI Blackfin");
MODULE_LICENSE("GPL");